Merge lp:~pedronis/ubuntu-push/unicast-preps into lp:ubuntu-push

Proposed by Samuele Pedroni
Status: Superseded
Proposed branch: lp:~pedronis/ubuntu-push/unicast-preps
Merge into: lp:ubuntu-push
Diff against target: 3632 lines (+1467/-388)
49 files modified
Makefile (+3/-0)
README (+12/-2)
bus/connectivity/connectivity.go (+8/-7)
bus/connectivity/connectivity_test.go (+77/-3)
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 (+45/-0)
protocol/messages_test.go (+18/-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/api/handlers_test.go (+3/-2)
server/broker/broker.go (+1/-1)
server/broker/exchanges.go (+31/-35)
server/broker/exchanges_test.go (+29/-25)
server/broker/exchg_impl_test.go (+19/-17)
server/broker/simple/simple.go (+10/-10)
server/broker/simple/simple_test.go (+5/-4)
server/broker/testsuite/suite.go (+36/-33)
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)
server/store/inmemory.go (+8/-6)
server/store/inmemory_test.go (+6/-3)
server/store/store.go (+3/-1)
testing/helpers.go (+11/-0)
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/unicast-preps
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+217654@code.launchpad.net

Commit message

reorganize things such that GetChannelSnapshot returns a bunch of Notification (with optional AppId, MsgId) so that it can be used together with the underlying store to store unicast notification user/device channels

Description of the change

