Merge lp:~pedronis/ubuntu-push/acceptance-extras into lp:ubuntu-push

Proposed by Samuele Pedroni
Status: Superseded
Proposed branch: lp:~pedronis/ubuntu-push/acceptance-extras
Merge into: lp:ubuntu-push
Diff against target: 2880 lines (+1244/-285)
40 files modified
Makefile (+3/-0)
README (+11/-2)
client/client.go (+8/-9)
client/client_test.go (+15/-1)
client/gethosts/gethost.go (+7/-5)
client/gethosts/gethost_test.go (+6/-2)
client/session/session.go (+86/-16)
client/session/session_test.go (+232/-16)
config/config.go (+130/-21)
config/config_test.go (+105/-0)
debian/changelog (+6/-0)
debian/control (+4/-0)
debian/rules (+4/-1)
dependencies.tsv (+2/-0)
logger/logger.go (+27/-0)
logger/logger_test.go (+25/-0)
protocol/messages.go (+32/-0)
protocol/messages_test.go (+10/-2)
protocol/state-diag-client.gv (+4/-1)
protocol/state-diag-client.svg (+77/-49)
protocol/state-diag-session.gv (+10/-0)
protocol/state-diag-session.svg (+132/-74)
server/acceptance/acceptanceclient.go (+5/-0)
server/acceptance/cmd/acceptanceclient.go (+7/-2)
server/acceptance/suites/broadcast.go (+6/-2)
server/acceptance/suites/suite.go (+9/-0)
server/broker/broker.go (+1/-1)
server/broker/exchanges.go (+12/-16)
server/broker/exchanges_test.go (+7/-4)
server/broker/simple/simple.go (+2/-2)
server/broker/testsuite/suite.go (+24/-21)
server/session/session.go (+23/-11)
server/session/session_test.go (+43/-14)
server/session/tracker.go (+12/-5)
server/session/tracker_test.go (+4/-4)
ubuntu-push-client.go (+29/-0)
util/auth.go (+36/-0)
util/auth_test.go (+53/-0)
whoopsie/identifier/identifier.go (+20/-4)
whoopsie/identifier/identifier_test.go (+15/-0)
To merge this branch: bzr merge lp:~pedronis/ubuntu-push/acceptance-extras
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+217456@code.launchpad.net

Commit message

extras to deal with auth in acceptance tests, support receiving conn warnings as well in acceptance

Description of the change

extras to deal with auth in acceptance tests, support receiving conn warnings as well in acceptance

To post a comment you must log in.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2014-03-31 17:58:54 +0000
3+++ Makefile 2014-04-28 14:37:38 +0000
4@@ -11,10 +11,13 @@
5 GODEPS += launchpad.net/go-dbus/v1
6 GODEPS += launchpad.net/go-xdg/v0
7 GODEPS += code.google.com/p/gosqlite/sqlite3
8+GODEPS += gopkg.in/qml.v0
9+GODEPS += gopkg.in/niemeyer/uoneauth.v1
10
11 TOTEST = $(shell env GOPATH=$(GOPATH) go list $(PROJECT)/...|grep -v acceptance|grep -v http13client )
12
13 bootstrap:
14+ $(RM) -r $(GOPATH)/pkg
15 mkdir -p $(GOPATH)/bin
16 mkdir -p $(GOPATH)/pkg
17 go get -u launchpad.net/godeps
18
19=== modified file 'README'
20--- README 2014-03-31 17:58:54 +0000
21+++ README 2014-04-28 14:37:38 +0000
22@@ -6,11 +6,20 @@
23 The code expects to be checked out as launchpad.net/ubuntu-push in a Go
24 workspace, see "go help gopath".
25
26-To setup Go dependencies, install libsqlite3-dev and run:
27+To setup Go dependencies, install the following libraries:
28+
29+ libsqlite3-dev
30+ qtbase5-private-dev
31+ qtdeclarative5-dev
32+ libqt5opengl5-dev
33+ libubuntuoneauth-2.0-dev
34+
35+and run:
36
37 make bootstrap
38
39-To run tests, install libgcrypt11-dev and libwhoopsie-dev and run:
40+To run tests, install libgcrypt11-dev, libwhoopsie-dev, and
41+libubuntuoneauth-2.0-dev and run:
42
43 make check
44
45
46=== modified file 'client/client.go'
47--- client/client.go 2014-04-11 16:37:48 +0000
48+++ client/client.go 2014-04-28 14:37:38 +0000
49@@ -26,6 +26,7 @@
50 "os"
51 "strings"
52
53+ "gopkg.in/qml.v0"
54 "launchpad.net/go-dbus/v1"
55
56 "launchpad.net/ubuntu-push/bus"
57@@ -57,7 +58,7 @@
58 // The PEM-encoded server certificate
59 CertPEMFile string `json:"cert_pem_file"`
60 // The logging level (one of "debug", "info", "error")
61- LogLevel string `json:"log_level"`
62+ LogLevel logger.ConfigLogLevel `json:"log_level"`
63 }
64
65 // PushClient is the Ubuntu Push Notifications client-side daemon.
66@@ -95,13 +96,13 @@
67
68 // configure loads its configuration, and sets it up.
69 func (client *PushClient) configure() error {
70- f, err := os.Open(client.configPath)
71+ _, err := os.Stat(client.configPath)
72 if err != nil {
73- return fmt.Errorf("opening config: %v", err)
74+ return fmt.Errorf("config: %v", err)
75 }
76- err = config.ReadConfig(f, &client.config)
77+ err = config.ReadFiles(&client.config, client.configPath, "<flags>")
78 if err != nil {
79- return fmt.Errorf("reading config: %v", err)
80+ return fmt.Errorf("config: %v", err)
81 }
82 // ignore spaces
83 client.config.Addr = strings.Replace(client.config.Addr, " ", "", -1)
84@@ -110,7 +111,8 @@
85 }
86
87 // later, we'll be specifying more logging options in the config file
88- client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel)
89+ client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel.Level())
90+ qml.SetLogger(client.log)
91
92 // overridden for testing
93 client.idder = identifier.New()
94@@ -285,9 +287,6 @@
95 h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}}
96 nots := notifications.Raw(client.notificationsEndp, client.log)
97 body := "Tap to open the system updater."
98- if msg != nil {
99- body = fmt.Sprintf("[%d] %s", msg.TopLevel, body)
100- }
101 not_id, err := nots.Notify(
102 "ubuntu-push-client", // app name
103 uint32(0), // id
104
105=== modified file 'client/client_test.go'
106--- client/client_test.go 2014-04-11 16:21:45 +0000
107+++ client/client_test.go 2014-04-28 14:37:38 +0000
108@@ -19,10 +19,12 @@
109 import (
110 "encoding/json"
111 "errors"
112+ "flag"
113 "fmt"
114 "io/ioutil"
115 "net/http"
116 "net/http/httptest"
117+ "os"
118 "path/filepath"
119 "reflect"
120 "testing"
121@@ -37,6 +39,7 @@
122 testibus "launchpad.net/ubuntu-push/bus/testing"
123 "launchpad.net/ubuntu-push/client/session"
124 "launchpad.net/ubuntu-push/client/session/levelmap"
125+ "launchpad.net/ubuntu-push/config"
126 helpers "launchpad.net/ubuntu-push/testing"
127 "launchpad.net/ubuntu-push/testing/condition"
128 "launchpad.net/ubuntu-push/util"
129@@ -79,6 +82,7 @@
130 }
131
132 func (cs *clientSuite) SetUpSuite(c *C) {
133+ config.IgnoreParsedFlags = true // because configure() uses <flags>
134 cs.timeouts = util.SwapTimeouts([]time.Duration{0})
135 cs.leveldbPath = ""
136 }
137@@ -142,6 +146,16 @@
138 c.Check(cli.config.ExchangeTimeout.TimeDuration(), Equals, time.Duration(10*time.Millisecond))
139 }
140
141+func (cs *clientSuite) TestConfigureWorksWithFlags(c *C) {
142+ flag.CommandLine = flag.NewFlagSet("client", flag.ContinueOnError)
143+ os.Args = []string{"client", "-addr", "foo:7777"}
144+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
145+ err := cli.configure()
146+ c.Assert(err, IsNil)
147+ c.Assert(cli.config, NotNil)
148+ c.Check(cli.config.Addr, Equals, "foo:7777")
149+}
150+
151 func (cs *clientSuite) TestConfigureSetsUpLog(c *C) {
152 cli := NewPushClient(cs.configPath, cs.leveldbPath)
153 c.Check(cli.log, IsNil)
154@@ -163,7 +177,7 @@
155 c.Check(cli.idder, IsNil)
156 err := cli.configure()
157 c.Assert(err, IsNil)
158- c.Assert(cli.idder, DeepEquals, identifier.New())
159+ c.Assert(cli.idder, FitsTypeOf, identifier.New())
160 }
161
162 func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) {
163
164=== modified file 'client/gethosts/gethost.go'
165--- client/gethosts/gethost.go 2014-03-24 15:32:29 +0000
166+++ client/gethosts/gethost.go 2014-04-28 14:37:38 +0000
167@@ -49,8 +49,10 @@
168 }
169 }
170
171-type expected struct {
172- Hosts []string
173+// Host contains the domain and hosts returned by the remote endpoint
174+type Host struct {
175+ Domain string
176+ Hosts []string
177 }
178
179 var (
180@@ -60,7 +62,7 @@
181 )
182
183 // Get gets a list of hosts consulting the endpoint.
184-func (gh *GetHost) Get() ([]string, error) {
185+func (gh *GetHost) Get() (*Host, error) {
186 resp, err := gh.cli.Get(gh.endpointUrl + "?h=" + gh.hash)
187 if err != nil {
188 return nil, err
189@@ -80,7 +82,7 @@
190 if err != nil {
191 return nil, err
192 }
193- var parsed expected
194+ var parsed Host
195 err = json.Unmarshal(body, &parsed)
196 if err != nil {
197 return nil, ErrTemporary
198@@ -88,5 +90,5 @@
199 if len(parsed.Hosts) == 0 {
200 return nil, ErrTemporary
201 }
202- return parsed.Hosts, nil
203+ return &parsed, nil
204 }
205
206=== modified file 'client/gethosts/gethost_test.go'
207--- client/gethosts/gethost_test.go 2014-03-31 14:31:07 +0000
208+++ client/gethosts/gethost_test.go 2014-04-28 14:37:38 +0000
209@@ -45,7 +45,8 @@
210 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
211 x := r.FormValue("h")
212 b, err := json.Marshal(map[string]interface{}{
213- "hosts": []string{"http://" + x},
214+ "domain": "example.com",
215+ "hosts": []string{"http://" + x},
216 })
217 if err != nil {
218 panic(err)
219@@ -57,7 +58,8 @@
220 gh := New("foobar", ts.URL, 1*time.Second)
221 res, err := gh.Get()
222 c.Assert(err, IsNil)
223- c.Check(res, DeepEquals, []string{"http://c1130408a700afe0"})
224+ c.Check(*res, DeepEquals,
225+ Host{Domain: "example.com", Hosts: []string{"http://c1130408a700afe0"}})
226 }
227
228 func (s *getHostsSuite) TestGetTimeout(c *C) {
229@@ -97,4 +99,6 @@
230
231 scenario(http.StatusOK, "{", ErrTemporary)
232 scenario(http.StatusOK, "{}", ErrTemporary)
233+ scenario(http.StatusOK, `{"domain": "example.com"}`, ErrTemporary)
234+ scenario(http.StatusOK, `{"hosts": ["one"]}`, nil)
235 }
236
237=== modified file 'client/session/session.go'
238--- client/session/session.go 2014-04-04 13:55:00 +0000
239+++ client/session/session.go 2014-04-28 14:37:38 +0000
240@@ -38,7 +38,11 @@
241 "launchpad.net/ubuntu-push/util"
242 )
243
244-var wireVersionBytes = []byte{protocol.ProtocolWireVersion}
245+var (
246+ wireVersionBytes = []byte{protocol.ProtocolWireVersion}
247+ getAuthorization = util.GetAuthorization
248+ shouldGetAuth = false
249+)
250
251 type Notification struct {
252 TopLevel int64
253@@ -73,7 +77,7 @@
254 )
255
256 type hostGetter interface {
257- Get() ([]string, error)
258+ Get() (*gethosts.Host, error)
259 }
260
261 // ClientSessionConfig groups the client session configuration.
262@@ -115,6 +119,28 @@
263 stateP *uint32
264 ErrCh chan error
265 MsgCh chan *Notification
266+ // authorization
267+ auth string
268+ // autoredial knobs
269+ shouldDelayP *uint32
270+ lastAutoRedial time.Time
271+ redialDelay func(*ClientSession) time.Duration
272+ redialJitter func(time.Duration) time.Duration
273+ redialDelays []time.Duration
274+ redialDelaysIdx int
275+}
276+
277+func redialDelay(sess *ClientSession) time.Duration {
278+ if sess.ShouldDelay() {
279+ t := sess.redialDelays[sess.redialDelaysIdx]
280+ if len(sess.redialDelays) > sess.redialDelaysIdx+1 {
281+ sess.redialDelaysIdx++
282+ }
283+ return t + sess.redialJitter(t)
284+ } else {
285+ sess.redialDelaysIdx = 0
286+ return 0
287+ }
288 }
289
290 func NewSession(serverAddrSpec string, conf ClientSessionConfig,
291@@ -131,6 +157,7 @@
292 if hostsEndpoint != "" {
293 getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout)
294 }
295+ var shouldDelay uint32 = 0
296 sess := &ClientSession{
297 ClientSessionConfig: conf,
298 getHost: getHost,
299@@ -139,10 +166,14 @@
300 Log: log,
301 Protocolator: protocol.NewProtocol0,
302 Levels: levels,
303- TLS: &tls.Config{InsecureSkipVerify: true}, // XXX
304+ TLS: &tls.Config{},
305 stateP: &state,
306 timeSince: time.Since,
307+ shouldDelayP: &shouldDelay,
308+ redialDelay: redialDelay,
309+ redialDelays: util.Timeouts(),
310 }
311+ sess.redialJitter = sess.Jitter
312 if sess.PEM != nil {
313 cp := x509.NewCertPool()
314 ok := cp.AppendCertsFromPEM(sess.PEM)
315@@ -154,6 +185,18 @@
316 return sess, nil
317 }
318
319+func (sess *ClientSession) ShouldDelay() bool {
320+ return atomic.LoadUint32(sess.shouldDelayP) != 0
321+}
322+
323+func (sess *ClientSession) setShouldDelay() {
324+ atomic.StoreUint32(sess.shouldDelayP, uint32(1))
325+}
326+
327+func (sess *ClientSession) clearShouldDelay() {
328+ atomic.StoreUint32(sess.shouldDelayP, uint32(0))
329+}
330+
331 func (sess *ClientSession) State() ClientSessionState {
332 return ClientSessionState(atomic.LoadUint32(sess.stateP))
333 }
334@@ -180,20 +223,38 @@
335 if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime {
336 return nil
337 }
338- hosts, err := sess.getHost.Get()
339+ host, err := sess.getHost.Get()
340 if err != nil {
341 sess.Log.Errorf("getHosts: %v", err)
342 sess.setState(Error)
343 return err
344 }
345 sess.deliveryHostsTimestamp = time.Now()
346- sess.deliveryHosts = hosts
347+ sess.deliveryHosts = host.Hosts
348+ if sess.TLS != nil {
349+ sess.TLS.ServerName = host.Domain
350+ }
351 } else {
352 sess.deliveryHosts = sess.fallbackHosts
353 }
354 return nil
355 }
356
357+// checkAuthorization checks the authorization within the phone
358+func (sess *ClientSession) checkAuthorization() error {
359+ // grab the authorization string from the accounts
360+ // TODO: remove this condition when we have a way to deal with failing authorizations
361+ if shouldGetAuth {
362+ auth, err := getAuthorization()
363+ if err != nil {
364+ // For now we just log the error, as we don't want to block unauthorized users
365+ sess.Log.Errorf("unable to get the authorization token from the account: %v", err)
366+ }
367+ sess.auth = auth
368+ }
369+ return nil
370+}
371+
372 func (sess *ClientSession) resetHosts() {
373 sess.deliveryHosts = nil
374 }
375@@ -234,6 +295,7 @@
376 // connect to a server using the configuration in the ClientSession
377 // and set up the connection.
378 func (sess *ClientSession) connect() error {
379+ sess.setShouldDelay()
380 sess.startConnectionAttempt()
381 var err error
382 var conn net.Conn
383@@ -263,7 +325,12 @@
384
385 func (sess *ClientSession) AutoRedial(doneCh chan uint32) {
386 sess.stopRedial()
387+ if time.Since(sess.lastAutoRedial) < 2*time.Second {
388+ sess.setShouldDelay()
389+ }
390+ time.Sleep(sess.redialDelay(sess))
391 sess.retrier = util.NewAutoRedialer(sess)
392+ sess.lastAutoRedial = time.Now()
393 go func() { doneCh <- sess.retrier.Redial() }()
394 }
395
396@@ -289,6 +356,7 @@
397 err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"})
398 if err == nil {
399 sess.Log.Debugf("ping.")
400+ sess.clearShouldDelay()
401 } else {
402 sess.setState(Error)
403 sess.Log.Errorf("unable to pong: %s", err)
404@@ -330,6 +398,7 @@
405 sess.Log.Errorf("unable to ack broadcast: %s", err)
406 return err
407 }
408+ sess.clearShouldDelay()
409 sess.Log.Debugf("broadcast chan:%v app:%v topLevel:%d payloads:%s",
410 bcast.ChanId, bcast.AppId, bcast.TopLevel, bcast.Payloads)
411 if bcast.ChanId == protocol.SystemChannelId {
412@@ -409,10 +478,9 @@
413 return err
414 }
415 err = proto.WriteMessage(protocol.ConnectMsg{
416- Type: "connect",
417- DeviceId: sess.DeviceId,
418- // xxx get the SSO Authorization string from the phone
419- Authorization: "",
420+ Type: "connect",
421+ DeviceId: sess.DeviceId,
422+ Authorization: sess.auth,
423 Levels: levels,
424 Info: sess.Info,
425 })
426@@ -447,13 +515,15 @@
427
428 // run calls connect, and if it works it calls start, and if it works
429 // it runs loop in a goroutine, and ships its return value over ErrCh.
430-func (sess *ClientSession) run(closer func(), hostGetter, connecter, starter, looper func() error) error {
431+func (sess *ClientSession) run(closer func(), authChecker, hostGetter, connecter, starter, looper func() error) error {
432 closer()
433- err := hostGetter()
434- if err != nil {
435- return err
436- }
437- err = connecter()
438+ if err := authChecker(); err != nil {
439+ return err
440+ }
441+ if err := hostGetter(); err != nil {
442+ return err
443+ }
444+ err := connecter()
445 if err == nil {
446 err = starter()
447 if err == nil {
448@@ -483,7 +553,7 @@
449 // keep on trying.
450 panic("can't Dial() without a protocol constructor.")
451 }
452- return sess.run(sess.doClose, sess.getHosts, sess.connect, sess.start, sess.loop)
453+ return sess.run(sess.doClose, sess.checkAuthorization, sess.getHosts, sess.connect, sess.start, sess.loop)
454 }
455
456 func init() {
457
458=== modified file 'client/session/session_test.go'
459--- client/session/session_test.go 2014-04-04 13:55:00 +0000
460+++ client/session/session_test.go 2014-04-28 14:37:38 +0000
461@@ -32,12 +32,12 @@
462
463 . "launchpad.net/gocheck"
464
465+ "launchpad.net/ubuntu-push/client/gethosts"
466 "launchpad.net/ubuntu-push/client/session/levelmap"
467- //"launchpad.net/ubuntu-push/client/gethosts"
468- "launchpad.net/ubuntu-push/logger"
469 "launchpad.net/ubuntu-push/protocol"
470 helpers "launchpad.net/ubuntu-push/testing"
471 "launchpad.net/ubuntu-push/testing/condition"
472+ "launchpad.net/ubuntu-push/util"
473 )
474
475 func TestSession(t *testing.T) { TestingT(t) }
476@@ -165,14 +165,26 @@
477 /////
478
479 type clientSessionSuite struct {
480- log logger.Logger
481+ log *helpers.TestLogger
482 lvls func() (levelmap.LevelMap, error)
483 }
484
485+func (cs *clientSessionSuite) SetUpSuite(c *C) {
486+ getAuthorization = func() (string, error) {
487+ return "some auth", nil
488+ }
489+ shouldGetAuth = true
490+}
491+
492 func (cs *clientSessionSuite) SetUpTest(c *C) {
493 cs.log = helpers.NewTestLogger(c, "debug")
494 }
495
496+func (cs *clientSessionSuite) TearDownSuite(c *C) {
497+ getAuthorization = util.GetAuthorization
498+ shouldGetAuth = false
499+}
500+
501 // in-memory level map testing
502 var _ = Suite(&clientSessionSuite{lvls: levelmap.NewLevelMap})
503
504@@ -182,6 +194,7 @@
505 var _ = Suite(&clientSqlevelsSessionSuite{})
506
507 func (cs *clientSqlevelsSessionSuite) SetUpSuite(c *C) {
508+ cs.clientSessionSuite.SetUpSuite(c)
509 cs.lvls = func() (levelmap.LevelMap, error) { return levelmap.NewSqliteLevelMap(":memory:") }
510 }
511
512@@ -214,6 +227,10 @@
513 c.Check(sess, NotNil)
514 c.Check(err, IsNil)
515 c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"})
516+ // the session is happy and redial delayer is default
517+ c.Check(sess.ShouldDelay(), Equals, false)
518+ c.Check(fmt.Sprintf("%#v", sess.redialDelay), Equals, fmt.Sprintf("%#v", redialDelay))
519+ c.Check(sess.redialDelays, DeepEquals, util.Timeouts())
520 // but no root CAs set
521 c.Check(sess.TLS.RootCAs, IsNil)
522 c.Check(sess.State(), Equals, Disconnected)
523@@ -264,16 +281,17 @@
524 }
525
526 type testHostGetter struct {
527- hosts []string
528- err error
529+ domain string
530+ hosts []string
531+ err error
532 }
533
534-func (thg *testHostGetter) Get() ([]string, error) {
535- return thg.hosts, thg.err
536+func (thg *testHostGetter) Get() (*gethosts.Host, error) {
537+ return &gethosts.Host{thg.domain, thg.hosts}, thg.err
538 }
539
540 func (cs *clientSessionSuite) TestGetHostsRemote(c *C) {
541- hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}
542+ hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
543 sess := &ClientSession{getHost: hostGetter, timeSince: time.Since}
544 err := sess.getHosts()
545 c.Assert(err, IsNil)
546@@ -284,7 +302,7 @@
547 sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)
548 c.Assert(err, IsNil)
549 hostsErr := errors.New("failed")
550- hostGetter := &testHostGetter{nil, hostsErr}
551+ hostGetter := &testHostGetter{"", nil, hostsErr}
552 sess.getHost = hostGetter
553 err = sess.getHosts()
554 c.Assert(err, Equals, hostsErr)
555@@ -293,7 +311,7 @@
556 }
557
558 func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) {
559- hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}
560+ hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
561 sess := &ClientSession{
562 getHost: hostGetter,
563 ClientSessionConfig: ClientSessionConfig{
564@@ -318,7 +336,7 @@
565 }
566
567 func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) {
568- hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}
569+ hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
570 sess := &ClientSession{
571 getHost: hostGetter,
572 ClientSessionConfig: ClientSessionConfig{
573@@ -341,6 +359,18 @@
574 }
575
576 /****************************************************************
577+ checkAuthorization() tests
578+****************************************************************/
579+
580+func (cs *clientSessionSuite) TestChecksAuthorizationFromServer(c *C) {
581+ sess := &ClientSession{}
582+ c.Assert(sess.auth, Equals, "")
583+ err := sess.checkAuthorization()
584+ c.Assert(err, IsNil)
585+ c.Check(sess.auth, Equals, "some auth")
586+}
587+
588+/****************************************************************
589 startConnectionAttempt()/nextHostToTry()/started tests
590 ****************************************************************/
591
592@@ -427,7 +457,9 @@
593 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
594 c.Assert(err, IsNil)
595 sess.deliveryHosts = []string{"nowhere"}
596+ sess.clearShouldDelay()
597 err = sess.connect()
598+ c.Check(sess.ShouldDelay(), Equals, true)
599 c.Check(err, ErrorMatches, ".*connect.*address.*")
600 c.Check(sess.State(), Equals, Error)
601 }
602@@ -439,7 +471,9 @@
603 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
604 c.Assert(err, IsNil)
605 sess.deliveryHosts = []string{srv.Addr().String()}
606+ sess.clearShouldDelay()
607 err = sess.connect()
608+ c.Check(sess.ShouldDelay(), Equals, true)
609 c.Check(err, IsNil)
610 c.Check(sess.Connection, NotNil)
611 c.Check(sess.State(), Equals, Connected)
612@@ -452,7 +486,9 @@
613 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
614 c.Assert(err, IsNil)
615 sess.deliveryHosts = []string{"nowhere", srv.Addr().String()}
616+ sess.clearShouldDelay()
617 err = sess.connect()
618+ c.Check(sess.ShouldDelay(), Equals, true)
619 c.Check(err, IsNil)
620 c.Check(sess.Connection, NotNil)
621 c.Check(sess.State(), Equals, Connected)
622@@ -466,7 +502,9 @@
623 srv.Close()
624 c.Assert(err, IsNil)
625 sess.deliveryHosts = []string{srv.Addr().String()}
626+ sess.clearShouldDelay()
627 err = sess.connect()
628+ c.Check(sess.ShouldDelay(), Equals, true)
629 c.Check(err, ErrorMatches, ".*connection refused")
630 c.Check(sess.State(), Equals, Error)
631 }
632@@ -548,6 +586,27 @@
633 c.Check(<-ch, Not(Equals), 0)
634 }
635
636+func (cs *clientSessionSuite) TestAutoRedialCallsRedialDelay(c *C) {
637+ sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
638+ c.Assert(err, IsNil)
639+ flag := false
640+ sess.redialDelay = func(sess *ClientSession) time.Duration { flag = true; return 0 }
641+ sess.AutoRedial(nil)
642+ c.Check(flag, Equals, true)
643+}
644+
645+func (cs *clientSessionSuite) TestAutoRedialSetsRedialDelayIfTooQuick(c *C) {
646+ sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
647+ c.Assert(err, IsNil)
648+ sess.redialDelay = func(sess *ClientSession) time.Duration { return 0 }
649+ sess.AutoRedial(nil)
650+ c.Check(sess.ShouldDelay(), Equals, false)
651+ sess.stopRedial()
652+ sess.clearShouldDelay()
653+ sess.AutoRedial(nil)
654+ c.Check(sess.ShouldDelay(), Equals, true)
655+}
656+
657 /****************************************************************
658 handlePing() tests
659 ****************************************************************/
660@@ -594,6 +653,24 @@
661 c.Check(s.sess.State(), Equals, Error)
662 }
663
664+func (s *msgSuite) TestHandlePingClearsDelay(c *C) {
665+ s.sess.setShouldDelay()
666+ s.upCh <- nil // no error
667+ c.Check(s.sess.handlePing(), IsNil)
668+ c.Assert(len(s.downCh), Equals, 1)
669+ c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"})
670+ c.Check(s.sess.ShouldDelay(), Equals, false)
671+}
672+
673+func (s *msgSuite) TestHandlePingDoesNotClearsDelayOnError(c *C) {
674+ s.sess.setShouldDelay()
675+ s.upCh <- errors.New("Pong")
676+ c.Check(s.sess.handlePing(), NotNil)
677+ c.Assert(len(s.downCh), Equals, 1)
678+ c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"})
679+ c.Check(s.sess.ShouldDelay(), Equals, true)
680+}
681+
682 /****************************************************************
683 handleBroadcast() tests
684 ****************************************************************/
685@@ -687,6 +764,32 @@
686 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"nak"})
687 }
688
689+func (s *msgSuite) TestHandleBroadcastClearsDelay(c *C) {
690+ s.sess.setShouldDelay()
691+
692+ msg := serverMsg{"broadcast", protocol.BroadcastMsg{},
693+ protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}}
694+ go func() { s.errCh <- s.sess.handleBroadcast(&msg) }()
695+ c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
696+ s.upCh <- nil // ack ok
697+ c.Check(<-s.errCh, IsNil)
698+
699+ c.Check(s.sess.ShouldDelay(), Equals, false)
700+}
701+
702+func (s *msgSuite) TestHandleBroadcastDoesNotClearDelayOnError(c *C) {
703+ s.sess.setShouldDelay()
704+
705+ msg := serverMsg{"broadcast", protocol.BroadcastMsg{},
706+ protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}}
707+ go func() { s.errCh <- s.sess.handleBroadcast(&msg) }()
708+ c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
709+ s.upCh <- errors.New("bcast")
710+ c.Check(<-s.errCh, NotNil)
711+
712+ c.Check(s.sess.ShouldDelay(), Equals, true)
713+}
714+
715 /****************************************************************
716 handleConnBroken() tests
717 ****************************************************************/
718@@ -852,9 +955,10 @@
719
720 c.Check(takeNext(downCh), Equals, "deadline 0")
721 c.Check(takeNext(downCh), DeepEquals, protocol.ConnectMsg{
722- Type: "connect",
723- DeviceId: sess.DeviceId,
724- Levels: map[string]int64{},
725+ Type: "connect",
726+ DeviceId: sess.DeviceId,
727+ Levels: map[string]int64{},
728+ Authorization: "",
729 })
730 upCh <- errors.New("Overflow error in /dev/null")
731 err = <-errCh
732@@ -959,6 +1063,7 @@
733 msg, ok := takeNext(downCh).(protocol.ConnectMsg)
734 c.Check(ok, Equals, true)
735 c.Check(msg.DeviceId, Equals, "wah")
736+ c.Check(msg.Authorization, Equals, "")
737 c.Check(msg.Info, DeepEquals, info)
738 upCh <- nil // no error
739 upCh <- protocol.ConnAckMsg{
740@@ -975,6 +1080,22 @@
741 run() tests
742 ****************************************************************/
743
744+func (cs *clientSessionSuite) TestRunBailsIfAuthCheckFails(c *C) {
745+ sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
746+ c.Assert(err, IsNil)
747+ failure := errors.New("TestRunBailsIfAuthCheckFails")
748+ has_closed := false
749+ err = sess.run(
750+ func() { has_closed = true },
751+ func() error { return failure },
752+ nil,
753+ nil,
754+ nil,
755+ nil)
756+ c.Check(err, Equals, failure)
757+ c.Check(has_closed, Equals, true)
758+}
759+
760 func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) {
761 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
762 c.Assert(err, IsNil)
763@@ -982,6 +1103,7 @@
764 has_closed := false
765 err = sess.run(
766 func() { has_closed = true },
767+ func() error { return nil },
768 func() error { return failure },
769 nil,
770 nil,
771@@ -997,6 +1119,7 @@
772 err = sess.run(
773 func() {},
774 func() error { return nil },
775+ func() error { return nil },
776 func() error { return failure },
777 nil,
778 nil)
779@@ -1011,6 +1134,7 @@
780 func() {},
781 func() error { return nil },
782 func() error { return nil },
783+ func() error { return nil },
784 func() error { return failure },
785 nil)
786 c.Check(err, Equals, failure)
787@@ -1030,6 +1154,7 @@
788 func() error { return nil },
789 func() error { return nil },
790 func() error { return nil },
791+ func() error { return nil },
792 func() error { sess.MsgCh <- notf; return <-failureCh })
793 c.Check(err, Equals, nil)
794 // if run doesn't error it sets up the channels
795@@ -1087,9 +1212,64 @@
796
797 var (
798 dialTestTimeout = 100 * time.Millisecond
799- dialTestConf = ClientSessionConfig{ExchangeTimeout: dialTestTimeout}
800+ dialTestConf = ClientSessionConfig{
801+ ExchangeTimeout: dialTestTimeout,
802+ PEM: helpers.TestCertPEMBlock,
803+ }
804 )
805
806+func (cs *clientSessionSuite) TestDialBadServerName(c *C) {
807+ // a borked server name
808+ cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock)
809+ c.Assert(err, IsNil)
810+ tlsCfg := &tls.Config{
811+ Certificates: []tls.Certificate{cert},
812+ SessionTicketsDisabled: true,
813+ }
814+
815+ lst, err := tls.Listen("tcp", "localhost:0", tlsCfg)
816+ c.Assert(err, IsNil)
817+ // advertise
818+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
819+ b, err := json.Marshal(map[string]interface{}{
820+ "domain": "xyzzy", // <-- *** THIS *** is the bit that'll break it
821+ "hosts": []string{"nowhere", lst.Addr().String()},
822+ })
823+ if err != nil {
824+ panic(err)
825+ }
826+ w.Header().Set("Content-Type", "application/json")
827+ w.Write(b)
828+ }))
829+ defer ts.Close()
830+
831+ sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log)
832+ c.Assert(err, IsNil)
833+ tconn := &testConn{}
834+ sess.Connection = tconn
835+
836+ upCh := make(chan interface{}, 5)
837+ downCh := make(chan interface{}, 5)
838+ errCh := make(chan error, 1)
839+ proto := &testProtocol{up: upCh, down: downCh}
840+ sess.Protocolator = func(net.Conn) protocol.Protocol { return proto }
841+
842+ go func() {
843+ errCh <- sess.Dial()
844+ }()
845+
846+ srv, err := lst.Accept()
847+ c.Assert(err, IsNil)
848+
849+ // connect done
850+
851+ _, err = protocol.ReadWireFormatVersion(srv, dialTestTimeout)
852+ c.Check(err, NotNil)
853+
854+ c.Check(<-errCh, NotNil)
855+ c.Check(sess.State(), Equals, Error)
856+}
857+
858 func (cs *clientSessionSuite) TestDialWorks(c *C) {
859 // happy path thoughts
860 cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock)
861@@ -1104,7 +1284,8 @@
862 // advertise
863 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
864 b, err := json.Marshal(map[string]interface{}{
865- "hosts": []string{"nowhere", lst.Addr().String()},
866+ "domain": "localhost",
867+ "hosts": []string{"nowhere", lst.Addr().String()},
868 })
869 if err != nil {
870 panic(err)
871@@ -1223,3 +1404,38 @@
872 c.Assert(err, IsNil)
873 // connect done
874 }
875+
876+/****************************************************************
877+ redialDelay() tests
878+****************************************************************/
879+
880+func (cs *clientSessionSuite) TestShouldDelay(c *C) {
881+ sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
882+ c.Assert(err, IsNil)
883+ c.Check(sess.ShouldDelay(), Equals, false)
884+ sess.setShouldDelay()
885+ c.Check(sess.ShouldDelay(), Equals, true)
886+ sess.clearShouldDelay()
887+ c.Check(sess.ShouldDelay(), Equals, false)
888+}
889+
890+func (cs *clientSessionSuite) TestRedialDelay(c *C) {
891+ sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
892+ c.Assert(err, IsNil)
893+ sess.redialDelays = []time.Duration{17, 42}
894+ n := 0
895+ sess.redialJitter = func(time.Duration) time.Duration { n++; return 0 }
896+ // we get increasing delays while we're unhappy
897+ sess.setShouldDelay()
898+ c.Check(redialDelay(sess), Equals, time.Duration(17))
899+ c.Check(redialDelay(sess), Equals, time.Duration(42))
900+ c.Check(redialDelay(sess), Equals, time.Duration(42))
901+ // once we're happy, delays drop to 0
902+ sess.clearShouldDelay()
903+ c.Check(redialDelay(sess), Equals, time.Duration(0))
904+ // and start again from the top if we become unhappy again
905+ sess.setShouldDelay()
906+ c.Check(redialDelay(sess), Equals, time.Duration(17))
907+ // and redialJitter got called every time shouldDelay was true
908+ c.Check(n, Equals, 4)
909+}
910
911=== modified file 'config/config.go'
912--- config/config.go 2014-03-25 18:49:18 +0000
913+++ config/config.go 2014-04-28 14:37:38 +0000
914@@ -20,6 +20,7 @@
915 import (
916 "encoding/json"
917 "errors"
918+ "flag"
919 "fmt"
920 "io"
921 "io/ioutil"
922@@ -27,6 +28,7 @@
923 "os"
924 "path/filepath"
925 "reflect"
926+ "strconv"
927 "strings"
928 "time"
929 )
930@@ -118,6 +120,22 @@
931 return fillDestConfig(destValue, p1)
932 }
933
934+// FromString are config holders that can be set by parsing a string.
935+type FromString interface {
936+ SetFromString(enc string) error
937+}
938+
939+// UnmarshalJSONViaString helps unmarshalling from JSON for FromString
940+// supporting config holders.
941+func UnmarshalJSONViaString(dest FromString, b []byte) error {
942+ var enc string
943+ err := json.Unmarshal(b, &enc)
944+ if err != nil {
945+ return err
946+ }
947+ return dest.SetFromString(enc)
948+}
949+
950 // ConfigTimeDuration can hold a time.Duration in a configuration struct,
951 // that is parsed from a string as supported by time.ParseDuration.
952 type ConfigTimeDuration struct {
953@@ -125,13 +143,11 @@
954 }
955
956 func (ctd *ConfigTimeDuration) UnmarshalJSON(b []byte) error {
957- var enc string
958- var v time.Duration
959- err := json.Unmarshal(b, &enc)
960- if err != nil {
961- return err
962- }
963- v, err = time.ParseDuration(enc)
964+ return UnmarshalJSONViaString(ctd, b)
965+}
966+
967+func (ctd *ConfigTimeDuration) SetFromString(enc string) error {
968+ v, err := time.ParseDuration(enc)
969 if err != nil {
970 return err
971 }
972@@ -148,12 +164,11 @@
973 type ConfigHostPort string
974
975 func (chp *ConfigHostPort) UnmarshalJSON(b []byte) error {
976- var enc string
977- err := json.Unmarshal(b, &enc)
978- if err != nil {
979- return err
980- }
981- _, _, err = net.SplitHostPort(enc)
982+ return UnmarshalJSONViaString(chp, b)
983+}
984+
985+func (chp *ConfigHostPort) SetFromString(enc string) error {
986+ _, _, err := net.SplitHostPort(enc)
987 if err != nil {
988 return err
989 }
990@@ -198,23 +213,117 @@
991 return ioutil.ReadFile(p)
992 }
993
994-// ReadFiles reads configuration from a set of files. Uses ReadConfig internally.
995+// used to implement getting config values with flag.Parse()
996+type val struct {
997+ destField destField
998+ accu map[string]json.RawMessage
999+}
1000+
1001+func (v *val) String() string { // used to show default
1002+ return string(v.accu[v.destField.configName()])
1003+}
1004+
1005+func (v *val) IsBoolFlag() bool {
1006+ return v.destField.fld.Type.Kind() == reflect.Bool
1007+}
1008+
1009+func (v *val) marshalAsNeeded(s string) (json.RawMessage, error) {
1010+ var toMarshal interface{}
1011+ switch v.destField.dest.(type) {
1012+ case *string, FromString:
1013+ toMarshal = s
1014+ case *bool:
1015+ bit, err := strconv.ParseBool(s)
1016+ if err != nil {
1017+ return nil, err
1018+ }
1019+ toMarshal = bit
1020+ default:
1021+ return json.RawMessage(s), nil
1022+ }
1023+ return json.Marshal(toMarshal)
1024+}
1025+
1026+func (v *val) Set(s string) error {
1027+ marshalled, err := v.marshalAsNeeded(s)
1028+ if err != nil {
1029+ return err
1030+ }
1031+ v.accu[v.destField.configName()] = marshalled
1032+ return nil
1033+}
1034+
1035+func readOneConfig(accu map[string]json.RawMessage, cfgPath string) error {
1036+ r, err := os.Open(cfgPath)
1037+ if err != nil {
1038+ return err
1039+ }
1040+ defer r.Close()
1041+ err = json.NewDecoder(r).Decode(&accu)
1042+ if err != nil {
1043+ return err
1044+ }
1045+ return nil
1046+}
1047+
1048+// used to implement -cfg@=
1049+type readConfigAtVal struct {
1050+ accu map[string]json.RawMessage
1051+}
1052+
1053+func (v *readConfigAtVal) String() string {
1054+ return "<config.json>"
1055+}
1056+
1057+func (v *readConfigAtVal) Set(path string) error {
1058+ return readOneConfig(v.accu, path)
1059+}
1060+
1061+// readUsingFlags gets config values from command line flags.
1062+func readUsingFlags(accu map[string]json.RawMessage, destValue reflect.Value) error {
1063+ if flag.Parsed() {
1064+ if IgnoreParsedFlags {
1065+ return nil
1066+ }
1067+ return fmt.Errorf("too late, flags already parsed")
1068+ }
1069+ destStruct := destValue.Elem()
1070+ for destField := range traverseStruct(destStruct) {
1071+ help := destField.fld.Tag.Get("help")
1072+ flag.Var(&val{destField, accu}, destField.configName(), help)
1073+ }
1074+ flag.Var(&readConfigAtVal{accu}, "cfg@", "get config values from file")
1075+ flag.Parse()
1076+ return nil
1077+}
1078+
1079+// IgnoreParsedFlags will just have ReadFiles ignore <flags> if the
1080+// command line was already parsed.
1081+var IgnoreParsedFlags = false
1082+
1083+// ReadFiles reads configuration from a set of files. The string
1084+// "<flags>" can be used as a pseudo file-path, it will consider
1085+// command line flags, invoking flag.Parse(). Among those the flag
1086+// -cfg@=FILE can be used to get further config values from FILE.
1087 func ReadFiles(destConfig interface{}, cfgFpaths ...string) error {
1088 destValue, err := checkDestConfig("destConfig", destConfig)
1089 if err != nil {
1090 return err
1091 }
1092 // do the parsing in two phases for better error handling
1093- var p1 map[string]json.RawMessage
1094+ p1 := make(map[string]json.RawMessage)
1095 readOne := false
1096 for _, cfgPath := range cfgFpaths {
1097+ if cfgPath == "<flags>" {
1098+ err := readUsingFlags(p1, destValue)
1099+ if err != nil {
1100+ return err
1101+ }
1102+ readOne = true
1103+ continue
1104+ }
1105 if _, err := os.Stat(cfgPath); err == nil {
1106- r, err := os.Open(cfgPath)
1107- if err != nil {
1108- return err
1109- }
1110- defer r.Close()
1111- err = json.NewDecoder(r).Decode(&p1)
1112+ err := readOneConfig(p1, cfgPath)
1113 if err != nil {
1114 return err
1115 }
1116
1117=== modified file 'config/config_test.go'
1118--- config/config_test.go 2014-03-25 18:49:18 +0000
1119+++ config/config_test.go 2014-04-28 14:37:38 +0000
1120@@ -18,6 +18,9 @@
1121
1122 import (
1123 "bytes"
1124+ "encoding/json"
1125+ "flag"
1126+ "fmt"
1127 "io/ioutil"
1128 "os"
1129 "path/filepath"
1130@@ -230,3 +233,105 @@
1131 c.Check(res, DeepEquals, []string{"b", "c_list", "d"})
1132
1133 }
1134+
1135+type testConfig3 struct {
1136+ A bool
1137+ B string
1138+ C []string `json:"c_list"`
1139+ D ConfigTimeDuration `help:"duration"`
1140+ E ConfigHostPort
1141+ F string
1142+}
1143+
1144+type configFlagsSuite struct{}
1145+
1146+var _ = Suite(&configFlagsSuite{})
1147+
1148+func (s *configFlagsSuite) SetUpTest(c *C) {
1149+ flag.CommandLine = flag.NewFlagSet("cmd", flag.PanicOnError)
1150+ // supress outputs
1151+ flag.Usage = func() { flag.PrintDefaults() }
1152+ flag.CommandLine.SetOutput(ioutil.Discard)
1153+}
1154+
1155+func (s *configFlagsSuite) TestReadUsingFlags(c *C) {
1156+ os.Args = []string{"cmd", "-a=1", "-b=foo", "-c_list", `["x","y"]`, "-d", "10s", "-e=localhost:80"}
1157+ var cfg testConfig3
1158+ p := make(map[string]json.RawMessage)
1159+ err := readUsingFlags(p, reflect.ValueOf(&cfg))
1160+ c.Assert(err, IsNil)
1161+ c.Check(p, DeepEquals, map[string]json.RawMessage{
1162+ "a": json.RawMessage("true"),
1163+ "b": json.RawMessage(`"foo"`),
1164+ "c_list": json.RawMessage(`["x","y"]`),
1165+ "d": json.RawMessage(`"10s"`),
1166+ "e": json.RawMessage(`"localhost:80"`),
1167+ })
1168+}
1169+
1170+func (s *configFlagsSuite) TestReadUsingFlagsBoolError(c *C) {
1171+ os.Args = []string{"cmd", "-a=zoo"}
1172+ var cfg testConfig3
1173+ p := make(map[string]json.RawMessage)
1174+ c.Check(func() { readUsingFlags(p, reflect.ValueOf(&cfg)) }, PanicMatches, ".*invalid boolean.*-a.*")
1175+}
1176+
1177+func (s *configFlagsSuite) TestReadFilesAndFlags(c *C) {
1178+ // test <flags> pseudo file
1179+ os.Args = []string{"cmd", "-b=x"}
1180+ tmpDir := c.MkDir()
1181+ cfgPath := filepath.Join(tmpDir, "cfg.json")
1182+ err := ioutil.WriteFile(cfgPath, []byte(`{"a": 42, "c_list": ["y", "z"]}`), os.ModePerm)
1183+ c.Assert(err, IsNil)
1184+ var cfg testConfig1
1185+ err = ReadFiles(&cfg, cfgPath, "<flags>")
1186+ c.Assert(err, IsNil)
1187+ c.Check(cfg.A, Equals, 42)
1188+ c.Check(cfg.B, Equals, "x")
1189+ c.Check(cfg.C, DeepEquals, []string{"y", "z"})
1190+}
1191+
1192+func (s *configFlagsSuite) TestReadFilesAndFlagsConfigAtSupport(c *C) {
1193+ // test <flags> pseudo file
1194+ tmpDir := c.MkDir()
1195+ cfgPath := filepath.Join(tmpDir, "cfg.json")
1196+ os.Args = []string{"cmd", "-a=42", fmt.Sprintf("-cfg@=%s", cfgPath)}
1197+ err := ioutil.WriteFile(cfgPath, []byte(`{"b": "x", "c_list": ["y", "z"]}`), os.ModePerm)
1198+ c.Assert(err, IsNil)
1199+ var cfg testConfig1
1200+ err = ReadFiles(&cfg, "<flags>")
1201+ c.Assert(err, IsNil)
1202+ c.Check(cfg.A, Equals, 42)
1203+ c.Check(cfg.B, Equals, "x")
1204+ c.Check(cfg.C, DeepEquals, []string{"y", "z"})
1205+}
1206+
1207+func (s *configFlagsSuite) TestReadUsingFlagsHelp(c *C) {
1208+ os.Args = []string{"cmd", "-h"}
1209+ buf := bytes.NewBufferString("")
1210+ flag.CommandLine.Init("cmd", flag.ContinueOnError)
1211+ flag.CommandLine.SetOutput(buf)
1212+ var cfg testConfig3
1213+ p := map[string]json.RawMessage{
1214+ "d": json.RawMessage(`"2s"`),
1215+ }
1216+ readUsingFlags(p, reflect.ValueOf(&cfg))
1217+ c.Check(buf.String(), Matches, `(?s).*-cfg@=<config.json>: get config values from file\n.*-d="2s": duration.*`)
1218+}
1219+
1220+func (s *configFlagsSuite) TestReadUsingFlagsAlreadyParsed(c *C) {
1221+ os.Args = []string{"cmd"}
1222+ flag.Parse()
1223+ var cfg struct{}
1224+ p := make(map[string]json.RawMessage)
1225+ err := readUsingFlags(p, reflect.ValueOf(&cfg))
1226+ c.Assert(err, ErrorMatches, "too late, flags already parsed")
1227+ err = ReadFiles(&cfg, "<flags>")
1228+ c.Assert(err, ErrorMatches, "too late, flags already parsed")
1229+ IgnoreParsedFlags = true
1230+ defer func() {
1231+ IgnoreParsedFlags = false
1232+ }()
1233+ err = ReadFiles(&cfg, "<flags>")
1234+ c.Assert(err, IsNil)
1235+}
1236
1237=== modified file 'debian/changelog'
1238--- debian/changelog 2014-04-11 18:31:57 +0000
1239+++ debian/changelog 2014-04-28 14:37:38 +0000
1240@@ -1,3 +1,9 @@
1241+ubuntu-push (0.21-0.ubuntu1) UNRELEASED; urgency=medium
1242+
1243+ * New upstream release: first auth bits, and Qt dependency.
1244+
1245+ -- John Lenton <john.lenton@canonical.com> Tue, 15 Apr 2014 14:04:35 +0100
1246+
1247 ubuntu-push (0.2+14.04.20140411-0ubuntu1) trusty; urgency=medium
1248
1249 [ John Lenton ]
1250
1251=== modified file 'debian/control'
1252--- debian/control 2014-03-25 16:26:20 +0000
1253+++ debian/control 2014-04-28 14:37:38 +0000
1254@@ -14,6 +14,10 @@
1255 libgcrypt11-dev,
1256 libglib2.0-dev (>= 2.31.6),
1257 libwhoopsie-dev,
1258+ qtbase5-private-dev,
1259+ qtdeclarative5-dev,
1260+ libqt5opengl5-dev,
1261+ libubuntuoneauth-2.0-dev,
1262 Standards-Version: 3.9.5
1263 Homepage: http://launchpad.net/ubuntu-push
1264 Vcs-Bzr: lp:ubuntu-push
1265
1266=== modified file 'debian/rules'
1267--- debian/rules 2014-03-24 12:22:55 +0000
1268+++ debian/rules 2014-04-28 14:37:38 +0000
1269@@ -2,9 +2,12 @@
1270 # -*- makefile -*-
1271
1272 export DH_GOPKG := launchpad.net/ubuntu-push
1273-export DEB_BUILD_OPTIONS := nostrip
1274 export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR)
1275
1276+override_dh_auto_build:
1277+ cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1) && tar xvzf ../../externals.tgz
1278+ dh_auto_build --buildsystem=golang
1279+
1280 override_dh_install:
1281 dh_install -Xusr/bin/cmd -Xusr/bin/dev --fail-missing
1282
1283
1284=== modified file 'dependencies.tsv'
1285--- dependencies.tsv 2014-03-12 13:23:26 +0000
1286+++ dependencies.tsv 2014-04-28 14:37:38 +0000
1287@@ -2,3 +2,5 @@
1288 launchpad.net/go-dbus/v1 bzr james@jamesh.id.au-20140206110213-pbzcr6ucaz3rqmnw 125
1289 launchpad.net/go-xdg/v0 bzr john.lenton@canonical.com-20140208094800-gubd5md7cro3mtxa 10
1290 launchpad.net/gocheck bzr gustavo@niemeyer.net-20140127131816-zshobk1qqme626xw 86
1291+gopkg.in/qml.v0 git master 8adbc8c2bf2da9f609df366683ad0f47a89c3d49
1292+gopkg.in/niemeyer/uoneauth.v1 git v1 0758ba882a143ad2862dbcac85a7ca145750b640
1293
1294=== added file 'externals.tgz'
1295Binary files externals.tgz 1970-01-01 00:00:00 +0000 and externals.tgz 2014-04-28 14:37:38 +0000 differ
1296=== modified file 'logger/logger.go'
1297--- logger/logger.go 2014-02-24 10:27:38 +0000
1298+++ logger/logger.go 2014-04-28 14:37:38 +0000
1299@@ -23,6 +23,8 @@
1300 "log"
1301 "os"
1302 "runtime"
1303+
1304+ "launchpad.net/ubuntu-push/config"
1305 )
1306
1307 // Logger is a simple logger interface with logging at levels.
1308@@ -119,3 +121,28 @@
1309 lg.outputFunc(2, fmt.Sprintf("DEBUG "+format, v...))
1310 }
1311 }
1312+
1313+// config bits
1314+
1315+// ConfigLogLevel can hold a log level in a configuration struct.
1316+type ConfigLogLevel string
1317+
1318+func (cll *ConfigLogLevel) ConfigFromJSONString() {}
1319+
1320+func (cll *ConfigLogLevel) UnmarshalJSON(b []byte) error {
1321+ return config.UnmarshalJSONViaString(cll, b)
1322+}
1323+
1324+func (cll *ConfigLogLevel) SetFromString(enc string) error {
1325+ _, ok := levelToNLevel[enc]
1326+ if !ok {
1327+ return fmt.Errorf("not a log level: %s", enc)
1328+ }
1329+ *cll = ConfigLogLevel(enc)
1330+ return nil
1331+}
1332+
1333+// Level returns the log level string held in cll.
1334+func (cll ConfigLogLevel) Level() string {
1335+ return string(cll)
1336+}
1337
1338=== modified file 'logger/logger_test.go'
1339--- logger/logger_test.go 2014-02-10 22:51:43 +0000
1340+++ logger/logger_test.go 2014-04-28 14:37:38 +0000
1341@@ -25,6 +25,8 @@
1342 "testing"
1343
1344 . "launchpad.net/gocheck"
1345+
1346+ "launchpad.net/ubuntu-push/config"
1347 )
1348
1349 func TestLogger(t *testing.T) { TestingT(t) }
1350@@ -138,3 +140,26 @@
1351 logger.Output(1, "foobaz")
1352 c.Check(buf.String(), Matches, "logger_test.go:[0-9]+: foobar\nlogger_test.go:[0-9]+: foobaz\n")
1353 }
1354+
1355+type testLogLevelConfig struct {
1356+ Lvl ConfigLogLevel
1357+}
1358+
1359+func (s *loggerSuite) TestReadConfigLogLevel(c *C) {
1360+ buf := bytes.NewBufferString(`{"lvl": "debug"}`)
1361+ var cfg testLogLevelConfig
1362+ err := config.ReadConfig(buf, &cfg)
1363+ c.Assert(err, IsNil)
1364+ c.Check(cfg.Lvl.Level(), Equals, "debug")
1365+}
1366+
1367+func (s *loggerSuite) TestReadConfigLogLevelErrors(c *C) {
1368+ var cfg testLogLevelConfig
1369+ checkError := func(jsonCfg string, expectedError string) {
1370+ buf := bytes.NewBufferString(jsonCfg)
1371+ err := config.ReadConfig(buf, &cfg)
1372+ c.Check(err, ErrorMatches, expectedError)
1373+ }
1374+ checkError(`{"lvl": 1}`, "lvl:.*type string")
1375+ checkError(`{"lvl": "foo"}`, "lvl: not a log level: foo")
1376+}
1377
1378=== modified file 'protocol/messages.go'
1379--- protocol/messages.go 2014-04-04 13:54:45 +0000
1380+++ protocol/messages.go 2014-04-28 14:37:38 +0000
1381@@ -54,6 +54,14 @@
1382 Split() (done bool)
1383 }
1384
1385+// OnewayMsg are messages that are not to be followed by a response,
1386+// after sending them the session either aborts or continues.
1387+type OnewayMsg interface {
1388+ SplittableMsg
1389+ // continue session after the message?
1390+ OnewayContinue() bool
1391+}
1392+
1393 // CONNBROKEN message, server side is breaking the connection for reason.
1394 type ConnBrokenMsg struct {
1395 Type string `json:"T"`
1396@@ -65,11 +73,35 @@
1397 return true
1398 }
1399
1400+func (m *ConnBrokenMsg) OnewayContinue() bool {
1401+ return false
1402+}
1403+
1404 // CONNBROKEN reasons
1405 const (
1406 BrokenHostMismatch = "host-mismatch"
1407 )
1408
1409+// CONNWARN message, server side is warning about partial functionality
1410+// because reason.
1411+type ConnWarnMsg struct {
1412+ Type string `json:"T"`
1413+ // reason
1414+ Reason string
1415+}
1416+
1417+func (m *ConnWarnMsg) Split() bool {
1418+ return true
1419+}
1420+func (m *ConnWarnMsg) OnewayContinue() bool {
1421+ return true
1422+}
1423+
1424+// CONNWARN reasons
1425+const (
1426+ WarnUnauthorized = "unauthorized"
1427+)
1428+
1429 // PING/PONG messages
1430 type PingPongMsg struct {
1431 Type string `json:"T"`
1432
1433=== modified file 'protocol/messages_test.go'
1434--- protocol/messages_test.go 2014-04-04 13:19:10 +0000
1435+++ protocol/messages_test.go 2014-04-28 14:37:38 +0000
1436@@ -104,6 +104,14 @@
1437 c.Check(b.splitting, Equals, 0)
1438 }
1439
1440-func (s *messagesSuite) TestSplitConnBrokenMsg(c *C) {
1441- c.Check((&ConnBrokenMsg{}).Split(), Equals, true)
1442+func (s *messagesSuite) TestConnBrokenMsg(c *C) {
1443+ m := &ConnBrokenMsg{}
1444+ c.Check(m.Split(), Equals, true)
1445+ c.Check(m.OnewayContinue(), Equals, false)
1446+}
1447+
1448+func (s *messagesSuite) TestConnWarnMsg(c *C) {
1449+ m := &ConnWarnMsg{}
1450+ c.Check(m.Split(), Equals, true)
1451+ c.Check(m.OnewayContinue(), Equals, true)
1452 }
1453
1454=== modified file 'protocol/state-diag-client.gv'
1455--- protocol/state-diag-client.gv 2014-01-16 20:07:13 +0000
1456+++ protocol/state-diag-client.gv 2014-04-28 14:37:38 +0000
1457@@ -2,7 +2,7 @@
1458 label = "State diagram for client";
1459 size="12,6";
1460 rankdir=LR;
1461- node [shape = doublecircle]; pingTimeout;
1462+ node [shape = doublecircle]; pingTimeout; connBroken;
1463 node [shape = circle];
1464 start1 -> start2 [ label = "Write wire version" ];
1465 start2 -> start3 [ label = "Write CONNECT" ];
1466@@ -13,4 +13,7 @@
1467 broadcast -> loop [label = "Write ACK"];
1468 loop -> pingTimeout [
1469 label = "Elapsed ping interval + exchange interval"];
1470+ loop -> connBroken [label = "Read CONNBROKEN"];
1471+ loop -> warn [label = "Read CONNWARN"];
1472+ warn -> loop;
1473 }
1474
1475=== modified file 'protocol/state-diag-client.svg'
1476--- protocol/state-diag-client.svg 2014-01-16 19:37:57 +0000
1477+++ protocol/state-diag-client.svg 2014-04-28 14:37:38 +0000
1478@@ -4,95 +4,123 @@
1479 <!-- Generated by graphviz version 2.26.3 (20100126.1600)
1480 -->
1481 <!-- Title: state_diagram_client Pages: 1 -->
1482-<svg width="864pt" height="279pt"
1483- viewBox="0.00 0.00 864.00 278.89" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
1484-<g id="graph1" class="graph" transform="scale(0.683544 0.683544) rotate(0) translate(4 404)">
1485+<svg width="822pt" height="432pt"
1486+ viewBox="0.00 0.00 822.36 432.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
1487+<g id="graph1" class="graph" transform="scale(0.650602 0.650602) rotate(0) translate(4 660)">
1488 <title>state_diagram_client</title>
1489-<polygon fill="white" stroke="white" points="-4,5 -4,-404 1261,-404 1261,5 -4,5"/>
1490+<polygon fill="white" stroke="white" points="-4,5 -4,-660 1261,-660 1261,5 -4,5"/>
1491 <text text-anchor="middle" x="628" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for client</text>
1492 <!-- pingTimeout -->
1493 <g id="node1" class="node"><title>pingTimeout</title>
1494-<ellipse fill="none" stroke="black" cx="1180" cy="-324" rx="72.1249" ry="72.1249"/>
1495-<ellipse fill="none" stroke="black" cx="1180" cy="-324" rx="76.1249" ry="76.1249"/>
1496-<text text-anchor="middle" x="1180" y="-320.4" font-family="Times Roman,serif" font-size="14.00">pingTimeout</text>
1497+<ellipse fill="none" stroke="black" cx="1180" cy="-580" rx="72.1249" ry="72.1249"/>
1498+<ellipse fill="none" stroke="black" cx="1180" cy="-580" rx="76.1249" ry="76.1249"/>
1499+<text text-anchor="middle" x="1180" y="-576.4" font-family="Times Roman,serif" font-size="14.00">pingTimeout</text>
1500+</g>
1501+<!-- connBroken -->
1502+<g id="node2" class="node"><title>connBroken</title>
1503+<ellipse fill="none" stroke="black" cx="1180" cy="-413" rx="68.8251" ry="69.2965"/>
1504+<ellipse fill="none" stroke="black" cx="1180" cy="-413" rx="72.7978" ry="73.2965"/>
1505+<text text-anchor="middle" x="1180" y="-409.4" font-family="Times Roman,serif" font-size="14.00">connBroken</text>
1506 </g>
1507 <!-- start1 -->
1508-<g id="node2" class="node"><title>start1</title>
1509-<ellipse fill="none" stroke="black" cx="42" cy="-166" rx="41.2167" ry="41.7193"/>
1510-<text text-anchor="middle" x="42" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start1</text>
1511+<g id="node3" class="node"><title>start1</title>
1512+<ellipse fill="none" stroke="black" cx="42" cy="-231" rx="41.2167" ry="41.7193"/>
1513+<text text-anchor="middle" x="42" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start1</text>
1514 </g>
1515 <!-- start2 -->
1516-<g id="node4" class="node"><title>start2</title>
1517-<ellipse fill="none" stroke="black" cx="292" cy="-166" rx="41.2167" ry="41.7193"/>
1518-<text text-anchor="middle" x="292" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start2</text>
1519+<g id="node5" class="node"><title>start2</title>
1520+<ellipse fill="none" stroke="black" cx="292" cy="-231" rx="41.2167" ry="41.7193"/>
1521+<text text-anchor="middle" x="292" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start2</text>
1522 </g>
1523 <!-- start1&#45;&gt;start2 -->
1524 <g id="edge2" class="edge"><title>start1&#45;&gt;start2</title>
1525-<path fill="none" stroke="black" d="M83.5631,-166C126.547,-166 193.757,-166 240.181,-166"/>
1526-<polygon fill="black" stroke="black" points="240.338,-169.5 250.338,-166 240.338,-162.5 240.338,-169.5"/>
1527-<text text-anchor="middle" x="167" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Write wire version</text>
1528+<path fill="none" stroke="black" d="M83.5631,-231C126.547,-231 193.757,-231 240.181,-231"/>
1529+<polygon fill="black" stroke="black" points="240.338,-234.5 250.338,-231 240.338,-227.5 240.338,-234.5"/>
1530+<text text-anchor="middle" x="167" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Write wire version</text>
1531 </g>
1532 <!-- start3 -->
1533-<g id="node6" class="node"><title>start3</title>
1534-<ellipse fill="none" stroke="black" cx="526" cy="-166" rx="41.2167" ry="41.7193"/>
1535-<text text-anchor="middle" x="526" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start3</text>
1536+<g id="node7" class="node"><title>start3</title>
1537+<ellipse fill="none" stroke="black" cx="526" cy="-231" rx="41.2167" ry="41.7193"/>
1538+<text text-anchor="middle" x="526" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start3</text>
1539 </g>
1540 <!-- start2&#45;&gt;start3 -->
1541 <g id="edge4" class="edge"><title>start2&#45;&gt;start3</title>
1542-<path fill="none" stroke="black" d="M333.565,-166C372.875,-166 431.992,-166 474.321,-166"/>
1543-<polygon fill="black" stroke="black" points="474.429,-169.5 484.429,-166 474.429,-162.5 474.429,-169.5"/>
1544-<text text-anchor="middle" x="409" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Write CONNECT</text>
1545+<path fill="none" stroke="black" d="M333.565,-231C372.875,-231 431.992,-231 474.321,-231"/>
1546+<polygon fill="black" stroke="black" points="474.429,-234.5 484.429,-231 474.429,-227.5 474.429,-234.5"/>
1547+<text text-anchor="middle" x="409" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Write CONNECT</text>
1548 </g>
1549 <!-- loop -->
1550-<g id="node8" class="node"><title>loop</title>
1551-<ellipse fill="none" stroke="black" cx="746" cy="-166" rx="31.8198" ry="31.8198"/>
1552-<text text-anchor="middle" x="746" y="-162.4" font-family="Times Roman,serif" font-size="14.00">loop</text>
1553+<g id="node9" class="node"><title>loop</title>
1554+<ellipse fill="none" stroke="black" cx="746" cy="-231" rx="31.8198" ry="31.8198"/>
1555+<text text-anchor="middle" x="746" y="-227.4" font-family="Times Roman,serif" font-size="14.00">loop</text>
1556 </g>
1557 <!-- start3&#45;&gt;loop -->
1558 <g id="edge6" class="edge"><title>start3&#45;&gt;loop</title>
1559-<path fill="none" stroke="black" d="M567.639,-166C606.633,-166 664.616,-166 703.793,-166"/>
1560-<polygon fill="black" stroke="black" points="703.818,-169.5 713.818,-166 703.818,-162.5 703.818,-169.5"/>
1561-<text text-anchor="middle" x="641" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Read CONNACK</text>
1562+<path fill="none" stroke="black" d="M567.639,-231C606.633,-231 664.616,-231 703.793,-231"/>
1563+<polygon fill="black" stroke="black" points="703.818,-234.5 713.818,-231 703.818,-227.5 703.818,-234.5"/>
1564+<text text-anchor="middle" x="641" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Read CONNACK</text>
1565 </g>
1566 <!-- loop&#45;&gt;pingTimeout -->
1567 <g id="edge16" class="edge"><title>loop&#45;&gt;pingTimeout</title>
1568-<path fill="none" stroke="black" d="M763.666,-192.937C772.211,-204.042 783.361,-216.128 796,-224 888.06,-281.339 1012.12,-305.973 1094,-316.443"/>
1569-<polygon fill="black" stroke="black" points="1093.67,-319.928 1104.02,-317.68 1094.53,-312.981 1093.67,-319.928"/>
1570-<text text-anchor="middle" x="941" y="-319.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval + exchange interval</text>
1571+<path fill="none" stroke="black" d="M750.211,-262.971C757.458,-313.528 773.689,-408.79 796,-434 872.806,-520.784 1006.81,-556.22 1094.46,-570.528"/>
1572+<polygon fill="black" stroke="black" points="1093.96,-573.992 1104.39,-572.09 1095.05,-567.078 1093.96,-573.992"/>
1573+<text text-anchor="middle" x="941" y="-572.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval + exchange interval</text>
1574+</g>
1575+<!-- loop&#45;&gt;connBroken -->
1576+<g id="edge18" class="edge"><title>loop&#45;&gt;connBroken</title>
1577+<path fill="none" stroke="black" d="M755.1,-261.824C762.755,-282.438 775.756,-308.526 796,-324 883.382,-390.791 1012.39,-408.797 1096.33,-412.948"/>
1578+<polygon fill="black" stroke="black" points="1096.19,-416.445 1106.33,-413.388 1096.5,-409.452 1096.19,-416.445"/>
1579+<text text-anchor="middle" x="941" y="-417.4" font-family="Times Roman,serif" font-size="14.00">Read CONNBROKEN</text>
1580 </g>
1581 <!-- pong -->
1582-<g id="node10" class="node"><title>pong</title>
1583-<ellipse fill="none" stroke="black" cx="1180" cy="-195" rx="34.8574" ry="35.3553"/>
1584-<text text-anchor="middle" x="1180" y="-191.4" font-family="Times Roman,serif" font-size="14.00">pong</text>
1585+<g id="node11" class="node"><title>pong</title>
1586+<ellipse fill="none" stroke="black" cx="1180" cy="-287" rx="34.8574" ry="35.3553"/>
1587+<text text-anchor="middle" x="1180" y="-283.4" font-family="Times Roman,serif" font-size="14.00">pong</text>
1588 </g>
1589 <!-- loop&#45;&gt;pong -->
1590 <g id="edge8" class="edge"><title>loop&#45;&gt;pong</title>
1591-<path fill="none" stroke="black" d="M775.392,-179.044C782.046,-181.465 789.167,-183.653 796,-185 916.362,-208.722 1062.02,-203.515 1134.48,-198.706"/>
1592-<polygon fill="black" stroke="black" points="1134.89,-202.186 1144.62,-198.003 1134.4,-195.203 1134.89,-202.186"/>
1593-<text text-anchor="middle" x="941" y="-207.4" font-family="Times Roman,serif" font-size="14.00">Read PING</text>
1594+<path fill="none" stroke="black" d="M768.467,-253.959C776.476,-260.698 786.005,-267.259 796,-271 911.696,-314.31 1060.9,-303.343 1134.62,-293.955"/>
1595+<polygon fill="black" stroke="black" points="1135.49,-297.371 1144.94,-292.588 1134.57,-290.432 1135.49,-297.371"/>
1596+<text text-anchor="middle" x="941" y="-307.4" font-family="Times Roman,serif" font-size="14.00">Read PING</text>
1597 </g>
1598 <!-- broadcast -->
1599-<g id="node12" class="node"><title>broadcast</title>
1600-<ellipse fill="none" stroke="black" cx="1180" cy="-84" rx="58.1882" ry="58.6899"/>
1601-<text text-anchor="middle" x="1180" y="-80.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>
1602+<g id="node13" class="node"><title>broadcast</title>
1603+<ellipse fill="none" stroke="black" cx="1180" cy="-176" rx="58.1882" ry="58.6899"/>
1604+<text text-anchor="middle" x="1180" y="-172.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>
1605 </g>
1606 <!-- loop&#45;&gt;broadcast -->
1607 <g id="edge10" class="edge"><title>loop&#45;&gt;broadcast</title>
1608-<path fill="none" stroke="black" d="M770.52,-145.1C778.217,-139.607 787.053,-134.301 796,-131 917.482,-86.1746 957.924,-122.075 1086,-103 1094.61,-101.717 1103.63,-100.165 1112.53,-98.5074"/>
1609-<polygon fill="black" stroke="black" points="1113.34,-101.917 1122.5,-96.5998 1112.02,-95.0419 1113.34,-101.917"/>
1610-<text text-anchor="middle" x="941" y="-136.4" font-family="Times Roman,serif" font-size="14.00">Read BROADCAST</text>
1611+<path fill="none" stroke="black" d="M775.45,-218.228C782.1,-215.791 789.205,-213.528 796,-212 922.145,-183.64 957.464,-202.973 1086,-189 1094.36,-188.091 1103.12,-187.028 1111.79,-185.909"/>
1612+<polygon fill="black" stroke="black" points="1112.44,-189.353 1121.9,-184.574 1111.53,-182.413 1112.44,-189.353"/>
1613+<text text-anchor="middle" x="941" y="-217.4" font-family="Times Roman,serif" font-size="14.00">Read BROADCAST</text>
1614+</g>
1615+<!-- warn -->
1616+<g id="node19" class="node"><title>warn</title>
1617+<ellipse fill="none" stroke="black" cx="1180" cy="-63" rx="36.7696" ry="36.7696"/>
1618+<text text-anchor="middle" x="1180" y="-59.4" font-family="Times Roman,serif" font-size="14.00">warn</text>
1619+</g>
1620+<!-- loop&#45;&gt;warn -->
1621+<g id="edge20" class="edge"><title>loop&#45;&gt;warn</title>
1622+<path fill="none" stroke="black" d="M753.357,-199.767C760.401,-177.027 773.396,-147.441 796,-131 901.425,-54.3166 958.242,-112.935 1086,-87 1101.84,-83.7841 1119.02,-79.6061 1134.3,-75.6396"/>
1623+<polygon fill="black" stroke="black" points="1135.26,-79.0068 1144.04,-73.0757 1133.48,-72.2376 1135.26,-79.0068"/>
1624+<text text-anchor="middle" x="941" y="-136.4" font-family="Times Roman,serif" font-size="14.00">Read CONNWARN</text>
1625 </g>
1626 <!-- pong&#45;&gt;loop -->
1627 <g id="edge12" class="edge"><title>pong&#45;&gt;loop</title>
1628-<path fill="none" stroke="black" d="M1147.19,-180.867C1129.44,-173.986 1106.92,-166.463 1086,-163 980.081,-145.465 853.051,-154.36 788.368,-160.981"/>
1629-<polygon fill="black" stroke="black" points="787.736,-157.528 778.16,-162.06 788.472,-164.489 787.736,-157.528"/>
1630-<text text-anchor="middle" x="941" y="-168.4" font-family="Times Roman,serif" font-size="14.00">Write PONG</text>
1631+<path fill="none" stroke="black" d="M1148.22,-271.079C1130.39,-262.942 1107.48,-253.77 1086,-249 1030.54,-236.684 866.695,-232.715 788.482,-231.502"/>
1632+<polygon fill="black" stroke="black" points="788.085,-227.996 778.035,-231.348 787.982,-234.995 788.085,-227.996"/>
1633+<text text-anchor="middle" x="941" y="-254.4" font-family="Times Roman,serif" font-size="14.00">Write PONG</text>
1634 </g>
1635 <!-- broadcast&#45;&gt;loop -->
1636 <g id="edge14" class="edge"><title>broadcast&#45;&gt;loop</title>
1637-<path fill="none" stroke="black" d="M1123.8,-67.0114C1044.83,-46.6166 899.156,-22.0001 796,-81 778.946,-90.7538 767.135,-108.842 759.293,-125.833"/>
1638-<polygon fill="black" stroke="black" points="756.044,-124.528 755.336,-135.099 762.482,-127.277 756.044,-124.528"/>
1639-<text text-anchor="middle" x="941" y="-86.4" font-family="Times Roman,serif" font-size="14.00">Write ACK</text>
1640+<path fill="none" stroke="black" d="M1121.7,-168.205C1028.72,-156.837 851.665,-139.849 796,-167 784,-172.853 774.037,-183.132 766.245,-193.762"/>
1641+<polygon fill="black" stroke="black" points="763.182,-192.043 760.465,-202.284 768.975,-195.973 763.182,-192.043"/>
1642+<text text-anchor="middle" x="941" y="-172.4" font-family="Times Roman,serif" font-size="14.00">Write ACK</text>
1643+</g>
1644+<!-- warn&#45;&gt;loop -->
1645+<g id="edge22" class="edge"><title>warn&#45;&gt;loop</title>
1646+<path fill="none" stroke="black" d="M1144.07,-53.3553C1070.4,-35.8873 900.397,-7.71825 796,-87 779.313,-99.6722 764.14,-151.763 754.991,-189.659"/>
1647+<polygon fill="black" stroke="black" points="751.574,-188.904 752.686,-199.44 758.387,-190.51 751.574,-188.904"/>
1648 </g>
1649 </g>
1650 </svg>
1651
1652=== modified file 'protocol/state-diag-session.gv'
1653--- protocol/state-diag-session.gv 2014-01-16 20:07:13 +0000
1654+++ protocol/state-diag-session.gv 2014-04-28 14:37:38 +0000
1655@@ -2,6 +2,7 @@
1656 label = "State diagram for session";
1657 size="12,6";
1658 rankdir=LR;
1659+ node [shape = doublecircle]; stop;
1660 node [shape = circle];
1661 start1 -> start2 [ label = "Read wire version" ];
1662 start2 -> start3 [ label = "Read CONNECT" ];
1663@@ -17,4 +18,13 @@
1664 split_broadcast -> split_ack_wait [label = "Write split BROADCAST"];
1665 split_ack_wait -> split_broadcast [label = "Read ACK"];
1666 split_broadcast -> loop [label = "All split msgs written"];
1667+ // other
1668+ loop -> conn_broken [label = "Receive connbroken request"];
1669+ loop -> conn_warn [label = "Receive connwarn request"];
1670+ conn_broken -> stop [label = "Write CONNBROKEN"];
1671+ conn_warn -> loop [label = "Write CONNWARN"];
1672+ // timeouts
1673+ ack_wait -> stop [label = "Elapsed exhange timeout"];
1674+ split_ack_wait -> stop [label = "Elapsed exhange timeout"];
1675+ pong_wait -> stop [label = "Elapsed exhange timeout"];
1676 }
1677
1678=== modified file 'protocol/state-diag-session.svg'
1679--- protocol/state-diag-session.svg 2014-01-16 19:37:57 +0000
1680+++ protocol/state-diag-session.svg 2014-04-28 14:37:38 +0000
1681@@ -4,139 +4,197 @@
1682 <!-- Generated by graphviz version 2.26.3 (20100126.1600)
1683 -->
1684 <!-- Title: state_diagram_session Pages: 1 -->
1685-<svg width="864pt" height="208pt"
1686- viewBox="0.00 0.00 864.00 207.94" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
1687-<g id="graph1" class="graph" transform="scale(0.435923 0.435923) rotate(0) translate(4 473)">
1688+<svg width="864pt" height="266pt"
1689+ viewBox="0.00 0.00 864.00 265.73" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
1690+<g id="graph1" class="graph" transform="scale(0.367035 0.367035) rotate(0) translate(4 720)">
1691 <title>state_diagram_session</title>
1692-<polygon fill="white" stroke="white" points="-4,5 -4,-473 1979,-473 1979,5 -4,5"/>
1693-<text text-anchor="middle" x="987" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for session</text>
1694+<polygon fill="white" stroke="white" points="-4,5 -4,-720 2351,-720 2351,5 -4,5"/>
1695+<text text-anchor="middle" x="1173" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for session</text>
1696+<!-- stop -->
1697+<g id="node1" class="node"><title>stop</title>
1698+<ellipse fill="none" stroke="black" cx="2309" cy="-335" rx="32.0813" ry="32.5269"/>
1699+<ellipse fill="none" stroke="black" cx="2309" cy="-335" rx="36.0265" ry="36.5269"/>
1700+<text text-anchor="middle" x="2309" y="-331.4" font-family="Times Roman,serif" font-size="14.00">stop</text>
1701+</g>
1702 <!-- start1 -->
1703-<g id="node1" class="node"><title>start1</title>
1704-<ellipse fill="none" stroke="black" cx="42" cy="-294" rx="41.2167" ry="41.7193"/>
1705-<text text-anchor="middle" x="42" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start1</text>
1706+<g id="node2" class="node"><title>start1</title>
1707+<ellipse fill="none" stroke="black" cx="42" cy="-395" rx="41.2167" ry="41.7193"/>
1708+<text text-anchor="middle" x="42" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start1</text>
1709 </g>
1710 <!-- start2 -->
1711-<g id="node3" class="node"><title>start2</title>
1712-<ellipse fill="none" stroke="black" cx="286" cy="-294" rx="41.2167" ry="41.7193"/>
1713-<text text-anchor="middle" x="286" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start2</text>
1714+<g id="node4" class="node"><title>start2</title>
1715+<ellipse fill="none" stroke="black" cx="286" cy="-395" rx="41.2167" ry="41.7193"/>
1716+<text text-anchor="middle" x="286" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start2</text>
1717 </g>
1718 <!-- start1&#45;&gt;start2 -->
1719 <g id="edge2" class="edge"><title>start1&#45;&gt;start2</title>
1720-<path fill="none" stroke="black" d="M83.6679,-294C125.213,-294 189.13,-294 233.981,-294"/>
1721-<polygon fill="black" stroke="black" points="234.096,-297.5 244.096,-294 234.096,-290.5 234.096,-297.5"/>
1722-<text text-anchor="middle" x="164" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Read wire version</text>
1723+<path fill="none" stroke="black" d="M83.6679,-395C125.213,-395 189.13,-395 233.981,-395"/>
1724+<polygon fill="black" stroke="black" points="234.096,-398.5 244.096,-395 234.096,-391.5 234.096,-398.5"/>
1725+<text text-anchor="middle" x="164" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Read wire version</text>
1726 </g>
1727 <!-- start3 -->
1728-<g id="node5" class="node"><title>start3</title>
1729-<ellipse fill="none" stroke="black" cx="516" cy="-294" rx="41.2167" ry="41.7193"/>
1730-<text text-anchor="middle" x="516" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start3</text>
1731+<g id="node6" class="node"><title>start3</title>
1732+<ellipse fill="none" stroke="black" cx="537" cy="-395" rx="41.2167" ry="41.7193"/>
1733+<text text-anchor="middle" x="537" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start3</text>
1734 </g>
1735 <!-- start2&#45;&gt;start3 -->
1736 <g id="edge4" class="edge"><title>start2&#45;&gt;start3</title>
1737-<path fill="none" stroke="black" d="M327.651,-294C365.959,-294 422.903,-294 464.145,-294"/>
1738-<polygon fill="black" stroke="black" points="464.271,-297.5 474.271,-294 464.271,-290.5 464.271,-297.5"/>
1739-<text text-anchor="middle" x="401" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Read CONNECT</text>
1740+<path fill="none" stroke="black" d="M327.729,-395C370.886,-395 438.364,-395 484.973,-395"/>
1741+<polygon fill="black" stroke="black" points="485.171,-398.5 495.171,-395 485.171,-391.5 485.171,-398.5"/>
1742+<text text-anchor="middle" x="401" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Read CONNECT</text>
1743 </g>
1744 <!-- loop -->
1745-<g id="node7" class="node"><title>loop</title>
1746-<ellipse fill="none" stroke="black" cx="740" cy="-294" rx="31.8198" ry="31.8198"/>
1747-<text text-anchor="middle" x="740" y="-290.4" font-family="Times Roman,serif" font-size="14.00">loop</text>
1748+<g id="node8" class="node"><title>loop</title>
1749+<ellipse fill="none" stroke="black" cx="790" cy="-395" rx="31.8198" ry="31.8198"/>
1750+<text text-anchor="middle" x="790" y="-391.4" font-family="Times Roman,serif" font-size="14.00">loop</text>
1751 </g>
1752 <!-- start3&#45;&gt;loop -->
1753 <g id="edge6" class="edge"><title>start3&#45;&gt;loop</title>
1754-<path fill="none" stroke="black" d="M557.608,-294C597.53,-294 657.517,-294 697.677,-294"/>
1755-<polygon fill="black" stroke="black" points="697.687,-297.5 707.687,-294 697.687,-290.5 697.687,-297.5"/>
1756-<text text-anchor="middle" x="633" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Write CONNACK</text>
1757+<path fill="none" stroke="black" d="M578.778,-395C625.49,-395 700.728,-395 747.665,-395"/>
1758+<polygon fill="black" stroke="black" points="747.805,-398.5 757.805,-395 747.805,-391.5 747.805,-398.5"/>
1759+<text text-anchor="middle" x="675" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Write CONNACK</text>
1760 </g>
1761 <!-- ping -->
1762-<g id="node9" class="node"><title>ping</title>
1763-<ellipse fill="none" stroke="black" cx="1063" cy="-416" rx="32.0265" ry="32.5269"/>
1764-<text text-anchor="middle" x="1063" y="-412.4" font-family="Times Roman,serif" font-size="14.00">ping</text>
1765+<g id="node10" class="node"><title>ping</title>
1766+<ellipse fill="none" stroke="black" cx="1135" cy="-593" rx="32.0265" ry="32.5269"/>
1767+<text text-anchor="middle" x="1135" y="-589.4" font-family="Times Roman,serif" font-size="14.00">ping</text>
1768 </g>
1769 <!-- loop&#45;&gt;ping -->
1770 <g id="edge8" class="edge"><title>loop&#45;&gt;ping</title>
1771-<path fill="none" stroke="black" d="M754.564,-322.853C763.046,-336.78 775.035,-352.491 790,-362 861.597,-407.491 963.396,-415.983 1020.29,-416.829"/>
1772-<polygon fill="black" stroke="black" points="1020.35,-420.33 1030.38,-416.906 1020.4,-413.33 1020.35,-420.33"/>
1773-<text text-anchor="middle" x="881" y="-418.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval</text>
1774+<path fill="none" stroke="black" d="M800.39,-425.317C809.609,-448.006 825.187,-478.237 848,-497 920.691,-556.785 1032.18,-579.907 1092.58,-588.403"/>
1775+<polygon fill="black" stroke="black" points="1092.15,-591.877 1102.53,-589.734 1093.08,-584.939 1092.15,-591.877"/>
1776+<text text-anchor="middle" x="946" y="-583.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval</text>
1777 </g>
1778 <!-- broadcast -->
1779-<g id="node11" class="node"><title>broadcast</title>
1780-<ellipse fill="none" stroke="black" cx="1063" cy="-200" rx="58.1882" ry="58.6899"/>
1781-<text text-anchor="middle" x="1063" y="-196.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>
1782+<g id="node12" class="node"><title>broadcast</title>
1783+<ellipse fill="none" stroke="black" cx="1135" cy="-281" rx="58.1882" ry="58.6899"/>
1784+<text text-anchor="middle" x="1135" y="-277.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>
1785 </g>
1786 <!-- loop&#45;&gt;broadcast -->
1787 <g id="edge10" class="edge"><title>loop&#45;&gt;broadcast</title>
1788-<path fill="none" stroke="black" d="M766.046,-274.934C773.498,-270.155 781.824,-265.421 790,-262 856.828,-234.035 938.382,-217.617 994.86,-208.779"/>
1789-<polygon fill="black" stroke="black" points="995.396,-212.238 1004.75,-207.269 994.34,-205.318 995.396,-212.238"/>
1790-<text text-anchor="middle" x="881" y="-267.4" font-family="Times Roman,serif" font-size="14.00">Receive broadcast request</text>
1791+<path fill="none" stroke="black" d="M811.332,-370.953C821.492,-360.892 834.388,-349.946 848,-343 917.32,-307.628 1006.03,-292.395 1066.35,-285.86"/>
1792+<polygon fill="black" stroke="black" points="1066.94,-289.319 1076.53,-284.811 1066.22,-282.355 1066.94,-289.319"/>
1793+<text text-anchor="middle" x="946" y="-348.4" font-family="Times Roman,serif" font-size="14.00">Receive broadcast request</text>
1794+</g>
1795+<!-- conn_broken -->
1796+<g id="node26" class="node"><title>conn_broken</title>
1797+<ellipse fill="none" stroke="black" cx="1361" cy="-99" rx="73.0388" ry="73.5391"/>
1798+<text text-anchor="middle" x="1361" y="-95.4" font-family="Times Roman,serif" font-size="14.00">conn_broken</text>
1799+</g>
1800+<!-- loop&#45;&gt;conn_broken -->
1801+<g id="edge28" class="edge"><title>loop&#45;&gt;conn_broken</title>
1802+<path fill="none" stroke="black" d="M793.216,-363.054C799.833,-304.219 817.014,-182.243 848,-155 967.196,-50.2026 1167.08,-63.6291 1278.91,-81.8408"/>
1803+<polygon fill="black" stroke="black" points="1278.34,-85.2954 1288.79,-83.4998 1279.5,-78.392 1278.34,-85.2954"/>
1804+<text text-anchor="middle" x="946" y="-160.4" font-family="Times Roman,serif" font-size="14.00">Receive connbroken request</text>
1805+</g>
1806+<!-- conn_warn -->
1807+<g id="node28" class="node"><title>conn_warn</title>
1808+<ellipse fill="none" stroke="black" cx="1135" cy="-477" rx="65.7609" ry="65.7609"/>
1809+<text text-anchor="middle" x="1135" y="-473.4" font-family="Times Roman,serif" font-size="14.00">conn_warn</text>
1810+</g>
1811+<!-- loop&#45;&gt;conn_warn -->
1812+<g id="edge30" class="edge"><title>loop&#45;&gt;conn_warn</title>
1813+<path fill="none" stroke="black" d="M814.092,-416.512C823.957,-424.185 835.89,-432.126 848,-437 915.942,-464.343 999.421,-473.523 1058.8,-476.355"/>
1814+<polygon fill="black" stroke="black" points="1058.71,-479.855 1068.85,-476.786 1059.01,-472.861 1058.71,-479.855"/>
1815+<text text-anchor="middle" x="946" y="-480.4" font-family="Times Roman,serif" font-size="14.00">Receive connwarn request</text>
1816 </g>
1817 <!-- pong_wait -->
1818-<g id="node13" class="node"><title>pong_wait</title>
1819-<ellipse fill="none" stroke="black" cx="1526" cy="-406" rx="62.9325" ry="62.9325"/>
1820-<text text-anchor="middle" x="1526" y="-402.4" font-family="Times Roman,serif" font-size="14.00">pong_wait</text>
1821+<g id="node14" class="node"><title>pong_wait</title>
1822+<ellipse fill="none" stroke="black" cx="537" cy="-653" rx="62.9325" ry="62.9325"/>
1823+<text text-anchor="middle" x="537" y="-649.4" font-family="Times Roman,serif" font-size="14.00">pong_wait</text>
1824 </g>
1825 <!-- ping&#45;&gt;pong_wait -->
1826 <g id="edge12" class="edge"><title>ping&#45;&gt;pong_wait</title>
1827-<path fill="none" stroke="black" d="M1095.56,-415.297C1169.19,-413.707 1350.04,-409.8 1452.36,-407.591"/>
1828-<polygon fill="black" stroke="black" points="1452.69,-411.084 1462.61,-407.369 1452.54,-404.086 1452.69,-411.084"/>
1829-<text text-anchor="middle" x="1289" y="-418.4" font-family="Times Roman,serif" font-size="14.00">Write PING</text>
1830+<path fill="none" stroke="black" d="M1103.4,-600.831C1035.81,-617.134 871.913,-654.261 732,-667 681.542,-671.594 668.481,-671.33 618,-667 615.134,-666.754 612.217,-666.46 609.275,-666.127"/>
1831+<polygon fill="black" stroke="black" points="609.573,-662.637 599.214,-664.858 608.697,-669.582 609.573,-662.637"/>
1832+<text text-anchor="middle" x="790" y="-670.4" font-family="Times Roman,serif" font-size="14.00">Write PING</text>
1833 </g>
1834 <!-- ack_wait -->
1835-<g id="node15" class="node"><title>ack_wait</title>
1836-<ellipse fill="none" stroke="black" cx="1526" cy="-269" rx="55.1543" ry="55.1543"/>
1837-<text text-anchor="middle" x="1526" y="-265.4" font-family="Times Roman,serif" font-size="14.00">ack_wait</text>
1838+<g id="node16" class="node"><title>ack_wait</title>
1839+<ellipse fill="none" stroke="black" cx="1598" cy="-373" rx="55.1543" ry="55.1543"/>
1840+<text text-anchor="middle" x="1598" y="-369.4" font-family="Times Roman,serif" font-size="14.00">ack_wait</text>
1841 </g>
1842 <!-- broadcast&#45;&gt;ack_wait -->
1843 <g id="edge14" class="edge"><title>broadcast&#45;&gt;ack_wait</title>
1844-<path fill="none" stroke="black" d="M1121.17,-208.669C1207.93,-221.599 1370.7,-245.856 1461.17,-259.339"/>
1845-<polygon fill="black" stroke="black" points="1460.9,-262.837 1471.3,-260.849 1461.93,-255.913 1460.9,-262.837"/>
1846-<text text-anchor="middle" x="1289" y="-257.4" font-family="Times Roman,serif" font-size="14.00">Write BROADCAST [fits one wire msg]</text>
1847+<path fill="none" stroke="black" d="M1193.25,-288.88C1264.95,-299.067 1390.23,-318.45 1496,-343 1508.9,-345.993 1522.57,-349.664 1535.58,-353.397"/>
1848+<polygon fill="black" stroke="black" points="1534.79,-356.813 1545.37,-356.254 1536.75,-350.093 1534.79,-356.813"/>
1849+<text text-anchor="middle" x="1361" y="-348.4" font-family="Times Roman,serif" font-size="14.00">Write BROADCAST [fits one wire msg]</text>
1850 </g>
1851 <!-- split_broadcast -->
1852-<g id="node17" class="node"><title>split_broadcast</title>
1853-<ellipse fill="none" stroke="black" cx="1526" cy="-110" rx="84.1457" ry="84.1457"/>
1854-<text text-anchor="middle" x="1526" y="-106.4" font-family="Times Roman,serif" font-size="14.00">split_broadcast</text>
1855+<g id="node18" class="node"><title>split_broadcast</title>
1856+<ellipse fill="none" stroke="black" cx="1598" cy="-216" rx="84.1457" ry="84.1457"/>
1857+<text text-anchor="middle" x="1598" y="-212.4" font-family="Times Roman,serif" font-size="14.00">split_broadcast</text>
1858 </g>
1859 <!-- broadcast&#45;&gt;split_broadcast -->
1860 <g id="edge16" class="edge"><title>broadcast&#45;&gt;split_broadcast</title>
1861-<path fill="none" stroke="black" d="M1120.7,-188.783C1199.06,-173.553 1340.01,-146.154 1433.29,-128.021"/>
1862-<polygon fill="black" stroke="black" points="1434.15,-131.421 1443.29,-126.077 1432.81,-124.549 1434.15,-131.421"/>
1863-<text text-anchor="middle" x="1289" y="-185.4" font-family="Times Roman,serif" font-size="14.00">BROADCAST does not fit one wire msg</text>
1864+<path fill="none" stroke="black" d="M1193.17,-272.834C1271.44,-261.846 1411.56,-242.174 1504.66,-229.104"/>
1865+<polygon fill="black" stroke="black" points="1505.23,-232.558 1514.65,-227.702 1504.26,-225.626 1505.23,-232.558"/>
1866+<text text-anchor="middle" x="1361" y="-272.4" font-family="Times Roman,serif" font-size="14.00">BROADCAST does not fit one wire msg</text>
1867+</g>
1868+<!-- pong_wait&#45;&gt;stop -->
1869+<g id="edge40" class="edge"><title>pong_wait&#45;&gt;stop</title>
1870+<path fill="none" stroke="black" d="M600.164,-653C651.344,-653 725.322,-653 790,-653 790,-653 790,-653 1972,-653 2131.11,-653 2245.53,-463.168 2289.33,-376.844"/>
1871+<polygon fill="black" stroke="black" points="2292.5,-378.343 2293.84,-367.834 2286.24,-375.212 2292.5,-378.343"/>
1872+<text text-anchor="middle" x="1361" y="-658.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text>
1873 </g>
1874 <!-- pong_wait&#45;&gt;loop -->
1875 <g id="edge18" class="edge"><title>pong_wait&#45;&gt;loop</title>
1876-<path fill="none" stroke="black" d="M1463.29,-398.59C1336,-383.27 1038.34,-346.004 790,-304 787.177,-303.523 784.269,-303.006 781.343,-302.468"/>
1877-<polygon fill="black" stroke="black" points="781.898,-299.011 771.42,-300.582 780.59,-305.888 781.898,-299.011"/>
1878-<text text-anchor="middle" x="1063" y="-359.4" font-family="Times Roman,serif" font-size="14.00">Read PONG</text>
1879+<path fill="none" stroke="black" d="M581.359,-607.765C632.774,-555.333 716.085,-470.376 760.273,-425.314"/>
1880+<polygon fill="black" stroke="black" points="762.774,-427.763 767.277,-418.172 757.776,-422.862 762.774,-427.763"/>
1881+<text text-anchor="middle" x="675" y="-574.4" font-family="Times Roman,serif" font-size="14.00">Read PONG</text>
1882+</g>
1883+<!-- ack_wait&#45;&gt;stop -->
1884+<g id="edge36" class="edge"><title>ack_wait&#45;&gt;stop</title>
1885+<path fill="none" stroke="black" d="M1653.02,-372.757C1765.96,-371.776 2032.03,-366.986 2254,-344 2256.89,-343.701 2259.85,-343.348 2262.84,-342.959"/>
1886+<polygon fill="black" stroke="black" points="2263.58,-346.389 2272.99,-341.517 2262.59,-339.459 2263.58,-346.389"/>
1887+<text text-anchor="middle" x="1972" y="-373.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text>
1888 </g>
1889 <!-- ack_wait&#45;&gt;loop -->
1890 <g id="edge20" class="edge"><title>ack_wait&#45;&gt;loop</title>
1891-<path fill="none" stroke="black" d="M1470.96,-271.898C1455.75,-272.644 1439.24,-273.404 1424,-274 1181.02,-283.507 889.323,-290.597 782.144,-293.057"/>
1892-<polygon fill="black" stroke="black" points="781.977,-289.56 772.059,-293.288 782.136,-296.558 781.977,-289.56"/>
1893-<text text-anchor="middle" x="1063" y="-292.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>
1894+<path fill="none" stroke="black" d="M1542.83,-374.081C1445.66,-375.995 1237.64,-380.146 1062,-384 966.886,-386.087 942.947,-382.989 848,-389 842.829,-389.327 837.406,-389.764 832.04,-390.252"/>
1895+<polygon fill="black" stroke="black" points="831.485,-386.79 821.871,-391.242 832.163,-393.757 831.485,-386.79"/>
1896+<text text-anchor="middle" x="1135" y="-389.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>
1897 </g>
1898 <!-- split_broadcast&#45;&gt;loop -->
1899 <g id="edge26" class="edge"><title>split_broadcast&#45;&gt;loop</title>
1900-<path fill="none" stroke="black" d="M1442.56,-99.9981C1336.06,-89.6718 1146.8,-79.5577 990,-115 894.383,-136.612 862.41,-141.921 790,-208 775.817,-220.943 764.522,-238.865 756.283,-254.999"/>
1901-<polygon fill="black" stroke="black" points="753.014,-253.718 751.785,-264.241 759.308,-256.781 753.014,-253.718"/>
1902-<text text-anchor="middle" x="1063" y="-120.4" font-family="Times Roman,serif" font-size="14.00">All split msgs written</text>
1903+<path fill="none" stroke="black" d="M1515.33,-201.109C1409.22,-184.681 1219.98,-164.458 1062,-196 960.985,-216.168 924.079,-215.554 848,-285 827.351,-303.849 812.751,-331.776 803.364,-354.774"/>
1904+<polygon fill="black" stroke="black" points="800.027,-353.699 799.658,-364.287 806.549,-356.24 800.027,-353.699"/>
1905+<text text-anchor="middle" x="1135" y="-201.4" font-family="Times Roman,serif" font-size="14.00">All split msgs written</text>
1906 </g>
1907 <!-- split_ack_wait -->
1908-<g id="node21" class="node"><title>split_ack_wait</title>
1909-<ellipse fill="none" stroke="black" cx="1893" cy="-110" rx="80.1095" ry="80.6102"/>
1910-<text text-anchor="middle" x="1893" y="-106.4" font-family="Times Roman,serif" font-size="14.00">split_ack_wait</text>
1911+<g id="node22" class="node"><title>split_ack_wait</title>
1912+<ellipse fill="none" stroke="black" cx="1972" cy="-257" rx="80.1095" ry="80.6102"/>
1913+<text text-anchor="middle" x="1972" y="-253.4" font-family="Times Roman,serif" font-size="14.00">split_ack_wait</text>
1914 </g>
1915 <!-- split_broadcast&#45;&gt;split_ack_wait -->
1916 <g id="edge22" class="edge"><title>split_broadcast&#45;&gt;split_ack_wait</title>
1917-<path fill="none" stroke="black" d="M1610.2,-110C1667.61,-110 1743.59,-110 1802.33,-110"/>
1918-<polygon fill="black" stroke="black" points="1802.35,-113.5 1812.35,-110 1802.34,-106.5 1802.35,-113.5"/>
1919-<text text-anchor="middle" x="1711" y="-115.4" font-family="Times Roman,serif" font-size="14.00">Write split BROADCAST</text>
1920+<path fill="none" stroke="black" d="M1680.55,-232.126C1687.12,-233.18 1693.66,-234.156 1700,-235 1760.22,-243.022 1828.36,-248.528 1881.38,-252.025"/>
1921+<polygon fill="black" stroke="black" points="1881.23,-255.523 1891.43,-252.676 1881.68,-248.537 1881.23,-255.523"/>
1922+<text text-anchor="middle" x="1783" y="-256.4" font-family="Times Roman,serif" font-size="14.00">Write split BROADCAST</text>
1923+</g>
1924+<!-- split_ack_wait&#45;&gt;stop -->
1925+<g id="edge38" class="edge"><title>split_ack_wait&#45;&gt;stop</title>
1926+<path fill="none" stroke="black" d="M2050.59,-275.189C2116.55,-290.456 2208.51,-311.74 2263.08,-324.372"/>
1927+<polygon fill="black" stroke="black" points="2262.62,-327.857 2273.15,-326.702 2264.2,-321.037 2262.62,-327.857"/>
1928+<text text-anchor="middle" x="2166" y="-327.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text>
1929 </g>
1930 <!-- split_ack_wait&#45;&gt;split_broadcast -->
1931 <g id="edge24" class="edge"><title>split_ack_wait&#45;&gt;split_broadcast</title>
1932-<path fill="none" stroke="black" d="M1814.64,-90.9448C1807.69,-89.7544 1800.74,-88.7397 1794,-88 1720.66,-79.9496 1701.36,-80.1783 1628,-88 1624.71,-88.3505 1621.38,-88.7628 1618.01,-89.2264"/>
1933-<polygon fill="black" stroke="black" points="1617.23,-85.8043 1607.87,-90.7602 1618.28,-92.7256 1617.23,-85.8043"/>
1934-<text text-anchor="middle" x="1711" y="-93.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>
1935+<path fill="none" stroke="black" d="M1899.33,-222.493C1888.37,-218.573 1877.04,-215.2 1866,-213 1808.89,-201.617 1743.56,-202.081 1691.71,-205.529"/>
1936+<polygon fill="black" stroke="black" points="1691.25,-202.053 1681.52,-206.256 1691.74,-209.035 1691.25,-202.053"/>
1937+<text text-anchor="middle" x="1783" y="-218.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>
1938+</g>
1939+<!-- conn_broken&#45;&gt;stop -->
1940+<g id="edge32" class="edge"><title>conn_broken&#45;&gt;stop</title>
1941+<path fill="none" stroke="black" d="M1434.61,-95.8477C1564.45,-92.5456 1841.16,-95.6985 2060,-168 2154.32,-199.163 2175.34,-218.326 2254,-279 2262.17,-285.305 2270.33,-292.782 2277.75,-300.185"/>
1942+<polygon fill="black" stroke="black" points="2275.42,-302.808 2284.91,-307.517 2280.43,-297.917 2275.42,-302.808"/>
1943+<text text-anchor="middle" x="1783" y="-127.4" font-family="Times Roman,serif" font-size="14.00">Write CONNBROKEN</text>
1944+</g>
1945+<!-- conn_warn&#45;&gt;loop -->
1946+<g id="edge34" class="edge"><title>conn_warn&#45;&gt;loop</title>
1947+<path fill="none" stroke="black" d="M1083.63,-435.301C1071.29,-427.246 1057.71,-419.822 1044,-415 972.758,-389.933 883.562,-389.406 832.05,-391.836"/>
1948+<polygon fill="black" stroke="black" points="831.758,-388.346 821.959,-392.373 832.131,-395.337 831.758,-388.346"/>
1949+<text text-anchor="middle" x="946" y="-420.4" font-family="Times Roman,serif" font-size="14.00">Write CONNWARN</text>
1950 </g>
1951 </g>
1952 </svg>
1953
1954=== modified file 'server/acceptance/acceptanceclient.go'
1955--- server/acceptance/acceptanceclient.go 2014-04-09 19:30:53 +0000
1956+++ server/acceptance/acceptanceclient.go 2014-04-28 14:37:38 +0000
1957@@ -44,6 +44,7 @@
1958 Levels map[string]int64
1959 Insecure bool // don't verify certs
1960 Prefix string // prefix for events
1961+ Auth string
1962 // connection
1963 Connection net.Conn
1964 }
1965@@ -73,6 +74,7 @@
1966 Type string `json:"T"`
1967 protocol.BroadcastMsg
1968 protocol.NotificationsMsg
1969+ protocol.ConnWarnMsg
1970 }
1971
1972 // Run the session with the server, emits a stream of events.
1973@@ -93,6 +95,7 @@
1974 "device": sess.Model,
1975 "channel": sess.ImageChannel,
1976 },
1977+ Authorization: sess.Auth,
1978 })
1979 if err != nil {
1980 return err
1981@@ -136,6 +139,8 @@
1982 return err
1983 }
1984 events <- fmt.Sprintf("%sbroadcast chan:%v app:%v topLevel:%d payloads:%s", sess.Prefix, recv.ChanId, recv.AppId, recv.TopLevel, pack)
1985+ case "warn":
1986+ events <- fmt.Sprintf("%swarn %s", sess.Prefix, recv.Reason)
1987 }
1988 }
1989 return nil
1990
1991=== modified file 'server/acceptance/cmd/acceptanceclient.go'
1992--- server/acceptance/cmd/acceptanceclient.go 2014-04-10 13:52:31 +0000
1993+++ server/acceptance/cmd/acceptanceclient.go 2014-04-28 14:37:38 +0000
1994@@ -48,13 +48,18 @@
1995 fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] <config.json> <device id>\n")
1996 flag.PrintDefaults()
1997 }
1998+ missingArg := func(what string) {
1999+ fmt.Fprintf(os.Stderr, "missing %s\n", what)
2000+ flag.Usage()
2001+ os.Exit(2)
2002+ }
2003 flag.Parse()
2004 narg := flag.NArg()
2005 switch {
2006 case narg < 1:
2007- log.Fatal("missing config file")
2008+ missingArg("config file")
2009 case narg < 2:
2010- log.Fatal("missing device-id")
2011+ missingArg("device-id")
2012 }
2013 configFName := flag.Arg(0)
2014 f, err := os.Open(configFName)
2015
2016=== modified file 'server/acceptance/suites/broadcast.go'
2017--- server/acceptance/suites/broadcast.go 2014-04-07 19:39:19 +0000
2018+++ server/acceptance/suites/broadcast.go 2014-04-28 14:37:38 +0000
2019@@ -265,7 +265,11 @@
2020
2021 func (s *BroadcastAcceptanceSuite) TestGetHosts(c *C) {
2022 gh := gethosts.New("", s.ServerAPIURL+"/delivery-hosts", 2*time.Second)
2023- hosts, err := gh.Get()
2024+ host, err := gh.Get()
2025 c.Assert(err, IsNil)
2026- c.Check(hosts, DeepEquals, []string{s.ServerAddr})
2027+ expected := &gethosts.Host{
2028+ Domain: "localhost",
2029+ Hosts: []string{s.ServerAddr},
2030+ }
2031+ c.Check(host, DeepEquals, expected)
2032 }
2033
2034=== modified file 'server/acceptance/suites/suite.go'
2035--- server/acceptance/suites/suite.go 2014-04-03 16:47:47 +0000
2036+++ server/acceptance/suites/suite.go 2014-04-28 14:37:38 +0000
2037@@ -44,10 +44,19 @@
2038
2039 // Start a client.
2040 func (h *ServerHandle) StartClient(c *C, devId string, levels map[string]int64) (events <-chan string, errorCh <-chan error, stop func()) {
2041+ return h.StartClientAuth(c, devId, levels, "")
2042+}
2043+
2044+// Start a client with auth.
2045+func (h *ServerHandle) StartClientAuth(c *C, devId string, levels map[string]int64, auth string) (events <-chan string, errorCh <-chan error, stop func()) {
2046 errCh := make(chan error, 1)
2047 cliEvents := make(chan string, 10)
2048 sess := testClientSession(h.ServerAddr, devId, "m1", "img1", false)
2049 sess.Levels = levels
2050+ sess.Auth = auth
2051+ if auth != "" {
2052+ sess.ExchangeTimeout = 5 * time.Second
2053+ }
2054 err := sess.Dial()
2055 c.Assert(err, IsNil)
2056 clientShutdown := make(chan bool, 1) // abused as an atomic flag
2057
2058=== modified file 'server/broker/broker.go'
2059--- server/broker/broker.go 2014-04-04 09:58:34 +0000
2060+++ server/broker/broker.go 2014-04-28 14:37:38 +0000
2061@@ -30,7 +30,7 @@
2062 // through them.
2063 type Broker interface {
2064 // Register the session.
2065- Register(*protocol.ConnectMsg) (BrokerSession, error)
2066+ Register(connMsg *protocol.ConnectMsg, sessionId string) (BrokerSession, error)
2067 // Unregister the session.
2068 Unregister(BrokerSession)
2069 }
2070
2071=== modified file 'server/broker/exchanges.go'
2072--- server/broker/exchanges.go 2014-04-04 13:19:10 +0000
2073+++ server/broker/exchanges.go 2014-04-28 14:37:38 +0000
2074@@ -28,9 +28,8 @@
2075
2076 // Scratch area for exchanges, sessions should hold one of these.
2077 type ExchangesScratchArea struct {
2078- broadcastMsg protocol.BroadcastMsg
2079- ackMsg protocol.AckMsg
2080- connBrokenMsg protocol.ConnBrokenMsg
2081+ broadcastMsg protocol.BroadcastMsg
2082+ ackMsg protocol.AckMsg
2083 }
2084
2085 // BroadcastExchange leads a session through delivering a BROADCAST.
2086@@ -119,23 +118,20 @@
2087 return nil
2088 }
2089
2090-// ConnBrokenExchange breaks a session giving a reason.
2091-type ConnBrokenExchange struct {
2092- Reason string
2093+// ConnMetaExchange allows to send a CONNBROKEN or CONNWARN message.
2094+type ConnMetaExchange struct {
2095+ Msg protocol.OnewayMsg
2096 }
2097
2098 // check interface already here
2099-var _ Exchange = (*ConnBrokenExchange)(nil)
2100+var _ Exchange = (*ConnMetaExchange)(nil)
2101
2102-// Prepare session for a CONNBROKEN.
2103-func (cbe *ConnBrokenExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) {
2104- scratchArea := sess.ExchangeScratchArea()
2105- scratchArea.connBrokenMsg.Type = "connbroken"
2106- scratchArea.connBrokenMsg.Reason = cbe.Reason
2107- return &scratchArea.connBrokenMsg, nil, nil
2108+// Prepare session for a CONNBROKEN/WARN.
2109+func (cbe *ConnMetaExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) {
2110+ return cbe.Msg, nil, nil
2111 }
2112
2113-// CONNBROKEN isn't acked
2114-func (cbe *ConnBrokenExchange) Acked(sess BrokerSession, done bool) error {
2115- panic("Acked should not get invoked on ConnBrokenExchange")
2116+// CONNBROKEN/WARN aren't acked.
2117+func (cbe *ConnMetaExchange) Acked(sess BrokerSession, done bool) error {
2118+ panic("Acked should not get invoked on ConnMetaExchange")
2119 }
2120
2121=== modified file 'server/broker/exchanges_test.go'
2122--- server/broker/exchanges_test.go 2014-04-04 13:19:10 +0000
2123+++ server/broker/exchanges_test.go 2014-04-28 14:37:38 +0000
2124@@ -24,6 +24,7 @@
2125
2126 . "launchpad.net/gocheck"
2127
2128+ "launchpad.net/ubuntu-push/protocol"
2129 "launchpad.net/ubuntu-push/server/broker"
2130 "launchpad.net/ubuntu-push/server/broker/testing"
2131 "launchpad.net/ubuntu-push/server/store"
2132@@ -249,16 +250,18 @@
2133 c.Check(sess.LevelsMap[store.SystemInternalChannelId], Equals, int64(5))
2134 }
2135
2136-func (s *exchangesSuite) TestConnBrokenExchange(c *C) {
2137+func (s *exchangesSuite) TestConnMetaExchange(c *C) {
2138 sess := &testing.TestBrokerSession{}
2139- cbe := &broker.ConnBrokenExchange{"REASON"}
2140+ var msg protocol.OnewayMsg = &protocol.ConnWarnMsg{"connwarn", "REASON"}
2141+ cbe := &broker.ConnMetaExchange{msg}
2142 outMsg, inMsg, err := cbe.Prepare(sess)
2143 c.Assert(err, IsNil)
2144+ c.Check(msg, Equals, outMsg)
2145 c.Check(inMsg, IsNil) // no answer is expected
2146 // check
2147 marshalled, err := json.Marshal(outMsg)
2148 c.Assert(err, IsNil)
2149- c.Check(string(marshalled), Equals, `{"T":"connbroken","Reason":"REASON"}`)
2150+ c.Check(string(marshalled), Equals, `{"T":"connwarn","Reason":"REASON"}`)
2151
2152- c.Check(func() { cbe.Acked(nil, true) }, PanicMatches, "Acked should not get invoked on ConnBrokenExchange")
2153+ c.Check(func() { cbe.Acked(nil, true) }, PanicMatches, "Acked should not get invoked on ConnMetaExchange")
2154 }
2155
2156=== modified file 'server/broker/simple/simple.go'
2157--- server/broker/simple/simple.go 2014-04-11 08:47:18 +0000
2158+++ server/broker/simple/simple.go 2014-04-28 14:37:38 +0000
2159@@ -166,7 +166,7 @@
2160
2161 // Register registers a session with the broker. It feeds the session
2162 // pending notifications as well.
2163-func (b *SimpleBroker) Register(connect *protocol.ConnectMsg) (broker.BrokerSession, error) {
2164+func (b *SimpleBroker) Register(connect *protocol.ConnectMsg, sessionId string) (broker.BrokerSession, error) {
2165 // xxx sanity check DeviceId
2166 model, err := broker.GetInfoString(connect, "device", "?")
2167 if err != nil {
2168@@ -224,7 +224,7 @@
2169 } else { // register
2170 prev := b.registry[sess.deviceId]
2171 if prev != nil { // kick it
2172- close(prev.exchanges)
2173+ prev.exchanges <- nil
2174 }
2175 b.registry[sess.deviceId] = sess
2176 sess.registered = true
2177
2178=== modified file 'server/broker/testsuite/suite.go'
2179--- server/broker/testsuite/suite.go 2014-04-11 08:47:18 +0000
2180+++ server/broker/testsuite/suite.go 2014-04-28 14:37:38 +0000
2181@@ -89,7 +89,7 @@
2182 "device": "model",
2183 "channel": "daily",
2184 },
2185- })
2186+ }, "s1")
2187 c.Assert(err, IsNil)
2188 c.Assert(s.RevealSession(b, "dev-1"), Equals, sess)
2189 c.Assert(sess.DeviceIdentifier(), Equals, "dev-1")
2190@@ -101,7 +101,7 @@
2191 }))
2192 b.Unregister(sess)
2193 // just to make sure the unregister was processed
2194- _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""})
2195+ _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}, "s2")
2196 c.Assert(err, IsNil)
2197 c.Check(s.RevealSession(b, "dev-1"), IsNil)
2198 }
2199@@ -111,7 +111,7 @@
2200 b := s.MakeBroker(sto, testBrokerConfig, nil)
2201 b.Start()
2202 defer b.Stop()
2203- _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1", Levels: map[string]int64{"z": 5}})
2204+ _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1", Levels: map[string]int64{"z": 5}}, "s1")
2205 c.Check(err, FitsTypeOf, &broker.ErrAbort{})
2206 }
2207
2208@@ -123,11 +123,11 @@
2209 info := map[string]interface{}{
2210 "device": -1,
2211 }
2212- _, err := b.Register(&protocol.ConnectMsg{Type: "connect", Info: info})
2213+ _, err := b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}, "s1")
2214 c.Check(err, Equals, broker.ErrUnexpectedValue)
2215 info["device"] = "m"
2216 info["channel"] = -1
2217- _, err = b.Register(&protocol.ConnectMsg{Type: "connect", Info: info})
2218+ _, err = b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}, "s2")
2219 c.Check(err, Equals, broker.ErrUnexpectedValue)
2220 }
2221
2222@@ -139,7 +139,7 @@
2223 b := s.MakeBroker(sto, testBrokerConfig, nil)
2224 b.Start()
2225 defer b.Stop()
2226- sess, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2227+ sess, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
2228 c.Assert(err, IsNil)
2229 c.Check(len(sess.SessionChannel()), Equals, 1)
2230 }
2231@@ -149,7 +149,7 @@
2232 b := s.MakeBroker(sto, testBrokerConfig, s.testlog)
2233 b.Start()
2234 defer b.Stop()
2235- _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2236+ _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
2237 c.Assert(err, IsNil)
2238 // but
2239 c.Check(s.testlog.Captured(), Matches, "ERROR unsuccessful feed pending, get channel snapshot for 0: get channel snapshot fail\n")
2240@@ -160,22 +160,25 @@
2241 b := s.MakeBroker(sto, testBrokerConfig, nil)
2242 b.Start()
2243 defer b.Stop()
2244- sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2245- c.Assert(err, IsNil)
2246- sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2247- c.Assert(err, IsNil)
2248- checkAndFalse := false
2249- // previous session got signaled by closing its channel
2250+ sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
2251+ c.Assert(err, IsNil)
2252+ sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s2")
2253+ c.Assert(err, IsNil)
2254+ // previous session got signaled by sending nil on its channel
2255+ var sentinel broker.Exchange
2256+ got := false
2257 select {
2258- case _, ok := <-sess1.SessionChannel():
2259- checkAndFalse = ok == false
2260- default:
2261+ case sentinel = <-sess1.SessionChannel():
2262+ got = true
2263+ case <-time.After(5 * time.Second):
2264+ c.Fatal("taking too long to get sentinel")
2265 }
2266- c.Check(checkAndFalse, Equals, true)
2267+ c.Check(got, Equals, true)
2268+ c.Check(sentinel, IsNil)
2269 c.Assert(s.RevealSession(b, "dev-1"), Equals, sess2)
2270 b.Unregister(sess1)
2271 // just to make sure the unregister was processed
2272- _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""})
2273+ _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}, "s3")
2274 c.Assert(err, IsNil)
2275 c.Check(s.RevealSession(b, "dev-1"), Equals, sess2)
2276 }
2277@@ -187,9 +190,9 @@
2278 b := s.MakeBroker(sto, testBrokerConfig, nil)
2279 b.Start()
2280 defer b.Stop()
2281- sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2282+ sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
2283 c.Assert(err, IsNil)
2284- sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-2"})
2285+ sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-2"}, "s2")
2286 c.Assert(err, IsNil)
2287 // add notification to channel *after* the registrations
2288 muchLater := time.Now().Add(10 * time.Minute)
2289@@ -241,7 +244,7 @@
2290 b := s.MakeBroker(sto, testBrokerConfig, s.testlog)
2291 b.Start()
2292 defer b.Stop()
2293- _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2294+ _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
2295 c.Assert(err, IsNil)
2296 b.Broadcast(store.SystemInternalChannelId)
2297 select {
2298
2299=== modified file 'server/session/session.go'
2300--- server/session/session.go 2014-04-11 08:47:18 +0000
2301+++ server/session/session.go 2014-04-28 14:37:38 +0000
2302@@ -18,6 +18,7 @@
2303 package session
2304
2305 import (
2306+ "errors"
2307 "net"
2308 "time"
2309
2310@@ -35,7 +36,7 @@
2311 }
2312
2313 // sessionStart manages the start of the protocol session.
2314-func sessionStart(proto protocol.Protocol, brkr broker.Broker, cfg SessionConfig) (broker.BrokerSession, error) {
2315+func sessionStart(proto protocol.Protocol, brkr broker.Broker, cfg SessionConfig, sessionId string) (broker.BrokerSession, error) {
2316 var connMsg protocol.ConnectMsg
2317 proto.SetDeadline(time.Now().Add(cfg.ExchangeTimeout()))
2318 err := proto.ReadMessage(&connMsg)
2319@@ -52,9 +53,11 @@
2320 if err != nil {
2321 return nil, err
2322 }
2323- return brkr.Register(&connMsg)
2324+ return brkr.Register(&connMsg, sessionId)
2325 }
2326
2327+var errOneway = errors.New("oneway")
2328+
2329 // exchange writes outMsg message, reads answer in inMsg
2330 func exchange(proto protocol.Protocol, outMsg, inMsg interface{}, exchangeTimeout time.Duration) error {
2331 proto.SetDeadline(time.Now().Add(exchangeTimeout))
2332@@ -62,7 +65,10 @@
2333 if err != nil {
2334 return err
2335 }
2336- if inMsg == nil { // no answer expected, breaking connection
2337+ if inMsg == nil { // no answer expected
2338+ if outMsg.(protocol.OnewayMsg).OnewayContinue() {
2339+ return errOneway
2340+ }
2341 return &broker.ErrAbort{"session broken for reason"}
2342 }
2343 err = proto.ReadMessage(inMsg)
2344@@ -78,6 +84,10 @@
2345 exchangeTimeout := cfg.ExchangeTimeout()
2346 pingTimer := time.NewTimer(pingInterval)
2347 intervalStart := time.Now()
2348+ pingTimerReset := func() {
2349+ pingTimer.Reset(pingInterval)
2350+ intervalStart = time.Now()
2351+ }
2352 ch := sess.SessionChannel()
2353 Loop:
2354 for {
2355@@ -93,16 +103,15 @@
2356 if pongMsg.Type != "pong" {
2357 return &broker.ErrAbort{"expected PONG message"}
2358 }
2359- pingTimer.Reset(pingInterval)
2360- case exchg, ok := <-ch:
2361+ pingTimerReset()
2362+ case exchg := <-ch:
2363 pingTimer.Stop()
2364- if !ok {
2365+ if exchg == nil {
2366 return &broker.ErrAbort{"terminated"}
2367 }
2368 outMsg, inMsg, err := exchg.Prepare(sess)
2369 if err == broker.ErrNop { // nothing to do
2370- pingTimer.Reset(pingInterval)
2371- intervalStart = time.Now()
2372+ pingTimerReset()
2373 continue Loop
2374 }
2375 if err != nil {
2376@@ -111,12 +120,15 @@
2377 for {
2378 done := outMsg.Split()
2379 err = exchange(proto, outMsg, inMsg, exchangeTimeout)
2380+ if err == errOneway {
2381+ pingTimerReset()
2382+ continue Loop
2383+ }
2384 if err != nil {
2385 return err
2386 }
2387 if done {
2388- pingTimer.Reset(pingInterval)
2389- intervalStart = time.Now()
2390+ pingTimerReset()
2391 }
2392 err = exchg.Acked(sess, done)
2393 if err != nil {
2394@@ -142,7 +154,7 @@
2395 return track.End(&broker.ErrAbort{"unexpected wire format version"})
2396 }
2397 proto := protocol.NewProtocol0(conn)
2398- sess, err := sessionStart(proto, brkr, cfg)
2399+ sess, err := sessionStart(proto, brkr, cfg, track.SessionId())
2400 if err != nil {
2401 return track.End(err)
2402 }
2403
2404=== modified file 'server/session/session_test.go'
2405--- server/session/session_test.go 2014-04-11 08:47:18 +0000
2406+++ server/session/session_test.go 2014-04-28 14:37:38 +0000
2407@@ -130,8 +130,8 @@
2408 return &testBroker{registration: make(chan interface{}, 2)}
2409 }
2410
2411-func (tb *testBroker) Register(connect *protocol.ConnectMsg) (broker.BrokerSession, error) {
2412- tb.registration <- "register " + connect.DeviceId
2413+func (tb *testBroker) Register(connect *protocol.ConnectMsg, sessionId string) (broker.BrokerSession, error) {
2414+ tb.registration <- fmt.Sprintf("register %s %s", connect.DeviceId, sessionId)
2415 return &testing.TestBrokerSession{DeviceId: connect.DeviceId}, tb.err
2416 }
2417
2418@@ -148,7 +148,7 @@
2419 brkr := newTestBroker()
2420 go func() {
2421 var err error
2422- sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout)
2423+ sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout, "s1")
2424 errCh <- err
2425 }()
2426 c.Check(takeNext(down), Equals, "deadline 5ms")
2427@@ -160,7 +160,7 @@
2428 up <- nil // no write error
2429 err := <-errCh
2430 c.Check(err, IsNil)
2431- c.Check(takeNext(brkr.registration), Equals, "register dev-1")
2432+ c.Check(takeNext(brkr.registration), Equals, "register dev-1 s1")
2433 c.Check(sess.DeviceIdentifier(), Equals, "dev-1")
2434 }
2435
2436@@ -175,7 +175,7 @@
2437 brkr.err = errRegister
2438 go func() {
2439 var err error
2440- sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout)
2441+ sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout, "s2")
2442 errCh <- err
2443 }()
2444 up <- protocol.ConnectMsg{Type: "connect", ClientVer: "1", DeviceId: "dev-1"}
2445@@ -190,7 +190,7 @@
2446 down := make(chan interface{}, 5)
2447 tp := &testProtocol{up, down}
2448 up <- io.ErrUnexpectedEOF
2449- _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout)
2450+ _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s3")
2451 c.Check(err, Equals, io.ErrUnexpectedEOF)
2452 }
2453
2454@@ -200,7 +200,7 @@
2455 tp := &testProtocol{up, down}
2456 up <- protocol.ConnectMsg{Type: "connect"}
2457 up <- io.ErrUnexpectedEOF
2458- _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout)
2459+ _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s4")
2460 c.Check(err, Equals, io.ErrUnexpectedEOF)
2461 // sanity
2462 c.Check(takeNext(down), Matches, "deadline.*")
2463@@ -212,7 +212,7 @@
2464 down := make(chan interface{}, 5)
2465 tp := &testProtocol{up, down}
2466 up <- protocol.ConnectMsg{Type: "what"}
2467- _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout)
2468+ _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s5")
2469 c.Check(err, DeepEquals, &broker.ErrAbort{"expected CONNECT message"})
2470 }
2471
2472@@ -222,14 +222,14 @@
2473 }
2474
2475 func (s *sessionSuite) TestSessionLoop(c *C) {
2476- nopTrack := NewTracker(s.testlog)
2477+ track := &testTracker{NewTracker(s.testlog), make(chan interface{}, 2)}
2478 errCh := make(chan error, 1)
2479 up := make(chan interface{}, 5)
2480 down := make(chan interface{}, 5)
2481 tp := &testProtocol{up, down}
2482 sess := &testing.TestBrokerSession{}
2483 go func() {
2484- errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)
2485+ errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, track)
2486 }()
2487 c.Check(takeNext(down), Equals, "deadline 2ms")
2488 c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"})
2489@@ -241,6 +241,9 @@
2490 up <- io.ErrUnexpectedEOF
2491 err := <-errCh
2492 c.Check(err, Equals, io.ErrUnexpectedEOF)
2493+ c.Check(track.interval, HasLen, 2)
2494+ c.Check((<-track.interval).(time.Duration) <= 8*time.Millisecond, Equals, true)
2495+ c.Check((<-track.interval).(time.Duration) <= 8*time.Millisecond, Equals, true)
2496 }
2497
2498 func (s *sessionSuite) TestSessionLoopWriteError(c *C) {
2499@@ -357,7 +360,7 @@
2500 go func() {
2501 errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)
2502 }()
2503- close(exchanges)
2504+ exchanges <- nil
2505 err := <-errCh
2506 c.Check(err, DeepEquals, &broker.ErrAbort{"terminated"})
2507 }
2508@@ -477,18 +480,44 @@
2509 down := make(chan interface{}, 5)
2510 tp := &testProtocol{up, down}
2511 exchanges := make(chan broker.Exchange, 1)
2512- exchanges <- &broker.ConnBrokenExchange{"REASON"}
2513+ msg := &protocol.ConnBrokenMsg{"connbroken", "BREASON"}
2514+ exchanges <- &broker.ConnMetaExchange{msg}
2515 sess := &testing.TestBrokerSession{Exchanges: exchanges}
2516 go func() {
2517 errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)
2518 }()
2519 c.Check(takeNext(down), Equals, "deadline 2ms")
2520- c.Check(takeNext(down), DeepEquals, protocol.ConnBrokenMsg{"connbroken", "REASON"})
2521+ c.Check(takeNext(down), DeepEquals, protocol.ConnBrokenMsg{"connbroken", "BREASON"})
2522 up <- nil // no write error
2523 err := <-errCh
2524 c.Check(err, DeepEquals, &broker.ErrAbort{"session broken for reason"})
2525 }
2526
2527+func (s *sessionSuite) TestSessionLoopConnWarnExchange(c *C) {
2528+ nopTrack := NewTracker(s.testlog)
2529+ errCh := make(chan error, 1)
2530+ up := make(chan interface{}, 5)
2531+ down := make(chan interface{}, 5)
2532+ tp := &testProtocol{up, down}
2533+ exchanges := make(chan broker.Exchange, 1)
2534+ msg := &protocol.ConnWarnMsg{"connwarn", "WREASON"}
2535+ exchanges <- &broker.ConnMetaExchange{msg}
2536+ sess := &testing.TestBrokerSession{Exchanges: exchanges}
2537+ go func() {
2538+ errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)
2539+ }()
2540+ c.Check(takeNext(down), Equals, "deadline 2ms")
2541+ c.Check(takeNext(down), DeepEquals, protocol.ConnWarnMsg{"connwarn", "WREASON"})
2542+ up <- nil // no write error
2543+ // session continues
2544+ c.Check(takeNext(down), Equals, "deadline 2ms")
2545+ c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"})
2546+ up <- nil // no write error
2547+ up <- io.EOF
2548+ err := <-errCh
2549+ c.Check(err, Equals, io.EOF)
2550+}
2551+
2552 type testTracker struct {
2553 SessionTracker
2554 interval chan interface{}
2555@@ -593,7 +622,7 @@
2556 msg, err = downStream.ReadBytes(byte('}'))
2557 c.Check(err, IsNil)
2558 c.Check(msg, DeepEquals, []byte("\x00\x0c{\"T\":\"ping\"}"))
2559- c.Check(takeNext(brkr.registration), Equals, "register DEV")
2560+ c.Check(takeNext(brkr.registration), Equals, "register DEV "+track.SessionId())
2561 c.Check(len(brkr.registration), Equals, 0) // not yet unregistered
2562 cli.Close()
2563 err = <-errCh
2564
2565=== modified file 'server/session/tracker.go'
2566--- server/session/tracker.go 2014-02-10 23:19:08 +0000
2567+++ server/session/tracker.go 2014-04-28 14:37:38 +0000
2568@@ -17,6 +17,7 @@
2569 package session
2570
2571 import (
2572+ "fmt"
2573 "net"
2574 "time"
2575
2576@@ -29,6 +30,8 @@
2577 logger.Logger
2578 // Session got started.
2579 Start(WithRemoteAddr)
2580+ // SessionId
2581+ SessionId() string
2582 // Session got registered with broker as sess BrokerSession.
2583 Registered(sess broker.BrokerSession)
2584 // Report effective elapsed ping interval.
2585@@ -47,7 +50,7 @@
2586 // Tracker implements SessionTracker simply.
2587 type tracker struct {
2588 logger.Logger
2589- sessionId int64 // xxx use timeuuid later
2590+ sessionId string
2591 }
2592
2593 func NewTracker(logger logger.Logger) SessionTracker {
2594@@ -55,18 +58,22 @@
2595 }
2596
2597 func (trk *tracker) Start(conn WithRemoteAddr) {
2598- trk.sessionId = time.Now().UnixNano() - sessionsEpoch
2599- trk.Debugf("session(%x) connected %v", trk.sessionId, conn.RemoteAddr())
2600+ trk.sessionId = fmt.Sprintf("%x", time.Now().UnixNano()-sessionsEpoch)
2601+ trk.Debugf("session(%s) connected %v", trk.sessionId, conn.RemoteAddr())
2602+}
2603+
2604+func (trk *tracker) SessionId() string {
2605+ return trk.sessionId
2606 }
2607
2608 func (trk *tracker) Registered(sess broker.BrokerSession) {
2609- trk.Infof("session(%x) registered %v", trk.sessionId, sess.DeviceIdentifier())
2610+ trk.Infof("session(%s) registered %v", trk.sessionId, sess.DeviceIdentifier())
2611 }
2612
2613 func (trk *tracker) EffectivePingInterval(time.Duration) {
2614 }
2615
2616 func (trk *tracker) End(err error) error {
2617- trk.Debugf("session(%x) ended with: %v", trk.sessionId, err)
2618+ trk.Debugf("session(%s) ended with: %v", trk.sessionId, err)
2619 return err
2620 }
2621
2622=== modified file 'server/session/tracker_test.go'
2623--- server/session/tracker_test.go 2014-02-10 23:19:08 +0000
2624+++ server/session/tracker_test.go 2014-04-28 14:37:38 +0000
2625@@ -46,8 +46,8 @@
2626 func (s *trackerSuite) TestSessionTrackStart(c *C) {
2627 track := NewTracker(s.testlog)
2628 track.Start(&testRemoteAddrable{})
2629- c.Check(track.(*tracker).sessionId, Not(Equals), 0)
2630- regExpected := fmt.Sprintf(`DEBUG session\(%x\) connected 127\.0\.0\.1:9999\n`, track.(*tracker).sessionId)
2631+ c.Check(track.SessionId(), Not(Equals), "")
2632+ regExpected := fmt.Sprintf(`DEBUG session\(%s\) connected 127\.0\.0\.1:9999\n`, track.SessionId())
2633 c.Check(s.testlog.Captured(), Matches, regExpected)
2634 }
2635
2636@@ -55,7 +55,7 @@
2637 track := NewTracker(s.testlog)
2638 track.Start(&testRemoteAddrable{})
2639 track.Registered(&testing.TestBrokerSession{DeviceId: "DEV-ID"})
2640- regExpected := fmt.Sprintf(`.*connected.*\nINFO session\(%x\) registered DEV-ID\n`, track.(*tracker).sessionId)
2641+ regExpected := fmt.Sprintf(`.*connected.*\nINFO session\(%s\) registered DEV-ID\n`, track.SessionId())
2642 c.Check(s.testlog.Captured(), Matches, regExpected)
2643 }
2644
2645@@ -63,6 +63,6 @@
2646 track := NewTracker(s.testlog)
2647 track.Start(&testRemoteAddrable{})
2648 track.End(&broker.ErrAbort{})
2649- regExpected := fmt.Sprintf(`.*connected.*\nDEBUG session\(%x\) ended with: session aborted \(\)\n`, track.(*tracker).sessionId)
2650+ regExpected := fmt.Sprintf(`.*connected.*\nDEBUG session\(%s\) ended with: session aborted \(\)\n`, track.SessionId())
2651 c.Check(s.testlog.Captured(), Matches, regExpected)
2652 }
2653
2654=== modified file 'ubuntu-push-client.go'
2655--- ubuntu-push-client.go 2014-03-12 13:25:20 +0000
2656+++ ubuntu-push-client.go 2014-04-28 14:37:38 +0000
2657@@ -19,12 +19,38 @@
2658 import (
2659 "log"
2660
2661+ "gopkg.in/qml.v0"
2662+ "launchpad.net/go-dbus/v1"
2663 "launchpad.net/go-xdg/v0"
2664
2665 "launchpad.net/ubuntu-push/client"
2666 )
2667
2668+const NAME = "com.ubuntu.PushNotifications"
2669+
2670+// grabName grabs ownership of the dbus name, and bails the client as
2671+// soon as somebody else grabs it.
2672+func grabName() {
2673+ conn, err := dbus.Connect(dbus.SessionBus)
2674+ if err != nil {
2675+ log.Fatalf("bus: %v", err)
2676+ }
2677+
2678+ flags := dbus.NameFlagAllowReplacement | dbus.NameFlagReplaceExisting
2679+ n := conn.RequestName(NAME, flags)
2680+ go func() {
2681+ for err := range n.C {
2682+ if err != nil {
2683+ log.Fatalf("FATAL: name channel got: %v", err)
2684+ }
2685+ }
2686+ }()
2687+}
2688+
2689 func main() {
2690+ // XXX: this is a quick hack to ensure unicity
2691+ grabName()
2692+
2693 cfgFname, err := xdg.Config.Find("ubuntu-push-client/config.json")
2694 if err != nil {
2695 log.Fatalf("unable to find a configuration file: %v", err)
2696@@ -33,6 +59,9 @@
2697 if err != nil {
2698 log.Fatalf("unable to open the levels database: %v", err)
2699 }
2700+
2701+ qml.Init(nil)
2702+
2703 cli := client.NewPushClient(cfgFname, lvlFname)
2704 err = cli.Start()
2705 if err != nil {
2706
2707=== added file 'util/auth.go'
2708--- util/auth.go 1970-01-01 00:00:00 +0000
2709+++ util/auth.go 2014-04-28 14:37:38 +0000
2710@@ -0,0 +1,36 @@
2711+/*
2712+ Copyright 2013-2014 Canonical Ltd.
2713+
2714+ This program is free software: you can redistribute it and/or modify it
2715+ under the terms of the GNU General Public License version 3, as published
2716+ by the Free Software Foundation.
2717+
2718+ This program is distributed in the hope that it will be useful, but
2719+ WITHOUT ANY WARRANTY; without even the implied warranties of
2720+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2721+ PURPOSE. See the GNU General Public License for more details.
2722+
2723+ You should have received a copy of the GNU General Public License along
2724+ with this program. If not, see <http://www.gnu.org/licenses/>.
2725+*/
2726+
2727+package util
2728+
2729+import (
2730+ "gopkg.in/niemeyer/uoneauth.v1"
2731+ "gopkg.in/qml.v0"
2732+)
2733+
2734+func GetAuthorization() (string, error) {
2735+ engine := qml.NewEngine()
2736+ defer engine.Destroy()
2737+ authService := uoneauth.NewService(engine)
2738+ var auth string
2739+ token, err := authService.Token()
2740+ if err != nil {
2741+ return "", err
2742+ } else {
2743+ auth = token.HeaderSignature("POST", "https://push.ubuntu.com")
2744+ }
2745+ return auth, nil
2746+}
2747
2748=== added file 'util/auth_test.go'
2749--- util/auth_test.go 1970-01-01 00:00:00 +0000
2750+++ util/auth_test.go 2014-04-28 14:37:38 +0000
2751@@ -0,0 +1,53 @@
2752+/*
2753+ Copyright 2013-2014 Canonical Ltd.
2754+
2755+ This program is free software: you can redistribute it and/or modify it
2756+ under the terms of the GNU General Public License version 3, as published
2757+ by the Free Software Foundation.
2758+
2759+ This program is distributed in the hope that it will be useful, but
2760+ WITHOUT ANY WARRANTY; without even the implied warranties of
2761+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2762+ PURPOSE. See the GNU General Public License for more details.
2763+
2764+ You should have received a copy of the GNU General Public License along
2765+ with this program. If not, see <http://www.gnu.org/licenses/>.
2766+*/
2767+
2768+package util
2769+
2770+import (
2771+ "os"
2772+
2773+ "gopkg.in/qml.v0"
2774+
2775+ . "launchpad.net/gocheck"
2776+)
2777+
2778+type authSuite struct{}
2779+
2780+var _ = Suite(&authSuite{})
2781+
2782+func (s *authSuite) SetUpSuite(c *C) {
2783+ if os.Getenv("PUSH_AUTH_TEST") == "1" {
2784+ qml.Init(nil)
2785+ }
2786+}
2787+
2788+func (s *authSuite) SetUpTest(c *C) {
2789+ qml.SetLogger(c)
2790+}
2791+
2792+func (s *authSuite) TestGetAuth(c *C) {
2793+ /*
2794+ * This test is only useful when the PUSH_AUTH_TEST environment
2795+ * variable is set to "1" - in which case the runner should have
2796+ * a Ubuntu One account setup via system-settings.
2797+ */
2798+ if os.Getenv("PUSH_AUTH_TEST") != "1" {
2799+ c.Skip("PUSH_AUTH_TEST not set to '1'")
2800+ }
2801+ auth, err := GetAuthorization()
2802+ c.Assert(err, IsNil)
2803+ c.Assert(auth, Matches, "OAuth .*oauth_consumer_key=.*")
2804+}
2805
2806=== modified file 'whoopsie/identifier/identifier.go'
2807--- whoopsie/identifier/identifier.go 2014-02-21 16:17:28 +0000
2808+++ whoopsie/identifier/identifier.go 2014-04-28 14:37:38 +0000
2809@@ -27,6 +27,7 @@
2810 import "C"
2811 import "unsafe"
2812 import "errors"
2813+import "time"
2814
2815 // an Id knows how to generate itself, and how to stringify itself.
2816 type Id interface {
2817@@ -36,12 +37,17 @@
2818
2819 // Identifier is the default Id implementation.
2820 type Identifier struct {
2821- value string
2822+ value string
2823+ generator func(**C.char, **C.GError)
2824+}
2825+
2826+func generator(csp **C.char, errp **C.GError) {
2827+ C.whoopsie_identifier_generate(csp, errp)
2828 }
2829
2830 // New creates an Identifier, but does not call Generate() on it.
2831 func New() Id {
2832- return &Identifier{}
2833+ return &Identifier{generator: generator}
2834 }
2835
2836 // Generate makes the Identifier create the identifier itself.
2837@@ -49,8 +55,18 @@
2838 var gerr *C.GError
2839 var cs *C.char
2840 defer C.g_free((C.gpointer)(unsafe.Pointer(cs)))
2841- C.whoopsie_identifier_generate(&cs, &gerr)
2842-
2843+
2844+ for i := 0; i < 200; i++ {
2845+ id.generator(&cs, &gerr)
2846+
2847+ if cs != nil || gerr != nil {
2848+ goto SuccessMaybe
2849+ }
2850+ time.Sleep(600 * time.Millisecond)
2851+ }
2852+ return errors.New("whoopsie_identifier_generate still bad after 2m; giving up")
2853+
2854+SuccessMaybe:
2855 if gerr != nil {
2856 return errors.New(C.GoString((*C.char)(gerr.message)))
2857 } else {
2858
2859=== modified file 'whoopsie/identifier/identifier_test.go'
2860--- whoopsie/identifier/identifier_test.go 2014-01-15 15:51:50 +0000
2861+++ whoopsie/identifier/identifier_test.go 2014-04-28 14:37:38 +0000
2862@@ -41,3 +41,18 @@
2863 func (s *IdentifierSuite) TestIdentifierInterface(c *C) {
2864 _ = []Id{New()}
2865 }
2866+
2867+// TestFailure checks that Identifier survives whoopsie shenanigans
2868+func (s *IdentifierSuite) TestIdentifierSurvivesShenanigans(c *C) {
2869+ count := 0
2870+ // using _Ctype* as a workaround for gocheck also having a C
2871+ gen := func(csp **_Ctype_char, errp **_Ctype_GError) {
2872+ count++
2873+ if count > 3 {
2874+ generator(csp, errp)
2875+ }
2876+ }
2877+ id := &Identifier{generator: gen}
2878+ id.Generate()
2879+ c.Check(id.String(), HasLen, 128)
2880+}

Subscribers

People subscribed via source and target branches