reorganize things such that GetChannelSnapshot returns a bunch of Notification (with optional AppId, MsgId) so that it can be used together with the underlying store to store unicast notification user/device channels

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-29 18:02:00 +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-29 18:02:00 +0000
22@@ -6,11 +6,21 @@
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 dependencies:
28+
29+ build-essential
30+ libsqlite3-dev
31+ qtbase5-private-dev
32+ qtdeclarative5-dev
33+ libqt5opengl5-dev
34+ libubuntuoneauth-2.0-dev
35+
36+and run:
37
38 make bootstrap
39
40-To run tests, install libgcrypt11-dev and libwhoopsie-dev and run:
41+To run tests, install libglib2.0-dev, libgcrypt11-dev, libwhoopsie-dev,
42+and run:
43
44 make check
45
46
47=== modified file 'bus/connectivity/connectivity.go'
48--- bus/connectivity/connectivity.go 2014-04-04 11:08:28 +0000
49+++ bus/connectivity/connectivity.go 2014-04-29 18:02:00 +0000
50@@ -72,19 +72,20 @@
51 cs.connAttempts += ar.Redial()
52 nm := networkmanager.New(cs.endp, cs.log)
53
54+ // set up the watch
55+ stateCh, err = nm.WatchState()
56+ if err != nil {
57+ cs.log.Debugf("failed to set up the state watch: %s", err)
58+ goto Continue
59+ }
60+
61 // Get the current state.
62 initial = nm.GetState()
63 if initial == networkmanager.Unknown {
64 cs.log.Debugf("Failed to get state.")
65 goto Continue
66 }
67-
68- // set up the watch
69- stateCh, err = nm.WatchState()
70- if err != nil {
71- cs.log.Debugf("failed to set up the state watch: %s", err)
72- goto Continue
73- }
74+ cs.log.Debugf("got initial state of %s", initial)
75
76 primary = nm.GetPrimaryConnection()
77 cs.log.Debugf("primary connection starts as %#v", primary)
78
79=== modified file 'bus/connectivity/connectivity_test.go'
80--- bus/connectivity/connectivity_test.go 2014-04-04 12:01:42 +0000
81+++ bus/connectivity/connectivity_test.go 2014-04-29 18:02:00 +0000
82@@ -17,8 +17,15 @@
83 package connectivity
84
85 import (
86+ "net/http/httptest"
87+ "sync"
88+ "testing"
89+ "time"
90+
91 "launchpad.net/go-dbus/v1"
92 . "launchpad.net/gocheck"
93+
94+ "launchpad.net/ubuntu-push/bus"
95 "launchpad.net/ubuntu-push/bus/networkmanager"
96 testingbus "launchpad.net/ubuntu-push/bus/testing"
97 "launchpad.net/ubuntu-push/config"
98@@ -26,9 +33,6 @@
99 helpers "launchpad.net/ubuntu-push/testing"
100 "launchpad.net/ubuntu-push/testing/condition"
101 "launchpad.net/ubuntu-push/util"
102- "net/http/httptest"
103- "testing"
104- "time"
105 )
106
107 // hook up gocheck
108@@ -115,6 +119,76 @@
109 c.Check(<-cs.networkStateCh, Equals, networkmanager.ConnectedGlobal)
110 }
111
112+// a racyEndpoint is an endpoint that behaves differently depending on
113+// how much time passes between getting the state and setting up the
114+// watch
115+type racyEndpoint struct {
116+ stateGot bool
117+ maxTime time.Time
118+ delta time.Duration
119+ lock sync.RWMutex
120+}
121+
122+func (rep *racyEndpoint) GetProperty(prop string) (interface{}, error) {
123+ switch prop {
124+ case "state":
125+ rep.lock.Lock()
126+ defer rep.lock.Unlock()
127+ rep.stateGot = true
128+ rep.maxTime = time.Now().Add(rep.delta)
129+ return uint32(networkmanager.Connecting), nil
130+ case "PrimaryConnection":
131+ return dbus.ObjectPath("/something"), nil
132+ default:
133+ return nil, nil
134+ }
135+}
136+
137+func (rep *racyEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error {
138+ if member == "StateChanged" {
139+ // we count never having gotten the state as happening "after" now.
140+ rep.lock.RLock()
141+ defer rep.lock.RUnlock()
142+ ok := !rep.stateGot || time.Now().Before(rep.maxTime)
143+ go func() {
144+ if ok {
145+ f(uint32(networkmanager.ConnectedGlobal))
146+ }
147+ d()
148+ }()
149+ }
150+ return nil
151+}
152+
153+func (*racyEndpoint) Close() {}
154+func (*racyEndpoint) Dial() error { return nil }
155+func (*racyEndpoint) String() string { return "racyEndpoint" }
156+func (*racyEndpoint) Call(string, []interface{}, ...interface{}) error { return nil }
157+
158+var _ bus.Endpoint = (*racyEndpoint)(nil)
159+
160+// takeNext takes a value from given channel with a 1s timeout
161+func takeNext(ch <-chan networkmanager.State) networkmanager.State {
162+ select {
163+ case <-time.After(time.Second):
164+ panic("channel stuck: too long waiting")
165+ case v := <-ch:
166+ return v
167+ }
168+}
169+
170+// test that if the nm state goes from connecting to connected very
171+// shortly after calling GetState, we don't lose the event.
172+func (s *ConnSuite) TestStartAvoidsRace(c *C) {
173+ for delta := time.Second; delta > 1; delta /= 2 {
174+ rep := &racyEndpoint{delta: delta}
175+ cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: rep}
176+ f := Commentf("when delta=%s", delta)
177+ c.Assert(cs.start(), Equals, networkmanager.Connecting, f)
178+ c.Assert(takeNext(cs.networkStateCh), Equals, networkmanager.ConnectedGlobal, f)
179+ }
180+}
181+
182 /*
183 tests for connectedStateStep()
184 */
185
186=== modified file 'client/client.go'
187--- client/client.go 2014-04-11 16:37:48 +0000
188+++ client/client.go 2014-04-29 18:02:00 +0000
189@@ -26,6 +26,7 @@
190 "os"
191 "strings"
192
193+ "gopkg.in/qml.v0"
194 "launchpad.net/go-dbus/v1"
195
196 "launchpad.net/ubuntu-push/bus"
197@@ -57,7 +58,7 @@
198 // The PEM-encoded server certificate
199 CertPEMFile string `json:"cert_pem_file"`
200 // The logging level (one of "debug", "info", "error")
201- LogLevel string `json:"log_level"`
202+ LogLevel logger.ConfigLogLevel `json:"log_level"`
203 }
204
205 // PushClient is the Ubuntu Push Notifications client-side daemon.
206@@ -95,13 +96,13 @@
207
208 // configure loads its configuration, and sets it up.
209 func (client *PushClient) configure() error {
210- f, err := os.Open(client.configPath)
211+ _, err := os.Stat(client.configPath)
212 if err != nil {
213- return fmt.Errorf("opening config: %v", err)
214+ return fmt.Errorf("config: %v", err)
215 }
216- err = config.ReadConfig(f, &client.config)
217+ err = config.ReadFiles(&client.config, client.configPath, "<flags>")
218 if err != nil {
219- return fmt.Errorf("reading config: %v", err)
220+ return fmt.Errorf("config: %v", err)
221 }
222 // ignore spaces
223 client.config.Addr = strings.Replace(client.config.Addr, " ", "", -1)
224@@ -110,7 +111,8 @@
225 }
226
227 // later, we'll be specifying more logging options in the config file
228- client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel)
229+ client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel.Level())
230+ qml.SetLogger(client.log)
231
232 // overridden for testing
233 client.idder = identifier.New()
234@@ -285,9 +287,6 @@
235 h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}}
236 nots := notifications.Raw(client.notificationsEndp, client.log)
237 body := "Tap to open the system updater."
238- if msg != nil {
239- body = fmt.Sprintf("[%d] %s", msg.TopLevel, body)
240- }
241 not_id, err := nots.Notify(
242 "ubuntu-push-client", // app name
243 uint32(0), // id
244
245=== modified file 'client/client_test.go'
246--- client/client_test.go 2014-04-11 16:21:45 +0000
247+++ client/client_test.go 2014-04-29 18:02:00 +0000
248@@ -19,10 +19,12 @@
249 import (
250 "encoding/json"
251 "errors"
252+ "flag"
253 "fmt"
254 "io/ioutil"
255 "net/http"
256 "net/http/httptest"
257+ "os"
258 "path/filepath"
259 "reflect"
260 "testing"
261@@ -37,6 +39,7 @@
262 testibus "launchpad.net/ubuntu-push/bus/testing"
263 "launchpad.net/ubuntu-push/client/session"
264 "launchpad.net/ubuntu-push/client/session/levelmap"
265+ "launchpad.net/ubuntu-push/config"
266 helpers "launchpad.net/ubuntu-push/testing"
267 "launchpad.net/ubuntu-push/testing/condition"
268 "launchpad.net/ubuntu-push/util"
269@@ -79,6 +82,7 @@
270 }
271
272 func (cs *clientSuite) SetUpSuite(c *C) {
273+ config.IgnoreParsedFlags = true // because configure() uses <flags>
274 cs.timeouts = util.SwapTimeouts([]time.Duration{0})
275 cs.leveldbPath = ""
276 }
277@@ -142,6 +146,16 @@
278 c.Check(cli.config.ExchangeTimeout.TimeDuration(), Equals, time.Duration(10*time.Millisecond))
279 }
280
281+func (cs *clientSuite) TestConfigureWorksWithFlags(c *C) {
282+ flag.CommandLine = flag.NewFlagSet("client", flag.ContinueOnError)
283+ os.Args = []string{"client", "-addr", "foo:7777"}
284+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
285+ err := cli.configure()
286+ c.Assert(err, IsNil)
287+ c.Assert(cli.config, NotNil)
288+ c.Check(cli.config.Addr, Equals, "foo:7777")
289+}
290+
291 func (cs *clientSuite) TestConfigureSetsUpLog(c *C) {
292 cli := NewPushClient(cs.configPath, cs.leveldbPath)
293 c.Check(cli.log, IsNil)
294@@ -163,7 +177,7 @@
295 c.Check(cli.idder, IsNil)
296 err := cli.configure()
297 c.Assert(err, IsNil)
298- c.Assert(cli.idder, DeepEquals, identifier.New())
299+ c.Assert(cli.idder, FitsTypeOf, identifier.New())
300 }
301
302 func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) {
303
304=== modified file 'client/gethosts/gethost.go'
305--- client/gethosts/gethost.go 2014-03-24 15:32:29 +0000
306+++ client/gethosts/gethost.go 2014-04-29 18:02:00 +0000
307@@ -49,8 +49,10 @@
308 }
309 }
310
311-type expected struct {
312- Hosts []string
313+// Host contains the domain and hosts returned by the remote endpoint
314+type Host struct {
315+ Domain string
316+ Hosts []string
317 }
318
319 var (
320@@ -60,7 +62,7 @@
321 )
322
323 // Get gets a list of hosts consulting the endpoint.
324-func (gh *GetHost) Get() ([]string, error) {
325+func (gh *GetHost) Get() (*Host, error) {
326 resp, err := gh.cli.Get(gh.endpointUrl + "?h=" + gh.hash)
327 if err != nil {
328 return nil, err
329@@ -80,7 +82,7 @@
330 if err != nil {
331 return nil, err
332 }
333- var parsed expected
334+ var parsed Host
335 err = json.Unmarshal(body, &parsed)
336 if err != nil {
337 return nil, ErrTemporary
338@@ -88,5 +90,5 @@
339 if len(parsed.Hosts) == 0 {
340 return nil, ErrTemporary
341 }
342- return parsed.Hosts, nil
343+ return &parsed, nil
344 }
345
346=== modified file 'client/gethosts/gethost_test.go'
347--- client/gethosts/gethost_test.go 2014-03-31 14:31:07 +0000
348+++ client/gethosts/gethost_test.go 2014-04-29 18:02:00 +0000
349@@ -45,7 +45,8 @@
350 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
351 x := r.FormValue("h")
352 b, err := json.Marshal(map[string]interface{}{
353- "hosts": []string{"http://" + x},
354+ "domain": "example.com",
355+ "hosts": []string{"http://" + x},
356 })
357 if err != nil {
358 panic(err)
359@@ -57,7 +58,8 @@
360 gh := New("foobar", ts.URL, 1*time.Second)
361 res, err := gh.Get()
362 c.Assert(err, IsNil)
363- c.Check(res, DeepEquals, []string{"http://c1130408a700afe0"})
364+ c.Check(*res, DeepEquals,
365+ Host{Domain: "example.com", Hosts: []string{"http://c1130408a700afe0"}})
366 }
367
368 func (s *getHostsSuite) TestGetTimeout(c *C) {
369@@ -97,4 +99,6 @@
370
371 scenario(http.StatusOK, "{", ErrTemporary)
372 scenario(http.StatusOK, "{}", ErrTemporary)
373+ scenario(http.StatusOK, `{"domain": "example.com"}`, ErrTemporary)
374+ scenario(http.StatusOK, `{"hosts": ["one"]}`, nil)
375 }
376
377=== modified file 'client/session/session.go'
378--- client/session/session.go 2014-04-04 13:55:00 +0000
379+++ client/session/session.go 2014-04-29 18:02:00 +0000
380@@ -38,7 +38,11 @@
381 "launchpad.net/ubuntu-push/util"
382 )
383
384-var wireVersionBytes = []byte{protocol.ProtocolWireVersion}
385+var (
386+ wireVersionBytes = []byte{protocol.ProtocolWireVersion}
387+ getAuthorization = util.GetAuthorization
388+ shouldGetAuth = false
389+)
390
391 type Notification struct {
392 TopLevel int64
393@@ -73,7 +77,7 @@
394 )
395
396 type hostGetter interface {
397- Get() ([]string, error)
398+ Get() (*gethosts.Host, error)
399 }
400
401 // ClientSessionConfig groups the client session configuration.
402@@ -115,6 +119,28 @@
403 stateP *uint32
404 ErrCh chan error
405 MsgCh chan *Notification
406+ // authorization
407+ auth string
408+ // autoredial knobs
409+ shouldDelayP *uint32
410+ lastAutoRedial time.Time
411+ redialDelay func(*ClientSession) time.Duration
412+ redialJitter func(time.Duration) time.Duration
413+ redialDelays []time.Duration
414+ redialDelaysIdx int
415+}
416+
417+func redialDelay(sess *ClientSession) time.Duration {
418+ if sess.ShouldDelay() {
419+ t := sess.redialDelays[sess.redialDelaysIdx]
420+ if len(sess.redialDelays) > sess.redialDelaysIdx+1 {
421+ sess.redialDelaysIdx++
422+ }
423+ return t + sess.redialJitter(t)
424+ } else {
425+ sess.redialDelaysIdx = 0
426+ return 0
427+ }
428 }
429
430 func NewSession(serverAddrSpec string, conf ClientSessionConfig,
431@@ -131,6 +157,7 @@
432 if hostsEndpoint != "" {
433 getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout)
434 }
435+ var shouldDelay uint32 = 0
436 sess := &ClientSession{
437 ClientSessionConfig: conf,
438 getHost: getHost,
439@@ -139,10 +166,14 @@
440 Log: log,
441 Protocolator: protocol.NewProtocol0,
442 Levels: levels,
443- TLS: &tls.Config{InsecureSkipVerify: true}, // XXX
444+ TLS: &tls.Config{},
445 stateP: &state,
446 timeSince: time.Since,
447+ shouldDelayP: &shouldDelay,
448+ redialDelay: redialDelay,
449+ redialDelays: util.Timeouts(),
450 }
451+ sess.redialJitter = sess.Jitter
452 if sess.PEM != nil {
453 cp := x509.NewCertPool()
454 ok := cp.AppendCertsFromPEM(sess.PEM)
455@@ -154,6 +185,18 @@
456 return sess, nil
457 }
458
459+func (sess *ClientSession) ShouldDelay() bool {
460+ return atomic.LoadUint32(sess.shouldDelayP) != 0
461+}
462+
463+func (sess *ClientSession) setShouldDelay() {
464+ atomic.StoreUint32(sess.shouldDelayP, uint32(1))
465+}
466+
467+func (sess *ClientSession) clearShouldDelay() {
468+ atomic.StoreUint32(sess.shouldDelayP, uint32(0))
469+}
470+
471 func (sess *ClientSession) State() ClientSessionState {
472 return ClientSessionState(atomic.LoadUint32(sess.stateP))
473 }
474@@ -180,20 +223,38 @@
475 if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime {
476 return nil
477 }
478- hosts, err := sess.getHost.Get()
479+ host, err := sess.getHost.Get()
480 if err != nil {
481 sess.Log.Errorf("getHosts: %v", err)
482 sess.setState(Error)
483 return err
484 }
485 sess.deliveryHostsTimestamp = time.Now()
486- sess.deliveryHosts = hosts
487+ sess.deliveryHosts = host.Hosts
488+ if sess.TLS != nil {
489+ sess.TLS.ServerName = host.Domain
490+ }
491 } else {
492 sess.deliveryHosts = sess.fallbackHosts
493 }
494 return nil
495 }
496
497+// checkAuthorization checks the authorization within the phone
498+func (sess *ClientSession) checkAuthorization() error {
499+ // grab the authorization string from the accounts
500+ // TODO: remove this condition when we have a way to deal with failing authorizations
501+ if shouldGetAuth {
502+ auth, err := getAuthorization()
503+ if err != nil {
504+ // For now we just log the error, as we don't want to block unauthorized users
505+ sess.Log.Errorf("unable to get the authorization token from the account: %v", err)
506+ }
507+ sess.auth = auth
508+ }
509+ return nil
510+}
511+
512 func (sess *ClientSession) resetHosts() {
513 sess.deliveryHosts = nil
514 }
515@@ -234,6 +295,7 @@
516 // connect to a server using the configuration in the ClientSession
517 // and set up the connection.
518 func (sess *ClientSession) connect() error {
519+ sess.setShouldDelay()
520 sess.startConnectionAttempt()
521 var err error
522 var conn net.Conn
523@@ -263,7 +325,12 @@
524
525 func (sess *ClientSession) AutoRedial(doneCh chan uint32) {
526 sess.stopRedial()
527+ if time.Since(sess.lastAutoRedial) < 2*time.Second {
528+ sess.setShouldDelay()
529+ }
530+ time.Sleep(sess.redialDelay(sess))
531 sess.retrier = util.NewAutoRedialer(sess)
532+ sess.lastAutoRedial = time.Now()
533 go func() { doneCh <- sess.retrier.Redial() }()
534 }
535
536@@ -289,6 +356,7 @@
537 err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"})
538 if err == nil {
539 sess.Log.Debugf("ping.")
540+ sess.clearShouldDelay()
541 } else {
542 sess.setState(Error)
543 sess.Log.Errorf("unable to pong: %s", err)
544@@ -330,6 +398,7 @@
545 sess.Log.Errorf("unable to ack broadcast: %s", err)
546 return err
547 }
548+ sess.clearShouldDelay()
549 sess.Log.Debugf("broadcast chan:%v app:%v topLevel:%d payloads:%s",
550 bcast.ChanId, bcast.AppId, bcast.TopLevel, bcast.Payloads)
551 if bcast.ChanId == protocol.SystemChannelId {
552@@ -409,10 +478,9 @@
553 return err
554 }
555 err = proto.WriteMessage(protocol.ConnectMsg{
556- Type: "connect",
557- DeviceId: sess.DeviceId,
558- // xxx get the SSO Authorization string from the phone
559- Authorization: "",
560+ Type: "connect",
561+ DeviceId: sess.DeviceId,
562+ Authorization: sess.auth,
563 Levels: levels,
564 Info: sess.Info,
565 })
566@@ -447,13 +515,15 @@
567
568 // run calls connect, and if it works it calls start, and if it works
569 // it runs loop in a goroutine, and ships its return value over ErrCh.
570-func (sess *ClientSession) run(closer func(), hostGetter, connecter, starter, looper func() error) error {
571+func (sess *ClientSession) run(closer func(), authChecker, hostGetter, connecter, starter, looper func() error) error {
572 closer()
573- err := hostGetter()
574- if err != nil {
575- return err
576- }
577- err = connecter()
578+ if err := authChecker(); err != nil {
579+ return err
580+ }
581+ if err := hostGetter(); err != nil {
582+ return err
583+ }
584+ err := connecter()
585 if err == nil {
586 err = starter()
587 if err == nil {
588@@ -483,7 +553,7 @@
589 // keep on trying.
590 panic("can't Dial() without a protocol constructor.")
591 }
592- return sess.run(sess.doClose, sess.getHosts, sess.connect, sess.start, sess.loop)
593+ return sess.run(sess.doClose, sess.checkAuthorization, sess.getHosts, sess.connect, sess.start, sess.loop)
594 }
595
596 func init() {
597
598=== modified file 'client/session/session_test.go'
599--- client/session/session_test.go 2014-04-04 13:55:00 +0000
600+++ client/session/session_test.go 2014-04-29 18:02:00 +0000
601@@ -32,12 +32,12 @@
602
603 . "launchpad.net/gocheck"
604
605+ "launchpad.net/ubuntu-push/client/gethosts"
606 "launchpad.net/ubuntu-push/client/session/levelmap"
607- //"launchpad.net/ubuntu-push/client/gethosts"
608- "launchpad.net/ubuntu-push/logger"
609 "launchpad.net/ubuntu-push/protocol"
610 helpers "launchpad.net/ubuntu-push/testing"
611 "launchpad.net/ubuntu-push/testing/condition"
612+ "launchpad.net/ubuntu-push/util"
613 )
614
615 func TestSession(t *testing.T) { TestingT(t) }
616@@ -165,14 +165,26 @@
617 /////
618
619 type clientSessionSuite struct {
620- log logger.Logger
621+ log *helpers.TestLogger
622 lvls func() (levelmap.LevelMap, error)
623 }
624
625+func (cs *clientSessionSuite) SetUpSuite(c *C) {
626+ getAuthorization = func() (string, error) {
627+ return "some auth", nil
628+ }
629+ shouldGetAuth = true
630+}
631+
632 func (cs *clientSessionSuite) SetUpTest(c *C) {
633 cs.log = helpers.NewTestLogger(c, "debug")
634 }
635
636+func (cs *clientSessionSuite) TearDownSuite(c *C) {
637+ getAuthorization = util.GetAuthorization
638+ shouldGetAuth = false
639+}
640+
641 // in-memory level map testing
642 var _ = Suite(&clientSessionSuite{lvls: levelmap.NewLevelMap})
643
644@@ -182,6 +194,7 @@
645 var _ = Suite(&clientSqlevelsSessionSuite{})
646
647 func (cs *clientSqlevelsSessionSuite) SetUpSuite(c *C) {
648+ cs.clientSessionSuite.SetUpSuite(c)
649 cs.lvls = func() (levelmap.LevelMap, error) { return levelmap.NewSqliteLevelMap(":memory:") }
650 }
651
652@@ -214,6 +227,10 @@
653 c.Check(sess, NotNil)
654 c.Check(err, IsNil)
655 c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"})
656+ // the session is happy and redial delayer is default
657+ c.Check(sess.ShouldDelay(), Equals, false)
658+ c.Check(fmt.Sprintf("%#v", sess.redialDelay), Equals, fmt.Sprintf("%#v", redialDelay))
659+ c.Check(sess.redialDelays, DeepEquals, util.Timeouts())
660 // but no root CAs set
661 c.Check(sess.TLS.RootCAs, IsNil)
662 c.Check(sess.State(), Equals, Disconnected)
663@@ -264,16 +281,17 @@
664 }
665
666 type testHostGetter struct {
667- hosts []string
668- err error
669+ domain string
670+ hosts []string
671+ err error
672 }
673
674-func (thg *testHostGetter) Get() ([]string, error) {
675- return thg.hosts, thg.err
676+func (thg *testHostGetter) Get() (*gethosts.Host, error) {
677+ return &gethosts.Host{thg.domain, thg.hosts}, thg.err
678 }
679
680 func (cs *clientSessionSuite) TestGetHostsRemote(c *C) {
681- hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}
682+ hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
683 sess := &ClientSession{getHost: hostGetter, timeSince: time.Since}
684 err := sess.getHosts()
685 c.Assert(err, IsNil)
686@@ -284,7 +302,7 @@
687 sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)
688 c.Assert(err, IsNil)
689 hostsErr := errors.New("failed")
690- hostGetter := &testHostGetter{nil, hostsErr}
691+ hostGetter := &testHostGetter{"", nil, hostsErr}
692 sess.getHost = hostGetter
693 err = sess.getHosts()
694 c.Assert(err, Equals, hostsErr)
695@@ -293,7 +311,7 @@
696 }
697
698 func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) {
699- hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}
700+ hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
701 sess := &ClientSession{
702 getHost: hostGetter,
703 ClientSessionConfig: ClientSessionConfig{
704@@ -318,7 +336,7 @@
705 }
706
707 func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) {
708- hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}
709+ hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
710 sess := &ClientSession{
711 getHost: hostGetter,
712 ClientSessionConfig: ClientSessionConfig{
713@@ -341,6 +359,18 @@
714 }
715
716 /****************************************************************
717+ checkAuthorization() tests
718+****************************************************************/
719+
720+func (cs *clientSessionSuite) TestChecksAuthorizationFromServer(c *C) {
721+ sess := &ClientSession{}
722+ c.Assert(sess.auth, Equals, "")
723+ err := sess.checkAuthorization()
724+ c.Assert(err, IsNil)
725+ c.Check(sess.auth, Equals, "some auth")
726+}
727+
728+/****************************************************************
729 startConnectionAttempt()/nextHostToTry()/started tests
730 ****************************************************************/
731
732@@ -427,7 +457,9 @@
733 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
734 c.Assert(err, IsNil)
735 sess.deliveryHosts = []string{"nowhere"}
736+ sess.clearShouldDelay()
737 err = sess.connect()
738+ c.Check(sess.ShouldDelay(), Equals, true)
739 c.Check(err, ErrorMatches, ".*connect.*address.*")
740 c.Check(sess.State(), Equals, Error)
741 }
742@@ -439,7 +471,9 @@
743 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
744 c.Assert(err, IsNil)
745 sess.deliveryHosts = []string{srv.Addr().String()}
746+ sess.clearShouldDelay()
747 err = sess.connect()
748+ c.Check(sess.ShouldDelay(), Equals, true)
749 c.Check(err, IsNil)
750 c.Check(sess.Connection, NotNil)
751 c.Check(sess.State(), Equals, Connected)
752@@ -452,7 +486,9 @@
753 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
754 c.Assert(err, IsNil)
755 sess.deliveryHosts = []string{"nowhere", srv.Addr().String()}
756+ sess.clearShouldDelay()
757 err = sess.connect()
758+ c.Check(sess.ShouldDelay(), Equals, true)
759 c.Check(err, IsNil)
760 c.Check(sess.Connection, NotNil)
761 c.Check(sess.State(), Equals, Connected)
762@@ -466,7 +502,9 @@
763 srv.Close()
764 c.Assert(err, IsNil)
765 sess.deliveryHosts = []string{srv.Addr().String()}
766+ sess.clearShouldDelay()
767 err = sess.connect()
768+ c.Check(sess.ShouldDelay(), Equals, true)
769 c.Check(err, ErrorMatches, ".*connection refused")
770 c.Check(sess.State(), Equals, Error)
771 }
772@@ -548,6 +586,27 @@
773 c.Check(<-ch, Not(Equals), 0)
774 }
775
776+func (cs *clientSessionSuite) TestAutoRedialCallsRedialDelay(c *C) {
777+ sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
778+ c.Assert(err, IsNil)
779+ flag := false
780+ sess.redialDelay = func(sess *ClientSession) time.Duration { flag = true; return 0 }
781+ sess.AutoRedial(nil)
782+ c.Check(flag, Equals, true)
783+}
784+
785+func (cs *clientSessionSuite) TestAutoRedialSetsRedialDelayIfTooQuick(c *C) {
786+ sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
787+ c.Assert(err, IsNil)
788+ sess.redialDelay = func(sess *ClientSession) time.Duration { return 0 }
789+ sess.AutoRedial(nil)
790+ c.Check(sess.ShouldDelay(), Equals, false)
791+ sess.stopRedial()
792+ sess.clearShouldDelay()
793+ sess.AutoRedial(nil)
794+ c.Check(sess.ShouldDelay(), Equals, true)
795+}
796+
797 /****************************************************************
798 handlePing() tests
799 ****************************************************************/
800@@ -594,6 +653,24 @@
801 c.Check(s.sess.State(), Equals, Error)
802 }
803
804+func (s *msgSuite) TestHandlePingClearsDelay(c *C) {
805+ s.sess.setShouldDelay()
806+ s.upCh <- nil // no error
807+ c.Check(s.sess.handlePing(), IsNil)
808+ c.Assert(len(s.downCh), Equals, 1)
809+ c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"})
810+ c.Check(s.sess.ShouldDelay(), Equals, false)
811+}
812+
813+func (s *msgSuite) TestHandlePingDoesNotClearsDelayOnError(c *C) {
814+ s.sess.setShouldDelay()
815+ s.upCh <- errors.New("Pong")
816+ c.Check(s.sess.handlePing(), NotNil)
817+ c.Assert(len(s.downCh), Equals, 1)
818+ c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"})
819+ c.Check(s.sess.ShouldDelay(), Equals, true)
820+}
821+
822 /****************************************************************
823 handleBroadcast() tests
824 ****************************************************************/
825@@ -687,6 +764,32 @@
826 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"nak"})
827 }
828
829+func (s *msgSuite) TestHandleBroadcastClearsDelay(c *C) {
830+ s.sess.setShouldDelay()
831+
832+ msg := serverMsg{"broadcast", protocol.BroadcastMsg{},
833+ protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}}
834+ go func() { s.errCh <- s.sess.handleBroadcast(&msg) }()
835+ c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
836+ s.upCh <- nil // ack ok
837+ c.Check(<-s.errCh, IsNil)
838+
839+ c.Check(s.sess.ShouldDelay(), Equals, false)
840+}
841+
842+func (s *msgSuite) TestHandleBroadcastDoesNotClearDelayOnError(c *C) {
843+ s.sess.setShouldDelay()
844+
845+ msg := serverMsg{"broadcast", protocol.BroadcastMsg{},
846+ protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}}
847+ go func() { s.errCh <- s.sess.handleBroadcast(&msg) }()
848+ c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
849+ s.upCh <- errors.New("bcast")
850+ c.Check(<-s.errCh, NotNil)
851+
852+ c.Check(s.sess.ShouldDelay(), Equals, true)
853+}
854+
855 /****************************************************************
856 handleConnBroken() tests
857 ****************************************************************/
858@@ -852,9 +955,10 @@
859
860 c.Check(takeNext(downCh), Equals, "deadline 0")
861 c.Check(takeNext(downCh), DeepEquals, protocol.ConnectMsg{
862- Type: "connect",
863- DeviceId: sess.DeviceId,
864- Levels: map[string]int64{},
865+ Type: "connect",
866+ DeviceId: sess.DeviceId,
867+ Levels: map[string]int64{},
868+ Authorization: "",
869 })
870 upCh <- errors.New("Overflow error in /dev/null")
871 err = <-errCh
872@@ -959,6 +1063,7 @@
873 msg, ok := takeNext(downCh).(protocol.ConnectMsg)
874 c.Check(ok, Equals, true)
875 c.Check(msg.DeviceId, Equals, "wah")
876+ c.Check(msg.Authorization, Equals, "")
877 c.Check(msg.Info, DeepEquals, info)
878 upCh <- nil // no error
879 upCh <- protocol.ConnAckMsg{
880@@ -975,6 +1080,22 @@
881 run() tests
882 ****************************************************************/
883
884+func (cs *clientSessionSuite) TestRunBailsIfAuthCheckFails(c *C) {
885+ sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
886+ c.Assert(err, IsNil)
887+ failure := errors.New("TestRunBailsIfAuthCheckFails")
888+ has_closed := false
889+ err = sess.run(
890+ func() { has_closed = true },
891+ func() error { return failure },
892+ nil,
893+ nil,
894+ nil,
895+ nil)
896+ c.Check(err, Equals, failure)
897+ c.Check(has_closed, Equals, true)
898+}
899+
900 func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) {
901 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
902 c.Assert(err, IsNil)
903@@ -982,6 +1103,7 @@
904 has_closed := false
905 err = sess.run(
906 func() { has_closed = true },
907+ func() error { return nil },
908 func() error { return failure },
909 nil,
910 nil,
911@@ -997,6 +1119,7 @@
912 err = sess.run(
913 func() {},
914 func() error { return nil },
915+ func() error { return nil },
916 func() error { return failure },
917 nil,
918 nil)
919@@ -1011,6 +1134,7 @@
920 func() {},
921 func() error { return nil },
922 func() error { return nil },
923+ func() error { return nil },
924 func() error { return failure },
925 nil)
926 c.Check(err, Equals, failure)
927@@ -1030,6 +1154,7 @@
928 func() error { return nil },
929 func() error { return nil },
930 func() error { return nil },
931+ func() error { return nil },
932 func() error { sess.MsgCh <- notf; return <-failureCh })
933 c.Check(err, Equals, nil)
934 // if run doesn't error it sets up the channels
935@@ -1087,9 +1212,64 @@
936
937 var (
938 dialTestTimeout = 100 * time.Millisecond
939- dialTestConf = ClientSessionConfig{ExchangeTimeout: dialTestTimeout}
940+ dialTestConf = ClientSessionConfig{
941+ ExchangeTimeout: dialTestTimeout,
942+ PEM: helpers.TestCertPEMBlock,
943+ }
944 )
945
946+func (cs *clientSessionSuite) TestDialBadServerName(c *C) {
947+ // a borked server name
948+ cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock)
949+ c.Assert(err, IsNil)
950+ tlsCfg := &tls.Config{
951+ Certificates: []tls.Certificate{cert},
952+ SessionTicketsDisabled: true,
953+ }
954+
955+ lst, err := tls.Listen("tcp", "localhost:0", tlsCfg)
956+ c.Assert(err, IsNil)
957+ // advertise
958+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
959+ b, err := json.Marshal(map[string]interface{}{
960+ "domain": "xyzzy", // <-- *** THIS *** is the bit that'll break it
961+ "hosts": []string{"nowhere", lst.Addr().String()},
962+ })
963+ if err != nil {
964+ panic(err)
965+ }
966+ w.Header().Set("Content-Type", "application/json")
967+ w.Write(b)
968+ }))
969+ defer ts.Close()
970+
971+ sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log)
972+ c.Assert(err, IsNil)
973+ tconn := &testConn{}
974+ sess.Connection = tconn
975+
976+ upCh := make(chan interface{}, 5)
977+ downCh := make(chan interface{}, 5)
978+ errCh := make(chan error, 1)
979+ proto := &testProtocol{up: upCh, down: downCh}
980+ sess.Protocolator = func(net.Conn) protocol.Protocol { return proto }
981+
982+ go func() {
983+ errCh <- sess.Dial()
984+ }()
985+
986+ srv, err := lst.Accept()
987+ c.Assert(err, IsNil)
988+
989+ // connect done
990+
991+ _, err = protocol.ReadWireFormatVersion(srv, dialTestTimeout)
992+ c.Check(err, NotNil)
993+
994+ c.Check(<-errCh, NotNil)
995+ c.Check(sess.State(), Equals, Error)
996+}
997+
998 func (cs *clientSessionSuite) TestDialWorks(c *C) {
999 // happy path thoughts
1000 cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock)
1001@@ -1104,7 +1284,8 @@
1002 // advertise
1003 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1004 b, err := json.Marshal(map[string]interface{}{
1005- "hosts": []string{"nowhere", lst.Addr().String()},
1006+ "domain": "localhost",
1007+ "hosts": []string{"nowhere", lst.Addr().String()},
1008 })
1009 if err != nil {
1010 panic(err)
1011@@ -1223,3 +1404,38 @@
1012 c.Assert(err, IsNil)
1013 // connect done
1014 }
1015+
1016+/****************************************************************
1017+ redialDelay() tests
1018+****************************************************************/
1019+
1020+func (cs *clientSessionSuite) TestShouldDelay(c *C) {
1021+ sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
1022+ c.Assert(err, IsNil)
1023+ c.Check(sess.ShouldDelay(), Equals, false)
1024+ sess.setShouldDelay()
1025+ c.Check(sess.ShouldDelay(), Equals, true)
1026+ sess.clearShouldDelay()
1027+ c.Check(sess.ShouldDelay(), Equals, false)
1028+}
1029+
1030+func (cs *clientSessionSuite) TestRedialDelay(c *C) {
1031+ sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
1032+ c.Assert(err, IsNil)
1033+ sess.redialDelays = []time.Duration{17, 42}
1034+ n := 0
1035+ sess.redialJitter = func(time.Duration) time.Duration { n++; return 0 }
1036+ // we get increasing delays while we're unhappy
1037+ sess.setShouldDelay()
1038+ c.Check(redialDelay(sess), Equals, time.Duration(17))
1039+ c.Check(redialDelay(sess), Equals, time.Duration(42))
1040+ c.Check(redialDelay(sess), Equals, time.Duration(42))
1041+ // once we're happy, delays drop to 0
1042+ sess.clearShouldDelay()
1043+ c.Check(redialDelay(sess), Equals, time.Duration(0))
1044+ // and start again from the top if we become unhappy again
1045+ sess.setShouldDelay()
1046+ c.Check(redialDelay(sess), Equals, time.Duration(17))
1047+ // and redialJitter got called every time shouldDelay was true
1048+ c.Check(n, Equals, 4)
1049+}
1050
1051=== modified file 'config/config.go'
1052--- config/config.go 2014-03-25 18:49:18 +0000
1053+++ config/config.go 2014-04-29 18:02:00 +0000
1054@@ -20,6 +20,7 @@
1055 import (
1056 "encoding/json"
1057 "errors"
1058+ "flag"
1059 "fmt"
1060 "io"
1061 "io/ioutil"
1062@@ -27,6 +28,7 @@
1063 "os"
1064 "path/filepath"
1065 "reflect"
1066+ "strconv"
1067 "strings"
1068 "time"
1069 )
1070@@ -118,6 +120,22 @@
1071 return fillDestConfig(destValue, p1)
1072 }
1073
1074+// FromString are config holders that can be set by parsing a string.
1075+type FromString interface {
1076+ SetFromString(enc string) error
1077+}
1078+
1079+// UnmarshalJSONViaString helps unmarshalling from JSON for FromString
1080+// supporting config holders.
1081+func UnmarshalJSONViaString(dest FromString, b []byte) error {
1082+ var enc string
1083+ err := json.Unmarshal(b, &enc)
1084+ if err != nil {
1085+ return err
1086+ }
1087+ return dest.SetFromString(enc)
1088+}
1089+
1090 // ConfigTimeDuration can hold a time.Duration in a configuration struct,
1091 // that is parsed from a string as supported by time.ParseDuration.
1092 type ConfigTimeDuration struct {
1093@@ -125,13 +143,11 @@
1094 }
1095
1096 func (ctd *ConfigTimeDuration) UnmarshalJSON(b []byte) error {
1097- var enc string
1098- var v time.Duration
1099- err := json.Unmarshal(b, &enc)
1100- if err != nil {
1101- return err
1102- }
1103- v, err = time.ParseDuration(enc)
1104+ return UnmarshalJSONViaString(ctd, b)
1105+}
1106+
1107+func (ctd *ConfigTimeDuration) SetFromString(enc string) error {
1108+ v, err := time.ParseDuration(enc)
1109 if err != nil {
1110 return err
1111 }
1112@@ -148,12 +164,11 @@
1113 type ConfigHostPort string
1114
1115 func (chp *ConfigHostPort) UnmarshalJSON(b []byte) error {
1116- var enc string
1117- err := json.Unmarshal(b, &enc)
1118- if err != nil {
1119- return err
1120- }
1121- _, _, err = net.SplitHostPort(enc)
1122+ return UnmarshalJSONViaString(chp, b)
1123+}
1124+
1125+func (chp *ConfigHostPort) SetFromString(enc string) error {
1126+ _, _, err := net.SplitHostPort(enc)
1127 if err != nil {
1128 return err
1129 }
1130@@ -198,23 +213,117 @@
1131 return ioutil.ReadFile(p)
1132 }
1133
1134-// ReadFiles reads configuration from a set of files. Uses ReadConfig internally.
1135+// used to implement getting config values with flag.Parse()
1136+type val struct {
1137+ destField destField
1138+ accu map[string]json.RawMessage
1139+}
1140+
1141+func (v *val) String() string { // used to show default
1142+ return string(v.accu[v.destField.configName()])
1143+}
1144+
1145+func (v *val) IsBoolFlag() bool {
1146+ return v.destField.fld.Type.Kind() == reflect.Bool
1147+}
1148+
1149+func (v *val) marshalAsNeeded(s string) (json.RawMessage, error) {
1150+ var toMarshal interface{}
1151+ switch v.destField.dest.(type) {
1152+ case *string, FromString:
1153+ toMarshal = s
1154+ case *bool:
1155+ bit, err := strconv.ParseBool(s)
1156+ if err != nil {
1157+ return nil, err
1158+ }
1159+ toMarshal = bit
1160+ default:
1161+ return json.RawMessage(s), nil
1162+ }
1163+ return json.Marshal(toMarshal)
1164+}
1165+
1166+func (v *val) Set(s string) error {
1167+ marshalled, err := v.marshalAsNeeded(s)
1168+ if err != nil {
1169+ return err
1170+ }
1171+ v.accu[v.destField.configName()] = marshalled
1172+ return nil
1173+}
1174+
1175+func readOneConfig(accu map[string]json.RawMessage, cfgPath string) error {
1176+ r, err := os.Open(cfgPath)
1177+ if err != nil {
1178+ return err
1179+ }
1180+ defer r.Close()
1181+ err = json.NewDecoder(r).Decode(&accu)
1182+ if err != nil {
1183+ return err
1184+ }
1185+ return nil
1186+}
1187+
1188+// used to implement -cfg@=
1189+type readConfigAtVal struct {
1190+ accu map[string]json.RawMessage
1191+}
1192+
1193+func (v *readConfigAtVal) String() string {
1194+ return "<config.json>"
1195+}
1196+
1197+func (v *readConfigAtVal) Set(path string) error {
1198+ return readOneConfig(v.accu, path)
1199+}
1200+
1201+// readUsingFlags gets config values from command line flags.
1202+func readUsingFlags(accu map[string]json.RawMessage, destValue reflect.Value) error {
1203+ if flag.Parsed() {
1204+ if IgnoreParsedFlags {
1205+ return nil
1206+ }
1207+ return fmt.Errorf("too late, flags already parsed")
1208+ }
1209+ destStruct := destValue.Elem()
1210+ for destField := range traverseStruct(destStruct) {
1211+ help := destField.fld.Tag.Get("help")
1212+ flag.Var(&val{destField, accu}, destField.configName(), help)
1213+ }
1214+ flag.Var(&readConfigAtVal{accu}, "cfg@", "get config values from file")
1215+ flag.Parse()
1216+ return nil
1217+}
1218+
1219+// IgnoreParsedFlags will just have ReadFiles ignore <flags> if the
1220+// command line was already parsed.
1221+var IgnoreParsedFlags = false
1222+
1223+// ReadFiles reads configuration from a set of files. The string
1224+// "<flags>" can be used as a pseudo file-path, it will consider
1225+// command line flags, invoking flag.Parse(). Among those the flag
1226+// -cfg@=FILE can be used to get further config values from FILE.
1227 func ReadFiles(destConfig interface{}, cfgFpaths ...string) error {
1228 destValue, err := checkDestConfig("destConfig", destConfig)
1229 if err != nil {
1230 return err
1231 }
1232 // do the parsing in two phases for better error handling
1233- var p1 map[string]json.RawMessage
1234+ p1 := make(map[string]json.RawMessage)
1235 readOne := false
1236 for _, cfgPath := range cfgFpaths {
1237+ if cfgPath == "<flags>" {
1238+ err := readUsingFlags(p1, destValue)
1239+ if err != nil {
1240+ return err
1241+ }
1242+ readOne = true
1243+ continue
1244+ }
1245 if _, err := os.Stat(cfgPath); err == nil {
1246- r, err := os.Open(cfgPath)
1247- if err != nil {
1248- return err
1249- }
1250- defer r.Close()
1251- err = json.NewDecoder(r).Decode(&p1)
1252+ err := readOneConfig(p1, cfgPath)
1253 if err != nil {
1254 return err
1255 }
1256
1257=== modified file 'config/config_test.go'
1258--- config/config_test.go 2014-03-25 18:49:18 +0000
1259+++ config/config_test.go 2014-04-29 18:02:00 +0000
1260@@ -18,6 +18,9 @@
1261
1262 import (
1263 "bytes"
1264+ "encoding/json"
1265+ "flag"
1266+ "fmt"
1267 "io/ioutil"
1268 "os"
1269 "path/filepath"
1270@@ -230,3 +233,105 @@
1271 c.Check(res, DeepEquals, []string{"b", "c_list", "d"})
1272
1273 }
1274+
1275+type testConfig3 struct {
1276+ A bool
1277+ B string
1278+ C []string `json:"c_list"`
1279+ D ConfigTimeDuration `help:"duration"`
1280+ E ConfigHostPort
1281+ F string
1282+}
1283+
1284+type configFlagsSuite struct{}
1285+
1286+var _ = Suite(&configFlagsSuite{})
1287+
1288+func (s *configFlagsSuite) SetUpTest(c *C) {
1289+ flag.CommandLine = flag.NewFlagSet("cmd", flag.PanicOnError)
1290+ // supress outputs
1291+ flag.Usage = func() { flag.PrintDefaults() }
1292+ flag.CommandLine.SetOutput(ioutil.Discard)
1293+}
1294+
1295+func (s *configFlagsSuite) TestReadUsingFlags(c *C) {
1296+ os.Args = []string{"cmd", "-a=1", "-b=foo", "-c_list", `["x","y"]`, "-d", "10s", "-e=localhost:80"}
1297+ var cfg testConfig3
1298+ p := make(map[string]json.RawMessage)
1299+ err := readUsingFlags(p, reflect.ValueOf(&cfg))
1300+ c.Assert(err, IsNil)
1301+ c.Check(p, DeepEquals, map[string]json.RawMessage{
1302+ "a": json.RawMessage("true"),
1303+ "b": json.RawMessage(`"foo"`),
1304+ "c_list": json.RawMessage(`["x","y"]`),
1305+ "d": json.RawMessage(`"10s"`),
1306+ "e": json.RawMessage(`"localhost:80"`),
1307+ })
1308+}
1309+
1310+func (s *configFlagsSuite) TestReadUsingFlagsBoolError(c *C) {
1311+ os.Args = []string{"cmd", "-a=zoo"}
1312+ var cfg testConfig3
1313+ p := make(map[string]json.RawMessage)
1314+ c.Check(func() { readUsingFlags(p, reflect.ValueOf(&cfg)) }, PanicMatches, ".*invalid boolean.*-a.*")
1315+}
1316+
1317+func (s *configFlagsSuite) TestReadFilesAndFlags(c *C) {
1318+ // test <flags> pseudo file
1319+ os.Args = []string{"cmd", "-b=x"}
1320+ tmpDir := c.MkDir()
1321+ cfgPath := filepath.Join(tmpDir, "cfg.json")
1322+ err := ioutil.WriteFile(cfgPath, []byte(`{"a": 42, "c_list": ["y", "z"]}`), os.ModePerm)
1323+ c.Assert(err, IsNil)
1324+ var cfg testConfig1
1325+ err = ReadFiles(&cfg, cfgPath, "<flags>")
1326+ c.Assert(err, IsNil)
1327+ c.Check(cfg.A, Equals, 42)
1328+ c.Check(cfg.B, Equals, "x")
1329+ c.Check(cfg.C, DeepEquals, []string{"y", "z"})
1330+}
1331+
1332+func (s *configFlagsSuite) TestReadFilesAndFlagsConfigAtSupport(c *C) {
1333+ // test <flags> pseudo file
1334+ tmpDir := c.MkDir()
1335+ cfgPath := filepath.Join(tmpDir, "cfg.json")
1336+ os.Args = []string{"cmd", "-a=42", fmt.Sprintf("-cfg@=%s", cfgPath)}
1337+ err := ioutil.WriteFile(cfgPath, []byte(`{"b": "x", "c_list": ["y", "z"]}`), os.ModePerm)
1338+ c.Assert(err, IsNil)
1339+ var cfg testConfig1
1340+ err = ReadFiles(&cfg, "<flags>")
1341+ c.Assert(err, IsNil)
1342+ c.Check(cfg.A, Equals, 42)
1343+ c.Check(cfg.B, Equals, "x")
1344+ c.Check(cfg.C, DeepEquals, []string{"y", "z"})
1345+}
1346+
1347+func (s *configFlagsSuite) TestReadUsingFlagsHelp(c *C) {
1348+ os.Args = []string{"cmd", "-h"}
1349+ buf := bytes.NewBufferString("")
1350+ flag.CommandLine.Init("cmd", flag.ContinueOnError)
1351+ flag.CommandLine.SetOutput(buf)
1352+ var cfg testConfig3
1353+ p := map[string]json.RawMessage{
1354+ "d": json.RawMessage(`"2s"`),
1355+ }
1356+ readUsingFlags(p, reflect.ValueOf(&cfg))
1357+ c.Check(buf.String(), Matches, `(?s).*-cfg@=<config.json>: get config values from file\n.*-d="2s": duration.*`)
1358+}
1359+
1360+func (s *configFlagsSuite) TestReadUsingFlagsAlreadyParsed(c *C) {
1361+ os.Args = []string{"cmd"}
1362+ flag.Parse()
1363+ var cfg struct{}
1364+ p := make(map[string]json.RawMessage)
1365+ err := readUsingFlags(p, reflect.ValueOf(&cfg))
1366+ c.Assert(err, ErrorMatches, "too late, flags already parsed")
1367+ err = ReadFiles(&cfg, "<flags>")
1368+ c.Assert(err, ErrorMatches, "too late, flags already parsed")
1369+ IgnoreParsedFlags = true
1370+ defer func() {
1371+ IgnoreParsedFlags = false
1372+ }()
1373+ err = ReadFiles(&cfg, "<flags>")
1374+ c.Assert(err, IsNil)
1375+}
1376
1377=== modified file 'debian/changelog'
1378--- debian/changelog 2014-04-11 18:31:57 +0000
1379+++ debian/changelog 2014-04-29 18:02:00 +0000
1380@@ -1,3 +1,9 @@
1381+ubuntu-push (0.21-0.ubuntu1) UNRELEASED; urgency=medium
1382+
1383+ * New upstream release: first auth bits, and Qt dependency.
1384+
1385+ -- John Lenton <john.lenton@canonical.com> Tue, 15 Apr 2014 14:04:35 +0100
1386+
1387 ubuntu-push (0.2+14.04.20140411-0ubuntu1) trusty; urgency=medium
1388
1389 [ John Lenton ]
1390
1391=== modified file 'debian/control'
1392--- debian/control 2014-03-25 16:26:20 +0000
1393+++ debian/control 2014-04-29 18:02:00 +0000
1394@@ -14,6 +14,10 @@
1395 libgcrypt11-dev,
1396 libglib2.0-dev (>= 2.31.6),
1397 libwhoopsie-dev,
1398+ qtbase5-private-dev,
1399+ qtdeclarative5-dev,
1400+ libqt5opengl5-dev,
1401+ libubuntuoneauth-2.0-dev,
1402 Standards-Version: 3.9.5
1403 Homepage: http://launchpad.net/ubuntu-push
1404 Vcs-Bzr: lp:ubuntu-push
1405
1406=== modified file 'debian/rules'
1407--- debian/rules 2014-03-24 12:22:55 +0000
1408+++ debian/rules 2014-04-29 18:02:00 +0000
1409@@ -2,9 +2,12 @@
1410 # -*- makefile -*-
1411
1412 export DH_GOPKG := launchpad.net/ubuntu-push
1413-export DEB_BUILD_OPTIONS := nostrip
1414 export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR)
1415
1416+override_dh_auto_build:
1417+ cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1) && tar xvzf ../../externals.tgz
1418+ dh_auto_build --buildsystem=golang
1419+
1420 override_dh_install:
1421 dh_install -Xusr/bin/cmd -Xusr/bin/dev --fail-missing
1422
1423
1424=== modified file 'dependencies.tsv'
1425--- dependencies.tsv 2014-03-12 13:23:26 +0000
1426+++ dependencies.tsv 2014-04-29 18:02:00 +0000
1427@@ -2,3 +2,5 @@
1428 launchpad.net/go-dbus/v1 bzr james@jamesh.id.au-20140206110213-pbzcr6ucaz3rqmnw 125
1429 launchpad.net/go-xdg/v0 bzr john.lenton@canonical.com-20140208094800-gubd5md7cro3mtxa 10
1430 launchpad.net/gocheck bzr gustavo@niemeyer.net-20140127131816-zshobk1qqme626xw 86
1431+gopkg.in/qml.v0 git master 8adbc8c2bf2da9f609df366683ad0f47a89c3d49
1432+gopkg.in/niemeyer/uoneauth.v1 git v1 0758ba882a143ad2862dbcac85a7ca145750b640
1433
1434=== added file 'externals.tgz'
1435Binary files externals.tgz 1970-01-01 00:00:00 +0000 and externals.tgz 2014-04-29 18:02:00 +0000 differ
1436=== modified file 'logger/logger.go'
1437--- logger/logger.go 2014-02-24 10:27:38 +0000
1438+++ logger/logger.go 2014-04-29 18:02:00 +0000
1439@@ -23,6 +23,8 @@
1440 "log"
1441 "os"
1442 "runtime"
1443+
1444+ "launchpad.net/ubuntu-push/config"
1445 )
1446
1447 // Logger is a simple logger interface with logging at levels.
1448@@ -119,3 +121,28 @@
1449 lg.outputFunc(2, fmt.Sprintf("DEBUG "+format, v...))
1450 }
1451 }
1452+
1453+// config bits
1454+
1455+// ConfigLogLevel can hold a log level in a configuration struct.
1456+type ConfigLogLevel string
1457+
1458+func (cll *ConfigLogLevel) ConfigFromJSONString() {}
1459+
1460+func (cll *ConfigLogLevel) UnmarshalJSON(b []byte) error {
1461+ return config.UnmarshalJSONViaString(cll, b)
1462+}
1463+
1464+func (cll *ConfigLogLevel) SetFromString(enc string) error {
1465+ _, ok := levelToNLevel[enc]
1466+ if !ok {
1467+ return fmt.Errorf("not a log level: %s", enc)
1468+ }
1469+ *cll = ConfigLogLevel(enc)
1470+ return nil
1471+}
1472+
1473+// Level returns the log level string held in cll.
1474+func (cll ConfigLogLevel) Level() string {
1475+ return string(cll)
1476+}
1477
1478=== modified file 'logger/logger_test.go'
1479--- logger/logger_test.go 2014-02-10 22:51:43 +0000
1480+++ logger/logger_test.go 2014-04-29 18:02:00 +0000
1481@@ -25,6 +25,8 @@
1482 "testing"
1483
1484 . "launchpad.net/gocheck"
1485+
1486+ "launchpad.net/ubuntu-push/config"
1487 )
1488
1489 func TestLogger(t *testing.T) { TestingT(t) }
1490@@ -138,3 +140,26 @@
1491 logger.Output(1, "foobaz")
1492 c.Check(buf.String(), Matches, "logger_test.go:[0-9]+: foobar\nlogger_test.go:[0-9]+: foobaz\n")
1493 }
1494+
1495+type testLogLevelConfig struct {
1496+ Lvl ConfigLogLevel
1497+}
1498+
1499+func (s *loggerSuite) TestReadConfigLogLevel(c *C) {
1500+ buf := bytes.NewBufferString(`{"lvl": "debug"}`)
1501+ var cfg testLogLevelConfig
1502+ err := config.ReadConfig(buf, &cfg)
1503+ c.Assert(err, IsNil)
1504+ c.Check(cfg.Lvl.Level(), Equals, "debug")
1505+}
1506+
1507+func (s *loggerSuite) TestReadConfigLogLevelErrors(c *C) {
1508+ var cfg testLogLevelConfig
1509+ checkError := func(jsonCfg string, expectedError string) {
1510+ buf := bytes.NewBufferString(jsonCfg)
1511+ err := config.ReadConfig(buf, &cfg)
1512+ c.Check(err, ErrorMatches, expectedError)
1513+ }
1514+ checkError(`{"lvl": 1}`, "lvl:.*type string")
1515+ checkError(`{"lvl": "foo"}`, "lvl: not a log level: foo")
1516+}
1517
1518=== modified file 'protocol/messages.go'
1519--- protocol/messages.go 2014-04-04 13:54:45 +0000
1520+++ protocol/messages.go 2014-04-29 18:02:00 +0000
1521@@ -54,6 +54,14 @@
1522 Split() (done bool)
1523 }
1524
1525+// OnewayMsg are messages that are not to be followed by a response,
1526+// after sending them the session either aborts or continues.
1527+type OnewayMsg interface {
1528+ SplittableMsg
1529+ // continue session after the message?
1530+ OnewayContinue() bool
1531+}
1532+
1533 // CONNBROKEN message, server side is breaking the connection for reason.
1534 type ConnBrokenMsg struct {
1535 Type string `json:"T"`
1536@@ -65,11 +73,35 @@
1537 return true
1538 }
1539
1540+func (m *ConnBrokenMsg) OnewayContinue() bool {
1541+ return false
1542+}
1543+
1544 // CONNBROKEN reasons
1545 const (
1546 BrokenHostMismatch = "host-mismatch"
1547 )
1548
1549+// CONNWARN message, server side is warning about partial functionality
1550+// because reason.
1551+type ConnWarnMsg struct {
1552+ Type string `json:"T"`
1553+ // reason
1554+ Reason string
1555+}
1556+
1557+func (m *ConnWarnMsg) Split() bool {
1558+ return true
1559+}
1560+func (m *ConnWarnMsg) OnewayContinue() bool {
1561+ return true
1562+}
1563+
1564+// CONNWARN reasons
1565+const (
1566+ WarnUnauthorized = "unauthorized"
1567+)
1568+
1569 // PING/PONG messages
1570 type PingPongMsg struct {
1571 Type string `json:"T"`
1572@@ -130,6 +162,19 @@
1573 Payload json.RawMessage `json:"P"`
1574 }
1575
1576+// ExtractPayloads gets only the payloads out of a slice of notications.
1577+func ExtractPayloads(notifications []Notification) []json.RawMessage {
1578+ n := len(notifications)
1579+ if n == 0 {
1580+ return nil
1581+ }
1582+ payloads := make([]json.RawMessage, n)
1583+ for i := 0; i < n; i++ {
1584+ payloads[i] = notifications[i].Payload
1585+ }
1586+ return payloads
1587+}
1588+
1589 // ACKnowledgement message
1590 type AckMsg struct {
1591 Type string `json:"T"`
1592
1593=== modified file 'protocol/messages_test.go'
1594--- protocol/messages_test.go 2014-04-04 13:19:10 +0000
1595+++ protocol/messages_test.go 2014-04-29 18:02:00 +0000
1596@@ -104,6 +104,22 @@
1597 c.Check(b.splitting, Equals, 0)
1598 }
1599
1600-func (s *messagesSuite) TestSplitConnBrokenMsg(c *C) {
1601- c.Check((&ConnBrokenMsg{}).Split(), Equals, true)
1602+func (s *messagesSuite) TestConnBrokenMsg(c *C) {
1603+ m := &ConnBrokenMsg{}
1604+ c.Check(m.Split(), Equals, true)
1605+ c.Check(m.OnewayContinue(), Equals, false)
1606+}
1607+
1608+func (s *messagesSuite) TestConnWarnMsg(c *C) {
1609+ m := &ConnWarnMsg{}
1610+ c.Check(m.Split(), Equals, true)
1611+ c.Check(m.OnewayContinue(), Equals, true)
1612+}
1613+
1614+func (s *messagesSuite) TestExtractPayloads(c *C) {
1615+ c.Check(ExtractPayloads(nil), IsNil)
1616+ p1 := json.RawMessage(`{"a":1}`)
1617+ p2 := json.RawMessage(`{"b":2}`)
1618+ ns := []Notification{Notification{Payload: p1}, Notification{Payload: p2}}
1619+ c.Check(ExtractPayloads(ns), DeepEquals, []json.RawMessage{p1, p2})
1620 }
1621
1622=== modified file 'protocol/state-diag-client.gv'
1623--- protocol/state-diag-client.gv 2014-01-16 20:07:13 +0000
1624+++ protocol/state-diag-client.gv 2014-04-29 18:02:00 +0000
1625@@ -2,7 +2,7 @@
1626 label = "State diagram for client";
1627 size="12,6";
1628 rankdir=LR;
1629- node [shape = doublecircle]; pingTimeout;
1630+ node [shape = doublecircle]; pingTimeout; connBroken;
1631 node [shape = circle];
1632 start1 -> start2 [ label = "Write wire version" ];
1633 start2 -> start3 [ label = "Write CONNECT" ];
1634@@ -13,4 +13,7 @@
1635 broadcast -> loop [label = "Write ACK"];
1636 loop -> pingTimeout [
1637 label = "Elapsed ping interval + exchange interval"];
1638+ loop -> connBroken [label = "Read CONNBROKEN"];
1639+ loop -> warn [label = "Read CONNWARN"];
1640+ warn -> loop;
1641 }
1642
1643=== modified file 'protocol/state-diag-client.svg'
1644--- protocol/state-diag-client.svg 2014-01-16 19:37:57 +0000
1645+++ protocol/state-diag-client.svg 2014-04-29 18:02:00 +0000
1646@@ -4,95 +4,123 @@
1647 <!-- Generated by graphviz version 2.26.3 (20100126.1600)
1648 -->
1649 <!-- Title: state_diagram_client Pages: 1 -->
1650-<svg width="864pt" height="279pt"
1651- viewBox="0.00 0.00 864.00 278.89" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
1652-<g id="graph1" class="graph" transform="scale(0.683544 0.683544) rotate(0) translate(4 404)">
1653+<svg width="822pt" height="432pt"
1654+ viewBox="0.00 0.00 822.36 432.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
1655+<g id="graph1" class="graph" transform="scale(0.650602 0.650602) rotate(0) translate(4 660)">
1656 <title>state_diagram_client</title>
1657-<polygon fill="white" stroke="white" points="-4,5 -4,-404 1261,-404 1261,5 -4,5"/>
1658+<polygon fill="white" stroke="white" points="-4,5 -4,-660 1261,-660 1261,5 -4,5"/>
1659 <text text-anchor="middle" x="628" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for client</text>
1660 <!-- pingTimeout -->
1661 <g id="node1" class="node"><title>pingTimeout</title>
1662-<ellipse fill="none" stroke="black" cx="1180" cy="-324" rx="72.1249" ry="72.1249"/>
1663-<ellipse fill="none" stroke="black" cx="1180" cy="-324" rx="76.1249" ry="76.1249"/>
1664-<text text-anchor="middle" x="1180" y="-320.4" font-family="Times Roman,serif" font-size="14.00">pingTimeout</text>
1665+<ellipse fill="none" stroke="black" cx="1180" cy="-580" rx="72.1249" ry="72.1249"/>
1666+<ellipse fill="none" stroke="black" cx="1180" cy="-580" rx="76.1249" ry="76.1249"/>
1667+<text text-anchor="middle" x="1180" y="-576.4" font-family="Times Roman,serif" font-size="14.00">pingTimeout</text>
1668+</g>
1669+<!-- connBroken -->
1670+<g id="node2" class="node"><title>connBroken</title>
1671+<ellipse fill="none" stroke="black" cx="1180" cy="-413" rx="68.8251" ry="69.2965"/>
1672+<ellipse fill="none" stroke="black" cx="1180" cy="-413" rx="72.7978" ry="73.2965"/>
1673+<text text-anchor="middle" x="1180" y="-409.4" font-family="Times Roman,serif" font-size="14.00">connBroken</text>
1674 </g>
1675 <!-- start1 -->
1676-<g id="node2" class="node"><title>start1</title>
1677-<ellipse fill="none" stroke="black" cx="42" cy="-166" rx="41.2167" ry="41.7193"/>
1678-<text text-anchor="middle" x="42" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start1</text>
1679+<g id="node3" class="node"><title>start1</title>
1680+<ellipse fill="none" stroke="black" cx="42" cy="-231" rx="41.2167" ry="41.7193"/>
1681+<text text-anchor="middle" x="42" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start1</text>
1682 </g>
1683 <!-- start2 -->
1684-<g id="node4" class="node"><title>start2</title>
1685-<ellipse fill="none" stroke="black" cx="292" cy="-166" rx="41.2167" ry="41.7193"/>
1686-<text text-anchor="middle" x="292" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start2</text>
1687+<g id="node5" class="node"><title>start2</title>
1688+<ellipse fill="none" stroke="black" cx="292" cy="-231" rx="41.2167" ry="41.7193"/>
1689+<text text-anchor="middle" x="292" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start2</text>
1690 </g>
1691 <!-- start1&#45;&gt;start2 -->
1692 <g id="edge2" class="edge"><title>start1&#45;&gt;start2</title>
1693-<path fill="none" stroke="black" d="M83.5631,-166C126.547,-166 193.757,-166 240.181,-166"/>
1694-<polygon fill="black" stroke="black" points="240.338,-169.5 250.338,-166 240.338,-162.5 240.338,-169.5"/>
1695-<text text-anchor="middle" x="167" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Write wire version</text>
1696+<path fill="none" stroke="black" d="M83.5631,-231C126.547,-231 193.757,-231 240.181,-231"/>
1697+<polygon fill="black" stroke="black" points="240.338,-234.5 250.338,-231 240.338,-227.5 240.338,-234.5"/>
1698+<text text-anchor="middle" x="167" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Write wire version</text>
1699 </g>
1700 <!-- start3 -->
1701-<g id="node6" class="node"><title>start3</title>
1702-<ellipse fill="none" stroke="black" cx="526" cy="-166" rx="41.2167" ry="41.7193"/>
1703-<text text-anchor="middle" x="526" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start3</text>
1704+<g id="node7" class="node"><title>start3</title>
1705+<ellipse fill="none" stroke="black" cx="526" cy="-231" rx="41.2167" ry="41.7193"/>
1706+<text text-anchor="middle" x="526" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start3</text>
1707 </g>
1708 <!-- start2&#45;&gt;start3 -->
1709 <g id="edge4" class="edge"><title>start2&#45;&gt;start3</title>
1710-<path fill="none" stroke="black" d="M333.565,-166C372.875,-166 431.992,-166 474.321,-166"/>
1711-<polygon fill="black" stroke="black" points="474.429,-169.5 484.429,-166 474.429,-162.5 474.429,-169.5"/>
1712-<text text-anchor="middle" x="409" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Write CONNECT</text>
1713+<path fill="none" stroke="black" d="M333.565,-231C372.875,-231 431.992,-231 474.321,-231"/>
1714+<polygon fill="black" stroke="black" points="474.429,-234.5 484.429,-231 474.429,-227.5 474.429,-234.5"/>
1715+<text text-anchor="middle" x="409" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Write CONNECT</text>
1716 </g>
1717 <!-- loop -->
1718-<g id="node8" class="node"><title>loop</title>
1719-<ellipse fill="none" stroke="black" cx="746" cy="-166" rx="31.8198" ry="31.8198"/>
1720-<text text-anchor="middle" x="746" y="-162.4" font-family="Times Roman,serif" font-size="14.00">loop</text>
1721+<g id="node9" class="node"><title>loop</title>
1722+<ellipse fill="none" stroke="black" cx="746" cy="-231" rx="31.8198" ry="31.8198"/>
1723+<text text-anchor="middle" x="746" y="-227.4" font-family="Times Roman,serif" font-size="14.00">loop</text>
1724 </g>
1725 <!-- start3&#45;&gt;loop -->
1726 <g id="edge6" class="edge"><title>start3&#45;&gt;loop</title>
1727-<path fill="none" stroke="black" d="M567.639,-166C606.633,-166 664.616,-166 703.793,-166"/>
1728-<polygon fill="black" stroke="black" points="703.818,-169.5 713.818,-166 703.818,-162.5 703.818,-169.5"/>
1729-<text text-anchor="middle" x="641" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Read CONNACK</text>
1730+<path fill="none" stroke="black" d="M567.639,-231C606.633,-231 664.616,-231 703.793,-231"/>
1731+<polygon fill="black" stroke="black" points="703.818,-234.5 713.818,-231 703.818,-227.5 703.818,-234.5"/>
1732+<text text-anchor="middle" x="641" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Read CONNACK</text>
1733 </g>
1734 <!-- loop&#45;&gt;pingTimeout -->
1735 <g id="edge16" class="edge"><title>loop&#45;&gt;pingTimeout</title>
1736-<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"/>
1737-<polygon fill="black" stroke="black" points="1093.67,-319.928 1104.02,-317.68 1094.53,-312.981 1093.67,-319.928"/>
1738-<text text-anchor="middle" x="941" y="-319.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval + exchange interval</text>
1739+<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"/>
1740+<polygon fill="black" stroke="black" points="1093.96,-573.992 1104.39,-572.09 1095.05,-567.078 1093.96,-573.992"/>
1741+<text text-anchor="middle" x="941" y="-572.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval + exchange interval</text>
1742+</g>
1743+<!-- loop&#45;&gt;connBroken -->
1744+<g id="edge18" class="edge"><title>loop&#45;&gt;connBroken</title>
1745+<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"/>
1746+<polygon fill="black" stroke="black" points="1096.19,-416.445 1106.33,-413.388 1096.5,-409.452 1096.19,-416.445"/>
1747+<text text-anchor="middle" x="941" y="-417.4" font-family="Times Roman,serif" font-size="14.00">Read CONNBROKEN</text>
1748 </g>
1749 <!-- pong -->
1750-<g id="node10" class="node"><title>pong</title>
1751-<ellipse fill="none" stroke="black" cx="1180" cy="-195" rx="34.8574" ry="35.3553"/>
1752-<text text-anchor="middle" x="1180" y="-191.4" font-family="Times Roman,serif" font-size="14.00">pong</text>
1753+<g id="node11" class="node"><title>pong</title>
1754+<ellipse fill="none" stroke="black" cx="1180" cy="-287" rx="34.8574" ry="35.3553"/>
1755+<text text-anchor="middle" x="1180" y="-283.4" font-family="Times Roman,serif" font-size="14.00">pong</text>
1756 </g>
1757 <!-- loop&#45;&gt;pong -->
1758 <g id="edge8" class="edge"><title>loop&#45;&gt;pong</title>
1759-<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"/>
1760-<polygon fill="black" stroke="black" points="1134.89,-202.186 1144.62,-198.003 1134.4,-195.203 1134.89,-202.186"/>
1761-<text text-anchor="middle" x="941" y="-207.4" font-family="Times Roman,serif" font-size="14.00">Read PING</text>
1762+<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"/>
1763+<polygon fill="black" stroke="black" points="1135.49,-297.371 1144.94,-292.588 1134.57,-290.432 1135.49,-297.371"/>
1764+<text text-anchor="middle" x="941" y="-307.4" font-family="Times Roman,serif" font-size="14.00">Read PING</text>
1765 </g>
1766 <!-- broadcast -->
1767-<g id="node12" class="node"><title>broadcast</title>
1768-<ellipse fill="none" stroke="black" cx="1180" cy="-84" rx="58.1882" ry="58.6899"/>
1769-<text text-anchor="middle" x="1180" y="-80.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>
1770+<g id="node13" class="node"><title>broadcast</title>
1771+<ellipse fill="none" stroke="black" cx="1180" cy="-176" rx="58.1882" ry="58.6899"/>
1772+<text text-anchor="middle" x="1180" y="-172.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>
1773 </g>
1774 <!-- loop&#45;&gt;broadcast -->
1775 <g id="edge10" class="edge"><title>loop&#45;&gt;broadcast</title>
1776-<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"/>
1777-<polygon fill="black" stroke="black" points="1113.34,-101.917 1122.5,-96.5998 1112.02,-95.0419 1113.34,-101.917"/>
1778-<text text-anchor="middle" x="941" y="-136.4" font-family="Times Roman,serif" font-size="14.00">Read BROADCAST</text>
1779+<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"/>
1780+<polygon fill="black" stroke="black" points="1112.44,-189.353 1121.9,-184.574 1111.53,-182.413 1112.44,-189.353"/>
1781+<text text-anchor="middle" x="941" y="-217.4" font-family="Times Roman,serif" font-size="14.00">Read BROADCAST</text>
1782+</g>
1783+<!-- warn -->
1784+<g id="node19" class="node"><title>warn</title>
1785+<ellipse fill="none" stroke="black" cx="1180" cy="-63" rx="36.7696" ry="36.7696"/>
1786+<text text-anchor="middle" x="1180" y="-59.4" font-family="Times Roman,serif" font-size="14.00">warn</text>
1787+</g>
1788+<!-- loop&#45;&gt;warn -->
1789+<g id="edge20" class="edge"><title>loop&#45;&gt;warn</title>
1790+<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"/>
1791+<polygon fill="black" stroke="black" points="1135.26,-79.0068 1144.04,-73.0757 1133.48,-72.2376 1135.26,-79.0068"/>
1792+<text text-anchor="middle" x="941" y="-136.4" font-family="Times Roman,serif" font-size="14.00">Read CONNWARN</text>
1793 </g>
1794 <!-- pong&#45;&gt;loop -->
1795 <g id="edge12" class="edge"><title>pong&#45;&gt;loop</title>
1796-<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"/>
1797-<polygon fill="black" stroke="black" points="787.736,-157.528 778.16,-162.06 788.472,-164.489 787.736,-157.528"/>
1798-<text text-anchor="middle" x="941" y="-168.4" font-family="Times Roman,serif" font-size="14.00">Write PONG</text>
1799+<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"/>
1800+<polygon fill="black" stroke="black" points="788.085,-227.996 778.035,-231.348 787.982,-234.995 788.085,-227.996"/>
1801+<text text-anchor="middle" x="941" y="-254.4" font-family="Times Roman,serif" font-size="14.00">Write PONG</text>
1802 </g>
1803 <!-- broadcast&#45;&gt;loop -->
1804 <g id="edge14" class="edge"><title>broadcast&#45;&gt;loop</title>
1805-<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"/>
1806-<polygon fill="black" stroke="black" points="756.044,-124.528 755.336,-135.099 762.482,-127.277 756.044,-124.528"/>
1807-<text text-anchor="middle" x="941" y="-86.4" font-family="Times Roman,serif" font-size="14.00">Write ACK</text>
1808+<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"/>
1809+<polygon fill="black" stroke="black" points="763.182,-192.043 760.465,-202.284 768.975,-195.973 763.182,-192.043"/>
1810+<text text-anchor="middle" x="941" y="-172.4" font-family="Times Roman,serif" font-size="14.00">Write ACK</text>
1811+</g>
1812+<!-- warn&#45;&gt;loop -->
1813+<g id="edge22" class="edge"><title>warn&#45;&gt;loop</title>
1814+<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"/>
1815+<polygon fill="black" stroke="black" points="751.574,-188.904 752.686,-199.44 758.387,-190.51 751.574,-188.904"/>
1816 </g>
1817 </g>
1818 </svg>
1819
1820=== modified file 'protocol/state-diag-session.gv'
1821--- protocol/state-diag-session.gv 2014-01-16 20:07:13 +0000
1822+++ protocol/state-diag-session.gv 2014-04-29 18:02:00 +0000
1823@@ -2,6 +2,7 @@
1824 label = "State diagram for session";
1825 size="12,6";
1826 rankdir=LR;
1827+ node [shape = doublecircle]; stop;
1828 node [shape = circle];
1829 start1 -> start2 [ label = "Read wire version" ];
1830 start2 -> start3 [ label = "Read CONNECT" ];
1831@@ -17,4 +18,13 @@
1832 split_broadcast -> split_ack_wait [label = "Write split BROADCAST"];
1833 split_ack_wait -> split_broadcast [label = "Read ACK"];
1834 split_broadcast -> loop [label = "All split msgs written"];
1835+ // other
1836+ loop -> conn_broken [label = "Receive connbroken request"];
1837+ loop -> conn_warn [label = "Receive connwarn request"];
1838+ conn_broken -> stop [label = "Write CONNBROKEN"];
1839+ conn_warn -> loop [label = "Write CONNWARN"];
1840+ // timeouts
1841+ ack_wait -> stop [label = "Elapsed exhange timeout"];
1842+ split_ack_wait -> stop [label = "Elapsed exhange timeout"];
1843+ pong_wait -> stop [label = "Elapsed exhange timeout"];
1844 }
1845
1846=== modified file 'protocol/state-diag-session.svg'
1847--- protocol/state-diag-session.svg 2014-01-16 19:37:57 +0000
1848+++ protocol/state-diag-session.svg 2014-04-29 18:02:00 +0000
1849@@ -4,139 +4,197 @@
1850 <!-- Generated by graphviz version 2.26.3 (20100126.1600)
1851 -->
1852 <!-- Title: state_diagram_session Pages: 1 -->
1853-<svg width="864pt" height="208pt"
1854- viewBox="0.00 0.00 864.00 207.94" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
1855-<g id="graph1" class="graph" transform="scale(0.435923 0.435923) rotate(0) translate(4 473)">
1856+<svg width="864pt" height="266pt"
1857+ viewBox="0.00 0.00 864.00 265.73" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
1858+<g id="graph1" class="graph" transform="scale(0.367035 0.367035) rotate(0) translate(4 720)">
1859 <title>state_diagram_session</title>
1860-<polygon fill="white" stroke="white" points="-4,5 -4,-473 1979,-473 1979,5 -4,5"/>
1861-<text text-anchor="middle" x="987" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for session</text>
1862+<polygon fill="white" stroke="white" points="-4,5 -4,-720 2351,-720 2351,5 -4,5"/>
1863+<text text-anchor="middle" x="1173" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for session</text>
1864+<!-- stop -->
1865+<g id="node1" class="node"><title>stop</title>
1866+<ellipse fill="none" stroke="black" cx="2309" cy="-335" rx="32.0813" ry="32.5269"/>
1867+<ellipse fill="none" stroke="black" cx="2309" cy="-335" rx="36.0265" ry="36.5269"/>
1868+<text text-anchor="middle" x="2309" y="-331.4" font-family="Times Roman,serif" font-size="14.00">stop</text>
1869+</g>
1870 <!-- start1 -->
1871-<g id="node1" class="node"><title>start1</title>
1872-<ellipse fill="none" stroke="black" cx="42" cy="-294" rx="41.2167" ry="41.7193"/>
1873-<text text-anchor="middle" x="42" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start1</text>
1874+<g id="node2" class="node"><title>start1</title>
1875+<ellipse fill="none" stroke="black" cx="42" cy="-395" rx="41.2167" ry="41.7193"/>
1876+<text text-anchor="middle" x="42" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start1</text>
1877 </g>
1878 <!-- start2 -->
1879-<g id="node3" class="node"><title>start2</title>
1880-<ellipse fill="none" stroke="black" cx="286" cy="-294" rx="41.2167" ry="41.7193"/>
1881-<text text-anchor="middle" x="286" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start2</text>
1882+<g id="node4" class="node"><title>start2</title>
1883+<ellipse fill="none" stroke="black" cx="286" cy="-395" rx="41.2167" ry="41.7193"/>
1884+<text text-anchor="middle" x="286" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start2</text>
1885 </g>
1886 <!-- start1&#45;&gt;start2 -->
1887 <g id="edge2" class="edge"><title>start1&#45;&gt;start2</title>
1888-<path fill="none" stroke="black" d="M83.6679,-294C125.213,-294 189.13,-294 233.981,-294"/>
1889-<polygon fill="black" stroke="black" points="234.096,-297.5 244.096,-294 234.096,-290.5 234.096,-297.5"/>
1890-<text text-anchor="middle" x="164" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Read wire version</text>
1891+<path fill="none" stroke="black" d="M83.6679,-395C125.213,-395 189.13,-395 233.981,-395"/>
1892+<polygon fill="black" stroke="black" points="234.096,-398.5 244.096,-395 234.096,-391.5 234.096,-398.5"/>
1893+<text text-anchor="middle" x="164" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Read wire version</text>
1894 </g>
1895 <!-- start3 -->
1896-<g id="node5" class="node"><title>start3</title>
1897-<ellipse fill="none" stroke="black" cx="516" cy="-294" rx="41.2167" ry="41.7193"/>
1898-<text text-anchor="middle" x="516" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start3</text>
1899+<g id="node6" class="node"><title>start3</title>
1900+<ellipse fill="none" stroke="black" cx="537" cy="-395" rx="41.2167" ry="41.7193"/>
1901+<text text-anchor="middle" x="537" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start3</text>
1902 </g>
1903 <!-- start2&#45;&gt;start3 -->
1904 <g id="edge4" class="edge"><title>start2&#45;&gt;start3</title>
1905-<path fill="none" stroke="black" d="M327.651,-294C365.959,-294 422.903,-294 464.145,-294"/>
1906-<polygon fill="black" stroke="black" points="464.271,-297.5 474.271,-294 464.271,-290.5 464.271,-297.5"/>
1907-<text text-anchor="middle" x="401" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Read CONNECT</text>
1908+<path fill="none" stroke="black" d="M327.729,-395C370.886,-395 438.364,-395 484.973,-395"/>
1909+<polygon fill="black" stroke="black" points="485.171,-398.5 495.171,-395 485.171,-391.5 485.171,-398.5"/>
1910+<text text-anchor="middle" x="401" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Read CONNECT</text>
1911 </g>
1912 <!-- loop -->
1913-<g id="node7" class="node"><title>loop</title>
1914-<ellipse fill="none" stroke="black" cx="740" cy="-294" rx="31.8198" ry="31.8198"/>
1915-<text text-anchor="middle" x="740" y="-290.4" font-family="Times Roman,serif" font-size="14.00">loop</text>
1916+<g id="node8" class="node"><title>loop</title>
1917+<ellipse fill="none" stroke="black" cx="790" cy="-395" rx="31.8198" ry="31.8198"/>
1918+<text text-anchor="middle" x="790" y="-391.4" font-family="Times Roman,serif" font-size="14.00">loop</text>
1919 </g>
1920 <!-- start3&#45;&gt;loop -->
1921 <g id="edge6" class="edge"><title>start3&#45;&gt;loop</title>
1922-<path fill="none" stroke="black" d="M557.608,-294C597.53,-294 657.517,-294 697.677,-294"/>
1923-<polygon fill="black" stroke="black" points="697.687,-297.5 707.687,-294 697.687,-290.5 697.687,-297.5"/>
1924-<text text-anchor="middle" x="633" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Write CONNACK</text>
1925+<path fill="none" stroke="black" d="M578.778,-395C625.49,-395 700.728,-395 747.665,-395"/>
1926+<polygon fill="black" stroke="black" points="747.805,-398.5 757.805,-395 747.805,-391.5 747.805,-398.5"/>
1927+<text text-anchor="middle" x="675" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Write CONNACK</text>
1928 </g>
1929 <!-- ping -->
1930-<g id="node9" class="node"><title>ping</title>
1931-<ellipse fill="none" stroke="black" cx="1063" cy="-416" rx="32.0265" ry="32.5269"/>
1932-<text text-anchor="middle" x="1063" y="-412.4" font-family="Times Roman,serif" font-size="14.00">ping</text>
1933+<g id="node10" class="node"><title>ping</title>
1934+<ellipse fill="none" stroke="black" cx="1135" cy="-593" rx="32.0265" ry="32.5269"/>
1935+<text text-anchor="middle" x="1135" y="-589.4" font-family="Times Roman,serif" font-size="14.00">ping</text>
1936 </g>
1937 <!-- loop&#45;&gt;ping -->
1938 <g id="edge8" class="edge"><title>loop&#45;&gt;ping</title>
1939-<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"/>
1940-<polygon fill="black" stroke="black" points="1020.35,-420.33 1030.38,-416.906 1020.4,-413.33 1020.35,-420.33"/>
1941-<text text-anchor="middle" x="881" y="-418.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval</text>
1942+<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"/>
1943+<polygon fill="black" stroke="black" points="1092.15,-591.877 1102.53,-589.734 1093.08,-584.939 1092.15,-591.877"/>
1944+<text text-anchor="middle" x="946" y="-583.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval</text>
1945 </g>
1946 <!-- broadcast -->
1947-<g id="node11" class="node"><title>broadcast</title>
1948-<ellipse fill="none" stroke="black" cx="1063" cy="-200" rx="58.1882" ry="58.6899"/>
1949-<text text-anchor="middle" x="1063" y="-196.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>
1950+<g id="node12" class="node"><title>broadcast</title>
1951+<ellipse fill="none" stroke="black" cx="1135" cy="-281" rx="58.1882" ry="58.6899"/>
1952+<text text-anchor="middle" x="1135" y="-277.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>
1953 </g>
1954 <!-- loop&#45;&gt;broadcast -->
1955 <g id="edge10" class="edge"><title>loop&#45;&gt;broadcast</title>
1956-<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"/>
1957-<polygon fill="black" stroke="black" points="995.396,-212.238 1004.75,-207.269 994.34,-205.318 995.396,-212.238"/>
1958-<text text-anchor="middle" x="881" y="-267.4" font-family="Times Roman,serif" font-size="14.00">Receive broadcast request</text>
1959+<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"/>
1960+<polygon fill="black" stroke="black" points="1066.94,-289.319 1076.53,-284.811 1066.22,-282.355 1066.94,-289.319"/>
1961+<text text-anchor="middle" x="946" y="-348.4" font-family="Times Roman,serif" font-size="14.00">Receive broadcast request</text>
1962+</g>
1963+<!-- conn_broken -->
1964+<g id="node26" class="node"><title>conn_broken</title>
1965+<ellipse fill="none" stroke="black" cx="1361" cy="-99" rx="73.0388" ry="73.5391"/>
1966+<text text-anchor="middle" x="1361" y="-95.4" font-family="Times Roman,serif" font-size="14.00">conn_broken</text>
1967+</g>
1968+<!-- loop&#45;&gt;conn_broken -->
1969+<g id="edge28" class="edge"><title>loop&#45;&gt;conn_broken</title>
1970+<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"/>
1971+<polygon fill="black" stroke="black" points="1278.34,-85.2954 1288.79,-83.4998 1279.5,-78.392 1278.34,-85.2954"/>
1972+<text text-anchor="middle" x="946" y="-160.4" font-family="Times Roman,serif" font-size="14.00">Receive connbroken request</text>
1973+</g>
1974+<!-- conn_warn -->
1975+<g id="node28" class="node"><title>conn_warn</title>
1976+<ellipse fill="none" stroke="black" cx="1135" cy="-477" rx="65.7609" ry="65.7609"/>
1977+<text text-anchor="middle" x="1135" y="-473.4" font-family="Times Roman,serif" font-size="14.00">conn_warn</text>
1978+</g>
1979+<!-- loop&#45;&gt;conn_warn -->
1980+<g id="edge30" class="edge"><title>loop&#45;&gt;conn_warn</title>
1981+<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"/>
1982+<polygon fill="black" stroke="black" points="1058.71,-479.855 1068.85,-476.786 1059.01,-472.861 1058.71,-479.855"/>
1983+<text text-anchor="middle" x="946" y="-480.4" font-family="Times Roman,serif" font-size="14.00">Receive connwarn request</text>
1984 </g>
1985 <!-- pong_wait -->
1986-<g id="node13" class="node"><title>pong_wait</title>
1987-<ellipse fill="none" stroke="black" cx="1526" cy="-406" rx="62.9325" ry="62.9325"/>
1988-<text text-anchor="middle" x="1526" y="-402.4" font-family="Times Roman,serif" font-size="14.00">pong_wait</text>
1989+<g id="node14" class="node"><title>pong_wait</title>
1990+<ellipse fill="none" stroke="black" cx="537" cy="-653" rx="62.9325" ry="62.9325"/>
1991+<text text-anchor="middle" x="537" y="-649.4" font-family="Times Roman,serif" font-size="14.00">pong_wait</text>
1992 </g>
1993 <!-- ping&#45;&gt;pong_wait -->
1994 <g id="edge12" class="edge"><title>ping&#45;&gt;pong_wait</title>
1995-<path fill="none" stroke="black" d="M1095.56,-415.297C1169.19,-413.707 1350.04,-409.8 1452.36,-407.591"/>
1996-<polygon fill="black" stroke="black" points="1452.69,-411.084 1462.61,-407.369 1452.54,-404.086 1452.69,-411.084"/>
1997-<text text-anchor="middle" x="1289" y="-418.4" font-family="Times Roman,serif" font-size="14.00">Write PING</text>
1998+<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"/>
1999+<polygon fill="black" stroke="black" points="609.573,-662.637 599.214,-664.858 608.697,-669.582 609.573,-662.637"/>
2000+<text text-anchor="middle" x="790" y="-670.4" font-family="Times Roman,serif" font-size="14.00">Write PING</text>
2001 </g>
2002 <!-- ack_wait -->
2003-<g id="node15" class="node"><title>ack_wait</title>
2004-<ellipse fill="none" stroke="black" cx="1526" cy="-269" rx="55.1543" ry="55.1543"/>
2005-<text text-anchor="middle" x="1526" y="-265.4" font-family="Times Roman,serif" font-size="14.00">ack_wait</text>
2006+<g id="node16" class="node"><title>ack_wait</title>
2007+<ellipse fill="none" stroke="black" cx="1598" cy="-373" rx="55.1543" ry="55.1543"/>
2008+<text text-anchor="middle" x="1598" y="-369.4" font-family="Times Roman,serif" font-size="14.00">ack_wait</text>
2009 </g>
2010 <!-- broadcast&#45;&gt;ack_wait -->
2011 <g id="edge14" class="edge"><title>broadcast&#45;&gt;ack_wait</title>
2012-<path fill="none" stroke="black" d="M1121.17,-208.669C1207.93,-221.599 1370.7,-245.856 1461.17,-259.339"/>
2013-<polygon fill="black" stroke="black" points="1460.9,-262.837 1471.3,-260.849 1461.93,-255.913 1460.9,-262.837"/>
2014-<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>
2015+<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"/>
2016+<polygon fill="black" stroke="black" points="1534.79,-356.813 1545.37,-356.254 1536.75,-350.093 1534.79,-356.813"/>
2017+<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>
2018 </g>
2019 <!-- split_broadcast -->
2020-<g id="node17" class="node"><title>split_broadcast</title>
2021-<ellipse fill="none" stroke="black" cx="1526" cy="-110" rx="84.1457" ry="84.1457"/>
2022-<text text-anchor="middle" x="1526" y="-106.4" font-family="Times Roman,serif" font-size="14.00">split_broadcast</text>
2023+<g id="node18" class="node"><title>split_broadcast</title>
2024+<ellipse fill="none" stroke="black" cx="1598" cy="-216" rx="84.1457" ry="84.1457"/>
2025+<text text-anchor="middle" x="1598" y="-212.4" font-family="Times Roman,serif" font-size="14.00">split_broadcast</text>
2026 </g>
2027 <!-- broadcast&#45;&gt;split_broadcast -->
2028 <g id="edge16" class="edge"><title>broadcast&#45;&gt;split_broadcast</title>
2029-<path fill="none" stroke="black" d="M1120.7,-188.783C1199.06,-173.553 1340.01,-146.154 1433.29,-128.021"/>
2030-<polygon fill="black" stroke="black" points="1434.15,-131.421 1443.29,-126.077 1432.81,-124.549 1434.15,-131.421"/>
2031-<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>
2032+<path fill="none" stroke="black" d="M1193.17,-272.834C1271.44,-261.846 1411.56,-242.174 1504.66,-229.104"/>
2033+<polygon fill="black" stroke="black" points="1505.23,-232.558 1514.65,-227.702 1504.26,-225.626 1505.23,-232.558"/>
2034+<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>
2035+</g>
2036+<!-- pong_wait&#45;&gt;stop -->
2037+<g id="edge40" class="edge"><title>pong_wait&#45;&gt;stop</title>
2038+<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"/>
2039+<polygon fill="black" stroke="black" points="2292.5,-378.343 2293.84,-367.834 2286.24,-375.212 2292.5,-378.343"/>
2040+<text text-anchor="middle" x="1361" y="-658.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text>
2041 </g>
2042 <!-- pong_wait&#45;&gt;loop -->
2043 <g id="edge18" class="edge"><title>pong_wait&#45;&gt;loop</title>
2044-<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"/>
2045-<polygon fill="black" stroke="black" points="781.898,-299.011 771.42,-300.582 780.59,-305.888 781.898,-299.011"/>
2046-<text text-anchor="middle" x="1063" y="-359.4" font-family="Times Roman,serif" font-size="14.00">Read PONG</text>
2047+<path fill="none" stroke="black" d="M581.359,-607.765C632.774,-555.333 716.085,-470.376 760.273,-425.314"/>
2048+<polygon fill="black" stroke="black" points="762.774,-427.763 767.277,-418.172 757.776,-422.862 762.774,-427.763"/>
2049+<text text-anchor="middle" x="675" y="-574.4" font-family="Times Roman,serif" font-size="14.00">Read PONG</text>
2050+</g>
2051+<!-- ack_wait&#45;&gt;stop -->
2052+<g id="edge36" class="edge"><title>ack_wait&#45;&gt;stop</title>
2053+<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"/>
2054+<polygon fill="black" stroke="black" points="2263.58,-346.389 2272.99,-341.517 2262.59,-339.459 2263.58,-346.389"/>
2055+<text text-anchor="middle" x="1972" y="-373.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text>
2056 </g>
2057 <!-- ack_wait&#45;&gt;loop -->
2058 <g id="edge20" class="edge"><title>ack_wait&#45;&gt;loop</title>
2059-<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"/>
2060-<polygon fill="black" stroke="black" points="781.977,-289.56 772.059,-293.288 782.136,-296.558 781.977,-289.56"/>
2061-<text text-anchor="middle" x="1063" y="-292.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>
2062+<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"/>
2063+<polygon fill="black" stroke="black" points="831.485,-386.79 821.871,-391.242 832.163,-393.757 831.485,-386.79"/>
2064+<text text-anchor="middle" x="1135" y="-389.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>
2065 </g>
2066 <!-- split_broadcast&#45;&gt;loop -->
2067 <g id="edge26" class="edge"><title>split_broadcast&#45;&gt;loop</title>
2068-<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"/>
2069-<polygon fill="black" stroke="black" points="753.014,-253.718 751.785,-264.241 759.308,-256.781 753.014,-253.718"/>
2070-<text text-anchor="middle" x="1063" y="-120.4" font-family="Times Roman,serif" font-size="14.00">All split msgs written</text>
2071+<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"/>
2072+<polygon fill="black" stroke="black" points="800.027,-353.699 799.658,-364.287 806.549,-356.24 800.027,-353.699"/>
2073+<text text-anchor="middle" x="1135" y="-201.4" font-family="Times Roman,serif" font-size="14.00">All split msgs written</text>
2074 </g>
2075 <!-- split_ack_wait -->
2076-<g id="node21" class="node"><title>split_ack_wait</title>
2077-<ellipse fill="none" stroke="black" cx="1893" cy="-110" rx="80.1095" ry="80.6102"/>
2078-<text text-anchor="middle" x="1893" y="-106.4" font-family="Times Roman,serif" font-size="14.00">split_ack_wait</text>
2079+<g id="node22" class="node"><title>split_ack_wait</title>
2080+<ellipse fill="none" stroke="black" cx="1972" cy="-257" rx="80.1095" ry="80.6102"/>
2081+<text text-anchor="middle" x="1972" y="-253.4" font-family="Times Roman,serif" font-size="14.00">split_ack_wait</text>
2082 </g>
2083 <!-- split_broadcast&#45;&gt;split_ack_wait -->
2084 <g id="edge22" class="edge"><title>split_broadcast&#45;&gt;split_ack_wait</title>
2085-<path fill="none" stroke="black" d="M1610.2,-110C1667.61,-110 1743.59,-110 1802.33,-110"/>
2086-<polygon fill="black" stroke="black" points="1802.35,-113.5 1812.35,-110 1802.34,-106.5 1802.35,-113.5"/>
2087-<text text-anchor="middle" x="1711" y="-115.4" font-family="Times Roman,serif" font-size="14.00">Write split BROADCAST</text>
2088+<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"/>
2089+<polygon fill="black" stroke="black" points="1881.23,-255.523 1891.43,-252.676 1881.68,-248.537 1881.23,-255.523"/>
2090+<text text-anchor="middle" x="1783" y="-256.4" font-family="Times Roman,serif" font-size="14.00">Write split BROADCAST</text>
2091+</g>
2092+<!-- split_ack_wait&#45;&gt;stop -->
2093+<g id="edge38" class="edge"><title>split_ack_wait&#45;&gt;stop</title>
2094+<path fill="none" stroke="black" d="M2050.59,-275.189C2116.55,-290.456 2208.51,-311.74 2263.08,-324.372"/>
2095+<polygon fill="black" stroke="black" points="2262.62,-327.857 2273.15,-326.702 2264.2,-321.037 2262.62,-327.857"/>
2096+<text text-anchor="middle" x="2166" y="-327.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text>
2097 </g>
2098 <!-- split_ack_wait&#45;&gt;split_broadcast -->
2099 <g id="edge24" class="edge"><title>split_ack_wait&#45;&gt;split_broadcast</title>
2100-<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"/>
2101-<polygon fill="black" stroke="black" points="1617.23,-85.8043 1607.87,-90.7602 1618.28,-92.7256 1617.23,-85.8043"/>
2102-<text text-anchor="middle" x="1711" y="-93.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>
2103+<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"/>
2104+<polygon fill="black" stroke="black" points="1691.25,-202.053 1681.52,-206.256 1691.74,-209.035 1691.25,-202.053"/>
2105+<text text-anchor="middle" x="1783" y="-218.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>
2106+</g>
2107+<!-- conn_broken&#45;&gt;stop -->
2108+<g id="edge32" class="edge"><title>conn_broken&#45;&gt;stop</title>
2109+<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"/>
2110+<polygon fill="black" stroke="black" points="2275.42,-302.808 2284.91,-307.517 2280.43,-297.917 2275.42,-302.808"/>
2111+<text text-anchor="middle" x="1783" y="-127.4" font-family="Times Roman,serif" font-size="14.00">Write CONNBROKEN</text>
2112+</g>
2113+<!-- conn_warn&#45;&gt;loop -->
2114+<g id="edge34" class="edge"><title>conn_warn&#45;&gt;loop</title>
2115+<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"/>
2116+<polygon fill="black" stroke="black" points="831.758,-388.346 821.959,-392.373 832.131,-395.337 831.758,-388.346"/>
2117+<text text-anchor="middle" x="946" y="-420.4" font-family="Times Roman,serif" font-size="14.00">Write CONNWARN</text>
2118 </g>
2119 </g>
2120 </svg>
2121
2122=== modified file 'server/acceptance/acceptanceclient.go'
2123--- server/acceptance/acceptanceclient.go 2014-04-09 19:30:53 +0000
2124+++ server/acceptance/acceptanceclient.go 2014-04-29 18:02:00 +0000
2125@@ -44,6 +44,7 @@
2126 Levels map[string]int64
2127 Insecure bool // don't verify certs
2128 Prefix string // prefix for events
2129+ Auth string
2130 // connection
2131 Connection net.Conn
2132 }
2133@@ -73,6 +74,7 @@
2134 Type string `json:"T"`
2135 protocol.BroadcastMsg
2136 protocol.NotificationsMsg
2137+ protocol.ConnWarnMsg
2138 }
2139
2140 // Run the session with the server, emits a stream of events.
2141@@ -93,6 +95,7 @@
2142 "device": sess.Model,
2143 "channel": sess.ImageChannel,
2144 },
2145+ Authorization: sess.Auth,
2146 })
2147 if err != nil {
2148 return err
2149@@ -136,6 +139,8 @@
2150 return err
2151 }
2152 events <- fmt.Sprintf("%sbroadcast chan:%v app:%v topLevel:%d payloads:%s", sess.Prefix, recv.ChanId, recv.AppId, recv.TopLevel, pack)
2153+ case "warn":
2154+ events <- fmt.Sprintf("%swarn %s", sess.Prefix, recv.Reason)
2155 }
2156 }
2157 return nil
2158
2159=== modified file 'server/acceptance/cmd/acceptanceclient.go'
2160--- server/acceptance/cmd/acceptanceclient.go 2014-04-10 13:52:31 +0000
2161+++ server/acceptance/cmd/acceptanceclient.go 2014-04-29 18:02:00 +0000
2162@@ -48,13 +48,18 @@
2163 fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] <config.json> <device id>\n")
2164 flag.PrintDefaults()
2165 }
2166+ missingArg := func(what string) {
2167+ fmt.Fprintf(os.Stderr, "missing %s\n", what)
2168+ flag.Usage()
2169+ os.Exit(2)
2170+ }
2171 flag.Parse()
2172 narg := flag.NArg()
2173 switch {
2174 case narg < 1:
2175- log.Fatal("missing config file")
2176+ missingArg("config file")
2177 case narg < 2:
2178- log.Fatal("missing device-id")
2179+ missingArg("device-id")
2180 }
2181 configFName := flag.Arg(0)
2182 f, err := os.Open(configFName)
2183
2184=== modified file 'server/acceptance/suites/broadcast.go'
2185--- server/acceptance/suites/broadcast.go 2014-04-07 19:39:19 +0000
2186+++ server/acceptance/suites/broadcast.go 2014-04-29 18:02:00 +0000
2187@@ -265,7 +265,11 @@
2188
2189 func (s *BroadcastAcceptanceSuite) TestGetHosts(c *C) {
2190 gh := gethosts.New("", s.ServerAPIURL+"/delivery-hosts", 2*time.Second)
2191- hosts, err := gh.Get()
2192+ host, err := gh.Get()
2193 c.Assert(err, IsNil)
2194- c.Check(hosts, DeepEquals, []string{s.ServerAddr})
2195+ expected := &gethosts.Host{
2196+ Domain: "localhost",
2197+ Hosts: []string{s.ServerAddr},
2198+ }
2199+ c.Check(host, DeepEquals, expected)
2200 }
2201
2202=== modified file 'server/acceptance/suites/suite.go'
2203--- server/acceptance/suites/suite.go 2014-04-03 16:47:47 +0000
2204+++ server/acceptance/suites/suite.go 2014-04-29 18:02:00 +0000
2205@@ -44,10 +44,19 @@
2206
2207 // Start a client.
2208 func (h *ServerHandle) StartClient(c *C, devId string, levels map[string]int64) (events <-chan string, errorCh <-chan error, stop func()) {
2209+ return h.StartClientAuth(c, devId, levels, "")
2210+}
2211+
2212+// Start a client with auth.
2213+func (h *ServerHandle) StartClientAuth(c *C, devId string, levels map[string]int64, auth string) (events <-chan string, errorCh <-chan error, stop func()) {
2214 errCh := make(chan error, 1)
2215 cliEvents := make(chan string, 10)
2216 sess := testClientSession(h.ServerAddr, devId, "m1", "img1", false)
2217 sess.Levels = levels
2218+ sess.Auth = auth
2219+ if auth != "" {
2220+ sess.ExchangeTimeout = 5 * time.Second
2221+ }
2222 err := sess.Dial()
2223 c.Assert(err, IsNil)
2224 clientShutdown := make(chan bool, 1) // abused as an atomic flag
2225
2226=== modified file 'server/api/handlers_test.go'
2227--- server/api/handlers_test.go 2014-02-21 11:32:38 +0000
2228+++ server/api/handlers_test.go 2014-04-29 18:02:00 +0000
2229@@ -30,6 +30,7 @@
2230
2231 . "launchpad.net/gocheck"
2232
2233+ "launchpad.net/ubuntu-push/protocol"
2234 "launchpad.net/ubuntu-push/server/store"
2235 helpers "launchpad.net/ubuntu-push/testing"
2236 )
2237@@ -142,11 +143,11 @@
2238 }
2239
2240 func (cbsend *checkBrokerSending) Broadcast(chanId store.InternalChannelId) {
2241- top, payloads, err := cbsend.store.GetChannelSnapshot(chanId)
2242+ top, notifications, err := cbsend.store.GetChannelSnapshot(chanId)
2243 cbsend.err = err
2244 cbsend.chanId = chanId
2245 cbsend.top = top
2246- cbsend.payloads = payloads
2247+ cbsend.payloads = protocol.ExtractPayloads(notifications)
2248 }
2249
2250 func (s *handlersSuite) TestDoBroadcast(c *C) {
2251
2252=== modified file 'server/broker/broker.go'
2253--- server/broker/broker.go 2014-04-04 09:58:34 +0000
2254+++ server/broker/broker.go 2014-04-29 18:02:00 +0000
2255@@ -30,7 +30,7 @@
2256 // through them.
2257 type Broker interface {
2258 // Register the session.
2259- Register(*protocol.ConnectMsg) (BrokerSession, error)
2260+ Register(connMsg *protocol.ConnectMsg, sessionId string) (BrokerSession, error)
2261 // Unregister the session.
2262 Unregister(BrokerSession)
2263 }
2264
2265=== modified file 'server/broker/exchanges.go'
2266--- server/broker/exchanges.go 2014-04-04 13:19:10 +0000
2267+++ server/broker/exchanges.go 2014-04-29 18:02:00 +0000
2268@@ -28,18 +28,17 @@
2269
2270 // Scratch area for exchanges, sessions should hold one of these.
2271 type ExchangesScratchArea struct {
2272- broadcastMsg protocol.BroadcastMsg
2273- ackMsg protocol.AckMsg
2274- connBrokenMsg protocol.ConnBrokenMsg
2275+ broadcastMsg protocol.BroadcastMsg
2276+ ackMsg protocol.AckMsg
2277 }
2278
2279 // BroadcastExchange leads a session through delivering a BROADCAST.
2280 // For simplicity it is fully public.
2281 type BroadcastExchange struct {
2282- ChanId store.InternalChannelId
2283- TopLevel int64
2284- NotificationPayloads []json.RawMessage
2285- Decoded []map[string]interface{}
2286+ ChanId store.InternalChannelId
2287+ TopLevel int64
2288+ Notifications []protocol.Notification
2289+ Decoded []map[string]interface{}
2290 }
2291
2292 // check interface already here
2293@@ -47,18 +46,18 @@
2294
2295 // Init ensures the BroadcastExchange is fully initialized for the sessions.
2296 func (sbe *BroadcastExchange) Init() {
2297- decoded := make([]map[string]interface{}, len(sbe.NotificationPayloads))
2298+ decoded := make([]map[string]interface{}, len(sbe.Notifications))
2299 sbe.Decoded = decoded
2300- for i, p := range sbe.NotificationPayloads {
2301- err := json.Unmarshal(p, &decoded[i])
2302+ for i, notif := range sbe.Notifications {
2303+ err := json.Unmarshal(notif.Payload, &decoded[i])
2304 if err != nil {
2305 decoded[i] = nil
2306 }
2307 }
2308 }
2309
2310-func filterByLevel(clientLevel, topLevel int64, payloads []json.RawMessage) []json.RawMessage {
2311- c := int64(len(payloads))
2312+func filterByLevel(clientLevel, topLevel int64, notifs []protocol.Notification) []protocol.Notification {
2313+ c := int64(len(notifs))
2314 if c == 0 {
2315 return nil
2316 }
2317@@ -67,32 +66,32 @@
2318 delta = 1
2319 }
2320 if delta < c {
2321- return payloads[c-delta:]
2322+ return notifs[c-delta:]
2323 } else {
2324- return payloads
2325+ return notifs
2326 }
2327 }
2328
2329-func channelFilter(tag string, chanId store.InternalChannelId, payloads []json.RawMessage, decoded []map[string]interface{}) []json.RawMessage {
2330- if len(payloads) != 0 && chanId == store.SystemInternalChannelId {
2331- decoded := decoded[len(decoded)-len(payloads):]
2332+func channelFilter(tag string, chanId store.InternalChannelId, notifs []protocol.Notification, decoded []map[string]interface{}) []json.RawMessage {
2333+ if len(notifs) != 0 && chanId == store.SystemInternalChannelId {
2334+ decoded := decoded[len(decoded)-len(notifs):]
2335 filtered := make([]json.RawMessage, 0)
2336 for i, decoded1 := range decoded {
2337 if _, ok := decoded1[tag]; ok {
2338- filtered = append(filtered, payloads[i])
2339+ filtered = append(filtered, notifs[i].Payload)
2340 }
2341 }
2342- payloads = filtered
2343+ return filtered
2344 }
2345- return payloads
2346+ return protocol.ExtractPayloads(notifs)
2347 }
2348
2349 // Prepare session for a BROADCAST.
2350 func (sbe *BroadcastExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) {
2351 clientLevel := sess.Levels()[sbe.ChanId]
2352- payloads := filterByLevel(clientLevel, sbe.TopLevel, sbe.NotificationPayloads)
2353+ notifs := filterByLevel(clientLevel, sbe.TopLevel, sbe.Notifications)
2354 tag := fmt.Sprintf("%s/%s", sess.DeviceImageChannel(), sess.DeviceImageModel())
2355- payloads = channelFilter(tag, sbe.ChanId, payloads, sbe.Decoded)
2356+ payloads := channelFilter(tag, sbe.ChanId, notifs, sbe.Decoded)
2357 if len(payloads) == 0 && sbe.TopLevel >= clientLevel {
2358 // empty and don't need to force resync => do nothing
2359 return nil, nil, ErrNop
2360@@ -119,23 +118,20 @@
2361 return nil
2362 }
2363
2364-// ConnBrokenExchange breaks a session giving a reason.
2365-type ConnBrokenExchange struct {
2366- Reason string
2367+// ConnMetaExchange allows to send a CONNBROKEN or CONNWARN message.
2368+type ConnMetaExchange struct {
2369+ Msg protocol.OnewayMsg
2370 }
2371
2372 // check interface already here
2373-var _ Exchange = (*ConnBrokenExchange)(nil)
2374+var _ Exchange = (*ConnMetaExchange)(nil)
2375
2376-// Prepare session for a CONNBROKEN.
2377-func (cbe *ConnBrokenExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) {
2378- scratchArea := sess.ExchangeScratchArea()
2379- scratchArea.connBrokenMsg.Type = "connbroken"
2380- scratchArea.connBrokenMsg.Reason = cbe.Reason
2381- return &scratchArea.connBrokenMsg, nil, nil
2382+// Prepare session for a CONNBROKEN/WARN.
2383+func (cbe *ConnMetaExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) {
2384+ return cbe.Msg, nil, nil
2385 }
2386
2387-// CONNBROKEN isn't acked
2388-func (cbe *ConnBrokenExchange) Acked(sess BrokerSession, done bool) error {
2389- panic("Acked should not get invoked on ConnBrokenExchange")
2390+// CONNBROKEN/WARN aren't acked.
2391+func (cbe *ConnMetaExchange) Acked(sess BrokerSession, done bool) error {
2392+ panic("Acked should not get invoked on ConnMetaExchange")
2393 }
2394
2395=== modified file 'server/broker/exchanges_test.go'
2396--- server/broker/exchanges_test.go 2014-04-04 13:19:10 +0000
2397+++ server/broker/exchanges_test.go 2014-04-29 18:02:00 +0000
2398@@ -24,9 +24,11 @@
2399
2400 . "launchpad.net/gocheck"
2401
2402+ "launchpad.net/ubuntu-push/protocol"
2403 "launchpad.net/ubuntu-push/server/broker"
2404 "launchpad.net/ubuntu-push/server/broker/testing"
2405 "launchpad.net/ubuntu-push/server/store"
2406+ help "launchpad.net/ubuntu-push/testing"
2407 )
2408
2409 func TestBroker(t *stdtesting.T) { TestingT(t) }
2410@@ -39,11 +41,11 @@
2411 exchg := &broker.BroadcastExchange{
2412 ChanId: store.SystemInternalChannelId,
2413 TopLevel: 3,
2414- NotificationPayloads: []json.RawMessage{
2415+ Notifications: help.Ns(
2416 json.RawMessage(`{"a":"x"}`),
2417 json.RawMessage(`[]`),
2418 json.RawMessage(`{"a":"y"}`),
2419- },
2420+ ),
2421 }
2422 exchg.Init()
2423 c.Check(exchg.Decoded, DeepEquals, []map[string]interface{}{
2424@@ -62,10 +64,10 @@
2425 exchg := &broker.BroadcastExchange{
2426 ChanId: store.SystemInternalChannelId,
2427 TopLevel: 3,
2428- NotificationPayloads: []json.RawMessage{
2429+ Notifications: help.Ns(
2430 json.RawMessage(`{"img1/m1":100}`),
2431 json.RawMessage(`{"img2/m2":200}`),
2432- },
2433+ ),
2434 }
2435 exchg.Init()
2436 outMsg, inMsg, err := exchg.Prepare(sess)
2437@@ -88,9 +90,9 @@
2438 ImageChannel: "img1",
2439 }
2440 exchg := &broker.BroadcastExchange{
2441- ChanId: store.SystemInternalChannelId,
2442- TopLevel: 3,
2443- NotificationPayloads: []json.RawMessage{},
2444+ ChanId: store.SystemInternalChannelId,
2445+ TopLevel: 3,
2446+ Notifications: []protocol.Notification{},
2447 }
2448 exchg.Init()
2449 outMsg, inMsg, err := exchg.Prepare(sess)
2450@@ -108,9 +110,9 @@
2451 ImageChannel: "img1",
2452 }
2453 exchg := &broker.BroadcastExchange{
2454- ChanId: store.SystemInternalChannelId,
2455- TopLevel: 3,
2456- NotificationPayloads: []json.RawMessage{},
2457+ ChanId: store.SystemInternalChannelId,
2458+ TopLevel: 3,
2459+ Notifications: []protocol.Notification{},
2460 }
2461 exchg.Init()
2462 outMsg, inMsg, err := exchg.Prepare(sess)
2463@@ -133,9 +135,9 @@
2464
2465 topLevel := int64(len(needsSplitting))
2466 exchg := &broker.BroadcastExchange{
2467- ChanId: store.SystemInternalChannelId,
2468- TopLevel: topLevel,
2469- NotificationPayloads: needsSplitting,
2470+ ChanId: store.SystemInternalChannelId,
2471+ TopLevel: topLevel,
2472+ Notifications: help.Ns(needsSplitting...),
2473 }
2474 exchg.Init()
2475 outMsg, _, err := exchg.Prepare(sess)
2476@@ -152,10 +154,10 @@
2477 exchg = &broker.BroadcastExchange{
2478 ChanId: store.SystemInternalChannelId,
2479 TopLevel: topLevel + 2,
2480- NotificationPayloads: []json.RawMessage{
2481+ Notifications: help.Ns(
2482 json.RawMessage(`{"img1/m1":"x"}`),
2483 json.RawMessage(`{"img1/m1":"y"}`),
2484- },
2485+ ),
2486 }
2487 exchg.Init()
2488 outMsg, _, err = exchg.Prepare(sess)
2489@@ -173,9 +175,9 @@
2490 exchg := &broker.BroadcastExchange{
2491 ChanId: store.SystemInternalChannelId,
2492 TopLevel: 3,
2493- NotificationPayloads: []json.RawMessage{
2494+ Notifications: help.Ns(
2495 json.RawMessage(`{"img2/m1":1}`),
2496- },
2497+ ),
2498 }
2499 exchg.Init()
2500 outMsg, inMsg, err := exchg.Prepare(sess)
2501@@ -202,10 +204,10 @@
2502 exchg := &broker.BroadcastExchange{
2503 ChanId: store.SystemInternalChannelId,
2504 TopLevel: 3,
2505- NotificationPayloads: []json.RawMessage{
2506+ Notifications: help.Ns(
2507 json.RawMessage(`{"img1/m1":100}`),
2508 json.RawMessage(`{"img1/m1":101}`),
2509- },
2510+ ),
2511 }
2512 exchg.Init()
2513 outMsg, inMsg, err := exchg.Prepare(sess)
2514@@ -229,11 +231,11 @@
2515 exchg := &broker.BroadcastExchange{
2516 ChanId: store.SystemInternalChannelId,
2517 TopLevel: 5,
2518- NotificationPayloads: []json.RawMessage{
2519+ Notifications: help.Ns(
2520 json.RawMessage(`{"img1/m1":100}`),
2521 json.RawMessage(`{"img2/m2":200}`),
2522 json.RawMessage(`{"img1/m1":101}`),
2523- },
2524+ ),
2525 }
2526 exchg.Init()
2527 outMsg, inMsg, err := exchg.Prepare(sess)
2528@@ -249,16 +251,18 @@
2529 c.Check(sess.LevelsMap[store.SystemInternalChannelId], Equals, int64(5))
2530 }
2531
2532-func (s *exchangesSuite) TestConnBrokenExchange(c *C) {
2533+func (s *exchangesSuite) TestConnMetaExchange(c *C) {
2534 sess := &testing.TestBrokerSession{}
2535- cbe := &broker.ConnBrokenExchange{"REASON"}
2536+ var msg protocol.OnewayMsg = &protocol.ConnWarnMsg{"connwarn", "REASON"}
2537+ cbe := &broker.ConnMetaExchange{msg}
2538 outMsg, inMsg, err := cbe.Prepare(sess)
2539 c.Assert(err, IsNil)
2540+ c.Check(msg, Equals, outMsg)
2541 c.Check(inMsg, IsNil) // no answer is expected
2542 // check
2543 marshalled, err := json.Marshal(outMsg)
2544 c.Assert(err, IsNil)
2545- c.Check(string(marshalled), Equals, `{"T":"connbroken","Reason":"REASON"}`)
2546+ c.Check(string(marshalled), Equals, `{"T":"connwarn","Reason":"REASON"}`)
2547
2548- c.Check(func() { cbe.Acked(nil, true) }, PanicMatches, "Acked should not get invoked on ConnBrokenExchange")
2549+ c.Check(func() { cbe.Acked(nil, true) }, PanicMatches, "Acked should not get invoked on ConnMetaExchange")
2550 }
2551
2552=== modified file 'server/broker/exchg_impl_test.go'
2553--- server/broker/exchg_impl_test.go 2014-04-03 16:00:53 +0000
2554+++ server/broker/exchg_impl_test.go 2014-04-29 18:02:00 +0000
2555@@ -22,6 +22,7 @@
2556 . "launchpad.net/gocheck"
2557
2558 "launchpad.net/ubuntu-push/server/store"
2559+ help "launchpad.net/ubuntu-push/testing"
2560 )
2561
2562 type exchangesImplSuite struct{}
2563@@ -29,27 +30,27 @@
2564 var _ = Suite(&exchangesImplSuite{})
2565
2566 func (s *exchangesImplSuite) TestFilterByLevel(c *C) {
2567- payloads := []json.RawMessage{
2568+ notifs := help.Ns(
2569 json.RawMessage(`{"a": 3}`),
2570 json.RawMessage(`{"a": 4}`),
2571 json.RawMessage(`{"a": 5}`),
2572- }
2573- res := filterByLevel(5, 5, payloads)
2574+ )
2575+ res := filterByLevel(5, 5, notifs)
2576 c.Check(len(res), Equals, 0)
2577- res = filterByLevel(4, 5, payloads)
2578+ res = filterByLevel(4, 5, notifs)
2579 c.Check(len(res), Equals, 1)
2580- c.Check(res[0], DeepEquals, json.RawMessage(`{"a": 5}`))
2581- res = filterByLevel(3, 5, payloads)
2582+ c.Check(res[0].Payload, DeepEquals, json.RawMessage(`{"a": 5}`))
2583+ res = filterByLevel(3, 5, notifs)
2584 c.Check(len(res), Equals, 2)
2585- c.Check(res[0], DeepEquals, json.RawMessage(`{"a": 4}`))
2586- res = filterByLevel(2, 5, payloads)
2587+ c.Check(res[0].Payload, DeepEquals, json.RawMessage(`{"a": 4}`))
2588+ res = filterByLevel(2, 5, notifs)
2589 c.Check(len(res), Equals, 3)
2590- res = filterByLevel(1, 5, payloads)
2591+ res = filterByLevel(1, 5, notifs)
2592 c.Check(len(res), Equals, 3)
2593 // too ahead, pick only last
2594- res = filterByLevel(10, 5, payloads)
2595+ res = filterByLevel(10, 5, notifs)
2596 c.Check(len(res), Equals, 1)
2597- c.Check(res[0], DeepEquals, json.RawMessage(`{"a": 5}`))
2598+ c.Check(res[0].Payload, DeepEquals, json.RawMessage(`{"a": 5}`))
2599 }
2600
2601 func (s *exchangesImplSuite) TestFilterByLevelEmpty(c *C) {
2602@@ -71,18 +72,19 @@
2603 err := json.Unmarshal(p, &decoded[i])
2604 c.Assert(err, IsNil)
2605 }
2606+ notifs := help.Ns(payloads...)
2607
2608 other := store.InternalChannelId("1")
2609
2610 c.Check(channelFilter("", store.SystemInternalChannelId, nil, nil), IsNil)
2611- c.Check(channelFilter("", other, payloads[1:], decoded), DeepEquals, payloads[1:])
2612+ c.Check(channelFilter("", other, notifs[1:], decoded), DeepEquals, payloads[1:])
2613
2614 // use tag when channel is the sytem channel
2615
2616- c.Check(channelFilter("c/z", store.SystemInternalChannelId, payloads, decoded), HasLen, 0)
2617-
2618- c.Check(channelFilter("a/x", store.SystemInternalChannelId, payloads, decoded), DeepEquals, []json.RawMessage{payloads[0], payloads[3]})
2619-
2620- c.Check(channelFilter("a/x", store.SystemInternalChannelId, payloads[1:], decoded), DeepEquals, []json.RawMessage{payloads[3]})
2621+ c.Check(channelFilter("c/z", store.SystemInternalChannelId, notifs, decoded), HasLen, 0)
2622+
2623+ c.Check(channelFilter("a/x", store.SystemInternalChannelId, notifs, decoded), DeepEquals, []json.RawMessage{payloads[0], payloads[3]})
2624+
2625+ c.Check(channelFilter("a/x", store.SystemInternalChannelId, notifs[1:], decoded), DeepEquals, []json.RawMessage{payloads[3]})
2626
2627 }
2628
2629=== modified file 'server/broker/simple/simple.go'
2630--- server/broker/simple/simple.go 2014-04-11 08:47:18 +0000
2631+++ server/broker/simple/simple.go 2014-04-29 18:02:00 +0000
2632@@ -144,7 +144,7 @@
2633 // find relevant channels, for now only system
2634 channels := []store.InternalChannelId{store.SystemInternalChannelId}
2635 for _, chanId := range channels {
2636- topLevel, payloads, err := b.sto.GetChannelSnapshot(chanId)
2637+ topLevel, notifications, err := b.sto.GetChannelSnapshot(chanId)
2638 if err != nil {
2639 // next broadcast will try again
2640 b.logger.Errorf("unsuccessful feed pending, get channel snapshot for %v: %v", chanId, err)
2641@@ -153,9 +153,9 @@
2642 clientLevel := sess.levels[chanId]
2643 if clientLevel != topLevel {
2644 broadcastExchg := &broker.BroadcastExchange{
2645- ChanId: chanId,
2646- TopLevel: topLevel,
2647- NotificationPayloads: payloads,
2648+ ChanId: chanId,
2649+ TopLevel: topLevel,
2650+ Notifications: notifications,
2651 }
2652 broadcastExchg.Init()
2653 sess.exchanges <- broadcastExchg
2654@@ -166,7 +166,7 @@
2655
2656 // Register registers a session with the broker. It feeds the session
2657 // pending notifications as well.
2658-func (b *SimpleBroker) Register(connect *protocol.ConnectMsg) (broker.BrokerSession, error) {
2659+func (b *SimpleBroker) Register(connect *protocol.ConnectMsg, sessionId string) (broker.BrokerSession, error) {
2660 // xxx sanity check DeviceId
2661 model, err := broker.GetInfoString(connect, "device", "?")
2662 if err != nil {
2663@@ -224,7 +224,7 @@
2664 } else { // register
2665 prev := b.registry[sess.deviceId]
2666 if prev != nil { // kick it
2667- close(prev.exchanges)
2668+ prev.exchanges <- nil
2669 }
2670 b.registry[sess.deviceId] = sess
2671 sess.registered = true
2672@@ -233,16 +233,16 @@
2673 case delivery := <-b.deliveryCh:
2674 switch delivery.kind {
2675 case broadcastDelivery:
2676- topLevel, payloads, err := b.sto.GetChannelSnapshot(delivery.chanId)
2677+ topLevel, notifications, err := b.sto.GetChannelSnapshot(delivery.chanId)
2678 if err != nil {
2679 // next broadcast will try again
2680 b.logger.Errorf("unsuccessful broadcast, get channel snapshot for %v: %v", delivery.chanId, err)
2681 continue Loop
2682 }
2683 broadcastExchg := &broker.BroadcastExchange{
2684- ChanId: delivery.chanId,
2685- TopLevel: topLevel,
2686- NotificationPayloads: payloads,
2687+ ChanId: delivery.chanId,
2688+ TopLevel: topLevel,
2689+ Notifications: notifications,
2690 }
2691 broadcastExchg.Init()
2692 for _, sess := range b.registry {
2693
2694=== modified file 'server/broker/simple/simple_test.go'
2695--- server/broker/simple/simple_test.go 2014-04-03 16:00:53 +0000
2696+++ server/broker/simple/simple_test.go 2014-04-29 18:02:00 +0000
2697@@ -26,6 +26,7 @@
2698 "launchpad.net/ubuntu-push/server/broker"
2699 "launchpad.net/ubuntu-push/server/broker/testing"
2700 "launchpad.net/ubuntu-push/server/store"
2701+ help "launchpad.net/ubuntu-push/testing"
2702 )
2703
2704 func TestSimple(t *stdtesting.T) { TestingT(t) }
2705@@ -58,10 +59,10 @@
2706 c.Assert(len(sess.exchanges), Equals, 1)
2707 exchg1 := <-sess.exchanges
2708 c.Check(exchg1, DeepEquals, &broker.BroadcastExchange{
2709- ChanId: store.SystemInternalChannelId,
2710- TopLevel: 1,
2711- NotificationPayloads: []json.RawMessage{notification1},
2712- Decoded: []map[string]interface{}{decoded1},
2713+ ChanId: store.SystemInternalChannelId,
2714+ TopLevel: 1,
2715+ Notifications: help.Ns(notification1),
2716+ Decoded: []map[string]interface{}{decoded1},
2717 })
2718 }
2719
2720
2721=== modified file 'server/broker/testsuite/suite.go'
2722--- server/broker/testsuite/suite.go 2014-04-11 08:47:18 +0000
2723+++ server/broker/testsuite/suite.go 2014-04-29 18:02:00 +0000
2724@@ -30,7 +30,7 @@
2725 "launchpad.net/ubuntu-push/server/broker"
2726 "launchpad.net/ubuntu-push/server/broker/testing"
2727 "launchpad.net/ubuntu-push/server/store"
2728- helpers "launchpad.net/ubuntu-push/testing"
2729+ help "launchpad.net/ubuntu-push/testing"
2730 )
2731
2732 // The expected interface for tested brokers.
2733@@ -51,11 +51,11 @@
2734 // Let us get to a broker.BroadcastExchange from an Exchange.
2735 RevealBroadcastExchange func(broker.Exchange) *broker.BroadcastExchange
2736 // private
2737- testlog *helpers.TestLogger
2738+ testlog *help.TestLogger
2739 }
2740
2741 func (s *CommonBrokerSuite) SetUpTest(c *C) {
2742- s.testlog = helpers.NewTestLogger(c, "error")
2743+ s.testlog = help.NewTestLogger(c, "error")
2744 }
2745
2746 var testBrokerConfig = &testing.TestBrokerConfig{10, 5}
2747@@ -89,7 +89,7 @@
2748 "device": "model",
2749 "channel": "daily",
2750 },
2751- })
2752+ }, "s1")
2753 c.Assert(err, IsNil)
2754 c.Assert(s.RevealSession(b, "dev-1"), Equals, sess)
2755 c.Assert(sess.DeviceIdentifier(), Equals, "dev-1")
2756@@ -101,7 +101,7 @@
2757 }))
2758 b.Unregister(sess)
2759 // just to make sure the unregister was processed
2760- _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""})
2761+ _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}, "s2")
2762 c.Assert(err, IsNil)
2763 c.Check(s.RevealSession(b, "dev-1"), IsNil)
2764 }
2765@@ -111,7 +111,7 @@
2766 b := s.MakeBroker(sto, testBrokerConfig, nil)
2767 b.Start()
2768 defer b.Stop()
2769- _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1", Levels: map[string]int64{"z": 5}})
2770+ _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1", Levels: map[string]int64{"z": 5}}, "s1")
2771 c.Check(err, FitsTypeOf, &broker.ErrAbort{})
2772 }
2773
2774@@ -123,11 +123,11 @@
2775 info := map[string]interface{}{
2776 "device": -1,
2777 }
2778- _, err := b.Register(&protocol.ConnectMsg{Type: "connect", Info: info})
2779+ _, err := b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}, "s1")
2780 c.Check(err, Equals, broker.ErrUnexpectedValue)
2781 info["device"] = "m"
2782 info["channel"] = -1
2783- _, err = b.Register(&protocol.ConnectMsg{Type: "connect", Info: info})
2784+ _, err = b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}, "s2")
2785 c.Check(err, Equals, broker.ErrUnexpectedValue)
2786 }
2787
2788@@ -139,7 +139,7 @@
2789 b := s.MakeBroker(sto, testBrokerConfig, nil)
2790 b.Start()
2791 defer b.Stop()
2792- sess, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2793+ sess, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
2794 c.Assert(err, IsNil)
2795 c.Check(len(sess.SessionChannel()), Equals, 1)
2796 }
2797@@ -149,7 +149,7 @@
2798 b := s.MakeBroker(sto, testBrokerConfig, s.testlog)
2799 b.Start()
2800 defer b.Stop()
2801- _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2802+ _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
2803 c.Assert(err, IsNil)
2804 // but
2805 c.Check(s.testlog.Captured(), Matches, "ERROR unsuccessful feed pending, get channel snapshot for 0: get channel snapshot fail\n")
2806@@ -160,22 +160,25 @@
2807 b := s.MakeBroker(sto, testBrokerConfig, nil)
2808 b.Start()
2809 defer b.Stop()
2810- sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2811- c.Assert(err, IsNil)
2812- sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2813- c.Assert(err, IsNil)
2814- checkAndFalse := false
2815- // previous session got signaled by closing its channel
2816+ sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
2817+ c.Assert(err, IsNil)
2818+ sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s2")
2819+ c.Assert(err, IsNil)
2820+ // previous session got signaled by sending nil on its channel
2821+ var sentinel broker.Exchange
2822+ got := false
2823 select {
2824- case _, ok := <-sess1.SessionChannel():
2825- checkAndFalse = ok == false
2826- default:
2827+ case sentinel = <-sess1.SessionChannel():
2828+ got = true
2829+ case <-time.After(5 * time.Second):
2830+ c.Fatal("taking too long to get sentinel")
2831 }
2832- c.Check(checkAndFalse, Equals, true)
2833+ c.Check(got, Equals, true)
2834+ c.Check(sentinel, IsNil)
2835 c.Assert(s.RevealSession(b, "dev-1"), Equals, sess2)
2836 b.Unregister(sess1)
2837 // just to make sure the unregister was processed
2838- _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""})
2839+ _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}, "s3")
2840 c.Assert(err, IsNil)
2841 c.Check(s.RevealSession(b, "dev-1"), Equals, sess2)
2842 }
2843@@ -187,9 +190,9 @@
2844 b := s.MakeBroker(sto, testBrokerConfig, nil)
2845 b.Start()
2846 defer b.Stop()
2847- sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2848+ sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
2849 c.Assert(err, IsNil)
2850- sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-2"})
2851+ sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-2"}, "s2")
2852 c.Assert(err, IsNil)
2853 // add notification to channel *after* the registrations
2854 muchLater := time.Now().Add(10 * time.Minute)
2855@@ -200,10 +203,10 @@
2856 c.Fatal("taking too long to get broadcast exchange")
2857 case exchg1 := <-sess1.SessionChannel():
2858 c.Check(s.RevealBroadcastExchange(exchg1), DeepEquals, &broker.BroadcastExchange{
2859- ChanId: store.SystemInternalChannelId,
2860- TopLevel: 1,
2861- NotificationPayloads: []json.RawMessage{notification1},
2862- Decoded: []map[string]interface{}{decoded1},
2863+ ChanId: store.SystemInternalChannelId,
2864+ TopLevel: 1,
2865+ Notifications: help.Ns(notification1),
2866+ Decoded: []map[string]interface{}{decoded1},
2867 })
2868 }
2869 select {
2870@@ -211,10 +214,10 @@
2871 c.Fatal("taking too long to get broadcast exchange")
2872 case exchg2 := <-sess2.SessionChannel():
2873 c.Check(s.RevealBroadcastExchange(exchg2), DeepEquals, &broker.BroadcastExchange{
2874- ChanId: store.SystemInternalChannelId,
2875- TopLevel: 1,
2876- NotificationPayloads: []json.RawMessage{notification1},
2877- Decoded: []map[string]interface{}{decoded1},
2878+ ChanId: store.SystemInternalChannelId,
2879+ TopLevel: 1,
2880+ Notifications: help.Ns(notification1),
2881+ Decoded: []map[string]interface{}{decoded1},
2882 })
2883 }
2884 }
2885@@ -224,7 +227,7 @@
2886 countdownToFail int
2887 }
2888
2889-func (sto *testFailingStore) GetChannelSnapshot(chanId store.InternalChannelId) (int64, []json.RawMessage, error) {
2890+func (sto *testFailingStore) GetChannelSnapshot(chanId store.InternalChannelId) (int64, []protocol.Notification, error) {
2891 if sto.countdownToFail == 0 {
2892 return 0, nil, errors.New("get channel snapshot fail")
2893 }
2894@@ -241,7 +244,7 @@
2895 b := s.MakeBroker(sto, testBrokerConfig, s.testlog)
2896 b.Start()
2897 defer b.Stop()
2898- _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})
2899+ _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
2900 c.Assert(err, IsNil)
2901 b.Broadcast(store.SystemInternalChannelId)
2902 select {
2903
2904=== modified file 'server/session/session.go'
2905--- server/session/session.go 2014-04-11 08:47:18 +0000
2906+++ server/session/session.go 2014-04-29 18:02:00 +0000
2907@@ -18,6 +18,7 @@
2908 package session
2909
2910 import (
2911+ "errors"
2912 "net"
2913 "time"
2914
2915@@ -35,7 +36,7 @@
2916 }
2917
2918 // sessionStart manages the start of the protocol session.
2919-func sessionStart(proto protocol.Protocol, brkr broker.Broker, cfg SessionConfig) (broker.BrokerSession, error) {
2920+func sessionStart(proto protocol.Protocol, brkr broker.Broker, cfg SessionConfig, sessionId string) (broker.BrokerSession, error) {
2921 var connMsg protocol.ConnectMsg
2922 proto.SetDeadline(time.Now().Add(cfg.ExchangeTimeout()))
2923 err := proto.ReadMessage(&connMsg)
2924@@ -52,9 +53,11 @@
2925 if err != nil {
2926 return nil, err
2927 }
2928- return brkr.Register(&connMsg)
2929+ return brkr.Register(&connMsg, sessionId)
2930 }
2931
2932+var errOneway = errors.New("oneway")
2933+
2934 // exchange writes outMsg message, reads answer in inMsg
2935 func exchange(proto protocol.Protocol, outMsg, inMsg interface{}, exchangeTimeout time.Duration) error {
2936 proto.SetDeadline(time.Now().Add(exchangeTimeout))
2937@@ -62,7 +65,10 @@
2938 if err != nil {
2939 return err
2940 }
2941- if inMsg == nil { // no answer expected, breaking connection
2942+ if inMsg == nil { // no answer expected
2943+ if outMsg.(protocol.OnewayMsg).OnewayContinue() {
2944+ return errOneway
2945+ }
2946 return &broker.ErrAbort{"session broken for reason"}
2947 }
2948 err = proto.ReadMessage(inMsg)
2949@@ -78,6 +84,10 @@
2950 exchangeTimeout := cfg.ExchangeTimeout()
2951 pingTimer := time.NewTimer(pingInterval)
2952 intervalStart := time.Now()
2953+ pingTimerReset := func() {
2954+ pingTimer.Reset(pingInterval)
2955+ intervalStart = time.Now()
2956+ }
2957 ch := sess.SessionChannel()
2958 Loop:
2959 for {
2960@@ -93,16 +103,15 @@
2961 if pongMsg.Type != "pong" {
2962 return &broker.ErrAbort{"expected PONG message"}
2963 }
2964- pingTimer.Reset(pingInterval)
2965- case exchg, ok := <-ch:
2966+ pingTimerReset()
2967+ case exchg := <-ch:
2968 pingTimer.Stop()
2969- if !ok {
2970+ if exchg == nil {
2971 return &broker.ErrAbort{"terminated"}
2972 }
2973 outMsg, inMsg, err := exchg.Prepare(sess)
2974 if err == broker.ErrNop { // nothing to do
2975- pingTimer.Reset(pingInterval)
2976- intervalStart = time.Now()
2977+ pingTimerReset()
2978 continue Loop
2979 }
2980 if err != nil {
2981@@ -111,12 +120,15 @@
2982 for {
2983 done := outMsg.Split()
2984 err = exchange(proto, outMsg, inMsg, exchangeTimeout)
2985+ if err == errOneway {
2986+ pingTimerReset()
2987+ continue Loop
2988+ }
2989 if err != nil {
2990 return err
2991 }
2992 if done {
2993- pingTimer.Reset(pingInterval)
2994- intervalStart = time.Now()
2995+ pingTimerReset()
2996 }
2997 err = exchg.Acked(sess, done)
2998 if err != nil {
2999@@ -142,7 +154,7 @@
3000 return track.End(&broker.ErrAbort{"unexpected wire format version"})
3001 }
3002 proto := protocol.NewProtocol0(conn)
3003- sess, err := sessionStart(proto, brkr, cfg)
3004+ sess, err := sessionStart(proto, brkr, cfg, track.SessionId())
3005 if err != nil {
3006 return track.End(err)
3007 }
3008
3009=== modified file 'server/session/session_test.go'
3010--- server/session/session_test.go 2014-04-11 08:47:18 +0000
3011+++ server/session/session_test.go 2014-04-29 18:02:00 +0000
3012@@ -130,8 +130,8 @@
3013 return &testBroker{registration: make(chan interface{}, 2)}
3014 }
3015
3016-func (tb *testBroker) Register(connect *protocol.ConnectMsg) (broker.BrokerSession, error) {
3017- tb.registration <- "register " + connect.DeviceId
3018+func (tb *testBroker) Register(connect *protocol.ConnectMsg, sessionId string) (broker.BrokerSession, error) {
3019+ tb.registration <- fmt.Sprintf("register %s %s", connect.DeviceId, sessionId)
3020 return &testing.TestBrokerSession{DeviceId: connect.DeviceId}, tb.err
3021 }
3022
3023@@ -148,7 +148,7 @@
3024 brkr := newTestBroker()
3025 go func() {
3026 var err error
3027- sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout)
3028+ sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout, "s1")
3029 errCh <- err
3030 }()
3031 c.Check(takeNext(down), Equals, "deadline 5ms")
3032@@ -160,7 +160,7 @@
3033 up <- nil // no write error
3034 err := <-errCh
3035 c.Check(err, IsNil)
3036- c.Check(takeNext(brkr.registration), Equals, "register dev-1")
3037+ c.Check(takeNext(brkr.registration), Equals, "register dev-1 s1")
3038 c.Check(sess.DeviceIdentifier(), Equals, "dev-1")
3039 }
3040
3041@@ -175,7 +175,7 @@
3042 brkr.err = errRegister
3043 go func() {
3044 var err error
3045- sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout)
3046+ sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout, "s2")
3047 errCh <- err
3048 }()
3049 up <- protocol.ConnectMsg{Type: "connect", ClientVer: "1", DeviceId: "dev-1"}
3050@@ -190,7 +190,7 @@
3051 down := make(chan interface{}, 5)
3052 tp := &testProtocol{up, down}
3053 up <- io.ErrUnexpectedEOF
3054- _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout)
3055+ _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s3")
3056 c.Check(err, Equals, io.ErrUnexpectedEOF)
3057 }
3058
3059@@ -200,7 +200,7 @@
3060 tp := &testProtocol{up, down}
3061 up <- protocol.ConnectMsg{Type: "connect"}
3062 up <- io.ErrUnexpectedEOF
3063- _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout)
3064+ _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s4")
3065 c.Check(err, Equals, io.ErrUnexpectedEOF)
3066 // sanity
3067 c.Check(takeNext(down), Matches, "deadline.*")
3068@@ -212,7 +212,7 @@
3069 down := make(chan interface{}, 5)
3070 tp := &testProtocol{up, down}
3071 up <- protocol.ConnectMsg{Type: "what"}
3072- _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout)
3073+ _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s5")
3074 c.Check(err, DeepEquals, &broker.ErrAbort{"expected CONNECT message"})
3075 }
3076
3077@@ -222,14 +222,14 @@
3078 }
3079
3080 func (s *sessionSuite) TestSessionLoop(c *C) {
3081- nopTrack := NewTracker(s.testlog)
3082+ track := &testTracker{NewTracker(s.testlog), make(chan interface{}, 2)}
3083 errCh := make(chan error, 1)
3084 up := make(chan interface{}, 5)
3085 down := make(chan interface{}, 5)
3086 tp := &testProtocol{up, down}
3087 sess := &testing.TestBrokerSession{}
3088 go func() {
3089- errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)
3090+ errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, track)
3091 }()
3092 c.Check(takeNext(down), Equals, "deadline 2ms")
3093 c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"})
3094@@ -241,6 +241,9 @@
3095 up <- io.ErrUnexpectedEOF
3096 err := <-errCh
3097 c.Check(err, Equals, io.ErrUnexpectedEOF)
3098+ c.Check(track.interval, HasLen, 2)
3099+ c.Check((<-track.interval).(time.Duration) <= 8*time.Millisecond, Equals, true)
3100+ c.Check((<-track.interval).(time.Duration) <= 8*time.Millisecond, Equals, true)
3101 }
3102
3103 func (s *sessionSuite) TestSessionLoopWriteError(c *C) {
3104@@ -357,7 +360,7 @@
3105 go func() {
3106 errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)
3107 }()
3108- close(exchanges)
3109+ exchanges <- nil
3110 err := <-errCh
3111 c.Check(err, DeepEquals, &broker.ErrAbort{"terminated"})
3112 }
3113@@ -477,18 +480,44 @@
3114 down := make(chan interface{}, 5)
3115 tp := &testProtocol{up, down}
3116 exchanges := make(chan broker.Exchange, 1)
3117- exchanges <- &broker.ConnBrokenExchange{"REASON"}
3118+ msg := &protocol.ConnBrokenMsg{"connbroken", "BREASON"}
3119+ exchanges <- &broker.ConnMetaExchange{msg}
3120 sess := &testing.TestBrokerSession{Exchanges: exchanges}
3121 go func() {
3122 errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)
3123 }()
3124 c.Check(takeNext(down), Equals, "deadline 2ms")
3125- c.Check(takeNext(down), DeepEquals, protocol.ConnBrokenMsg{"connbroken", "REASON"})
3126+ c.Check(takeNext(down), DeepEquals, protocol.ConnBrokenMsg{"connbroken", "BREASON"})
3127 up <- nil // no write error
3128 err := <-errCh
3129 c.Check(err, DeepEquals, &broker.ErrAbort{"session broken for reason"})
3130 }
3131
3132+func (s *sessionSuite) TestSessionLoopConnWarnExchange(c *C) {
3133+ nopTrack := NewTracker(s.testlog)
3134+ errCh := make(chan error, 1)
3135+ up := make(chan interface{}, 5)
3136+ down := make(chan interface{}, 5)
3137+ tp := &testProtocol{up, down}
3138+ exchanges := make(chan broker.Exchange, 1)
3139+ msg := &protocol.ConnWarnMsg{"connwarn", "WREASON"}
3140+ exchanges <- &broker.ConnMetaExchange{msg}
3141+ sess := &testing.TestBrokerSession{Exchanges: exchanges}
3142+ go func() {
3143+ errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)
3144+ }()
3145+ c.Check(takeNext(down), Equals, "deadline 2ms")
3146+ c.Check(takeNext(down), DeepEquals, protocol.ConnWarnMsg{"connwarn", "WREASON"})
3147+ up <- nil // no write error
3148+ // session continues
3149+ c.Check(takeNext(down), Equals, "deadline 2ms")
3150+ c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"})
3151+ up <- nil // no write error
3152+ up <- io.EOF
3153+ err := <-errCh
3154+ c.Check(err, Equals, io.EOF)
3155+}
3156+
3157 type testTracker struct {
3158 SessionTracker
3159 interval chan interface{}
3160@@ -593,7 +622,7 @@
3161 msg, err = downStream.ReadBytes(byte('}'))
3162 c.Check(err, IsNil)
3163 c.Check(msg, DeepEquals, []byte("\x00\x0c{\"T\":\"ping\"}"))
3164- c.Check(takeNext(brkr.registration), Equals, "register DEV")
3165+ c.Check(takeNext(brkr.registration), Equals, "register DEV "+track.SessionId())
3166 c.Check(len(brkr.registration), Equals, 0) // not yet unregistered
3167 cli.Close()
3168 err = <-errCh
3169
3170=== modified file 'server/session/tracker.go'
3171--- server/session/tracker.go 2014-02-10 23:19:08 +0000
3172+++ server/session/tracker.go 2014-04-29 18:02:00 +0000
3173@@ -17,6 +17,7 @@
3174 package session
3175
3176 import (
3177+ "fmt"
3178 "net"
3179 "time"
3180
3181@@ -29,6 +30,8 @@
3182 logger.Logger
3183 // Session got started.
3184 Start(WithRemoteAddr)
3185+ // SessionId
3186+ SessionId() string
3187 // Session got registered with broker as sess BrokerSession.
3188 Registered(sess broker.BrokerSession)
3189 // Report effective elapsed ping interval.
3190@@ -47,7 +50,7 @@
3191 // Tracker implements SessionTracker simply.
3192 type tracker struct {
3193 logger.Logger
3194- sessionId int64 // xxx use timeuuid later
3195+ sessionId string
3196 }
3197
3198 func NewTracker(logger logger.Logger) SessionTracker {
3199@@ -55,18 +58,22 @@
3200 }
3201
3202 func (trk *tracker) Start(conn WithRemoteAddr) {
3203- trk.sessionId = time.Now().UnixNano() - sessionsEpoch
3204- trk.Debugf("session(%x) connected %v", trk.sessionId, conn.RemoteAddr())
3205+ trk.sessionId = fmt.Sprintf("%x", time.Now().UnixNano()-sessionsEpoch)
3206+ trk.Debugf("session(%s) connected %v", trk.sessionId, conn.RemoteAddr())
3207+}
3208+
3209+func (trk *tracker) SessionId() string {
3210+ return trk.sessionId
3211 }
3212
3213 func (trk *tracker) Registered(sess broker.BrokerSession) {
3214- trk.Infof("session(%x) registered %v", trk.sessionId, sess.DeviceIdentifier())
3215+ trk.Infof("session(%s) registered %v", trk.sessionId, sess.DeviceIdentifier())
3216 }
3217
3218 func (trk *tracker) EffectivePingInterval(time.Duration) {
3219 }
3220
3221 func (trk *tracker) End(err error) error {
3222- trk.Debugf("session(%x) ended with: %v", trk.sessionId, err)
3223+ trk.Debugf("session(%s) ended with: %v", trk.sessionId, err)
3224 return err
3225 }
3226
3227=== modified file 'server/session/tracker_test.go'
3228--- server/session/tracker_test.go 2014-02-10 23:19:08 +0000
3229+++ server/session/tracker_test.go 2014-04-29 18:02:00 +0000
3230@@ -46,8 +46,8 @@
3231 func (s *trackerSuite) TestSessionTrackStart(c *C) {
3232 track := NewTracker(s.testlog)
3233 track.Start(&testRemoteAddrable{})
3234- c.Check(track.(*tracker).sessionId, Not(Equals), 0)
3235- regExpected := fmt.Sprintf(`DEBUG session\(%x\) connected 127\.0\.0\.1:9999\n`, track.(*tracker).sessionId)
3236+ c.Check(track.SessionId(), Not(Equals), "")
3237+ regExpected := fmt.Sprintf(`DEBUG session\(%s\) connected 127\.0\.0\.1:9999\n`, track.SessionId())
3238 c.Check(s.testlog.Captured(), Matches, regExpected)
3239 }
3240
3241@@ -55,7 +55,7 @@
3242 track := NewTracker(s.testlog)
3243 track.Start(&testRemoteAddrable{})
3244 track.Registered(&testing.TestBrokerSession{DeviceId: "DEV-ID"})
3245- regExpected := fmt.Sprintf(`.*connected.*\nINFO session\(%x\) registered DEV-ID\n`, track.(*tracker).sessionId)
3246+ regExpected := fmt.Sprintf(`.*connected.*\nINFO session\(%s\) registered DEV-ID\n`, track.SessionId())
3247 c.Check(s.testlog.Captured(), Matches, regExpected)
3248 }
3249
3250@@ -63,6 +63,6 @@
3251 track := NewTracker(s.testlog)
3252 track.Start(&testRemoteAddrable{})
3253 track.End(&broker.ErrAbort{})
3254- regExpected := fmt.Sprintf(`.*connected.*\nDEBUG session\(%x\) ended with: session aborted \(\)\n`, track.(*tracker).sessionId)
3255+ regExpected := fmt.Sprintf(`.*connected.*\nDEBUG session\(%s\) ended with: session aborted \(\)\n`, track.SessionId())
3256 c.Check(s.testlog.Captured(), Matches, regExpected)
3257 }
3258
3259=== modified file 'server/store/inmemory.go'
3260--- server/store/inmemory.go 2014-02-18 14:19:05 +0000
3261+++ server/store/inmemory.go 2014-04-29 18:02:00 +0000
3262@@ -20,11 +20,13 @@
3263 "encoding/json"
3264 "sync"
3265 "time"
3266+
3267+ "launchpad.net/ubuntu-push/protocol"
3268 )
3269
3270 // one stored notification
3271 type notification struct {
3272- payload json.RawMessage
3273+ protocol.Notification
3274 expiration time.Time
3275 }
3276
3277@@ -63,14 +65,14 @@
3278 }
3279 prev.topLevel++
3280 prev.notifications = append(prev.notifications, notification{
3281- payload: notificationPayload,
3282- expiration: expiration,
3283+ Notification: protocol.Notification{Payload: notificationPayload},
3284+ expiration: expiration,
3285 })
3286 sto.store[chanId] = prev
3287 return nil
3288 }
3289
3290-func (sto *InMemoryPendingStore) GetChannelSnapshot(chanId InternalChannelId) (int64, []json.RawMessage, error) {
3291+func (sto *InMemoryPendingStore) GetChannelSnapshot(chanId InternalChannelId) (int64, []protocol.Notification, error) {
3292 sto.lock.Lock()
3293 defer sto.lock.Unlock()
3294 channel, ok := sto.store[chanId]
3295@@ -79,13 +81,13 @@
3296 }
3297 topLevel := channel.topLevel
3298 n := len(channel.notifications)
3299- res := make([]json.RawMessage, 0, n)
3300+ res := make([]protocol.Notification, 0, n)
3301 now := time.Now()
3302 for _, notification := range channel.notifications {
3303 if notification.expiration.Before(now) {
3304 continue
3305 }
3306- res = append(res, notification.payload)
3307+ res = append(res, notification.Notification)
3308 }
3309 return topLevel, res, nil
3310 }
3311
3312=== modified file 'server/store/inmemory_test.go'
3313--- server/store/inmemory_test.go 2014-02-14 12:38:38 +0000
3314+++ server/store/inmemory_test.go 2014-04-29 18:02:00 +0000
3315@@ -21,6 +21,9 @@
3316 "time"
3317
3318 . "launchpad.net/gocheck"
3319+
3320+ "launchpad.net/ubuntu-push/protocol"
3321+ help "launchpad.net/ubuntu-push/testing"
3322 )
3323
3324 type inMemorySuite struct{}
3325@@ -45,7 +48,7 @@
3326 top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId)
3327 c.Assert(err, IsNil)
3328 c.Check(top, Equals, int64(0))
3329- c.Check(res, DeepEquals, []json.RawMessage(nil))
3330+ c.Check(res, DeepEquals, []protocol.Notification(nil))
3331 }
3332
3333 func (s *inMemorySuite) TestAppendToChannelAndGetChannelSnapshot(c *C) {
3334@@ -61,7 +64,7 @@
3335 top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId)
3336 c.Assert(err, IsNil)
3337 c.Check(top, Equals, int64(2))
3338- c.Check(res, DeepEquals, []json.RawMessage{notification1, notification2})
3339+ c.Check(res, DeepEquals, help.Ns(notification1, notification2))
3340 }
3341
3342 func (s *inMemorySuite) TestAppendToChannelAndGetChannelSnapshotWithExpiration(c *C) {
3343@@ -81,5 +84,5 @@
3344 top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId)
3345 c.Assert(err, IsNil)
3346 c.Check(top, Equals, int64(2))
3347- c.Check(res, DeepEquals, []json.RawMessage{notification1})
3348+ c.Check(res, DeepEquals, help.Ns(notification1))
3349 }
3350
3351=== modified file 'server/store/store.go'
3352--- server/store/store.go 2014-02-18 13:43:07 +0000
3353+++ server/store/store.go 2014-04-29 18:02:00 +0000
3354@@ -22,6 +22,8 @@
3355 "encoding/json"
3356 "errors"
3357 "time"
3358+
3359+ "launchpad.net/ubuntu-push/protocol"
3360 )
3361
3362 type InternalChannelId string
3363@@ -70,7 +72,7 @@
3364 AppendToChannel(chanId InternalChannelId, notification json.RawMessage, expiration time.Time) error
3365 // GetChannelSnapshot gets all the current notifications and
3366 // current top level in the channel.
3367- GetChannelSnapshot(chanId InternalChannelId) (topLevel int64, payloads []json.RawMessage, err error)
3368+ GetChannelSnapshot(chanId InternalChannelId) (topLevel int64, notifications []protocol.Notification, err error)
3369 // Close is to be called when done with the store.
3370 Close()
3371 }
3372
3373=== modified file 'testing/helpers.go'
3374--- testing/helpers.go 2014-02-21 16:04:44 +0000
3375+++ testing/helpers.go 2014-04-29 18:02:00 +0000
3376@@ -18,6 +18,7 @@
3377 package testing
3378
3379 import (
3380+ "encoding/json"
3381 "fmt"
3382 "os"
3383 "path/filepath"
3384@@ -26,6 +27,7 @@
3385 "sync"
3386
3387 "launchpad.net/ubuntu-push/logger"
3388+ "launchpad.net/ubuntu-push/protocol"
3389 )
3390
3391 type captureHelper struct {
3392@@ -122,3 +124,12 @@
3393 }
3394 return filepath.Join(dir, relativePath)
3395 }
3396+
3397+// Ns makes a []Notification from just payloads.
3398+func Ns(payloads ...json.RawMessage) []protocol.Notification {
3399+ res := make([]protocol.Notification, len(payloads))
3400+ for i := 0; i < len(payloads); i++ {
3401+ res[i].Payload = payloads[i]
3402+ }
3403+ return res
3404+}
3405
3406=== modified file 'ubuntu-push-client.go'
3407--- ubuntu-push-client.go 2014-03-12 13:25:20 +0000
3408+++ ubuntu-push-client.go 2014-04-29 18:02:00 +0000
3409@@ -19,12 +19,38 @@
3410 import (
3411 "log"
3412
3413+ "gopkg.in/qml.v0"
3414+ "launchpad.net/go-dbus/v1"
3415 "launchpad.net/go-xdg/v0"
3416
3417 "launchpad.net/ubuntu-push/client"
3418 )
3419
3420+const NAME = "com.ubuntu.PushNotifications"
3421+
3422+// grabName grabs ownership of the dbus name, and bails the client as
3423+// soon as somebody else grabs it.
3424+func grabName() {
3425+ conn, err := dbus.Connect(dbus.SessionBus)
3426+ if err != nil {
3427+ log.Fatalf("bus: %v", err)
3428+ }
3429+
3430+ flags := dbus.NameFlagAllowReplacement | dbus.NameFlagReplaceExisting
3431+ n := conn.RequestName(NAME, flags)
3432+ go func() {
3433+ for err := range n.C {
3434+ if err != nil {
3435+ log.Fatalf("FATAL: name channel got: %v", err)
3436+ }
3437+ }
3438+ }()
3439+}
3440+
3441 func main() {
3442+ // XXX: this is a quick hack to ensure unicity
3443+ grabName()
3444+
3445 cfgFname, err := xdg.Config.Find("ubuntu-push-client/config.json")
3446 if err != nil {
3447 log.Fatalf("unable to find a configuration file: %v", err)
3448@@ -33,6 +59,9 @@
3449 if err != nil {
3450 log.Fatalf("unable to open the levels database: %v", err)
3451 }
3452+
3453+ qml.Init(nil)
3454+
3455 cli := client.NewPushClient(cfgFname, lvlFname)
3456 err = cli.Start()
3457 if err != nil {
3458
3459=== added file 'util/auth.go'
3460--- util/auth.go 1970-01-01 00:00:00 +0000
3461+++ util/auth.go 2014-04-29 18:02:00 +0000
3462@@ -0,0 +1,36 @@
3463+/*
3464+ Copyright 2013-2014 Canonical Ltd.
3465+
3466+ This program is free software: you can redistribute it and/or modify it
3467+ under the terms of the GNU General Public License version 3, as published
3468+ by the Free Software Foundation.
3469+
3470+ This program is distributed in the hope that it will be useful, but
3471+ WITHOUT ANY WARRANTY; without even the implied warranties of
3472+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3473+ PURPOSE. See the GNU General Public License for more details.
3474+
3475+ You should have received a copy of the GNU General Public License along
3476+ with this program. If not, see <http://www.gnu.org/licenses/>.
3477+*/
3478+
3479+package util
3480+
3481+import (
3482+ "gopkg.in/niemeyer/uoneauth.v1"
3483+ "gopkg.in/qml.v0"
3484+)
3485+
3486+func GetAuthorization() (string, error) {
3487+ engine := qml.NewEngine()
3488+ defer engine.Destroy()
3489+ authService := uoneauth.NewService(engine)
3490+ var auth string
3491+ token, err := authService.Token()
3492+ if err != nil {
3493+ return "", err
3494+ } else {
3495+ auth = token.HeaderSignature("POST", "https://push.ubuntu.com")
3496+ }
3497+ return auth, nil
3498+}
3499
3500=== added file 'util/auth_test.go'
3501--- util/auth_test.go 1970-01-01 00:00:00 +0000
3502+++ util/auth_test.go 2014-04-29 18:02:00 +0000
3503@@ -0,0 +1,53 @@
3504+/*
3505+ Copyright 2013-2014 Canonical Ltd.
3506+
3507+ This program is free software: you can redistribute it and/or modify it
3508+ under the terms of the GNU General Public License version 3, as published
3509+ by the Free Software Foundation.
3510+
3511+ This program is distributed in the hope that it will be useful, but
3512+ WITHOUT ANY WARRANTY; without even the implied warranties of
3513+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3514+ PURPOSE. See the GNU General Public License for more details.
3515+
3516+ You should have received a copy of the GNU General Public License along
3517+ with this program. If not, see <http://www.gnu.org/licenses/>.
3518+*/
3519+
3520+package util
3521+
3522+import (
3523+ "os"
3524+
3525+ "gopkg.in/qml.v0"
3526+
3527+ . "launchpad.net/gocheck"
3528+)
3529+
3530+type authSuite struct{}
3531+
3532+var _ = Suite(&authSuite{})
3533+
3534+func (s *authSuite) SetUpSuite(c *C) {
3535+ if os.Getenv("PUSH_AUTH_TEST") == "1" {
3536+ qml.Init(nil)
3537+ }
3538+}
3539+
3540+func (s *authSuite) SetUpTest(c *C) {
3541+ qml.SetLogger(c)
3542+}
3543+
3544+func (s *authSuite) TestGetAuth(c *C) {
3545+ /*
3546+ * This test is only useful when the PUSH_AUTH_TEST environment
3547+ * variable is set to "1" - in which case the runner should have
3548+ * a Ubuntu One account setup via system-settings.
3549+ */
3550+ if os.Getenv("PUSH_AUTH_TEST") != "1" {
3551+ c.Skip("PUSH_AUTH_TEST not set to '1'")
3552+ }
3553+ auth, err := GetAuthorization()
3554+ c.Assert(err, IsNil)
3555+ c.Assert(auth, Matches, "OAuth .*oauth_consumer_key=.*")
3556+}
3557
3558=== modified file 'whoopsie/identifier/identifier.go'
3559--- whoopsie/identifier/identifier.go 2014-02-21 16:17:28 +0000
3560+++ whoopsie/identifier/identifier.go 2014-04-29 18:02:00 +0000
3561@@ -27,6 +27,7 @@
3562 import "C"
3563 import "unsafe"
3564 import "errors"
3565+import "time"
3566
3567 // an Id knows how to generate itself, and how to stringify itself.
3568 type Id interface {
3569@@ -36,12 +37,17 @@
3570
3571 // Identifier is the default Id implementation.
3572 type Identifier struct {
3573- value string
3574+ value string
3575+ generator func(**C.char, **C.GError)
3576+}
3577+
3578+func generator(csp **C.char, errp **C.GError) {
3579+ C.whoopsie_identifier_generate(csp, errp)
3580 }
3581
3582 // New creates an Identifier, but does not call Generate() on it.
3583 func New() Id {
3584- return &Identifier{}
3585+ return &Identifier{generator: generator}
3586 }
3587
3588 // Generate makes the Identifier create the identifier itself.
3589@@ -49,8 +55,18 @@
3590 var gerr *C.GError
3591 var cs *C.char
3592 defer C.g_free((C.gpointer)(unsafe.Pointer(cs)))
3593- C.whoopsie_identifier_generate(&cs, &gerr)
3594-
3595+
3596+ for i := 0; i < 200; i++ {
3597+ id.generator(&cs, &gerr)
3598+
3599+ if cs != nil || gerr != nil {
3600+ goto SuccessMaybe
3601+ }
3602+ time.Sleep(600 * time.Millisecond)
3603+ }
3604+ return errors.New("whoopsie_identifier_generate still bad after 2m; giving up")
3605+
3606+SuccessMaybe:
3607 if gerr != nil {
3608 return errors.New(C.GoString((*C.char)(gerr.message)))
3609 } else {
3610
3611=== modified file 'whoopsie/identifier/identifier_test.go'
3612--- whoopsie/identifier/identifier_test.go 2014-01-15 15:51:50 +0000
3613+++ whoopsie/identifier/identifier_test.go 2014-04-29 18:02:00 +0000
3614@@ -41,3 +41,18 @@
3615 func (s *IdentifierSuite) TestIdentifierInterface(c *C) {
3616 _ = []Id{New()}
3617 }
3618+
3619+// TestFailure checks that Identifier survives whoopsie shenanigans
3620+func (s *IdentifierSuite) TestIdentifierSurvivesShenanigans(c *C) {
3621+ count := 0
3622+ // using _Ctype* as a workaround for gocheck also having a C
3623+ gen := func(csp **_Ctype_char, errp **_Ctype_GError) {
3624+ count++
3625+ if count > 3 {
3626+ generator(csp, errp)
3627+ }
3628+ }
3629+ id := &Identifier{generator: gen}
3630+ id.Generate()
3631+ c.Check(id.String(), HasLen, 128)
3632+}

Subscribers

People subscribed via source and target branches