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

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

Commit message

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

Description of the change

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

To post a comment you must log in.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2014-03-31 17:58:54 +0000
+++ Makefile 2014-04-28 14:37:38 +0000
@@ -11,10 +11,13 @@
11GODEPS += launchpad.net/go-dbus/v111GODEPS += launchpad.net/go-dbus/v1
12GODEPS += launchpad.net/go-xdg/v012GODEPS += launchpad.net/go-xdg/v0
13GODEPS += code.google.com/p/gosqlite/sqlite313GODEPS += code.google.com/p/gosqlite/sqlite3
14GODEPS += gopkg.in/qml.v0
15GODEPS += gopkg.in/niemeyer/uoneauth.v1
1416
15TOTEST = $(shell env GOPATH=$(GOPATH) go list $(PROJECT)/...|grep -v acceptance|grep -v http13client )17TOTEST = $(shell env GOPATH=$(GOPATH) go list $(PROJECT)/...|grep -v acceptance|grep -v http13client )
1618
17bootstrap:19bootstrap:
20 $(RM) -r $(GOPATH)/pkg
18 mkdir -p $(GOPATH)/bin21 mkdir -p $(GOPATH)/bin
19 mkdir -p $(GOPATH)/pkg22 mkdir -p $(GOPATH)/pkg
20 go get -u launchpad.net/godeps23 go get -u launchpad.net/godeps
2124
=== modified file 'README'
--- README 2014-03-31 17:58:54 +0000
+++ README 2014-04-28 14:37:38 +0000
@@ -6,11 +6,20 @@
6The code expects to be checked out as launchpad.net/ubuntu-push in a Go6The code expects to be checked out as launchpad.net/ubuntu-push in a Go
7workspace, see "go help gopath".7workspace, see "go help gopath".
88
9To setup Go dependencies, install libsqlite3-dev and run:9To setup Go dependencies, install the following libraries:
10
11 libsqlite3-dev
12 qtbase5-private-dev
13 qtdeclarative5-dev
14 libqt5opengl5-dev
15 libubuntuoneauth-2.0-dev
16
17and run:
1018
11 make bootstrap19 make bootstrap
1220
13To run tests, install libgcrypt11-dev and libwhoopsie-dev and run:21To run tests, install libgcrypt11-dev, libwhoopsie-dev, and
22libubuntuoneauth-2.0-dev and run:
1423
15 make check24 make check
1625
1726
=== modified file 'client/client.go'
--- client/client.go 2014-04-11 16:37:48 +0000
+++ client/client.go 2014-04-28 14:37:38 +0000
@@ -26,6 +26,7 @@
26 "os"26 "os"
27 "strings"27 "strings"
2828
29 "gopkg.in/qml.v0"
29 "launchpad.net/go-dbus/v1"30 "launchpad.net/go-dbus/v1"
3031
31 "launchpad.net/ubuntu-push/bus"32 "launchpad.net/ubuntu-push/bus"
@@ -57,7 +58,7 @@
57 // The PEM-encoded server certificate58 // The PEM-encoded server certificate
58 CertPEMFile string `json:"cert_pem_file"`59 CertPEMFile string `json:"cert_pem_file"`
59 // The logging level (one of "debug", "info", "error")60 // The logging level (one of "debug", "info", "error")
60 LogLevel string `json:"log_level"`61 LogLevel logger.ConfigLogLevel `json:"log_level"`
61}62}
6263
63// PushClient is the Ubuntu Push Notifications client-side daemon.64// PushClient is the Ubuntu Push Notifications client-side daemon.
@@ -95,13 +96,13 @@
9596
96// configure loads its configuration, and sets it up.97// configure loads its configuration, and sets it up.
97func (client *PushClient) configure() error {98func (client *PushClient) configure() error {
98 f, err := os.Open(client.configPath)99 _, err := os.Stat(client.configPath)
99 if err != nil {100 if err != nil {
100 return fmt.Errorf("opening config: %v", err)101 return fmt.Errorf("config: %v", err)
101 }102 }
102 err = config.ReadConfig(f, &client.config)103 err = config.ReadFiles(&client.config, client.configPath, "<flags>")
103 if err != nil {104 if err != nil {
104 return fmt.Errorf("reading config: %v", err)105 return fmt.Errorf("config: %v", err)
105 }106 }
106 // ignore spaces107 // ignore spaces
107 client.config.Addr = strings.Replace(client.config.Addr, " ", "", -1)108 client.config.Addr = strings.Replace(client.config.Addr, " ", "", -1)
@@ -110,7 +111,8 @@
110 }111 }
111112
112 // later, we'll be specifying more logging options in the config file113 // later, we'll be specifying more logging options in the config file
113 client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel)114 client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel.Level())
115 qml.SetLogger(client.log)
114116
115 // overridden for testing117 // overridden for testing
116 client.idder = identifier.New()118 client.idder = identifier.New()
@@ -285,9 +287,6 @@
285 h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}}287 h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}}
286 nots := notifications.Raw(client.notificationsEndp, client.log)288 nots := notifications.Raw(client.notificationsEndp, client.log)
287 body := "Tap to open the system updater."289 body := "Tap to open the system updater."
288 if msg != nil {
289 body = fmt.Sprintf("[%d] %s", msg.TopLevel, body)
290 }
291 not_id, err := nots.Notify(290 not_id, err := nots.Notify(
292 "ubuntu-push-client", // app name291 "ubuntu-push-client", // app name
293 uint32(0), // id292 uint32(0), // id
294293
=== modified file 'client/client_test.go'
--- client/client_test.go 2014-04-11 16:21:45 +0000
+++ client/client_test.go 2014-04-28 14:37:38 +0000
@@ -19,10 +19,12 @@
19import (19import (
20 "encoding/json"20 "encoding/json"
21 "errors"21 "errors"
22 "flag"
22 "fmt"23 "fmt"
23 "io/ioutil"24 "io/ioutil"
24 "net/http"25 "net/http"
25 "net/http/httptest"26 "net/http/httptest"
27 "os"
26 "path/filepath"28 "path/filepath"
27 "reflect"29 "reflect"
28 "testing"30 "testing"
@@ -37,6 +39,7 @@
37 testibus "launchpad.net/ubuntu-push/bus/testing"39 testibus "launchpad.net/ubuntu-push/bus/testing"
38 "launchpad.net/ubuntu-push/client/session"40 "launchpad.net/ubuntu-push/client/session"
39 "launchpad.net/ubuntu-push/client/session/levelmap"41 "launchpad.net/ubuntu-push/client/session/levelmap"
42 "launchpad.net/ubuntu-push/config"
40 helpers "launchpad.net/ubuntu-push/testing"43 helpers "launchpad.net/ubuntu-push/testing"
41 "launchpad.net/ubuntu-push/testing/condition"44 "launchpad.net/ubuntu-push/testing/condition"
42 "launchpad.net/ubuntu-push/util"45 "launchpad.net/ubuntu-push/util"
@@ -79,6 +82,7 @@
79}82}
8083
81func (cs *clientSuite) SetUpSuite(c *C) {84func (cs *clientSuite) SetUpSuite(c *C) {
85 config.IgnoreParsedFlags = true // because configure() uses <flags>
82 cs.timeouts = util.SwapTimeouts([]time.Duration{0})86 cs.timeouts = util.SwapTimeouts([]time.Duration{0})
83 cs.leveldbPath = ""87 cs.leveldbPath = ""
84}88}
@@ -142,6 +146,16 @@
142 c.Check(cli.config.ExchangeTimeout.TimeDuration(), Equals, time.Duration(10*time.Millisecond))146 c.Check(cli.config.ExchangeTimeout.TimeDuration(), Equals, time.Duration(10*time.Millisecond))
143}147}
144148
149func (cs *clientSuite) TestConfigureWorksWithFlags(c *C) {
150 flag.CommandLine = flag.NewFlagSet("client", flag.ContinueOnError)
151 os.Args = []string{"client", "-addr", "foo:7777"}
152 cli := NewPushClient(cs.configPath, cs.leveldbPath)
153 err := cli.configure()
154 c.Assert(err, IsNil)
155 c.Assert(cli.config, NotNil)
156 c.Check(cli.config.Addr, Equals, "foo:7777")
157}
158
145func (cs *clientSuite) TestConfigureSetsUpLog(c *C) {159func (cs *clientSuite) TestConfigureSetsUpLog(c *C) {
146 cli := NewPushClient(cs.configPath, cs.leveldbPath)160 cli := NewPushClient(cs.configPath, cs.leveldbPath)
147 c.Check(cli.log, IsNil)161 c.Check(cli.log, IsNil)
@@ -163,7 +177,7 @@
163 c.Check(cli.idder, IsNil)177 c.Check(cli.idder, IsNil)
164 err := cli.configure()178 err := cli.configure()
165 c.Assert(err, IsNil)179 c.Assert(err, IsNil)
166 c.Assert(cli.idder, DeepEquals, identifier.New())180 c.Assert(cli.idder, FitsTypeOf, identifier.New())
167}181}
168182
169func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) {183func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) {
170184
=== modified file 'client/gethosts/gethost.go'
--- client/gethosts/gethost.go 2014-03-24 15:32:29 +0000
+++ client/gethosts/gethost.go 2014-04-28 14:37:38 +0000
@@ -49,8 +49,10 @@
49 }49 }
50}50}
5151
52type expected struct {52// Host contains the domain and hosts returned by the remote endpoint
53 Hosts []string53type Host struct {
54 Domain string
55 Hosts []string
54}56}
5557
56var (58var (
@@ -60,7 +62,7 @@
60)62)
6163
62// Get gets a list of hosts consulting the endpoint.64// Get gets a list of hosts consulting the endpoint.
63func (gh *GetHost) Get() ([]string, error) {65func (gh *GetHost) Get() (*Host, error) {
64 resp, err := gh.cli.Get(gh.endpointUrl + "?h=" + gh.hash)66 resp, err := gh.cli.Get(gh.endpointUrl + "?h=" + gh.hash)
65 if err != nil {67 if err != nil {
66 return nil, err68 return nil, err
@@ -80,7 +82,7 @@
80 if err != nil {82 if err != nil {
81 return nil, err83 return nil, err
82 }84 }
83 var parsed expected85 var parsed Host
84 err = json.Unmarshal(body, &parsed)86 err = json.Unmarshal(body, &parsed)
85 if err != nil {87 if err != nil {
86 return nil, ErrTemporary88 return nil, ErrTemporary
@@ -88,5 +90,5 @@
88 if len(parsed.Hosts) == 0 {90 if len(parsed.Hosts) == 0 {
89 return nil, ErrTemporary91 return nil, ErrTemporary
90 }92 }
91 return parsed.Hosts, nil93 return &parsed, nil
92}94}
9395
=== modified file 'client/gethosts/gethost_test.go'
--- client/gethosts/gethost_test.go 2014-03-31 14:31:07 +0000
+++ client/gethosts/gethost_test.go 2014-04-28 14:37:38 +0000
@@ -45,7 +45,8 @@
45 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {45 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
46 x := r.FormValue("h")46 x := r.FormValue("h")
47 b, err := json.Marshal(map[string]interface{}{47 b, err := json.Marshal(map[string]interface{}{
48 "hosts": []string{"http://" + x},48 "domain": "example.com",
49 "hosts": []string{"http://" + x},
49 })50 })
50 if err != nil {51 if err != nil {
51 panic(err)52 panic(err)
@@ -57,7 +58,8 @@
57 gh := New("foobar", ts.URL, 1*time.Second)58 gh := New("foobar", ts.URL, 1*time.Second)
58 res, err := gh.Get()59 res, err := gh.Get()
59 c.Assert(err, IsNil)60 c.Assert(err, IsNil)
60 c.Check(res, DeepEquals, []string{"http://c1130408a700afe0"})61 c.Check(*res, DeepEquals,
62 Host{Domain: "example.com", Hosts: []string{"http://c1130408a700afe0"}})
61}63}
6264
63func (s *getHostsSuite) TestGetTimeout(c *C) {65func (s *getHostsSuite) TestGetTimeout(c *C) {
@@ -97,4 +99,6 @@
9799
98 scenario(http.StatusOK, "{", ErrTemporary)100 scenario(http.StatusOK, "{", ErrTemporary)
99 scenario(http.StatusOK, "{}", ErrTemporary)101 scenario(http.StatusOK, "{}", ErrTemporary)
102 scenario(http.StatusOK, `{"domain": "example.com"}`, ErrTemporary)
103 scenario(http.StatusOK, `{"hosts": ["one"]}`, nil)
100}104}
101105
=== modified file 'client/session/session.go'
--- client/session/session.go 2014-04-04 13:55:00 +0000
+++ client/session/session.go 2014-04-28 14:37:38 +0000
@@ -38,7 +38,11 @@
38 "launchpad.net/ubuntu-push/util"38 "launchpad.net/ubuntu-push/util"
39)39)
4040
41var wireVersionBytes = []byte{protocol.ProtocolWireVersion}41var (
42 wireVersionBytes = []byte{protocol.ProtocolWireVersion}
43 getAuthorization = util.GetAuthorization
44 shouldGetAuth = false
45)
4246
43type Notification struct {47type Notification struct {
44 TopLevel int6448 TopLevel int64
@@ -73,7 +77,7 @@
73)77)
7478
75type hostGetter interface {79type hostGetter interface {
76 Get() ([]string, error)80 Get() (*gethosts.Host, error)
77}81}
7882
79// ClientSessionConfig groups the client session configuration.83// ClientSessionConfig groups the client session configuration.
@@ -115,6 +119,28 @@
115 stateP *uint32119 stateP *uint32
116 ErrCh chan error120 ErrCh chan error
117 MsgCh chan *Notification121 MsgCh chan *Notification
122 // authorization
123 auth string
124 // autoredial knobs
125 shouldDelayP *uint32
126 lastAutoRedial time.Time
127 redialDelay func(*ClientSession) time.Duration
128 redialJitter func(time.Duration) time.Duration
129 redialDelays []time.Duration
130 redialDelaysIdx int
131}
132
133func redialDelay(sess *ClientSession) time.Duration {
134 if sess.ShouldDelay() {
135 t := sess.redialDelays[sess.redialDelaysIdx]
136 if len(sess.redialDelays) > sess.redialDelaysIdx+1 {
137 sess.redialDelaysIdx++
138 }
139 return t + sess.redialJitter(t)
140 } else {
141 sess.redialDelaysIdx = 0
142 return 0
143 }
118}144}
119145
120func NewSession(serverAddrSpec string, conf ClientSessionConfig,146func NewSession(serverAddrSpec string, conf ClientSessionConfig,
@@ -131,6 +157,7 @@
131 if hostsEndpoint != "" {157 if hostsEndpoint != "" {
132 getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout)158 getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout)
133 }159 }
160 var shouldDelay uint32 = 0
134 sess := &ClientSession{161 sess := &ClientSession{
135 ClientSessionConfig: conf,162 ClientSessionConfig: conf,
136 getHost: getHost,163 getHost: getHost,
@@ -139,10 +166,14 @@
139 Log: log,166 Log: log,
140 Protocolator: protocol.NewProtocol0,167 Protocolator: protocol.NewProtocol0,
141 Levels: levels,168 Levels: levels,
142 TLS: &tls.Config{InsecureSkipVerify: true}, // XXX169 TLS: &tls.Config{},
143 stateP: &state,170 stateP: &state,
144 timeSince: time.Since,171 timeSince: time.Since,
172 shouldDelayP: &shouldDelay,
173 redialDelay: redialDelay,
174 redialDelays: util.Timeouts(),
145 }175 }
176 sess.redialJitter = sess.Jitter
146 if sess.PEM != nil {177 if sess.PEM != nil {
147 cp := x509.NewCertPool()178 cp := x509.NewCertPool()
148 ok := cp.AppendCertsFromPEM(sess.PEM)179 ok := cp.AppendCertsFromPEM(sess.PEM)
@@ -154,6 +185,18 @@
154 return sess, nil185 return sess, nil
155}186}
156187
188func (sess *ClientSession) ShouldDelay() bool {
189 return atomic.LoadUint32(sess.shouldDelayP) != 0
190}
191
192func (sess *ClientSession) setShouldDelay() {
193 atomic.StoreUint32(sess.shouldDelayP, uint32(1))
194}
195
196func (sess *ClientSession) clearShouldDelay() {
197 atomic.StoreUint32(sess.shouldDelayP, uint32(0))
198}
199
157func (sess *ClientSession) State() ClientSessionState {200func (sess *ClientSession) State() ClientSessionState {
158 return ClientSessionState(atomic.LoadUint32(sess.stateP))201 return ClientSessionState(atomic.LoadUint32(sess.stateP))
159}202}
@@ -180,20 +223,38 @@
180 if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime {223 if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime {
181 return nil224 return nil
182 }225 }
183 hosts, err := sess.getHost.Get()226 host, err := sess.getHost.Get()
184 if err != nil {227 if err != nil {
185 sess.Log.Errorf("getHosts: %v", err)228 sess.Log.Errorf("getHosts: %v", err)
186 sess.setState(Error)229 sess.setState(Error)
187 return err230 return err
188 }231 }
189 sess.deliveryHostsTimestamp = time.Now()232 sess.deliveryHostsTimestamp = time.Now()
190 sess.deliveryHosts = hosts233 sess.deliveryHosts = host.Hosts
234 if sess.TLS != nil {
235 sess.TLS.ServerName = host.Domain
236 }
191 } else {237 } else {
192 sess.deliveryHosts = sess.fallbackHosts238 sess.deliveryHosts = sess.fallbackHosts
193 }239 }
194 return nil240 return nil
195}241}
196242
243// checkAuthorization checks the authorization within the phone
244func (sess *ClientSession) checkAuthorization() error {
245 // grab the authorization string from the accounts
246 // TODO: remove this condition when we have a way to deal with failing authorizations
247 if shouldGetAuth {
248 auth, err := getAuthorization()
249 if err != nil {
250 // For now we just log the error, as we don't want to block unauthorized users
251 sess.Log.Errorf("unable to get the authorization token from the account: %v", err)
252 }
253 sess.auth = auth
254 }
255 return nil
256}
257
197func (sess *ClientSession) resetHosts() {258func (sess *ClientSession) resetHosts() {
198 sess.deliveryHosts = nil259 sess.deliveryHosts = nil
199}260}
@@ -234,6 +295,7 @@
234// connect to a server using the configuration in the ClientSession295// connect to a server using the configuration in the ClientSession
235// and set up the connection.296// and set up the connection.
236func (sess *ClientSession) connect() error {297func (sess *ClientSession) connect() error {
298 sess.setShouldDelay()
237 sess.startConnectionAttempt()299 sess.startConnectionAttempt()
238 var err error300 var err error
239 var conn net.Conn301 var conn net.Conn
@@ -263,7 +325,12 @@
263325
264func (sess *ClientSession) AutoRedial(doneCh chan uint32) {326func (sess *ClientSession) AutoRedial(doneCh chan uint32) {
265 sess.stopRedial()327 sess.stopRedial()
328 if time.Since(sess.lastAutoRedial) < 2*time.Second {
329 sess.setShouldDelay()
330 }
331 time.Sleep(sess.redialDelay(sess))
266 sess.retrier = util.NewAutoRedialer(sess)332 sess.retrier = util.NewAutoRedialer(sess)
333 sess.lastAutoRedial = time.Now()
267 go func() { doneCh <- sess.retrier.Redial() }()334 go func() { doneCh <- sess.retrier.Redial() }()
268}335}
269336
@@ -289,6 +356,7 @@
289 err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"})356 err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"})
290 if err == nil {357 if err == nil {
291 sess.Log.Debugf("ping.")358 sess.Log.Debugf("ping.")
359 sess.clearShouldDelay()
292 } else {360 } else {
293 sess.setState(Error)361 sess.setState(Error)
294 sess.Log.Errorf("unable to pong: %s", err)362 sess.Log.Errorf("unable to pong: %s", err)
@@ -330,6 +398,7 @@
330 sess.Log.Errorf("unable to ack broadcast: %s", err)398 sess.Log.Errorf("unable to ack broadcast: %s", err)
331 return err399 return err
332 }400 }
401 sess.clearShouldDelay()
333 sess.Log.Debugf("broadcast chan:%v app:%v topLevel:%d payloads:%s",402 sess.Log.Debugf("broadcast chan:%v app:%v topLevel:%d payloads:%s",
334 bcast.ChanId, bcast.AppId, bcast.TopLevel, bcast.Payloads)403 bcast.ChanId, bcast.AppId, bcast.TopLevel, bcast.Payloads)
335 if bcast.ChanId == protocol.SystemChannelId {404 if bcast.ChanId == protocol.SystemChannelId {
@@ -409,10 +478,9 @@
409 return err478 return err
410 }479 }
411 err = proto.WriteMessage(protocol.ConnectMsg{480 err = proto.WriteMessage(protocol.ConnectMsg{
412 Type: "connect",481 Type: "connect",
413 DeviceId: sess.DeviceId,482 DeviceId: sess.DeviceId,
414 // xxx get the SSO Authorization string from the phone483 Authorization: sess.auth,
415 Authorization: "",
416 Levels: levels,484 Levels: levels,
417 Info: sess.Info,485 Info: sess.Info,
418 })486 })
@@ -447,13 +515,15 @@
447515
448// run calls connect, and if it works it calls start, and if it works516// run calls connect, and if it works it calls start, and if it works
449// it runs loop in a goroutine, and ships its return value over ErrCh.517// it runs loop in a goroutine, and ships its return value over ErrCh.
450func (sess *ClientSession) run(closer func(), hostGetter, connecter, starter, looper func() error) error {518func (sess *ClientSession) run(closer func(), authChecker, hostGetter, connecter, starter, looper func() error) error {
451 closer()519 closer()
452 err := hostGetter()520 if err := authChecker(); err != nil {
453 if err != nil {521 return err
454 return err522 }
455 }523 if err := hostGetter(); err != nil {
456 err = connecter()524 return err
525 }
526 err := connecter()
457 if err == nil {527 if err == nil {
458 err = starter()528 err = starter()
459 if err == nil {529 if err == nil {
@@ -483,7 +553,7 @@
483 // keep on trying.553 // keep on trying.
484 panic("can't Dial() without a protocol constructor.")554 panic("can't Dial() without a protocol constructor.")
485 }555 }
486 return sess.run(sess.doClose, sess.getHosts, sess.connect, sess.start, sess.loop)556 return sess.run(sess.doClose, sess.checkAuthorization, sess.getHosts, sess.connect, sess.start, sess.loop)
487}557}
488558
489func init() {559func init() {
490560
=== modified file 'client/session/session_test.go'
--- client/session/session_test.go 2014-04-04 13:55:00 +0000
+++ client/session/session_test.go 2014-04-28 14:37:38 +0000
@@ -32,12 +32,12 @@
3232
33 . "launchpad.net/gocheck"33 . "launchpad.net/gocheck"
3434
35 "launchpad.net/ubuntu-push/client/gethosts"
35 "launchpad.net/ubuntu-push/client/session/levelmap"36 "launchpad.net/ubuntu-push/client/session/levelmap"
36 //"launchpad.net/ubuntu-push/client/gethosts"
37 "launchpad.net/ubuntu-push/logger"
38 "launchpad.net/ubuntu-push/protocol"37 "launchpad.net/ubuntu-push/protocol"
39 helpers "launchpad.net/ubuntu-push/testing"38 helpers "launchpad.net/ubuntu-push/testing"
40 "launchpad.net/ubuntu-push/testing/condition"39 "launchpad.net/ubuntu-push/testing/condition"
40 "launchpad.net/ubuntu-push/util"
41)41)
4242
43func TestSession(t *testing.T) { TestingT(t) }43func TestSession(t *testing.T) { TestingT(t) }
@@ -165,14 +165,26 @@
165/////165/////
166166
167type clientSessionSuite struct {167type clientSessionSuite struct {
168 log logger.Logger168 log *helpers.TestLogger
169 lvls func() (levelmap.LevelMap, error)169 lvls func() (levelmap.LevelMap, error)
170}170}
171171
172func (cs *clientSessionSuite) SetUpSuite(c *C) {
173 getAuthorization = func() (string, error) {
174 return "some auth", nil
175 }
176 shouldGetAuth = true
177}
178
172func (cs *clientSessionSuite) SetUpTest(c *C) {179func (cs *clientSessionSuite) SetUpTest(c *C) {
173 cs.log = helpers.NewTestLogger(c, "debug")180 cs.log = helpers.NewTestLogger(c, "debug")
174}181}
175182
183func (cs *clientSessionSuite) TearDownSuite(c *C) {
184 getAuthorization = util.GetAuthorization
185 shouldGetAuth = false
186}
187
176// in-memory level map testing188// in-memory level map testing
177var _ = Suite(&clientSessionSuite{lvls: levelmap.NewLevelMap})189var _ = Suite(&clientSessionSuite{lvls: levelmap.NewLevelMap})
178190
@@ -182,6 +194,7 @@
182var _ = Suite(&clientSqlevelsSessionSuite{})194var _ = Suite(&clientSqlevelsSessionSuite{})
183195
184func (cs *clientSqlevelsSessionSuite) SetUpSuite(c *C) {196func (cs *clientSqlevelsSessionSuite) SetUpSuite(c *C) {
197 cs.clientSessionSuite.SetUpSuite(c)
185 cs.lvls = func() (levelmap.LevelMap, error) { return levelmap.NewSqliteLevelMap(":memory:") }198 cs.lvls = func() (levelmap.LevelMap, error) { return levelmap.NewSqliteLevelMap(":memory:") }
186}199}
187200
@@ -214,6 +227,10 @@
214 c.Check(sess, NotNil)227 c.Check(sess, NotNil)
215 c.Check(err, IsNil)228 c.Check(err, IsNil)
216 c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"})229 c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"})
230 // the session is happy and redial delayer is default
231 c.Check(sess.ShouldDelay(), Equals, false)
232 c.Check(fmt.Sprintf("%#v", sess.redialDelay), Equals, fmt.Sprintf("%#v", redialDelay))
233 c.Check(sess.redialDelays, DeepEquals, util.Timeouts())
217 // but no root CAs set234 // but no root CAs set
218 c.Check(sess.TLS.RootCAs, IsNil)235 c.Check(sess.TLS.RootCAs, IsNil)
219 c.Check(sess.State(), Equals, Disconnected)236 c.Check(sess.State(), Equals, Disconnected)
@@ -264,16 +281,17 @@
264}281}
265282
266type testHostGetter struct {283type testHostGetter struct {
267 hosts []string284 domain string
268 err error285 hosts []string
286 err error
269}287}
270288
271func (thg *testHostGetter) Get() ([]string, error) {289func (thg *testHostGetter) Get() (*gethosts.Host, error) {
272 return thg.hosts, thg.err290 return &gethosts.Host{thg.domain, thg.hosts}, thg.err
273}291}
274292
275func (cs *clientSessionSuite) TestGetHostsRemote(c *C) {293func (cs *clientSessionSuite) TestGetHostsRemote(c *C) {
276 hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}294 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
277 sess := &ClientSession{getHost: hostGetter, timeSince: time.Since}295 sess := &ClientSession{getHost: hostGetter, timeSince: time.Since}
278 err := sess.getHosts()296 err := sess.getHosts()
279 c.Assert(err, IsNil)297 c.Assert(err, IsNil)
@@ -284,7 +302,7 @@
284 sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)302 sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)
285 c.Assert(err, IsNil)303 c.Assert(err, IsNil)
286 hostsErr := errors.New("failed")304 hostsErr := errors.New("failed")
287 hostGetter := &testHostGetter{nil, hostsErr}305 hostGetter := &testHostGetter{"", nil, hostsErr}
288 sess.getHost = hostGetter306 sess.getHost = hostGetter
289 err = sess.getHosts()307 err = sess.getHosts()
290 c.Assert(err, Equals, hostsErr)308 c.Assert(err, Equals, hostsErr)
@@ -293,7 +311,7 @@
293}311}
294312
295func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) {313func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) {
296 hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}314 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
297 sess := &ClientSession{315 sess := &ClientSession{
298 getHost: hostGetter,316 getHost: hostGetter,
299 ClientSessionConfig: ClientSessionConfig{317 ClientSessionConfig: ClientSessionConfig{
@@ -318,7 +336,7 @@
318}336}
319337
320func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) {338func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) {
321 hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}339 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
322 sess := &ClientSession{340 sess := &ClientSession{
323 getHost: hostGetter,341 getHost: hostGetter,
324 ClientSessionConfig: ClientSessionConfig{342 ClientSessionConfig: ClientSessionConfig{
@@ -341,6 +359,18 @@
341}359}
342360
343/****************************************************************361/****************************************************************
362 checkAuthorization() tests
363****************************************************************/
364
365func (cs *clientSessionSuite) TestChecksAuthorizationFromServer(c *C) {
366 sess := &ClientSession{}
367 c.Assert(sess.auth, Equals, "")
368 err := sess.checkAuthorization()
369 c.Assert(err, IsNil)
370 c.Check(sess.auth, Equals, "some auth")
371}
372
373/****************************************************************
344 startConnectionAttempt()/nextHostToTry()/started tests374 startConnectionAttempt()/nextHostToTry()/started tests
345****************************************************************/375****************************************************************/
346376
@@ -427,7 +457,9 @@
427 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)457 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
428 c.Assert(err, IsNil)458 c.Assert(err, IsNil)
429 sess.deliveryHosts = []string{"nowhere"}459 sess.deliveryHosts = []string{"nowhere"}
460 sess.clearShouldDelay()
430 err = sess.connect()461 err = sess.connect()
462 c.Check(sess.ShouldDelay(), Equals, true)
431 c.Check(err, ErrorMatches, ".*connect.*address.*")463 c.Check(err, ErrorMatches, ".*connect.*address.*")
432 c.Check(sess.State(), Equals, Error)464 c.Check(sess.State(), Equals, Error)
433}465}
@@ -439,7 +471,9 @@
439 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)471 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
440 c.Assert(err, IsNil)472 c.Assert(err, IsNil)
441 sess.deliveryHosts = []string{srv.Addr().String()}473 sess.deliveryHosts = []string{srv.Addr().String()}
474 sess.clearShouldDelay()
442 err = sess.connect()475 err = sess.connect()
476 c.Check(sess.ShouldDelay(), Equals, true)
443 c.Check(err, IsNil)477 c.Check(err, IsNil)
444 c.Check(sess.Connection, NotNil)478 c.Check(sess.Connection, NotNil)
445 c.Check(sess.State(), Equals, Connected)479 c.Check(sess.State(), Equals, Connected)
@@ -452,7 +486,9 @@
452 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)486 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
453 c.Assert(err, IsNil)487 c.Assert(err, IsNil)
454 sess.deliveryHosts = []string{"nowhere", srv.Addr().String()}488 sess.deliveryHosts = []string{"nowhere", srv.Addr().String()}
489 sess.clearShouldDelay()
455 err = sess.connect()490 err = sess.connect()
491 c.Check(sess.ShouldDelay(), Equals, true)
456 c.Check(err, IsNil)492 c.Check(err, IsNil)
457 c.Check(sess.Connection, NotNil)493 c.Check(sess.Connection, NotNil)
458 c.Check(sess.State(), Equals, Connected)494 c.Check(sess.State(), Equals, Connected)
@@ -466,7 +502,9 @@
466 srv.Close()502 srv.Close()
467 c.Assert(err, IsNil)503 c.Assert(err, IsNil)
468 sess.deliveryHosts = []string{srv.Addr().String()}504 sess.deliveryHosts = []string{srv.Addr().String()}
505 sess.clearShouldDelay()
469 err = sess.connect()506 err = sess.connect()
507 c.Check(sess.ShouldDelay(), Equals, true)
470 c.Check(err, ErrorMatches, ".*connection refused")508 c.Check(err, ErrorMatches, ".*connection refused")
471 c.Check(sess.State(), Equals, Error)509 c.Check(sess.State(), Equals, Error)
472}510}
@@ -548,6 +586,27 @@
548 c.Check(<-ch, Not(Equals), 0)586 c.Check(<-ch, Not(Equals), 0)
549}587}
550588
589func (cs *clientSessionSuite) TestAutoRedialCallsRedialDelay(c *C) {
590 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
591 c.Assert(err, IsNil)
592 flag := false
593 sess.redialDelay = func(sess *ClientSession) time.Duration { flag = true; return 0 }
594 sess.AutoRedial(nil)
595 c.Check(flag, Equals, true)
596}
597
598func (cs *clientSessionSuite) TestAutoRedialSetsRedialDelayIfTooQuick(c *C) {
599 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
600 c.Assert(err, IsNil)
601 sess.redialDelay = func(sess *ClientSession) time.Duration { return 0 }
602 sess.AutoRedial(nil)
603 c.Check(sess.ShouldDelay(), Equals, false)
604 sess.stopRedial()
605 sess.clearShouldDelay()
606 sess.AutoRedial(nil)
607 c.Check(sess.ShouldDelay(), Equals, true)
608}
609
551/****************************************************************610/****************************************************************
552 handlePing() tests611 handlePing() tests
553****************************************************************/612****************************************************************/
@@ -594,6 +653,24 @@
594 c.Check(s.sess.State(), Equals, Error)653 c.Check(s.sess.State(), Equals, Error)
595}654}
596655
656func (s *msgSuite) TestHandlePingClearsDelay(c *C) {
657 s.sess.setShouldDelay()
658 s.upCh <- nil // no error
659 c.Check(s.sess.handlePing(), IsNil)
660 c.Assert(len(s.downCh), Equals, 1)
661 c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"})
662 c.Check(s.sess.ShouldDelay(), Equals, false)
663}
664
665func (s *msgSuite) TestHandlePingDoesNotClearsDelayOnError(c *C) {
666 s.sess.setShouldDelay()
667 s.upCh <- errors.New("Pong")
668 c.Check(s.sess.handlePing(), NotNil)
669 c.Assert(len(s.downCh), Equals, 1)
670 c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"})
671 c.Check(s.sess.ShouldDelay(), Equals, true)
672}
673
597/****************************************************************674/****************************************************************
598 handleBroadcast() tests675 handleBroadcast() tests
599****************************************************************/676****************************************************************/
@@ -687,6 +764,32 @@
687 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"nak"})764 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"nak"})
688}765}
689766
767func (s *msgSuite) TestHandleBroadcastClearsDelay(c *C) {
768 s.sess.setShouldDelay()
769
770 msg := serverMsg{"broadcast", protocol.BroadcastMsg{},
771 protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}}
772 go func() { s.errCh <- s.sess.handleBroadcast(&msg) }()
773 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
774 s.upCh <- nil // ack ok
775 c.Check(<-s.errCh, IsNil)
776
777 c.Check(s.sess.ShouldDelay(), Equals, false)
778}
779
780func (s *msgSuite) TestHandleBroadcastDoesNotClearDelayOnError(c *C) {
781 s.sess.setShouldDelay()
782
783 msg := serverMsg{"broadcast", protocol.BroadcastMsg{},
784 protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}}
785 go func() { s.errCh <- s.sess.handleBroadcast(&msg) }()
786 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
787 s.upCh <- errors.New("bcast")
788 c.Check(<-s.errCh, NotNil)
789
790 c.Check(s.sess.ShouldDelay(), Equals, true)
791}
792
690/****************************************************************793/****************************************************************
691 handleConnBroken() tests794 handleConnBroken() tests
692****************************************************************/795****************************************************************/
@@ -852,9 +955,10 @@
852955
853 c.Check(takeNext(downCh), Equals, "deadline 0")956 c.Check(takeNext(downCh), Equals, "deadline 0")
854 c.Check(takeNext(downCh), DeepEquals, protocol.ConnectMsg{957 c.Check(takeNext(downCh), DeepEquals, protocol.ConnectMsg{
855 Type: "connect",958 Type: "connect",
856 DeviceId: sess.DeviceId,959 DeviceId: sess.DeviceId,
857 Levels: map[string]int64{},960 Levels: map[string]int64{},
961 Authorization: "",
858 })962 })
859 upCh <- errors.New("Overflow error in /dev/null")963 upCh <- errors.New("Overflow error in /dev/null")
860 err = <-errCh964 err = <-errCh
@@ -959,6 +1063,7 @@
959 msg, ok := takeNext(downCh).(protocol.ConnectMsg)1063 msg, ok := takeNext(downCh).(protocol.ConnectMsg)
960 c.Check(ok, Equals, true)1064 c.Check(ok, Equals, true)
961 c.Check(msg.DeviceId, Equals, "wah")1065 c.Check(msg.DeviceId, Equals, "wah")
1066 c.Check(msg.Authorization, Equals, "")
962 c.Check(msg.Info, DeepEquals, info)1067 c.Check(msg.Info, DeepEquals, info)
963 upCh <- nil // no error1068 upCh <- nil // no error
964 upCh <- protocol.ConnAckMsg{1069 upCh <- protocol.ConnAckMsg{
@@ -975,6 +1080,22 @@
975 run() tests1080 run() tests
976****************************************************************/1081****************************************************************/
9771082
1083func (cs *clientSessionSuite) TestRunBailsIfAuthCheckFails(c *C) {
1084 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1085 c.Assert(err, IsNil)
1086 failure := errors.New("TestRunBailsIfAuthCheckFails")
1087 has_closed := false
1088 err = sess.run(
1089 func() { has_closed = true },
1090 func() error { return failure },
1091 nil,
1092 nil,
1093 nil,
1094 nil)
1095 c.Check(err, Equals, failure)
1096 c.Check(has_closed, Equals, true)
1097}
1098
978func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) {1099func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) {
979 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1100 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
980 c.Assert(err, IsNil)1101 c.Assert(err, IsNil)
@@ -982,6 +1103,7 @@
982 has_closed := false1103 has_closed := false
983 err = sess.run(1104 err = sess.run(
984 func() { has_closed = true },1105 func() { has_closed = true },
1106 func() error { return nil },
985 func() error { return failure },1107 func() error { return failure },
986 nil,1108 nil,
987 nil,1109 nil,
@@ -997,6 +1119,7 @@
997 err = sess.run(1119 err = sess.run(
998 func() {},1120 func() {},
999 func() error { return nil },1121 func() error { return nil },
1122 func() error { return nil },
1000 func() error { return failure },1123 func() error { return failure },
1001 nil,1124 nil,
1002 nil)1125 nil)
@@ -1011,6 +1134,7 @@
1011 func() {},1134 func() {},
1012 func() error { return nil },1135 func() error { return nil },
1013 func() error { return nil },1136 func() error { return nil },
1137 func() error { return nil },
1014 func() error { return failure },1138 func() error { return failure },
1015 nil)1139 nil)
1016 c.Check(err, Equals, failure)1140 c.Check(err, Equals, failure)
@@ -1030,6 +1154,7 @@
1030 func() error { return nil },1154 func() error { return nil },
1031 func() error { return nil },1155 func() error { return nil },
1032 func() error { return nil },1156 func() error { return nil },
1157 func() error { return nil },
1033 func() error { sess.MsgCh <- notf; return <-failureCh })1158 func() error { sess.MsgCh <- notf; return <-failureCh })
1034 c.Check(err, Equals, nil)1159 c.Check(err, Equals, nil)
1035 // if run doesn't error it sets up the channels1160 // if run doesn't error it sets up the channels
@@ -1087,9 +1212,64 @@
10871212
1088var (1213var (
1089 dialTestTimeout = 100 * time.Millisecond1214 dialTestTimeout = 100 * time.Millisecond
1090 dialTestConf = ClientSessionConfig{ExchangeTimeout: dialTestTimeout}1215 dialTestConf = ClientSessionConfig{
1216 ExchangeTimeout: dialTestTimeout,
1217 PEM: helpers.TestCertPEMBlock,
1218 }
1091)1219)
10921220
1221func (cs *clientSessionSuite) TestDialBadServerName(c *C) {
1222 // a borked server name
1223 cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock)
1224 c.Assert(err, IsNil)
1225 tlsCfg := &tls.Config{
1226 Certificates: []tls.Certificate{cert},
1227 SessionTicketsDisabled: true,
1228 }
1229
1230 lst, err := tls.Listen("tcp", "localhost:0", tlsCfg)
1231 c.Assert(err, IsNil)
1232 // advertise
1233 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1234 b, err := json.Marshal(map[string]interface{}{
1235 "domain": "xyzzy", // <-- *** THIS *** is the bit that'll break it
1236 "hosts": []string{"nowhere", lst.Addr().String()},
1237 })
1238 if err != nil {
1239 panic(err)
1240 }
1241 w.Header().Set("Content-Type", "application/json")
1242 w.Write(b)
1243 }))
1244 defer ts.Close()
1245
1246 sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log)
1247 c.Assert(err, IsNil)
1248 tconn := &testConn{}
1249 sess.Connection = tconn
1250
1251 upCh := make(chan interface{}, 5)
1252 downCh := make(chan interface{}, 5)
1253 errCh := make(chan error, 1)
1254 proto := &testProtocol{up: upCh, down: downCh}
1255 sess.Protocolator = func(net.Conn) protocol.Protocol { return proto }
1256
1257 go func() {
1258 errCh <- sess.Dial()
1259 }()
1260
1261 srv, err := lst.Accept()
1262 c.Assert(err, IsNil)
1263
1264 // connect done
1265
1266 _, err = protocol.ReadWireFormatVersion(srv, dialTestTimeout)
1267 c.Check(err, NotNil)
1268
1269 c.Check(<-errCh, NotNil)
1270 c.Check(sess.State(), Equals, Error)
1271}
1272
1093func (cs *clientSessionSuite) TestDialWorks(c *C) {1273func (cs *clientSessionSuite) TestDialWorks(c *C) {
1094 // happy path thoughts1274 // happy path thoughts
1095 cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock)1275 cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock)
@@ -1104,7 +1284,8 @@
1104 // advertise1284 // advertise
1105 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {1285 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1106 b, err := json.Marshal(map[string]interface{}{1286 b, err := json.Marshal(map[string]interface{}{
1107 "hosts": []string{"nowhere", lst.Addr().String()},1287 "domain": "localhost",
1288 "hosts": []string{"nowhere", lst.Addr().String()},
1108 })1289 })
1109 if err != nil {1290 if err != nil {
1110 panic(err)1291 panic(err)
@@ -1223,3 +1404,38 @@
1223 c.Assert(err, IsNil)1404 c.Assert(err, IsNil)
1224 // connect done1405 // connect done
1225}1406}
1407
1408/****************************************************************
1409 redialDelay() tests
1410****************************************************************/
1411
1412func (cs *clientSessionSuite) TestShouldDelay(c *C) {
1413 sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
1414 c.Assert(err, IsNil)
1415 c.Check(sess.ShouldDelay(), Equals, false)
1416 sess.setShouldDelay()
1417 c.Check(sess.ShouldDelay(), Equals, true)
1418 sess.clearShouldDelay()
1419 c.Check(sess.ShouldDelay(), Equals, false)
1420}
1421
1422func (cs *clientSessionSuite) TestRedialDelay(c *C) {
1423 sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
1424 c.Assert(err, IsNil)
1425 sess.redialDelays = []time.Duration{17, 42}
1426 n := 0
1427 sess.redialJitter = func(time.Duration) time.Duration { n++; return 0 }
1428 // we get increasing delays while we're unhappy
1429 sess.setShouldDelay()
1430 c.Check(redialDelay(sess), Equals, time.Duration(17))
1431 c.Check(redialDelay(sess), Equals, time.Duration(42))
1432 c.Check(redialDelay(sess), Equals, time.Duration(42))
1433 // once we're happy, delays drop to 0
1434 sess.clearShouldDelay()
1435 c.Check(redialDelay(sess), Equals, time.Duration(0))
1436 // and start again from the top if we become unhappy again
1437 sess.setShouldDelay()
1438 c.Check(redialDelay(sess), Equals, time.Duration(17))
1439 // and redialJitter got called every time shouldDelay was true
1440 c.Check(n, Equals, 4)
1441}
12261442
=== modified file 'config/config.go'
--- config/config.go 2014-03-25 18:49:18 +0000
+++ config/config.go 2014-04-28 14:37:38 +0000
@@ -20,6 +20,7 @@
20import (20import (
21 "encoding/json"21 "encoding/json"
22 "errors"22 "errors"
23 "flag"
23 "fmt"24 "fmt"
24 "io"25 "io"
25 "io/ioutil"26 "io/ioutil"
@@ -27,6 +28,7 @@
27 "os"28 "os"
28 "path/filepath"29 "path/filepath"
29 "reflect"30 "reflect"
31 "strconv"
30 "strings"32 "strings"
31 "time"33 "time"
32)34)
@@ -118,6 +120,22 @@
118 return fillDestConfig(destValue, p1)120 return fillDestConfig(destValue, p1)
119}121}
120122
123// FromString are config holders that can be set by parsing a string.
124type FromString interface {
125 SetFromString(enc string) error
126}
127
128// UnmarshalJSONViaString helps unmarshalling from JSON for FromString
129// supporting config holders.
130func UnmarshalJSONViaString(dest FromString, b []byte) error {
131 var enc string
132 err := json.Unmarshal(b, &enc)
133 if err != nil {
134 return err
135 }
136 return dest.SetFromString(enc)
137}
138
121// ConfigTimeDuration can hold a time.Duration in a configuration struct,139// ConfigTimeDuration can hold a time.Duration in a configuration struct,
122// that is parsed from a string as supported by time.ParseDuration.140// that is parsed from a string as supported by time.ParseDuration.
123type ConfigTimeDuration struct {141type ConfigTimeDuration struct {
@@ -125,13 +143,11 @@
125}143}
126144
127func (ctd *ConfigTimeDuration) UnmarshalJSON(b []byte) error {145func (ctd *ConfigTimeDuration) UnmarshalJSON(b []byte) error {
128 var enc string146 return UnmarshalJSONViaString(ctd, b)
129 var v time.Duration147}
130 err := json.Unmarshal(b, &enc)148
131 if err != nil {149func (ctd *ConfigTimeDuration) SetFromString(enc string) error {
132 return err150 v, err := time.ParseDuration(enc)
133 }
134 v, err = time.ParseDuration(enc)
135 if err != nil {151 if err != nil {
136 return err152 return err
137 }153 }
@@ -148,12 +164,11 @@
148type ConfigHostPort string164type ConfigHostPort string
149165
150func (chp *ConfigHostPort) UnmarshalJSON(b []byte) error {166func (chp *ConfigHostPort) UnmarshalJSON(b []byte) error {
151 var enc string167 return UnmarshalJSONViaString(chp, b)
152 err := json.Unmarshal(b, &enc)168}
153 if err != nil {169
154 return err170func (chp *ConfigHostPort) SetFromString(enc string) error {
155 }171 _, _, err := net.SplitHostPort(enc)
156 _, _, err = net.SplitHostPort(enc)
157 if err != nil {172 if err != nil {
158 return err173 return err
159 }174 }
@@ -198,23 +213,117 @@
198 return ioutil.ReadFile(p)213 return ioutil.ReadFile(p)
199}214}
200215
201// ReadFiles reads configuration from a set of files. Uses ReadConfig internally.216// used to implement getting config values with flag.Parse()
217type val struct {
218 destField destField
219 accu map[string]json.RawMessage
220}
221
222func (v *val) String() string { // used to show default
223 return string(v.accu[v.destField.configName()])
224}
225
226func (v *val) IsBoolFlag() bool {
227 return v.destField.fld.Type.Kind() == reflect.Bool
228}
229
230func (v *val) marshalAsNeeded(s string) (json.RawMessage, error) {
231 var toMarshal interface{}
232 switch v.destField.dest.(type) {
233 case *string, FromString:
234 toMarshal = s
235 case *bool:
236 bit, err := strconv.ParseBool(s)
237 if err != nil {
238 return nil, err
239 }
240 toMarshal = bit
241 default:
242 return json.RawMessage(s), nil
243 }
244 return json.Marshal(toMarshal)
245}
246
247func (v *val) Set(s string) error {
248 marshalled, err := v.marshalAsNeeded(s)
249 if err != nil {
250 return err
251 }
252 v.accu[v.destField.configName()] = marshalled
253 return nil
254}
255
256func readOneConfig(accu map[string]json.RawMessage, cfgPath string) error {
257 r, err := os.Open(cfgPath)
258 if err != nil {
259 return err
260 }
261 defer r.Close()
262 err = json.NewDecoder(r).Decode(&accu)
263 if err != nil {
264 return err
265 }
266 return nil
267}
268
269// used to implement -cfg@=
270type readConfigAtVal struct {
271 accu map[string]json.RawMessage
272}
273
274func (v *readConfigAtVal) String() string {
275 return "<config.json>"
276}
277
278func (v *readConfigAtVal) Set(path string) error {
279 return readOneConfig(v.accu, path)
280}
281
282// readUsingFlags gets config values from command line flags.
283func readUsingFlags(accu map[string]json.RawMessage, destValue reflect.Value) error {
284 if flag.Parsed() {
285 if IgnoreParsedFlags {
286 return nil
287 }
288 return fmt.Errorf("too late, flags already parsed")
289 }
290 destStruct := destValue.Elem()
291 for destField := range traverseStruct(destStruct) {
292 help := destField.fld.Tag.Get("help")
293 flag.Var(&val{destField, accu}, destField.configName(), help)
294 }
295 flag.Var(&readConfigAtVal{accu}, "cfg@", "get config values from file")
296 flag.Parse()
297 return nil
298}
299
300// IgnoreParsedFlags will just have ReadFiles ignore <flags> if the
301// command line was already parsed.
302var IgnoreParsedFlags = false
303
304// ReadFiles reads configuration from a set of files. The string
305// "<flags>" can be used as a pseudo file-path, it will consider
306// command line flags, invoking flag.Parse(). Among those the flag
307// -cfg@=FILE can be used to get further config values from FILE.
202func ReadFiles(destConfig interface{}, cfgFpaths ...string) error {308func ReadFiles(destConfig interface{}, cfgFpaths ...string) error {
203 destValue, err := checkDestConfig("destConfig", destConfig)309 destValue, err := checkDestConfig("destConfig", destConfig)
204 if err != nil {310 if err != nil {
205 return err311 return err
206 }312 }
207 // do the parsing in two phases for better error handling313 // do the parsing in two phases for better error handling
208 var p1 map[string]json.RawMessage314 p1 := make(map[string]json.RawMessage)
209 readOne := false315 readOne := false
210 for _, cfgPath := range cfgFpaths {316 for _, cfgPath := range cfgFpaths {
317 if cfgPath == "<flags>" {
318 err := readUsingFlags(p1, destValue)
319 if err != nil {
320 return err
321 }
322 readOne = true
323 continue
324 }
211 if _, err := os.Stat(cfgPath); err == nil {325 if _, err := os.Stat(cfgPath); err == nil {
212 r, err := os.Open(cfgPath)326 err := readOneConfig(p1, cfgPath)
213 if err != nil {
214 return err
215 }
216 defer r.Close()
217 err = json.NewDecoder(r).Decode(&p1)
218 if err != nil {327 if err != nil {
219 return err328 return err
220 }329 }
221330
=== modified file 'config/config_test.go'
--- config/config_test.go 2014-03-25 18:49:18 +0000
+++ config/config_test.go 2014-04-28 14:37:38 +0000
@@ -18,6 +18,9 @@
1818
19import (19import (
20 "bytes"20 "bytes"
21 "encoding/json"
22 "flag"
23 "fmt"
21 "io/ioutil"24 "io/ioutil"
22 "os"25 "os"
23 "path/filepath"26 "path/filepath"
@@ -230,3 +233,105 @@
230 c.Check(res, DeepEquals, []string{"b", "c_list", "d"})233 c.Check(res, DeepEquals, []string{"b", "c_list", "d"})
231234
232}235}
236
237type testConfig3 struct {
238 A bool
239 B string
240 C []string `json:"c_list"`
241 D ConfigTimeDuration `help:"duration"`
242 E ConfigHostPort
243 F string
244}
245
246type configFlagsSuite struct{}
247
248var _ = Suite(&configFlagsSuite{})
249
250func (s *configFlagsSuite) SetUpTest(c *C) {
251 flag.CommandLine = flag.NewFlagSet("cmd", flag.PanicOnError)
252 // supress outputs
253 flag.Usage = func() { flag.PrintDefaults() }
254 flag.CommandLine.SetOutput(ioutil.Discard)
255}
256
257func (s *configFlagsSuite) TestReadUsingFlags(c *C) {
258 os.Args = []string{"cmd", "-a=1", "-b=foo", "-c_list", `["x","y"]`, "-d", "10s", "-e=localhost:80"}
259 var cfg testConfig3
260 p := make(map[string]json.RawMessage)
261 err := readUsingFlags(p, reflect.ValueOf(&cfg))
262 c.Assert(err, IsNil)
263 c.Check(p, DeepEquals, map[string]json.RawMessage{
264 "a": json.RawMessage("true"),
265 "b": json.RawMessage(`"foo"`),
266 "c_list": json.RawMessage(`["x","y"]`),
267 "d": json.RawMessage(`"10s"`),
268 "e": json.RawMessage(`"localhost:80"`),
269 })
270}
271
272func (s *configFlagsSuite) TestReadUsingFlagsBoolError(c *C) {
273 os.Args = []string{"cmd", "-a=zoo"}
274 var cfg testConfig3
275 p := make(map[string]json.RawMessage)
276 c.Check(func() { readUsingFlags(p, reflect.ValueOf(&cfg)) }, PanicMatches, ".*invalid boolean.*-a.*")
277}
278
279func (s *configFlagsSuite) TestReadFilesAndFlags(c *C) {
280 // test <flags> pseudo file
281 os.Args = []string{"cmd", "-b=x"}
282 tmpDir := c.MkDir()
283 cfgPath := filepath.Join(tmpDir, "cfg.json")
284 err := ioutil.WriteFile(cfgPath, []byte(`{"a": 42, "c_list": ["y", "z"]}`), os.ModePerm)
285 c.Assert(err, IsNil)
286 var cfg testConfig1
287 err = ReadFiles(&cfg, cfgPath, "<flags>")
288 c.Assert(err, IsNil)
289 c.Check(cfg.A, Equals, 42)
290 c.Check(cfg.B, Equals, "x")
291 c.Check(cfg.C, DeepEquals, []string{"y", "z"})
292}
293
294func (s *configFlagsSuite) TestReadFilesAndFlagsConfigAtSupport(c *C) {
295 // test <flags> pseudo file
296 tmpDir := c.MkDir()
297 cfgPath := filepath.Join(tmpDir, "cfg.json")
298 os.Args = []string{"cmd", "-a=42", fmt.Sprintf("-cfg@=%s", cfgPath)}
299 err := ioutil.WriteFile(cfgPath, []byte(`{"b": "x", "c_list": ["y", "z"]}`), os.ModePerm)
300 c.Assert(err, IsNil)
301 var cfg testConfig1
302 err = ReadFiles(&cfg, "<flags>")
303 c.Assert(err, IsNil)
304 c.Check(cfg.A, Equals, 42)
305 c.Check(cfg.B, Equals, "x")
306 c.Check(cfg.C, DeepEquals, []string{"y", "z"})
307}
308
309func (s *configFlagsSuite) TestReadUsingFlagsHelp(c *C) {
310 os.Args = []string{"cmd", "-h"}
311 buf := bytes.NewBufferString("")
312 flag.CommandLine.Init("cmd", flag.ContinueOnError)
313 flag.CommandLine.SetOutput(buf)
314 var cfg testConfig3
315 p := map[string]json.RawMessage{
316 "d": json.RawMessage(`"2s"`),
317 }
318 readUsingFlags(p, reflect.ValueOf(&cfg))
319 c.Check(buf.String(), Matches, `(?s).*-cfg@=<config.json>: get config values from file\n.*-d="2s": duration.*`)
320}
321
322func (s *configFlagsSuite) TestReadUsingFlagsAlreadyParsed(c *C) {
323 os.Args = []string{"cmd"}
324 flag.Parse()
325 var cfg struct{}
326 p := make(map[string]json.RawMessage)
327 err := readUsingFlags(p, reflect.ValueOf(&cfg))
328 c.Assert(err, ErrorMatches, "too late, flags already parsed")
329 err = ReadFiles(&cfg, "<flags>")
330 c.Assert(err, ErrorMatches, "too late, flags already parsed")
331 IgnoreParsedFlags = true
332 defer func() {
333 IgnoreParsedFlags = false
334 }()
335 err = ReadFiles(&cfg, "<flags>")
336 c.Assert(err, IsNil)
337}
233338
=== modified file 'debian/changelog'
--- debian/changelog 2014-04-11 18:31:57 +0000
+++ debian/changelog 2014-04-28 14:37:38 +0000
@@ -1,3 +1,9 @@
1ubuntu-push (0.21-0.ubuntu1) UNRELEASED; urgency=medium
2
3 * New upstream release: first auth bits, and Qt dependency.
4
5 -- John Lenton <john.lenton@canonical.com> Tue, 15 Apr 2014 14:04:35 +0100
6
1ubuntu-push (0.2+14.04.20140411-0ubuntu1) trusty; urgency=medium7ubuntu-push (0.2+14.04.20140411-0ubuntu1) trusty; urgency=medium
28
3 [ John Lenton ]9 [ John Lenton ]
410
=== modified file 'debian/control'
--- debian/control 2014-03-25 16:26:20 +0000
+++ debian/control 2014-04-28 14:37:38 +0000
@@ -14,6 +14,10 @@
14 libgcrypt11-dev,14 libgcrypt11-dev,
15 libglib2.0-dev (>= 2.31.6),15 libglib2.0-dev (>= 2.31.6),
16 libwhoopsie-dev,16 libwhoopsie-dev,
17 qtbase5-private-dev,
18 qtdeclarative5-dev,
19 libqt5opengl5-dev,
20 libubuntuoneauth-2.0-dev,
17Standards-Version: 3.9.521Standards-Version: 3.9.5
18Homepage: http://launchpad.net/ubuntu-push22Homepage: http://launchpad.net/ubuntu-push
19Vcs-Bzr: lp:ubuntu-push23Vcs-Bzr: lp:ubuntu-push
2024
=== modified file 'debian/rules'
--- debian/rules 2014-03-24 12:22:55 +0000
+++ debian/rules 2014-04-28 14:37:38 +0000
@@ -2,9 +2,12 @@
2# -*- makefile -*-2# -*- makefile -*-
33
4export DH_GOPKG := launchpad.net/ubuntu-push4export DH_GOPKG := launchpad.net/ubuntu-push
5export DEB_BUILD_OPTIONS := nostrip
6export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR)5export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR)
76
7override_dh_auto_build:
8 cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1) && tar xvzf ../../externals.tgz
9 dh_auto_build --buildsystem=golang
10
8override_dh_install:11override_dh_install:
9 dh_install -Xusr/bin/cmd -Xusr/bin/dev --fail-missing12 dh_install -Xusr/bin/cmd -Xusr/bin/dev --fail-missing
1013
1114
=== modified file 'dependencies.tsv'
--- dependencies.tsv 2014-03-12 13:23:26 +0000
+++ dependencies.tsv 2014-04-28 14:37:38 +0000
@@ -2,3 +2,5 @@
2launchpad.net/go-dbus/v1 bzr james@jamesh.id.au-20140206110213-pbzcr6ucaz3rqmnw 1252launchpad.net/go-dbus/v1 bzr james@jamesh.id.au-20140206110213-pbzcr6ucaz3rqmnw 125
3launchpad.net/go-xdg/v0 bzr john.lenton@canonical.com-20140208094800-gubd5md7cro3mtxa 103launchpad.net/go-xdg/v0 bzr john.lenton@canonical.com-20140208094800-gubd5md7cro3mtxa 10
4launchpad.net/gocheck bzr gustavo@niemeyer.net-20140127131816-zshobk1qqme626xw 864launchpad.net/gocheck bzr gustavo@niemeyer.net-20140127131816-zshobk1qqme626xw 86
5gopkg.in/qml.v0 git master 8adbc8c2bf2da9f609df366683ad0f47a89c3d49
6gopkg.in/niemeyer/uoneauth.v1 git v1 0758ba882a143ad2862dbcac85a7ca145750b640
57
=== added file 'externals.tgz'
6Binary files externals.tgz 1970-01-01 00:00:00 +0000 and externals.tgz 2014-04-28 14:37:38 +0000 differ8Binary files externals.tgz 1970-01-01 00:00:00 +0000 and externals.tgz 2014-04-28 14:37:38 +0000 differ
=== modified file 'logger/logger.go'
--- logger/logger.go 2014-02-24 10:27:38 +0000
+++ logger/logger.go 2014-04-28 14:37:38 +0000
@@ -23,6 +23,8 @@
23 "log"23 "log"
24 "os"24 "os"
25 "runtime"25 "runtime"
26
27 "launchpad.net/ubuntu-push/config"
26)28)
2729
28// Logger is a simple logger interface with logging at levels.30// Logger is a simple logger interface with logging at levels.
@@ -119,3 +121,28 @@
119 lg.outputFunc(2, fmt.Sprintf("DEBUG "+format, v...))121 lg.outputFunc(2, fmt.Sprintf("DEBUG "+format, v...))
120 }122 }
121}123}
124
125// config bits
126
127// ConfigLogLevel can hold a log level in a configuration struct.
128type ConfigLogLevel string
129
130func (cll *ConfigLogLevel) ConfigFromJSONString() {}
131
132func (cll *ConfigLogLevel) UnmarshalJSON(b []byte) error {
133 return config.UnmarshalJSONViaString(cll, b)
134}
135
136func (cll *ConfigLogLevel) SetFromString(enc string) error {
137 _, ok := levelToNLevel[enc]
138 if !ok {
139 return fmt.Errorf("not a log level: %s", enc)
140 }
141 *cll = ConfigLogLevel(enc)
142 return nil
143}
144
145// Level returns the log level string held in cll.
146func (cll ConfigLogLevel) Level() string {
147 return string(cll)
148}
122149
=== modified file 'logger/logger_test.go'
--- logger/logger_test.go 2014-02-10 22:51:43 +0000
+++ logger/logger_test.go 2014-04-28 14:37:38 +0000
@@ -25,6 +25,8 @@
25 "testing"25 "testing"
2626
27 . "launchpad.net/gocheck"27 . "launchpad.net/gocheck"
28
29 "launchpad.net/ubuntu-push/config"
28)30)
2931
30func TestLogger(t *testing.T) { TestingT(t) }32func TestLogger(t *testing.T) { TestingT(t) }
@@ -138,3 +140,26 @@
138 logger.Output(1, "foobaz")140 logger.Output(1, "foobaz")
139 c.Check(buf.String(), Matches, "logger_test.go:[0-9]+: foobar\nlogger_test.go:[0-9]+: foobaz\n")141 c.Check(buf.String(), Matches, "logger_test.go:[0-9]+: foobar\nlogger_test.go:[0-9]+: foobaz\n")
140}142}
143
144type testLogLevelConfig struct {
145 Lvl ConfigLogLevel
146}
147
148func (s *loggerSuite) TestReadConfigLogLevel(c *C) {
149 buf := bytes.NewBufferString(`{"lvl": "debug"}`)
150 var cfg testLogLevelConfig
151 err := config.ReadConfig(buf, &cfg)
152 c.Assert(err, IsNil)
153 c.Check(cfg.Lvl.Level(), Equals, "debug")
154}
155
156func (s *loggerSuite) TestReadConfigLogLevelErrors(c *C) {
157 var cfg testLogLevelConfig
158 checkError := func(jsonCfg string, expectedError string) {
159 buf := bytes.NewBufferString(jsonCfg)
160 err := config.ReadConfig(buf, &cfg)
161 c.Check(err, ErrorMatches, expectedError)
162 }
163 checkError(`{"lvl": 1}`, "lvl:.*type string")
164 checkError(`{"lvl": "foo"}`, "lvl: not a log level: foo")
165}
141166
=== modified file 'protocol/messages.go'
--- protocol/messages.go 2014-04-04 13:54:45 +0000
+++ protocol/messages.go 2014-04-28 14:37:38 +0000
@@ -54,6 +54,14 @@
54 Split() (done bool)54 Split() (done bool)
55}55}
5656
57// OnewayMsg are messages that are not to be followed by a response,
58// after sending them the session either aborts or continues.
59type OnewayMsg interface {
60 SplittableMsg
61 // continue session after the message?
62 OnewayContinue() bool
63}
64
57// CONNBROKEN message, server side is breaking the connection for reason.65// CONNBROKEN message, server side is breaking the connection for reason.
58type ConnBrokenMsg struct {66type ConnBrokenMsg struct {
59 Type string `json:"T"`67 Type string `json:"T"`
@@ -65,11 +73,35 @@
65 return true73 return true
66}74}
6775
76func (m *ConnBrokenMsg) OnewayContinue() bool {
77 return false
78}
79
68// CONNBROKEN reasons80// CONNBROKEN reasons
69const (81const (
70 BrokenHostMismatch = "host-mismatch"82 BrokenHostMismatch = "host-mismatch"
71)83)
7284
85// CONNWARN message, server side is warning about partial functionality
86// because reason.
87type ConnWarnMsg struct {
88 Type string `json:"T"`
89 // reason
90 Reason string
91}
92
93func (m *ConnWarnMsg) Split() bool {
94 return true
95}
96func (m *ConnWarnMsg) OnewayContinue() bool {
97 return true
98}
99
100// CONNWARN reasons
101const (
102 WarnUnauthorized = "unauthorized"
103)
104
73// PING/PONG messages105// PING/PONG messages
74type PingPongMsg struct {106type PingPongMsg struct {
75 Type string `json:"T"`107 Type string `json:"T"`
76108
=== modified file 'protocol/messages_test.go'
--- protocol/messages_test.go 2014-04-04 13:19:10 +0000
+++ protocol/messages_test.go 2014-04-28 14:37:38 +0000
@@ -104,6 +104,14 @@
104 c.Check(b.splitting, Equals, 0)104 c.Check(b.splitting, Equals, 0)
105}105}
106106
107func (s *messagesSuite) TestSplitConnBrokenMsg(c *C) {107func (s *messagesSuite) TestConnBrokenMsg(c *C) {
108 c.Check((&ConnBrokenMsg{}).Split(), Equals, true)108 m := &ConnBrokenMsg{}
109 c.Check(m.Split(), Equals, true)
110 c.Check(m.OnewayContinue(), Equals, false)
111}
112
113func (s *messagesSuite) TestConnWarnMsg(c *C) {
114 m := &ConnWarnMsg{}
115 c.Check(m.Split(), Equals, true)
116 c.Check(m.OnewayContinue(), Equals, true)
109}117}
110118
=== modified file 'protocol/state-diag-client.gv'
--- protocol/state-diag-client.gv 2014-01-16 20:07:13 +0000
+++ protocol/state-diag-client.gv 2014-04-28 14:37:38 +0000
@@ -2,7 +2,7 @@
2 label = "State diagram for client";2 label = "State diagram for client";
3 size="12,6";3 size="12,6";
4 rankdir=LR;4 rankdir=LR;
5 node [shape = doublecircle]; pingTimeout;5 node [shape = doublecircle]; pingTimeout; connBroken;
6 node [shape = circle];6 node [shape = circle];
7 start1 -> start2 [ label = "Write wire version" ];7 start1 -> start2 [ label = "Write wire version" ];
8 start2 -> start3 [ label = "Write CONNECT" ];8 start2 -> start3 [ label = "Write CONNECT" ];
@@ -13,4 +13,7 @@
13 broadcast -> loop [label = "Write ACK"];13 broadcast -> loop [label = "Write ACK"];
14 loop -> pingTimeout [14 loop -> pingTimeout [
15 label = "Elapsed ping interval + exchange interval"];15 label = "Elapsed ping interval + exchange interval"];
16 loop -> connBroken [label = "Read CONNBROKEN"];
17 loop -> warn [label = "Read CONNWARN"];
18 warn -> loop;
16}19}
1720
=== modified file 'protocol/state-diag-client.svg'
--- protocol/state-diag-client.svg 2014-01-16 19:37:57 +0000
+++ protocol/state-diag-client.svg 2014-04-28 14:37:38 +0000
@@ -4,95 +4,123 @@
4<!-- Generated by graphviz version 2.26.3 (20100126.1600)4<!-- Generated by graphviz version 2.26.3 (20100126.1600)
5 -->5 -->
6<!-- Title: state_diagram_client Pages: 1 -->6<!-- Title: state_diagram_client Pages: 1 -->
7<svg width="864pt" height="279pt"7<svg width="822pt" height="432pt"
8 viewBox="0.00 0.00 864.00 278.89" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">8 viewBox="0.00 0.00 822.36 432.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
9<g id="graph1" class="graph" transform="scale(0.683544 0.683544) rotate(0) translate(4 404)">9<g id="graph1" class="graph" transform="scale(0.650602 0.650602) rotate(0) translate(4 660)">
10<title>state_diagram_client</title>10<title>state_diagram_client</title>
11<polygon fill="white" stroke="white" points="-4,5 -4,-404 1261,-404 1261,5 -4,5"/>11<polygon fill="white" stroke="white" points="-4,5 -4,-660 1261,-660 1261,5 -4,5"/>
12<text text-anchor="middle" x="628" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for client</text>12<text text-anchor="middle" x="628" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for client</text>
13<!-- pingTimeout -->13<!-- pingTimeout -->
14<g id="node1" class="node"><title>pingTimeout</title>14<g id="node1" class="node"><title>pingTimeout</title>
15<ellipse fill="none" stroke="black" cx="1180" cy="-324" rx="72.1249" ry="72.1249"/>15<ellipse fill="none" stroke="black" cx="1180" cy="-580" rx="72.1249" ry="72.1249"/>
16<ellipse fill="none" stroke="black" cx="1180" cy="-324" rx="76.1249" ry="76.1249"/>16<ellipse fill="none" stroke="black" cx="1180" cy="-580" rx="76.1249" ry="76.1249"/>
17<text text-anchor="middle" x="1180" y="-320.4" font-family="Times Roman,serif" font-size="14.00">pingTimeout</text>17<text text-anchor="middle" x="1180" y="-576.4" font-family="Times Roman,serif" font-size="14.00">pingTimeout</text>
18</g>
19<!-- connBroken -->
20<g id="node2" class="node"><title>connBroken</title>
21<ellipse fill="none" stroke="black" cx="1180" cy="-413" rx="68.8251" ry="69.2965"/>
22<ellipse fill="none" stroke="black" cx="1180" cy="-413" rx="72.7978" ry="73.2965"/>
23<text text-anchor="middle" x="1180" y="-409.4" font-family="Times Roman,serif" font-size="14.00">connBroken</text>
18</g>24</g>
19<!-- start1 -->25<!-- start1 -->
20<g id="node2" class="node"><title>start1</title>26<g id="node3" class="node"><title>start1</title>
21<ellipse fill="none" stroke="black" cx="42" cy="-166" rx="41.2167" ry="41.7193"/>27<ellipse fill="none" stroke="black" cx="42" cy="-231" rx="41.2167" ry="41.7193"/>
22<text text-anchor="middle" x="42" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start1</text>28<text text-anchor="middle" x="42" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start1</text>
23</g>29</g>
24<!-- start2 -->30<!-- start2 -->
25<g id="node4" class="node"><title>start2</title>31<g id="node5" class="node"><title>start2</title>
26<ellipse fill="none" stroke="black" cx="292" cy="-166" rx="41.2167" ry="41.7193"/>32<ellipse fill="none" stroke="black" cx="292" cy="-231" rx="41.2167" ry="41.7193"/>
27<text text-anchor="middle" x="292" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start2</text>33<text text-anchor="middle" x="292" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start2</text>
28</g>34</g>
29<!-- start1&#45;&gt;start2 -->35<!-- start1&#45;&gt;start2 -->
30<g id="edge2" class="edge"><title>start1&#45;&gt;start2</title>36<g id="edge2" class="edge"><title>start1&#45;&gt;start2</title>
31<path fill="none" stroke="black" d="M83.5631,-166C126.547,-166 193.757,-166 240.181,-166"/>37<path fill="none" stroke="black" d="M83.5631,-231C126.547,-231 193.757,-231 240.181,-231"/>
32<polygon fill="black" stroke="black" points="240.338,-169.5 250.338,-166 240.338,-162.5 240.338,-169.5"/>38<polygon fill="black" stroke="black" points="240.338,-234.5 250.338,-231 240.338,-227.5 240.338,-234.5"/>
33<text text-anchor="middle" x="167" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Write wire version</text>39<text text-anchor="middle" x="167" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Write wire version</text>
34</g>40</g>
35<!-- start3 -->41<!-- start3 -->
36<g id="node6" class="node"><title>start3</title>42<g id="node7" class="node"><title>start3</title>
37<ellipse fill="none" stroke="black" cx="526" cy="-166" rx="41.2167" ry="41.7193"/>43<ellipse fill="none" stroke="black" cx="526" cy="-231" rx="41.2167" ry="41.7193"/>
38<text text-anchor="middle" x="526" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start3</text>44<text text-anchor="middle" x="526" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start3</text>
39</g>45</g>
40<!-- start2&#45;&gt;start3 -->46<!-- start2&#45;&gt;start3 -->
41<g id="edge4" class="edge"><title>start2&#45;&gt;start3</title>47<g id="edge4" class="edge"><title>start2&#45;&gt;start3</title>
42<path fill="none" stroke="black" d="M333.565,-166C372.875,-166 431.992,-166 474.321,-166"/>48<path fill="none" stroke="black" d="M333.565,-231C372.875,-231 431.992,-231 474.321,-231"/>
43<polygon fill="black" stroke="black" points="474.429,-169.5 484.429,-166 474.429,-162.5 474.429,-169.5"/>49<polygon fill="black" stroke="black" points="474.429,-234.5 484.429,-231 474.429,-227.5 474.429,-234.5"/>
44<text text-anchor="middle" x="409" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Write CONNECT</text>50<text text-anchor="middle" x="409" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Write CONNECT</text>
45</g>51</g>
46<!-- loop -->52<!-- loop -->
47<g id="node8" class="node"><title>loop</title>53<g id="node9" class="node"><title>loop</title>
48<ellipse fill="none" stroke="black" cx="746" cy="-166" rx="31.8198" ry="31.8198"/>54<ellipse fill="none" stroke="black" cx="746" cy="-231" rx="31.8198" ry="31.8198"/>
49<text text-anchor="middle" x="746" y="-162.4" font-family="Times Roman,serif" font-size="14.00">loop</text>55<text text-anchor="middle" x="746" y="-227.4" font-family="Times Roman,serif" font-size="14.00">loop</text>
50</g>56</g>
51<!-- start3&#45;&gt;loop -->57<!-- start3&#45;&gt;loop -->
52<g id="edge6" class="edge"><title>start3&#45;&gt;loop</title>58<g id="edge6" class="edge"><title>start3&#45;&gt;loop</title>
53<path fill="none" stroke="black" d="M567.639,-166C606.633,-166 664.616,-166 703.793,-166"/>59<path fill="none" stroke="black" d="M567.639,-231C606.633,-231 664.616,-231 703.793,-231"/>
54<polygon fill="black" stroke="black" points="703.818,-169.5 713.818,-166 703.818,-162.5 703.818,-169.5"/>60<polygon fill="black" stroke="black" points="703.818,-234.5 713.818,-231 703.818,-227.5 703.818,-234.5"/>
55<text text-anchor="middle" x="641" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Read CONNACK</text>61<text text-anchor="middle" x="641" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Read CONNACK</text>
56</g>62</g>
57<!-- loop&#45;&gt;pingTimeout -->63<!-- loop&#45;&gt;pingTimeout -->
58<g id="edge16" class="edge"><title>loop&#45;&gt;pingTimeout</title>64<g id="edge16" class="edge"><title>loop&#45;&gt;pingTimeout</title>
59<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"/>65<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"/>
60<polygon fill="black" stroke="black" points="1093.67,-319.928 1104.02,-317.68 1094.53,-312.981 1093.67,-319.928"/>66<polygon fill="black" stroke="black" points="1093.96,-573.992 1104.39,-572.09 1095.05,-567.078 1093.96,-573.992"/>
61<text text-anchor="middle" x="941" y="-319.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval + exchange interval</text>67<text text-anchor="middle" x="941" y="-572.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval + exchange interval</text>
68</g>
69<!-- loop&#45;&gt;connBroken -->
70<g id="edge18" class="edge"><title>loop&#45;&gt;connBroken</title>
71<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"/>
72<polygon fill="black" stroke="black" points="1096.19,-416.445 1106.33,-413.388 1096.5,-409.452 1096.19,-416.445"/>
73<text text-anchor="middle" x="941" y="-417.4" font-family="Times Roman,serif" font-size="14.00">Read CONNBROKEN</text>
62</g>74</g>
63<!-- pong -->75<!-- pong -->
64<g id="node10" class="node"><title>pong</title>76<g id="node11" class="node"><title>pong</title>
65<ellipse fill="none" stroke="black" cx="1180" cy="-195" rx="34.8574" ry="35.3553"/>77<ellipse fill="none" stroke="black" cx="1180" cy="-287" rx="34.8574" ry="35.3553"/>
66<text text-anchor="middle" x="1180" y="-191.4" font-family="Times Roman,serif" font-size="14.00">pong</text>78<text text-anchor="middle" x="1180" y="-283.4" font-family="Times Roman,serif" font-size="14.00">pong</text>
67</g>79</g>
68<!-- loop&#45;&gt;pong -->80<!-- loop&#45;&gt;pong -->
69<g id="edge8" class="edge"><title>loop&#45;&gt;pong</title>81<g id="edge8" class="edge"><title>loop&#45;&gt;pong</title>
70<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"/>82<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"/>
71<polygon fill="black" stroke="black" points="1134.89,-202.186 1144.62,-198.003 1134.4,-195.203 1134.89,-202.186"/>83<polygon fill="black" stroke="black" points="1135.49,-297.371 1144.94,-292.588 1134.57,-290.432 1135.49,-297.371"/>
72<text text-anchor="middle" x="941" y="-207.4" font-family="Times Roman,serif" font-size="14.00">Read PING</text>84<text text-anchor="middle" x="941" y="-307.4" font-family="Times Roman,serif" font-size="14.00">Read PING</text>
73</g>85</g>
74<!-- broadcast -->86<!-- broadcast -->
75<g id="node12" class="node"><title>broadcast</title>87<g id="node13" class="node"><title>broadcast</title>
76<ellipse fill="none" stroke="black" cx="1180" cy="-84" rx="58.1882" ry="58.6899"/>88<ellipse fill="none" stroke="black" cx="1180" cy="-176" rx="58.1882" ry="58.6899"/>
77<text text-anchor="middle" x="1180" y="-80.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>89<text text-anchor="middle" x="1180" y="-172.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>
78</g>90</g>
79<!-- loop&#45;&gt;broadcast -->91<!-- loop&#45;&gt;broadcast -->
80<g id="edge10" class="edge"><title>loop&#45;&gt;broadcast</title>92<g id="edge10" class="edge"><title>loop&#45;&gt;broadcast</title>
81<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"/>93<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"/>
82<polygon fill="black" stroke="black" points="1113.34,-101.917 1122.5,-96.5998 1112.02,-95.0419 1113.34,-101.917"/>94<polygon fill="black" stroke="black" points="1112.44,-189.353 1121.9,-184.574 1111.53,-182.413 1112.44,-189.353"/>
83<text text-anchor="middle" x="941" y="-136.4" font-family="Times Roman,serif" font-size="14.00">Read BROADCAST</text>95<text text-anchor="middle" x="941" y="-217.4" font-family="Times Roman,serif" font-size="14.00">Read BROADCAST</text>
96</g>
97<!-- warn -->
98<g id="node19" class="node"><title>warn</title>
99<ellipse fill="none" stroke="black" cx="1180" cy="-63" rx="36.7696" ry="36.7696"/>
100<text text-anchor="middle" x="1180" y="-59.4" font-family="Times Roman,serif" font-size="14.00">warn</text>
101</g>
102<!-- loop&#45;&gt;warn -->
103<g id="edge20" class="edge"><title>loop&#45;&gt;warn</title>
104<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"/>
105<polygon fill="black" stroke="black" points="1135.26,-79.0068 1144.04,-73.0757 1133.48,-72.2376 1135.26,-79.0068"/>
106<text text-anchor="middle" x="941" y="-136.4" font-family="Times Roman,serif" font-size="14.00">Read CONNWARN</text>
84</g>107</g>
85<!-- pong&#45;&gt;loop -->108<!-- pong&#45;&gt;loop -->
86<g id="edge12" class="edge"><title>pong&#45;&gt;loop</title>109<g id="edge12" class="edge"><title>pong&#45;&gt;loop</title>
87<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"/>110<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"/>
88<polygon fill="black" stroke="black" points="787.736,-157.528 778.16,-162.06 788.472,-164.489 787.736,-157.528"/>111<polygon fill="black" stroke="black" points="788.085,-227.996 778.035,-231.348 787.982,-234.995 788.085,-227.996"/>
89<text text-anchor="middle" x="941" y="-168.4" font-family="Times Roman,serif" font-size="14.00">Write PONG</text>112<text text-anchor="middle" x="941" y="-254.4" font-family="Times Roman,serif" font-size="14.00">Write PONG</text>
90</g>113</g>
91<!-- broadcast&#45;&gt;loop -->114<!-- broadcast&#45;&gt;loop -->
92<g id="edge14" class="edge"><title>broadcast&#45;&gt;loop</title>115<g id="edge14" class="edge"><title>broadcast&#45;&gt;loop</title>
93<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"/>116<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"/>
94<polygon fill="black" stroke="black" points="756.044,-124.528 755.336,-135.099 762.482,-127.277 756.044,-124.528"/>117<polygon fill="black" stroke="black" points="763.182,-192.043 760.465,-202.284 768.975,-195.973 763.182,-192.043"/>
95<text text-anchor="middle" x="941" y="-86.4" font-family="Times Roman,serif" font-size="14.00">Write ACK</text>118<text text-anchor="middle" x="941" y="-172.4" font-family="Times Roman,serif" font-size="14.00">Write ACK</text>
119</g>
120<!-- warn&#45;&gt;loop -->
121<g id="edge22" class="edge"><title>warn&#45;&gt;loop</title>
122<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"/>
123<polygon fill="black" stroke="black" points="751.574,-188.904 752.686,-199.44 758.387,-190.51 751.574,-188.904"/>
96</g>124</g>
97</g>125</g>
98</svg>126</svg>
99127
=== modified file 'protocol/state-diag-session.gv'
--- protocol/state-diag-session.gv 2014-01-16 20:07:13 +0000
+++ protocol/state-diag-session.gv 2014-04-28 14:37:38 +0000
@@ -2,6 +2,7 @@
2 label = "State diagram for session";2 label = "State diagram for session";
3 size="12,6";3 size="12,6";
4 rankdir=LR;4 rankdir=LR;
5 node [shape = doublecircle]; stop;
5 node [shape = circle];6 node [shape = circle];
6 start1 -> start2 [ label = "Read wire version" ];7 start1 -> start2 [ label = "Read wire version" ];
7 start2 -> start3 [ label = "Read CONNECT" ];8 start2 -> start3 [ label = "Read CONNECT" ];
@@ -17,4 +18,13 @@
17 split_broadcast -> split_ack_wait [label = "Write split BROADCAST"];18 split_broadcast -> split_ack_wait [label = "Write split BROADCAST"];
18 split_ack_wait -> split_broadcast [label = "Read ACK"];19 split_ack_wait -> split_broadcast [label = "Read ACK"];
19 split_broadcast -> loop [label = "All split msgs written"];20 split_broadcast -> loop [label = "All split msgs written"];
21 // other
22 loop -> conn_broken [label = "Receive connbroken request"];
23 loop -> conn_warn [label = "Receive connwarn request"];
24 conn_broken -> stop [label = "Write CONNBROKEN"];
25 conn_warn -> loop [label = "Write CONNWARN"];
26 // timeouts
27 ack_wait -> stop [label = "Elapsed exhange timeout"];
28 split_ack_wait -> stop [label = "Elapsed exhange timeout"];
29 pong_wait -> stop [label = "Elapsed exhange timeout"];
20}30}
2131
=== modified file 'protocol/state-diag-session.svg'
--- protocol/state-diag-session.svg 2014-01-16 19:37:57 +0000
+++ protocol/state-diag-session.svg 2014-04-28 14:37:38 +0000
@@ -4,139 +4,197 @@
4<!-- Generated by graphviz version 2.26.3 (20100126.1600)4<!-- Generated by graphviz version 2.26.3 (20100126.1600)
5 -->5 -->
6<!-- Title: state_diagram_session Pages: 1 -->6<!-- Title: state_diagram_session Pages: 1 -->
7<svg width="864pt" height="208pt"7<svg width="864pt" height="266pt"
8 viewBox="0.00 0.00 864.00 207.94" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">8 viewBox="0.00 0.00 864.00 265.73" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
9<g id="graph1" class="graph" transform="scale(0.435923 0.435923) rotate(0) translate(4 473)">9<g id="graph1" class="graph" transform="scale(0.367035 0.367035) rotate(0) translate(4 720)">
10<title>state_diagram_session</title>10<title>state_diagram_session</title>
11<polygon fill="white" stroke="white" points="-4,5 -4,-473 1979,-473 1979,5 -4,5"/>11<polygon fill="white" stroke="white" points="-4,5 -4,-720 2351,-720 2351,5 -4,5"/>
12<text text-anchor="middle" x="987" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for session</text>12<text text-anchor="middle" x="1173" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for session</text>
13<!-- stop -->
14<g id="node1" class="node"><title>stop</title>
15<ellipse fill="none" stroke="black" cx="2309" cy="-335" rx="32.0813" ry="32.5269"/>
16<ellipse fill="none" stroke="black" cx="2309" cy="-335" rx="36.0265" ry="36.5269"/>
17<text text-anchor="middle" x="2309" y="-331.4" font-family="Times Roman,serif" font-size="14.00">stop</text>
18</g>
13<!-- start1 -->19<!-- start1 -->
14<g id="node1" class="node"><title>start1</title>20<g id="node2" class="node"><title>start1</title>
15<ellipse fill="none" stroke="black" cx="42" cy="-294" rx="41.2167" ry="41.7193"/>21<ellipse fill="none" stroke="black" cx="42" cy="-395" rx="41.2167" ry="41.7193"/>
16<text text-anchor="middle" x="42" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start1</text>22<text text-anchor="middle" x="42" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start1</text>
17</g>23</g>
18<!-- start2 -->24<!-- start2 -->
19<g id="node3" class="node"><title>start2</title>25<g id="node4" class="node"><title>start2</title>
20<ellipse fill="none" stroke="black" cx="286" cy="-294" rx="41.2167" ry="41.7193"/>26<ellipse fill="none" stroke="black" cx="286" cy="-395" rx="41.2167" ry="41.7193"/>
21<text text-anchor="middle" x="286" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start2</text>27<text text-anchor="middle" x="286" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start2</text>
22</g>28</g>
23<!-- start1&#45;&gt;start2 -->29<!-- start1&#45;&gt;start2 -->
24<g id="edge2" class="edge"><title>start1&#45;&gt;start2</title>30<g id="edge2" class="edge"><title>start1&#45;&gt;start2</title>
25<path fill="none" stroke="black" d="M83.6679,-294C125.213,-294 189.13,-294 233.981,-294"/>31<path fill="none" stroke="black" d="M83.6679,-395C125.213,-395 189.13,-395 233.981,-395"/>
26<polygon fill="black" stroke="black" points="234.096,-297.5 244.096,-294 234.096,-290.5 234.096,-297.5"/>32<polygon fill="black" stroke="black" points="234.096,-398.5 244.096,-395 234.096,-391.5 234.096,-398.5"/>
27<text text-anchor="middle" x="164" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Read wire version</text>33<text text-anchor="middle" x="164" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Read wire version</text>
28</g>34</g>
29<!-- start3 -->35<!-- start3 -->
30<g id="node5" class="node"><title>start3</title>36<g id="node6" class="node"><title>start3</title>
31<ellipse fill="none" stroke="black" cx="516" cy="-294" rx="41.2167" ry="41.7193"/>37<ellipse fill="none" stroke="black" cx="537" cy="-395" rx="41.2167" ry="41.7193"/>
32<text text-anchor="middle" x="516" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start3</text>38<text text-anchor="middle" x="537" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start3</text>
33</g>39</g>
34<!-- start2&#45;&gt;start3 -->40<!-- start2&#45;&gt;start3 -->
35<g id="edge4" class="edge"><title>start2&#45;&gt;start3</title>41<g id="edge4" class="edge"><title>start2&#45;&gt;start3</title>
36<path fill="none" stroke="black" d="M327.651,-294C365.959,-294 422.903,-294 464.145,-294"/>42<path fill="none" stroke="black" d="M327.729,-395C370.886,-395 438.364,-395 484.973,-395"/>
37<polygon fill="black" stroke="black" points="464.271,-297.5 474.271,-294 464.271,-290.5 464.271,-297.5"/>43<polygon fill="black" stroke="black" points="485.171,-398.5 495.171,-395 485.171,-391.5 485.171,-398.5"/>
38<text text-anchor="middle" x="401" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Read CONNECT</text>44<text text-anchor="middle" x="401" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Read CONNECT</text>
39</g>45</g>
40<!-- loop -->46<!-- loop -->
41<g id="node7" class="node"><title>loop</title>47<g id="node8" class="node"><title>loop</title>
42<ellipse fill="none" stroke="black" cx="740" cy="-294" rx="31.8198" ry="31.8198"/>48<ellipse fill="none" stroke="black" cx="790" cy="-395" rx="31.8198" ry="31.8198"/>
43<text text-anchor="middle" x="740" y="-290.4" font-family="Times Roman,serif" font-size="14.00">loop</text>49<text text-anchor="middle" x="790" y="-391.4" font-family="Times Roman,serif" font-size="14.00">loop</text>
44</g>50</g>
45<!-- start3&#45;&gt;loop -->51<!-- start3&#45;&gt;loop -->
46<g id="edge6" class="edge"><title>start3&#45;&gt;loop</title>52<g id="edge6" class="edge"><title>start3&#45;&gt;loop</title>
47<path fill="none" stroke="black" d="M557.608,-294C597.53,-294 657.517,-294 697.677,-294"/>53<path fill="none" stroke="black" d="M578.778,-395C625.49,-395 700.728,-395 747.665,-395"/>
48<polygon fill="black" stroke="black" points="697.687,-297.5 707.687,-294 697.687,-290.5 697.687,-297.5"/>54<polygon fill="black" stroke="black" points="747.805,-398.5 757.805,-395 747.805,-391.5 747.805,-398.5"/>
49<text text-anchor="middle" x="633" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Write CONNACK</text>55<text text-anchor="middle" x="675" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Write CONNACK</text>
50</g>56</g>
51<!-- ping -->57<!-- ping -->
52<g id="node9" class="node"><title>ping</title>58<g id="node10" class="node"><title>ping</title>
53<ellipse fill="none" stroke="black" cx="1063" cy="-416" rx="32.0265" ry="32.5269"/>59<ellipse fill="none" stroke="black" cx="1135" cy="-593" rx="32.0265" ry="32.5269"/>
54<text text-anchor="middle" x="1063" y="-412.4" font-family="Times Roman,serif" font-size="14.00">ping</text>60<text text-anchor="middle" x="1135" y="-589.4" font-family="Times Roman,serif" font-size="14.00">ping</text>
55</g>61</g>
56<!-- loop&#45;&gt;ping -->62<!-- loop&#45;&gt;ping -->
57<g id="edge8" class="edge"><title>loop&#45;&gt;ping</title>63<g id="edge8" class="edge"><title>loop&#45;&gt;ping</title>
58<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"/>64<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"/>
59<polygon fill="black" stroke="black" points="1020.35,-420.33 1030.38,-416.906 1020.4,-413.33 1020.35,-420.33"/>65<polygon fill="black" stroke="black" points="1092.15,-591.877 1102.53,-589.734 1093.08,-584.939 1092.15,-591.877"/>
60<text text-anchor="middle" x="881" y="-418.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval</text>66<text text-anchor="middle" x="946" y="-583.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval</text>
61</g>67</g>
62<!-- broadcast -->68<!-- broadcast -->
63<g id="node11" class="node"><title>broadcast</title>69<g id="node12" class="node"><title>broadcast</title>
64<ellipse fill="none" stroke="black" cx="1063" cy="-200" rx="58.1882" ry="58.6899"/>70<ellipse fill="none" stroke="black" cx="1135" cy="-281" rx="58.1882" ry="58.6899"/>
65<text text-anchor="middle" x="1063" y="-196.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>71<text text-anchor="middle" x="1135" y="-277.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text>
66</g>72</g>
67<!-- loop&#45;&gt;broadcast -->73<!-- loop&#45;&gt;broadcast -->
68<g id="edge10" class="edge"><title>loop&#45;&gt;broadcast</title>74<g id="edge10" class="edge"><title>loop&#45;&gt;broadcast</title>
69<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"/>75<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"/>
70<polygon fill="black" stroke="black" points="995.396,-212.238 1004.75,-207.269 994.34,-205.318 995.396,-212.238"/>76<polygon fill="black" stroke="black" points="1066.94,-289.319 1076.53,-284.811 1066.22,-282.355 1066.94,-289.319"/>
71<text text-anchor="middle" x="881" y="-267.4" font-family="Times Roman,serif" font-size="14.00">Receive broadcast request</text>77<text text-anchor="middle" x="946" y="-348.4" font-family="Times Roman,serif" font-size="14.00">Receive broadcast request</text>
78</g>
79<!-- conn_broken -->
80<g id="node26" class="node"><title>conn_broken</title>
81<ellipse fill="none" stroke="black" cx="1361" cy="-99" rx="73.0388" ry="73.5391"/>
82<text text-anchor="middle" x="1361" y="-95.4" font-family="Times Roman,serif" font-size="14.00">conn_broken</text>
83</g>
84<!-- loop&#45;&gt;conn_broken -->
85<g id="edge28" class="edge"><title>loop&#45;&gt;conn_broken</title>
86<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"/>
87<polygon fill="black" stroke="black" points="1278.34,-85.2954 1288.79,-83.4998 1279.5,-78.392 1278.34,-85.2954"/>
88<text text-anchor="middle" x="946" y="-160.4" font-family="Times Roman,serif" font-size="14.00">Receive connbroken request</text>
89</g>
90<!-- conn_warn -->
91<g id="node28" class="node"><title>conn_warn</title>
92<ellipse fill="none" stroke="black" cx="1135" cy="-477" rx="65.7609" ry="65.7609"/>
93<text text-anchor="middle" x="1135" y="-473.4" font-family="Times Roman,serif" font-size="14.00">conn_warn</text>
94</g>
95<!-- loop&#45;&gt;conn_warn -->
96<g id="edge30" class="edge"><title>loop&#45;&gt;conn_warn</title>
97<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"/>
98<polygon fill="black" stroke="black" points="1058.71,-479.855 1068.85,-476.786 1059.01,-472.861 1058.71,-479.855"/>
99<text text-anchor="middle" x="946" y="-480.4" font-family="Times Roman,serif" font-size="14.00">Receive connwarn request</text>
72</g>100</g>
73<!-- pong_wait -->101<!-- pong_wait -->
74<g id="node13" class="node"><title>pong_wait</title>102<g id="node14" class="node"><title>pong_wait</title>
75<ellipse fill="none" stroke="black" cx="1526" cy="-406" rx="62.9325" ry="62.9325"/>103<ellipse fill="none" stroke="black" cx="537" cy="-653" rx="62.9325" ry="62.9325"/>
76<text text-anchor="middle" x="1526" y="-402.4" font-family="Times Roman,serif" font-size="14.00">pong_wait</text>104<text text-anchor="middle" x="537" y="-649.4" font-family="Times Roman,serif" font-size="14.00">pong_wait</text>
77</g>105</g>
78<!-- ping&#45;&gt;pong_wait -->106<!-- ping&#45;&gt;pong_wait -->
79<g id="edge12" class="edge"><title>ping&#45;&gt;pong_wait</title>107<g id="edge12" class="edge"><title>ping&#45;&gt;pong_wait</title>
80<path fill="none" stroke="black" d="M1095.56,-415.297C1169.19,-413.707 1350.04,-409.8 1452.36,-407.591"/>108<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"/>
81<polygon fill="black" stroke="black" points="1452.69,-411.084 1462.61,-407.369 1452.54,-404.086 1452.69,-411.084"/>109<polygon fill="black" stroke="black" points="609.573,-662.637 599.214,-664.858 608.697,-669.582 609.573,-662.637"/>
82<text text-anchor="middle" x="1289" y="-418.4" font-family="Times Roman,serif" font-size="14.00">Write PING</text>110<text text-anchor="middle" x="790" y="-670.4" font-family="Times Roman,serif" font-size="14.00">Write PING</text>
83</g>111</g>
84<!-- ack_wait -->112<!-- ack_wait -->
85<g id="node15" class="node"><title>ack_wait</title>113<g id="node16" class="node"><title>ack_wait</title>
86<ellipse fill="none" stroke="black" cx="1526" cy="-269" rx="55.1543" ry="55.1543"/>114<ellipse fill="none" stroke="black" cx="1598" cy="-373" rx="55.1543" ry="55.1543"/>
87<text text-anchor="middle" x="1526" y="-265.4" font-family="Times Roman,serif" font-size="14.00">ack_wait</text>115<text text-anchor="middle" x="1598" y="-369.4" font-family="Times Roman,serif" font-size="14.00">ack_wait</text>
88</g>116</g>
89<!-- broadcast&#45;&gt;ack_wait -->117<!-- broadcast&#45;&gt;ack_wait -->
90<g id="edge14" class="edge"><title>broadcast&#45;&gt;ack_wait</title>118<g id="edge14" class="edge"><title>broadcast&#45;&gt;ack_wait</title>
91<path fill="none" stroke="black" d="M1121.17,-208.669C1207.93,-221.599 1370.7,-245.856 1461.17,-259.339"/>119<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"/>
92<polygon fill="black" stroke="black" points="1460.9,-262.837 1471.3,-260.849 1461.93,-255.913 1460.9,-262.837"/>120<polygon fill="black" stroke="black" points="1534.79,-356.813 1545.37,-356.254 1536.75,-350.093 1534.79,-356.813"/>
93<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>121<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>
94</g>122</g>
95<!-- split_broadcast -->123<!-- split_broadcast -->
96<g id="node17" class="node"><title>split_broadcast</title>124<g id="node18" class="node"><title>split_broadcast</title>
97<ellipse fill="none" stroke="black" cx="1526" cy="-110" rx="84.1457" ry="84.1457"/>125<ellipse fill="none" stroke="black" cx="1598" cy="-216" rx="84.1457" ry="84.1457"/>
98<text text-anchor="middle" x="1526" y="-106.4" font-family="Times Roman,serif" font-size="14.00">split_broadcast</text>126<text text-anchor="middle" x="1598" y="-212.4" font-family="Times Roman,serif" font-size="14.00">split_broadcast</text>
99</g>127</g>
100<!-- broadcast&#45;&gt;split_broadcast -->128<!-- broadcast&#45;&gt;split_broadcast -->
101<g id="edge16" class="edge"><title>broadcast&#45;&gt;split_broadcast</title>129<g id="edge16" class="edge"><title>broadcast&#45;&gt;split_broadcast</title>
102<path fill="none" stroke="black" d="M1120.7,-188.783C1199.06,-173.553 1340.01,-146.154 1433.29,-128.021"/>130<path fill="none" stroke="black" d="M1193.17,-272.834C1271.44,-261.846 1411.56,-242.174 1504.66,-229.104"/>
103<polygon fill="black" stroke="black" points="1434.15,-131.421 1443.29,-126.077 1432.81,-124.549 1434.15,-131.421"/>131<polygon fill="black" stroke="black" points="1505.23,-232.558 1514.65,-227.702 1504.26,-225.626 1505.23,-232.558"/>
104<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>132<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>
133</g>
134<!-- pong_wait&#45;&gt;stop -->
135<g id="edge40" class="edge"><title>pong_wait&#45;&gt;stop</title>
136<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"/>
137<polygon fill="black" stroke="black" points="2292.5,-378.343 2293.84,-367.834 2286.24,-375.212 2292.5,-378.343"/>
138<text text-anchor="middle" x="1361" y="-658.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text>
105</g>139</g>
106<!-- pong_wait&#45;&gt;loop -->140<!-- pong_wait&#45;&gt;loop -->
107<g id="edge18" class="edge"><title>pong_wait&#45;&gt;loop</title>141<g id="edge18" class="edge"><title>pong_wait&#45;&gt;loop</title>
108<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"/>142<path fill="none" stroke="black" d="M581.359,-607.765C632.774,-555.333 716.085,-470.376 760.273,-425.314"/>
109<polygon fill="black" stroke="black" points="781.898,-299.011 771.42,-300.582 780.59,-305.888 781.898,-299.011"/>143<polygon fill="black" stroke="black" points="762.774,-427.763 767.277,-418.172 757.776,-422.862 762.774,-427.763"/>
110<text text-anchor="middle" x="1063" y="-359.4" font-family="Times Roman,serif" font-size="14.00">Read PONG</text>144<text text-anchor="middle" x="675" y="-574.4" font-family="Times Roman,serif" font-size="14.00">Read PONG</text>
145</g>
146<!-- ack_wait&#45;&gt;stop -->
147<g id="edge36" class="edge"><title>ack_wait&#45;&gt;stop</title>
148<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"/>
149<polygon fill="black" stroke="black" points="2263.58,-346.389 2272.99,-341.517 2262.59,-339.459 2263.58,-346.389"/>
150<text text-anchor="middle" x="1972" y="-373.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text>
111</g>151</g>
112<!-- ack_wait&#45;&gt;loop -->152<!-- ack_wait&#45;&gt;loop -->
113<g id="edge20" class="edge"><title>ack_wait&#45;&gt;loop</title>153<g id="edge20" class="edge"><title>ack_wait&#45;&gt;loop</title>
114<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"/>154<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"/>
115<polygon fill="black" stroke="black" points="781.977,-289.56 772.059,-293.288 782.136,-296.558 781.977,-289.56"/>155<polygon fill="black" stroke="black" points="831.485,-386.79 821.871,-391.242 832.163,-393.757 831.485,-386.79"/>
116<text text-anchor="middle" x="1063" y="-292.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>156<text text-anchor="middle" x="1135" y="-389.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>
117</g>157</g>
118<!-- split_broadcast&#45;&gt;loop -->158<!-- split_broadcast&#45;&gt;loop -->
119<g id="edge26" class="edge"><title>split_broadcast&#45;&gt;loop</title>159<g id="edge26" class="edge"><title>split_broadcast&#45;&gt;loop</title>
120<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"/>160<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"/>
121<polygon fill="black" stroke="black" points="753.014,-253.718 751.785,-264.241 759.308,-256.781 753.014,-253.718"/>161<polygon fill="black" stroke="black" points="800.027,-353.699 799.658,-364.287 806.549,-356.24 800.027,-353.699"/>
122<text text-anchor="middle" x="1063" y="-120.4" font-family="Times Roman,serif" font-size="14.00">All split msgs written</text>162<text text-anchor="middle" x="1135" y="-201.4" font-family="Times Roman,serif" font-size="14.00">All split msgs written</text>
123</g>163</g>
124<!-- split_ack_wait -->164<!-- split_ack_wait -->
125<g id="node21" class="node"><title>split_ack_wait</title>165<g id="node22" class="node"><title>split_ack_wait</title>
126<ellipse fill="none" stroke="black" cx="1893" cy="-110" rx="80.1095" ry="80.6102"/>166<ellipse fill="none" stroke="black" cx="1972" cy="-257" rx="80.1095" ry="80.6102"/>
127<text text-anchor="middle" x="1893" y="-106.4" font-family="Times Roman,serif" font-size="14.00">split_ack_wait</text>167<text text-anchor="middle" x="1972" y="-253.4" font-family="Times Roman,serif" font-size="14.00">split_ack_wait</text>
128</g>168</g>
129<!-- split_broadcast&#45;&gt;split_ack_wait -->169<!-- split_broadcast&#45;&gt;split_ack_wait -->
130<g id="edge22" class="edge"><title>split_broadcast&#45;&gt;split_ack_wait</title>170<g id="edge22" class="edge"><title>split_broadcast&#45;&gt;split_ack_wait</title>
131<path fill="none" stroke="black" d="M1610.2,-110C1667.61,-110 1743.59,-110 1802.33,-110"/>171<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"/>
132<polygon fill="black" stroke="black" points="1802.35,-113.5 1812.35,-110 1802.34,-106.5 1802.35,-113.5"/>172<polygon fill="black" stroke="black" points="1881.23,-255.523 1891.43,-252.676 1881.68,-248.537 1881.23,-255.523"/>
133<text text-anchor="middle" x="1711" y="-115.4" font-family="Times Roman,serif" font-size="14.00">Write split BROADCAST</text>173<text text-anchor="middle" x="1783" y="-256.4" font-family="Times Roman,serif" font-size="14.00">Write split BROADCAST</text>
174</g>
175<!-- split_ack_wait&#45;&gt;stop -->
176<g id="edge38" class="edge"><title>split_ack_wait&#45;&gt;stop</title>
177<path fill="none" stroke="black" d="M2050.59,-275.189C2116.55,-290.456 2208.51,-311.74 2263.08,-324.372"/>
178<polygon fill="black" stroke="black" points="2262.62,-327.857 2273.15,-326.702 2264.2,-321.037 2262.62,-327.857"/>
179<text text-anchor="middle" x="2166" y="-327.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text>
134</g>180</g>
135<!-- split_ack_wait&#45;&gt;split_broadcast -->181<!-- split_ack_wait&#45;&gt;split_broadcast -->
136<g id="edge24" class="edge"><title>split_ack_wait&#45;&gt;split_broadcast</title>182<g id="edge24" class="edge"><title>split_ack_wait&#45;&gt;split_broadcast</title>
137<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"/>183<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"/>
138<polygon fill="black" stroke="black" points="1617.23,-85.8043 1607.87,-90.7602 1618.28,-92.7256 1617.23,-85.8043"/>184<polygon fill="black" stroke="black" points="1691.25,-202.053 1681.52,-206.256 1691.74,-209.035 1691.25,-202.053"/>
139<text text-anchor="middle" x="1711" y="-93.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>185<text text-anchor="middle" x="1783" y="-218.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text>
186</g>
187<!-- conn_broken&#45;&gt;stop -->
188<g id="edge32" class="edge"><title>conn_broken&#45;&gt;stop</title>
189<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"/>
190<polygon fill="black" stroke="black" points="2275.42,-302.808 2284.91,-307.517 2280.43,-297.917 2275.42,-302.808"/>
191<text text-anchor="middle" x="1783" y="-127.4" font-family="Times Roman,serif" font-size="14.00">Write CONNBROKEN</text>
192</g>
193<!-- conn_warn&#45;&gt;loop -->
194<g id="edge34" class="edge"><title>conn_warn&#45;&gt;loop</title>
195<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"/>
196<polygon fill="black" stroke="black" points="831.758,-388.346 821.959,-392.373 832.131,-395.337 831.758,-388.346"/>
197<text text-anchor="middle" x="946" y="-420.4" font-family="Times Roman,serif" font-size="14.00">Write CONNWARN</text>
140</g>198</g>
141</g>199</g>
142</svg>200</svg>
143201
=== modified file 'server/acceptance/acceptanceclient.go'
--- server/acceptance/acceptanceclient.go 2014-04-09 19:30:53 +0000
+++ server/acceptance/acceptanceclient.go 2014-04-28 14:37:38 +0000
@@ -44,6 +44,7 @@
44 Levels map[string]int6444 Levels map[string]int64
45 Insecure bool // don't verify certs45 Insecure bool // don't verify certs
46 Prefix string // prefix for events46 Prefix string // prefix for events
47 Auth string
47 // connection48 // connection
48 Connection net.Conn49 Connection net.Conn
49}50}
@@ -73,6 +74,7 @@
73 Type string `json:"T"`74 Type string `json:"T"`
74 protocol.BroadcastMsg75 protocol.BroadcastMsg
75 protocol.NotificationsMsg76 protocol.NotificationsMsg
77 protocol.ConnWarnMsg
76}78}
7779
78// Run the session with the server, emits a stream of events.80// Run the session with the server, emits a stream of events.
@@ -93,6 +95,7 @@
93 "device": sess.Model,95 "device": sess.Model,
94 "channel": sess.ImageChannel,96 "channel": sess.ImageChannel,
95 },97 },
98 Authorization: sess.Auth,
96 })99 })
97 if err != nil {100 if err != nil {
98 return err101 return err
@@ -136,6 +139,8 @@
136 return err139 return err
137 }140 }
138 events <- fmt.Sprintf("%sbroadcast chan:%v app:%v topLevel:%d payloads:%s", sess.Prefix, recv.ChanId, recv.AppId, recv.TopLevel, pack)141 events <- fmt.Sprintf("%sbroadcast chan:%v app:%v topLevel:%d payloads:%s", sess.Prefix, recv.ChanId, recv.AppId, recv.TopLevel, pack)
142 case "warn":
143 events <- fmt.Sprintf("%swarn %s", sess.Prefix, recv.Reason)
139 }144 }
140 }145 }
141 return nil146 return nil
142147
=== modified file 'server/acceptance/cmd/acceptanceclient.go'
--- server/acceptance/cmd/acceptanceclient.go 2014-04-10 13:52:31 +0000
+++ server/acceptance/cmd/acceptanceclient.go 2014-04-28 14:37:38 +0000
@@ -48,13 +48,18 @@
48 fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] <config.json> <device id>\n")48 fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] <config.json> <device id>\n")
49 flag.PrintDefaults()49 flag.PrintDefaults()
50 }50 }
51 missingArg := func(what string) {
52 fmt.Fprintf(os.Stderr, "missing %s\n", what)
53 flag.Usage()
54 os.Exit(2)
55 }
51 flag.Parse()56 flag.Parse()
52 narg := flag.NArg()57 narg := flag.NArg()
53 switch {58 switch {
54 case narg < 1:59 case narg < 1:
55 log.Fatal("missing config file")60 missingArg("config file")
56 case narg < 2:61 case narg < 2:
57 log.Fatal("missing device-id")62 missingArg("device-id")
58 }63 }
59 configFName := flag.Arg(0)64 configFName := flag.Arg(0)
60 f, err := os.Open(configFName)65 f, err := os.Open(configFName)
6166
=== modified file 'server/acceptance/suites/broadcast.go'
--- server/acceptance/suites/broadcast.go 2014-04-07 19:39:19 +0000
+++ server/acceptance/suites/broadcast.go 2014-04-28 14:37:38 +0000
@@ -265,7 +265,11 @@
265265
266func (s *BroadcastAcceptanceSuite) TestGetHosts(c *C) {266func (s *BroadcastAcceptanceSuite) TestGetHosts(c *C) {
267 gh := gethosts.New("", s.ServerAPIURL+"/delivery-hosts", 2*time.Second)267 gh := gethosts.New("", s.ServerAPIURL+"/delivery-hosts", 2*time.Second)
268 hosts, err := gh.Get()268 host, err := gh.Get()
269 c.Assert(err, IsNil)269 c.Assert(err, IsNil)
270 c.Check(hosts, DeepEquals, []string{s.ServerAddr})270 expected := &gethosts.Host{
271 Domain: "localhost",
272 Hosts: []string{s.ServerAddr},
273 }
274 c.Check(host, DeepEquals, expected)
271}275}
272276
=== modified file 'server/acceptance/suites/suite.go'
--- server/acceptance/suites/suite.go 2014-04-03 16:47:47 +0000
+++ server/acceptance/suites/suite.go 2014-04-28 14:37:38 +0000
@@ -44,10 +44,19 @@
4444
45// Start a client.45// Start a client.
46func (h *ServerHandle) StartClient(c *C, devId string, levels map[string]int64) (events <-chan string, errorCh <-chan error, stop func()) {46func (h *ServerHandle) StartClient(c *C, devId string, levels map[string]int64) (events <-chan string, errorCh <-chan error, stop func()) {
47 return h.StartClientAuth(c, devId, levels, "")
48}
49
50// Start a client with auth.
51func (h *ServerHandle) StartClientAuth(c *C, devId string, levels map[string]int64, auth string) (events <-chan string, errorCh <-chan error, stop func()) {
47 errCh := make(chan error, 1)52 errCh := make(chan error, 1)
48 cliEvents := make(chan string, 10)53 cliEvents := make(chan string, 10)
49 sess := testClientSession(h.ServerAddr, devId, "m1", "img1", false)54 sess := testClientSession(h.ServerAddr, devId, "m1", "img1", false)
50 sess.Levels = levels55 sess.Levels = levels
56 sess.Auth = auth
57 if auth != "" {
58 sess.ExchangeTimeout = 5 * time.Second
59 }
51 err := sess.Dial()60 err := sess.Dial()
52 c.Assert(err, IsNil)61 c.Assert(err, IsNil)
53 clientShutdown := make(chan bool, 1) // abused as an atomic flag62 clientShutdown := make(chan bool, 1) // abused as an atomic flag
5463
=== modified file 'server/broker/broker.go'
--- server/broker/broker.go 2014-04-04 09:58:34 +0000
+++ server/broker/broker.go 2014-04-28 14:37:38 +0000
@@ -30,7 +30,7 @@
30// through them.30// through them.
31type Broker interface {31type Broker interface {
32 // Register the session.32 // Register the session.
33 Register(*protocol.ConnectMsg) (BrokerSession, error)33 Register(connMsg *protocol.ConnectMsg, sessionId string) (BrokerSession, error)
34 // Unregister the session.34 // Unregister the session.
35 Unregister(BrokerSession)35 Unregister(BrokerSession)
36}36}
3737
=== modified file 'server/broker/exchanges.go'
--- server/broker/exchanges.go 2014-04-04 13:19:10 +0000
+++ server/broker/exchanges.go 2014-04-28 14:37:38 +0000
@@ -28,9 +28,8 @@
2828
29// Scratch area for exchanges, sessions should hold one of these.29// Scratch area for exchanges, sessions should hold one of these.
30type ExchangesScratchArea struct {30type ExchangesScratchArea struct {
31 broadcastMsg protocol.BroadcastMsg31 broadcastMsg protocol.BroadcastMsg
32 ackMsg protocol.AckMsg32 ackMsg protocol.AckMsg
33 connBrokenMsg protocol.ConnBrokenMsg
34}33}
3534
36// BroadcastExchange leads a session through delivering a BROADCAST.35// BroadcastExchange leads a session through delivering a BROADCAST.
@@ -119,23 +118,20 @@
119 return nil118 return nil
120}119}
121120
122// ConnBrokenExchange breaks a session giving a reason.121// ConnMetaExchange allows to send a CONNBROKEN or CONNWARN message.
123type ConnBrokenExchange struct {122type ConnMetaExchange struct {
124 Reason string123 Msg protocol.OnewayMsg
125}124}
126125
127// check interface already here126// check interface already here
128var _ Exchange = (*ConnBrokenExchange)(nil)127var _ Exchange = (*ConnMetaExchange)(nil)
129128
130// Prepare session for a CONNBROKEN.129// Prepare session for a CONNBROKEN/WARN.
131func (cbe *ConnBrokenExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) {130func (cbe *ConnMetaExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) {
132 scratchArea := sess.ExchangeScratchArea()131 return cbe.Msg, nil, nil
133 scratchArea.connBrokenMsg.Type = "connbroken"
134 scratchArea.connBrokenMsg.Reason = cbe.Reason
135 return &scratchArea.connBrokenMsg, nil, nil
136}132}
137133
138// CONNBROKEN isn't acked134// CONNBROKEN/WARN aren't acked.
139func (cbe *ConnBrokenExchange) Acked(sess BrokerSession, done bool) error {135func (cbe *ConnMetaExchange) Acked(sess BrokerSession, done bool) error {
140 panic("Acked should not get invoked on ConnBrokenExchange")136 panic("Acked should not get invoked on ConnMetaExchange")
141}137}
142138
=== modified file 'server/broker/exchanges_test.go'
--- server/broker/exchanges_test.go 2014-04-04 13:19:10 +0000
+++ server/broker/exchanges_test.go 2014-04-28 14:37:38 +0000
@@ -24,6 +24,7 @@
2424
25 . "launchpad.net/gocheck"25 . "launchpad.net/gocheck"
2626
27 "launchpad.net/ubuntu-push/protocol"
27 "launchpad.net/ubuntu-push/server/broker"28 "launchpad.net/ubuntu-push/server/broker"
28 "launchpad.net/ubuntu-push/server/broker/testing"29 "launchpad.net/ubuntu-push/server/broker/testing"
29 "launchpad.net/ubuntu-push/server/store"30 "launchpad.net/ubuntu-push/server/store"
@@ -249,16 +250,18 @@
249 c.Check(sess.LevelsMap[store.SystemInternalChannelId], Equals, int64(5))250 c.Check(sess.LevelsMap[store.SystemInternalChannelId], Equals, int64(5))
250}251}
251252
252func (s *exchangesSuite) TestConnBrokenExchange(c *C) {253func (s *exchangesSuite) TestConnMetaExchange(c *C) {
253 sess := &testing.TestBrokerSession{}254 sess := &testing.TestBrokerSession{}
254 cbe := &broker.ConnBrokenExchange{"REASON"}255 var msg protocol.OnewayMsg = &protocol.ConnWarnMsg{"connwarn", "REASON"}
256 cbe := &broker.ConnMetaExchange{msg}
255 outMsg, inMsg, err := cbe.Prepare(sess)257 outMsg, inMsg, err := cbe.Prepare(sess)
256 c.Assert(err, IsNil)258 c.Assert(err, IsNil)
259 c.Check(msg, Equals, outMsg)
257 c.Check(inMsg, IsNil) // no answer is expected260 c.Check(inMsg, IsNil) // no answer is expected
258 // check261 // check
259 marshalled, err := json.Marshal(outMsg)262 marshalled, err := json.Marshal(outMsg)
260 c.Assert(err, IsNil)263 c.Assert(err, IsNil)
261 c.Check(string(marshalled), Equals, `{"T":"connbroken","Reason":"REASON"}`)264 c.Check(string(marshalled), Equals, `{"T":"connwarn","Reason":"REASON"}`)
262265
263 c.Check(func() { cbe.Acked(nil, true) }, PanicMatches, "Acked should not get invoked on ConnBrokenExchange")266 c.Check(func() { cbe.Acked(nil, true) }, PanicMatches, "Acked should not get invoked on ConnMetaExchange")
264}267}
265268
=== modified file 'server/broker/simple/simple.go'
--- server/broker/simple/simple.go 2014-04-11 08:47:18 +0000
+++ server/broker/simple/simple.go 2014-04-28 14:37:38 +0000
@@ -166,7 +166,7 @@
166166
167// Register registers a session with the broker. It feeds the session167// Register registers a session with the broker. It feeds the session
168// pending notifications as well.168// pending notifications as well.
169func (b *SimpleBroker) Register(connect *protocol.ConnectMsg) (broker.BrokerSession, error) {169func (b *SimpleBroker) Register(connect *protocol.ConnectMsg, sessionId string) (broker.BrokerSession, error) {
170 // xxx sanity check DeviceId170 // xxx sanity check DeviceId
171 model, err := broker.GetInfoString(connect, "device", "?")171 model, err := broker.GetInfoString(connect, "device", "?")
172 if err != nil {172 if err != nil {
@@ -224,7 +224,7 @@
224 } else { // register224 } else { // register
225 prev := b.registry[sess.deviceId]225 prev := b.registry[sess.deviceId]
226 if prev != nil { // kick it226 if prev != nil { // kick it
227 close(prev.exchanges)227 prev.exchanges <- nil
228 }228 }
229 b.registry[sess.deviceId] = sess229 b.registry[sess.deviceId] = sess
230 sess.registered = true230 sess.registered = true
231231
=== modified file 'server/broker/testsuite/suite.go'
--- server/broker/testsuite/suite.go 2014-04-11 08:47:18 +0000
+++ server/broker/testsuite/suite.go 2014-04-28 14:37:38 +0000
@@ -89,7 +89,7 @@
89 "device": "model",89 "device": "model",
90 "channel": "daily",90 "channel": "daily",
91 },91 },
92 })92 }, "s1")
93 c.Assert(err, IsNil)93 c.Assert(err, IsNil)
94 c.Assert(s.RevealSession(b, "dev-1"), Equals, sess)94 c.Assert(s.RevealSession(b, "dev-1"), Equals, sess)
95 c.Assert(sess.DeviceIdentifier(), Equals, "dev-1")95 c.Assert(sess.DeviceIdentifier(), Equals, "dev-1")
@@ -101,7 +101,7 @@
101 }))101 }))
102 b.Unregister(sess)102 b.Unregister(sess)
103 // just to make sure the unregister was processed103 // just to make sure the unregister was processed
104 _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""})104 _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}, "s2")
105 c.Assert(err, IsNil)105 c.Assert(err, IsNil)
106 c.Check(s.RevealSession(b, "dev-1"), IsNil)106 c.Check(s.RevealSession(b, "dev-1"), IsNil)
107}107}
@@ -111,7 +111,7 @@
111 b := s.MakeBroker(sto, testBrokerConfig, nil)111 b := s.MakeBroker(sto, testBrokerConfig, nil)
112 b.Start()112 b.Start()
113 defer b.Stop()113 defer b.Stop()
114 _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1", Levels: map[string]int64{"z": 5}})114 _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1", Levels: map[string]int64{"z": 5}}, "s1")
115 c.Check(err, FitsTypeOf, &broker.ErrAbort{})115 c.Check(err, FitsTypeOf, &broker.ErrAbort{})
116}116}
117117
@@ -123,11 +123,11 @@
123 info := map[string]interface{}{123 info := map[string]interface{}{
124 "device": -1,124 "device": -1,
125 }125 }
126 _, err := b.Register(&protocol.ConnectMsg{Type: "connect", Info: info})126 _, err := b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}, "s1")
127 c.Check(err, Equals, broker.ErrUnexpectedValue)127 c.Check(err, Equals, broker.ErrUnexpectedValue)
128 info["device"] = "m"128 info["device"] = "m"
129 info["channel"] = -1129 info["channel"] = -1
130 _, err = b.Register(&protocol.ConnectMsg{Type: "connect", Info: info})130 _, err = b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}, "s2")
131 c.Check(err, Equals, broker.ErrUnexpectedValue)131 c.Check(err, Equals, broker.ErrUnexpectedValue)
132}132}
133133
@@ -139,7 +139,7 @@
139 b := s.MakeBroker(sto, testBrokerConfig, nil)139 b := s.MakeBroker(sto, testBrokerConfig, nil)
140 b.Start()140 b.Start()
141 defer b.Stop()141 defer b.Stop()
142 sess, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})142 sess, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
143 c.Assert(err, IsNil)143 c.Assert(err, IsNil)
144 c.Check(len(sess.SessionChannel()), Equals, 1)144 c.Check(len(sess.SessionChannel()), Equals, 1)
145}145}
@@ -149,7 +149,7 @@
149 b := s.MakeBroker(sto, testBrokerConfig, s.testlog)149 b := s.MakeBroker(sto, testBrokerConfig, s.testlog)
150 b.Start()150 b.Start()
151 defer b.Stop()151 defer b.Stop()
152 _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})152 _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
153 c.Assert(err, IsNil)153 c.Assert(err, IsNil)
154 // but154 // but
155 c.Check(s.testlog.Captured(), Matches, "ERROR unsuccessful feed pending, get channel snapshot for 0: get channel snapshot fail\n")155 c.Check(s.testlog.Captured(), Matches, "ERROR unsuccessful feed pending, get channel snapshot for 0: get channel snapshot fail\n")
@@ -160,22 +160,25 @@
160 b := s.MakeBroker(sto, testBrokerConfig, nil)160 b := s.MakeBroker(sto, testBrokerConfig, nil)
161 b.Start()161 b.Start()
162 defer b.Stop()162 defer b.Stop()
163 sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})163 sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
164 c.Assert(err, IsNil)164 c.Assert(err, IsNil)
165 sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})165 sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s2")
166 c.Assert(err, IsNil)166 c.Assert(err, IsNil)
167 checkAndFalse := false167 // previous session got signaled by sending nil on its channel
168 // previous session got signaled by closing its channel168 var sentinel broker.Exchange
169 got := false
169 select {170 select {
170 case _, ok := <-sess1.SessionChannel():171 case sentinel = <-sess1.SessionChannel():
171 checkAndFalse = ok == false172 got = true
172 default:173 case <-time.After(5 * time.Second):
174 c.Fatal("taking too long to get sentinel")
173 }175 }
174 c.Check(checkAndFalse, Equals, true)176 c.Check(got, Equals, true)
177 c.Check(sentinel, IsNil)
175 c.Assert(s.RevealSession(b, "dev-1"), Equals, sess2)178 c.Assert(s.RevealSession(b, "dev-1"), Equals, sess2)
176 b.Unregister(sess1)179 b.Unregister(sess1)
177 // just to make sure the unregister was processed180 // just to make sure the unregister was processed
178 _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""})181 _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}, "s3")
179 c.Assert(err, IsNil)182 c.Assert(err, IsNil)
180 c.Check(s.RevealSession(b, "dev-1"), Equals, sess2)183 c.Check(s.RevealSession(b, "dev-1"), Equals, sess2)
181}184}
@@ -187,9 +190,9 @@
187 b := s.MakeBroker(sto, testBrokerConfig, nil)190 b := s.MakeBroker(sto, testBrokerConfig, nil)
188 b.Start()191 b.Start()
189 defer b.Stop()192 defer b.Stop()
190 sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})193 sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
191 c.Assert(err, IsNil)194 c.Assert(err, IsNil)
192 sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-2"})195 sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-2"}, "s2")
193 c.Assert(err, IsNil)196 c.Assert(err, IsNil)
194 // add notification to channel *after* the registrations197 // add notification to channel *after* the registrations
195 muchLater := time.Now().Add(10 * time.Minute)198 muchLater := time.Now().Add(10 * time.Minute)
@@ -241,7 +244,7 @@
241 b := s.MakeBroker(sto, testBrokerConfig, s.testlog)244 b := s.MakeBroker(sto, testBrokerConfig, s.testlog)
242 b.Start()245 b.Start()
243 defer b.Stop()246 defer b.Stop()
244 _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"})247 _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1")
245 c.Assert(err, IsNil)248 c.Assert(err, IsNil)
246 b.Broadcast(store.SystemInternalChannelId)249 b.Broadcast(store.SystemInternalChannelId)
247 select {250 select {
248251
=== modified file 'server/session/session.go'
--- server/session/session.go 2014-04-11 08:47:18 +0000
+++ server/session/session.go 2014-04-28 14:37:38 +0000
@@ -18,6 +18,7 @@
18package session18package session
1919
20import (20import (
21 "errors"
21 "net"22 "net"
22 "time"23 "time"
2324
@@ -35,7 +36,7 @@
35}36}
3637
37// sessionStart manages the start of the protocol session.38// sessionStart manages the start of the protocol session.
38func sessionStart(proto protocol.Protocol, brkr broker.Broker, cfg SessionConfig) (broker.BrokerSession, error) {39func sessionStart(proto protocol.Protocol, brkr broker.Broker, cfg SessionConfig, sessionId string) (broker.BrokerSession, error) {
39 var connMsg protocol.ConnectMsg40 var connMsg protocol.ConnectMsg
40 proto.SetDeadline(time.Now().Add(cfg.ExchangeTimeout()))41 proto.SetDeadline(time.Now().Add(cfg.ExchangeTimeout()))
41 err := proto.ReadMessage(&connMsg)42 err := proto.ReadMessage(&connMsg)
@@ -52,9 +53,11 @@
52 if err != nil {53 if err != nil {
53 return nil, err54 return nil, err
54 }55 }
55 return brkr.Register(&connMsg)56 return brkr.Register(&connMsg, sessionId)
56}57}
5758
59var errOneway = errors.New("oneway")
60
58// exchange writes outMsg message, reads answer in inMsg61// exchange writes outMsg message, reads answer in inMsg
59func exchange(proto protocol.Protocol, outMsg, inMsg interface{}, exchangeTimeout time.Duration) error {62func exchange(proto protocol.Protocol, outMsg, inMsg interface{}, exchangeTimeout time.Duration) error {
60 proto.SetDeadline(time.Now().Add(exchangeTimeout))63 proto.SetDeadline(time.Now().Add(exchangeTimeout))
@@ -62,7 +65,10 @@
62 if err != nil {65 if err != nil {
63 return err66 return err
64 }67 }
65 if inMsg == nil { // no answer expected, breaking connection68 if inMsg == nil { // no answer expected
69 if outMsg.(protocol.OnewayMsg).OnewayContinue() {
70 return errOneway
71 }
66 return &broker.ErrAbort{"session broken for reason"}72 return &broker.ErrAbort{"session broken for reason"}
67 }73 }
68 err = proto.ReadMessage(inMsg)74 err = proto.ReadMessage(inMsg)
@@ -78,6 +84,10 @@
78 exchangeTimeout := cfg.ExchangeTimeout()84 exchangeTimeout := cfg.ExchangeTimeout()
79 pingTimer := time.NewTimer(pingInterval)85 pingTimer := time.NewTimer(pingInterval)
80 intervalStart := time.Now()86 intervalStart := time.Now()
87 pingTimerReset := func() {
88 pingTimer.Reset(pingInterval)
89 intervalStart = time.Now()
90 }
81 ch := sess.SessionChannel()91 ch := sess.SessionChannel()
82Loop:92Loop:
83 for {93 for {
@@ -93,16 +103,15 @@
93 if pongMsg.Type != "pong" {103 if pongMsg.Type != "pong" {
94 return &broker.ErrAbort{"expected PONG message"}104 return &broker.ErrAbort{"expected PONG message"}
95 }105 }
96 pingTimer.Reset(pingInterval)106 pingTimerReset()
97 case exchg, ok := <-ch:107 case exchg := <-ch:
98 pingTimer.Stop()108 pingTimer.Stop()
99 if !ok {109 if exchg == nil {
100 return &broker.ErrAbort{"terminated"}110 return &broker.ErrAbort{"terminated"}
101 }111 }
102 outMsg, inMsg, err := exchg.Prepare(sess)112 outMsg, inMsg, err := exchg.Prepare(sess)
103 if err == broker.ErrNop { // nothing to do113 if err == broker.ErrNop { // nothing to do
104 pingTimer.Reset(pingInterval)114 pingTimerReset()
105 intervalStart = time.Now()
106 continue Loop115 continue Loop
107 }116 }
108 if err != nil {117 if err != nil {
@@ -111,12 +120,15 @@
111 for {120 for {
112 done := outMsg.Split()121 done := outMsg.Split()
113 err = exchange(proto, outMsg, inMsg, exchangeTimeout)122 err = exchange(proto, outMsg, inMsg, exchangeTimeout)
123 if err == errOneway {
124 pingTimerReset()
125 continue Loop
126 }
114 if err != nil {127 if err != nil {
115 return err128 return err
116 }129 }
117 if done {130 if done {
118 pingTimer.Reset(pingInterval)131 pingTimerReset()
119 intervalStart = time.Now()
120 }132 }
121 err = exchg.Acked(sess, done)133 err = exchg.Acked(sess, done)
122 if err != nil {134 if err != nil {
@@ -142,7 +154,7 @@
142 return track.End(&broker.ErrAbort{"unexpected wire format version"})154 return track.End(&broker.ErrAbort{"unexpected wire format version"})
143 }155 }
144 proto := protocol.NewProtocol0(conn)156 proto := protocol.NewProtocol0(conn)
145 sess, err := sessionStart(proto, brkr, cfg)157 sess, err := sessionStart(proto, brkr, cfg, track.SessionId())
146 if err != nil {158 if err != nil {
147 return track.End(err)159 return track.End(err)
148 }160 }
149161
=== modified file 'server/session/session_test.go'
--- server/session/session_test.go 2014-04-11 08:47:18 +0000
+++ server/session/session_test.go 2014-04-28 14:37:38 +0000
@@ -130,8 +130,8 @@
130 return &testBroker{registration: make(chan interface{}, 2)}130 return &testBroker{registration: make(chan interface{}, 2)}
131}131}
132132
133func (tb *testBroker) Register(connect *protocol.ConnectMsg) (broker.BrokerSession, error) {133func (tb *testBroker) Register(connect *protocol.ConnectMsg, sessionId string) (broker.BrokerSession, error) {
134 tb.registration <- "register " + connect.DeviceId134 tb.registration <- fmt.Sprintf("register %s %s", connect.DeviceId, sessionId)
135 return &testing.TestBrokerSession{DeviceId: connect.DeviceId}, tb.err135 return &testing.TestBrokerSession{DeviceId: connect.DeviceId}, tb.err
136}136}
137137
@@ -148,7 +148,7 @@
148 brkr := newTestBroker()148 brkr := newTestBroker()
149 go func() {149 go func() {
150 var err error150 var err error
151 sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout)151 sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout, "s1")
152 errCh <- err152 errCh <- err
153 }()153 }()
154 c.Check(takeNext(down), Equals, "deadline 5ms")154 c.Check(takeNext(down), Equals, "deadline 5ms")
@@ -160,7 +160,7 @@
160 up <- nil // no write error160 up <- nil // no write error
161 err := <-errCh161 err := <-errCh
162 c.Check(err, IsNil)162 c.Check(err, IsNil)
163 c.Check(takeNext(brkr.registration), Equals, "register dev-1")163 c.Check(takeNext(brkr.registration), Equals, "register dev-1 s1")
164 c.Check(sess.DeviceIdentifier(), Equals, "dev-1")164 c.Check(sess.DeviceIdentifier(), Equals, "dev-1")
165}165}
166166
@@ -175,7 +175,7 @@
175 brkr.err = errRegister175 brkr.err = errRegister
176 go func() {176 go func() {
177 var err error177 var err error
178 sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout)178 sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout, "s2")
179 errCh <- err179 errCh <- err
180 }()180 }()
181 up <- protocol.ConnectMsg{Type: "connect", ClientVer: "1", DeviceId: "dev-1"}181 up <- protocol.ConnectMsg{Type: "connect", ClientVer: "1", DeviceId: "dev-1"}
@@ -190,7 +190,7 @@
190 down := make(chan interface{}, 5)190 down := make(chan interface{}, 5)
191 tp := &testProtocol{up, down}191 tp := &testProtocol{up, down}
192 up <- io.ErrUnexpectedEOF192 up <- io.ErrUnexpectedEOF
193 _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout)193 _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s3")
194 c.Check(err, Equals, io.ErrUnexpectedEOF)194 c.Check(err, Equals, io.ErrUnexpectedEOF)
195}195}
196196
@@ -200,7 +200,7 @@
200 tp := &testProtocol{up, down}200 tp := &testProtocol{up, down}
201 up <- protocol.ConnectMsg{Type: "connect"}201 up <- protocol.ConnectMsg{Type: "connect"}
202 up <- io.ErrUnexpectedEOF202 up <- io.ErrUnexpectedEOF
203 _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout)203 _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s4")
204 c.Check(err, Equals, io.ErrUnexpectedEOF)204 c.Check(err, Equals, io.ErrUnexpectedEOF)
205 // sanity205 // sanity
206 c.Check(takeNext(down), Matches, "deadline.*")206 c.Check(takeNext(down), Matches, "deadline.*")
@@ -212,7 +212,7 @@
212 down := make(chan interface{}, 5)212 down := make(chan interface{}, 5)
213 tp := &testProtocol{up, down}213 tp := &testProtocol{up, down}
214 up <- protocol.ConnectMsg{Type: "what"}214 up <- protocol.ConnectMsg{Type: "what"}
215 _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout)215 _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s5")
216 c.Check(err, DeepEquals, &broker.ErrAbort{"expected CONNECT message"})216 c.Check(err, DeepEquals, &broker.ErrAbort{"expected CONNECT message"})
217}217}
218218
@@ -222,14 +222,14 @@
222}222}
223223
224func (s *sessionSuite) TestSessionLoop(c *C) {224func (s *sessionSuite) TestSessionLoop(c *C) {
225 nopTrack := NewTracker(s.testlog)225 track := &testTracker{NewTracker(s.testlog), make(chan interface{}, 2)}
226 errCh := make(chan error, 1)226 errCh := make(chan error, 1)
227 up := make(chan interface{}, 5)227 up := make(chan interface{}, 5)
228 down := make(chan interface{}, 5)228 down := make(chan interface{}, 5)
229 tp := &testProtocol{up, down}229 tp := &testProtocol{up, down}
230 sess := &testing.TestBrokerSession{}230 sess := &testing.TestBrokerSession{}
231 go func() {231 go func() {
232 errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)232 errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, track)
233 }()233 }()
234 c.Check(takeNext(down), Equals, "deadline 2ms")234 c.Check(takeNext(down), Equals, "deadline 2ms")
235 c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"})235 c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"})
@@ -241,6 +241,9 @@
241 up <- io.ErrUnexpectedEOF241 up <- io.ErrUnexpectedEOF
242 err := <-errCh242 err := <-errCh
243 c.Check(err, Equals, io.ErrUnexpectedEOF)243 c.Check(err, Equals, io.ErrUnexpectedEOF)
244 c.Check(track.interval, HasLen, 2)
245 c.Check((<-track.interval).(time.Duration) <= 8*time.Millisecond, Equals, true)
246 c.Check((<-track.interval).(time.Duration) <= 8*time.Millisecond, Equals, true)
244}247}
245248
246func (s *sessionSuite) TestSessionLoopWriteError(c *C) {249func (s *sessionSuite) TestSessionLoopWriteError(c *C) {
@@ -357,7 +360,7 @@
357 go func() {360 go func() {
358 errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)361 errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)
359 }()362 }()
360 close(exchanges)363 exchanges <- nil
361 err := <-errCh364 err := <-errCh
362 c.Check(err, DeepEquals, &broker.ErrAbort{"terminated"})365 c.Check(err, DeepEquals, &broker.ErrAbort{"terminated"})
363}366}
@@ -477,18 +480,44 @@
477 down := make(chan interface{}, 5)480 down := make(chan interface{}, 5)
478 tp := &testProtocol{up, down}481 tp := &testProtocol{up, down}
479 exchanges := make(chan broker.Exchange, 1)482 exchanges := make(chan broker.Exchange, 1)
480 exchanges <- &broker.ConnBrokenExchange{"REASON"}483 msg := &protocol.ConnBrokenMsg{"connbroken", "BREASON"}
484 exchanges <- &broker.ConnMetaExchange{msg}
481 sess := &testing.TestBrokerSession{Exchanges: exchanges}485 sess := &testing.TestBrokerSession{Exchanges: exchanges}
482 go func() {486 go func() {
483 errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)487 errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)
484 }()488 }()
485 c.Check(takeNext(down), Equals, "deadline 2ms")489 c.Check(takeNext(down), Equals, "deadline 2ms")
486 c.Check(takeNext(down), DeepEquals, protocol.ConnBrokenMsg{"connbroken", "REASON"})490 c.Check(takeNext(down), DeepEquals, protocol.ConnBrokenMsg{"connbroken", "BREASON"})
487 up <- nil // no write error491 up <- nil // no write error
488 err := <-errCh492 err := <-errCh
489 c.Check(err, DeepEquals, &broker.ErrAbort{"session broken for reason"})493 c.Check(err, DeepEquals, &broker.ErrAbort{"session broken for reason"})
490}494}
491495
496func (s *sessionSuite) TestSessionLoopConnWarnExchange(c *C) {
497 nopTrack := NewTracker(s.testlog)
498 errCh := make(chan error, 1)
499 up := make(chan interface{}, 5)
500 down := make(chan interface{}, 5)
501 tp := &testProtocol{up, down}
502 exchanges := make(chan broker.Exchange, 1)
503 msg := &protocol.ConnWarnMsg{"connwarn", "WREASON"}
504 exchanges <- &broker.ConnMetaExchange{msg}
505 sess := &testing.TestBrokerSession{Exchanges: exchanges}
506 go func() {
507 errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack)
508 }()
509 c.Check(takeNext(down), Equals, "deadline 2ms")
510 c.Check(takeNext(down), DeepEquals, protocol.ConnWarnMsg{"connwarn", "WREASON"})
511 up <- nil // no write error
512 // session continues
513 c.Check(takeNext(down), Equals, "deadline 2ms")
514 c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"})
515 up <- nil // no write error
516 up <- io.EOF
517 err := <-errCh
518 c.Check(err, Equals, io.EOF)
519}
520
492type testTracker struct {521type testTracker struct {
493 SessionTracker522 SessionTracker
494 interval chan interface{}523 interval chan interface{}
@@ -593,7 +622,7 @@
593 msg, err = downStream.ReadBytes(byte('}'))622 msg, err = downStream.ReadBytes(byte('}'))
594 c.Check(err, IsNil)623 c.Check(err, IsNil)
595 c.Check(msg, DeepEquals, []byte("\x00\x0c{\"T\":\"ping\"}"))624 c.Check(msg, DeepEquals, []byte("\x00\x0c{\"T\":\"ping\"}"))
596 c.Check(takeNext(brkr.registration), Equals, "register DEV")625 c.Check(takeNext(brkr.registration), Equals, "register DEV "+track.SessionId())
597 c.Check(len(brkr.registration), Equals, 0) // not yet unregistered626 c.Check(len(brkr.registration), Equals, 0) // not yet unregistered
598 cli.Close()627 cli.Close()
599 err = <-errCh628 err = <-errCh
600629
=== modified file 'server/session/tracker.go'
--- server/session/tracker.go 2014-02-10 23:19:08 +0000
+++ server/session/tracker.go 2014-04-28 14:37:38 +0000
@@ -17,6 +17,7 @@
17package session17package session
1818
19import (19import (
20 "fmt"
20 "net"21 "net"
21 "time"22 "time"
2223
@@ -29,6 +30,8 @@
29 logger.Logger30 logger.Logger
30 // Session got started.31 // Session got started.
31 Start(WithRemoteAddr)32 Start(WithRemoteAddr)
33 // SessionId
34 SessionId() string
32 // Session got registered with broker as sess BrokerSession.35 // Session got registered with broker as sess BrokerSession.
33 Registered(sess broker.BrokerSession)36 Registered(sess broker.BrokerSession)
34 // Report effective elapsed ping interval.37 // Report effective elapsed ping interval.
@@ -47,7 +50,7 @@
47// Tracker implements SessionTracker simply.50// Tracker implements SessionTracker simply.
48type tracker struct {51type tracker struct {
49 logger.Logger52 logger.Logger
50 sessionId int64 // xxx use timeuuid later53 sessionId string
51}54}
5255
53func NewTracker(logger logger.Logger) SessionTracker {56func NewTracker(logger logger.Logger) SessionTracker {
@@ -55,18 +58,22 @@
55}58}
5659
57func (trk *tracker) Start(conn WithRemoteAddr) {60func (trk *tracker) Start(conn WithRemoteAddr) {
58 trk.sessionId = time.Now().UnixNano() - sessionsEpoch61 trk.sessionId = fmt.Sprintf("%x", time.Now().UnixNano()-sessionsEpoch)
59 trk.Debugf("session(%x) connected %v", trk.sessionId, conn.RemoteAddr())62 trk.Debugf("session(%s) connected %v", trk.sessionId, conn.RemoteAddr())
63}
64
65func (trk *tracker) SessionId() string {
66 return trk.sessionId
60}67}
6168
62func (trk *tracker) Registered(sess broker.BrokerSession) {69func (trk *tracker) Registered(sess broker.BrokerSession) {
63 trk.Infof("session(%x) registered %v", trk.sessionId, sess.DeviceIdentifier())70 trk.Infof("session(%s) registered %v", trk.sessionId, sess.DeviceIdentifier())
64}71}
6572
66func (trk *tracker) EffectivePingInterval(time.Duration) {73func (trk *tracker) EffectivePingInterval(time.Duration) {
67}74}
6875
69func (trk *tracker) End(err error) error {76func (trk *tracker) End(err error) error {
70 trk.Debugf("session(%x) ended with: %v", trk.sessionId, err)77 trk.Debugf("session(%s) ended with: %v", trk.sessionId, err)
71 return err78 return err
72}79}
7380
=== modified file 'server/session/tracker_test.go'
--- server/session/tracker_test.go 2014-02-10 23:19:08 +0000
+++ server/session/tracker_test.go 2014-04-28 14:37:38 +0000
@@ -46,8 +46,8 @@
46func (s *trackerSuite) TestSessionTrackStart(c *C) {46func (s *trackerSuite) TestSessionTrackStart(c *C) {
47 track := NewTracker(s.testlog)47 track := NewTracker(s.testlog)
48 track.Start(&testRemoteAddrable{})48 track.Start(&testRemoteAddrable{})
49 c.Check(track.(*tracker).sessionId, Not(Equals), 0)49 c.Check(track.SessionId(), Not(Equals), "")
50 regExpected := fmt.Sprintf(`DEBUG session\(%x\) connected 127\.0\.0\.1:9999\n`, track.(*tracker).sessionId)50 regExpected := fmt.Sprintf(`DEBUG session\(%s\) connected 127\.0\.0\.1:9999\n`, track.SessionId())
51 c.Check(s.testlog.Captured(), Matches, regExpected)51 c.Check(s.testlog.Captured(), Matches, regExpected)
52}52}
5353
@@ -55,7 +55,7 @@
55 track := NewTracker(s.testlog)55 track := NewTracker(s.testlog)
56 track.Start(&testRemoteAddrable{})56 track.Start(&testRemoteAddrable{})
57 track.Registered(&testing.TestBrokerSession{DeviceId: "DEV-ID"})57 track.Registered(&testing.TestBrokerSession{DeviceId: "DEV-ID"})
58 regExpected := fmt.Sprintf(`.*connected.*\nINFO session\(%x\) registered DEV-ID\n`, track.(*tracker).sessionId)58 regExpected := fmt.Sprintf(`.*connected.*\nINFO session\(%s\) registered DEV-ID\n`, track.SessionId())
59 c.Check(s.testlog.Captured(), Matches, regExpected)59 c.Check(s.testlog.Captured(), Matches, regExpected)
60}60}
6161
@@ -63,6 +63,6 @@
63 track := NewTracker(s.testlog)63 track := NewTracker(s.testlog)
64 track.Start(&testRemoteAddrable{})64 track.Start(&testRemoteAddrable{})
65 track.End(&broker.ErrAbort{})65 track.End(&broker.ErrAbort{})
66 regExpected := fmt.Sprintf(`.*connected.*\nDEBUG session\(%x\) ended with: session aborted \(\)\n`, track.(*tracker).sessionId)66 regExpected := fmt.Sprintf(`.*connected.*\nDEBUG session\(%s\) ended with: session aborted \(\)\n`, track.SessionId())
67 c.Check(s.testlog.Captured(), Matches, regExpected)67 c.Check(s.testlog.Captured(), Matches, regExpected)
68}68}
6969
=== modified file 'ubuntu-push-client.go'
--- ubuntu-push-client.go 2014-03-12 13:25:20 +0000
+++ ubuntu-push-client.go 2014-04-28 14:37:38 +0000
@@ -19,12 +19,38 @@
19import (19import (
20 "log"20 "log"
2121
22 "gopkg.in/qml.v0"
23 "launchpad.net/go-dbus/v1"
22 "launchpad.net/go-xdg/v0"24 "launchpad.net/go-xdg/v0"
2325
24 "launchpad.net/ubuntu-push/client"26 "launchpad.net/ubuntu-push/client"
25)27)
2628
29const NAME = "com.ubuntu.PushNotifications"
30
31// grabName grabs ownership of the dbus name, and bails the client as
32// soon as somebody else grabs it.
33func grabName() {
34 conn, err := dbus.Connect(dbus.SessionBus)
35 if err != nil {
36 log.Fatalf("bus: %v", err)
37 }
38
39 flags := dbus.NameFlagAllowReplacement | dbus.NameFlagReplaceExisting
40 n := conn.RequestName(NAME, flags)
41 go func() {
42 for err := range n.C {
43 if err != nil {
44 log.Fatalf("FATAL: name channel got: %v", err)
45 }
46 }
47 }()
48}
49
27func main() {50func main() {
51 // XXX: this is a quick hack to ensure unicity
52 grabName()
53
28 cfgFname, err := xdg.Config.Find("ubuntu-push-client/config.json")54 cfgFname, err := xdg.Config.Find("ubuntu-push-client/config.json")
29 if err != nil {55 if err != nil {
30 log.Fatalf("unable to find a configuration file: %v", err)56 log.Fatalf("unable to find a configuration file: %v", err)
@@ -33,6 +59,9 @@
33 if err != nil {59 if err != nil {
34 log.Fatalf("unable to open the levels database: %v", err)60 log.Fatalf("unable to open the levels database: %v", err)
35 }61 }
62
63 qml.Init(nil)
64
36 cli := client.NewPushClient(cfgFname, lvlFname)65 cli := client.NewPushClient(cfgFname, lvlFname)
37 err = cli.Start()66 err = cli.Start()
38 if err != nil {67 if err != nil {
3968
=== added file 'util/auth.go'
--- util/auth.go 1970-01-01 00:00:00 +0000
+++ util/auth.go 2014-04-28 14:37:38 +0000
@@ -0,0 +1,36 @@
1/*
2 Copyright 2013-2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package util
18
19import (
20 "gopkg.in/niemeyer/uoneauth.v1"
21 "gopkg.in/qml.v0"
22)
23
24func GetAuthorization() (string, error) {
25 engine := qml.NewEngine()
26 defer engine.Destroy()
27 authService := uoneauth.NewService(engine)
28 var auth string
29 token, err := authService.Token()
30 if err != nil {
31 return "", err
32 } else {
33 auth = token.HeaderSignature("POST", "https://push.ubuntu.com")
34 }
35 return auth, nil
36}
037
=== added file 'util/auth_test.go'
--- util/auth_test.go 1970-01-01 00:00:00 +0000
+++ util/auth_test.go 2014-04-28 14:37:38 +0000
@@ -0,0 +1,53 @@
1/*
2 Copyright 2013-2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package util
18
19import (
20 "os"
21
22 "gopkg.in/qml.v0"
23
24 . "launchpad.net/gocheck"
25)
26
27type authSuite struct{}
28
29var _ = Suite(&authSuite{})
30
31func (s *authSuite) SetUpSuite(c *C) {
32 if os.Getenv("PUSH_AUTH_TEST") == "1" {
33 qml.Init(nil)
34 }
35}
36
37func (s *authSuite) SetUpTest(c *C) {
38 qml.SetLogger(c)
39}
40
41func (s *authSuite) TestGetAuth(c *C) {
42 /*
43 * This test is only useful when the PUSH_AUTH_TEST environment
44 * variable is set to "1" - in which case the runner should have
45 * a Ubuntu One account setup via system-settings.
46 */
47 if os.Getenv("PUSH_AUTH_TEST") != "1" {
48 c.Skip("PUSH_AUTH_TEST not set to '1'")
49 }
50 auth, err := GetAuthorization()
51 c.Assert(err, IsNil)
52 c.Assert(auth, Matches, "OAuth .*oauth_consumer_key=.*")
53}
054
=== modified file 'whoopsie/identifier/identifier.go'
--- whoopsie/identifier/identifier.go 2014-02-21 16:17:28 +0000
+++ whoopsie/identifier/identifier.go 2014-04-28 14:37:38 +0000
@@ -27,6 +27,7 @@
27import "C"27import "C"
28import "unsafe"28import "unsafe"
29import "errors"29import "errors"
30import "time"
3031
31// an Id knows how to generate itself, and how to stringify itself.32// an Id knows how to generate itself, and how to stringify itself.
32type Id interface {33type Id interface {
@@ -36,12 +37,17 @@
3637
37// Identifier is the default Id implementation.38// Identifier is the default Id implementation.
38type Identifier struct {39type Identifier struct {
39 value string40 value string
41 generator func(**C.char, **C.GError)
42}
43
44func generator(csp **C.char, errp **C.GError) {
45 C.whoopsie_identifier_generate(csp, errp)
40}46}
4147
42// New creates an Identifier, but does not call Generate() on it.48// New creates an Identifier, but does not call Generate() on it.
43func New() Id {49func New() Id {
44 return &Identifier{}50 return &Identifier{generator: generator}
45}51}
4652
47// Generate makes the Identifier create the identifier itself.53// Generate makes the Identifier create the identifier itself.
@@ -49,8 +55,18 @@
49 var gerr *C.GError55 var gerr *C.GError
50 var cs *C.char56 var cs *C.char
51 defer C.g_free((C.gpointer)(unsafe.Pointer(cs)))57 defer C.g_free((C.gpointer)(unsafe.Pointer(cs)))
52 C.whoopsie_identifier_generate(&cs, &gerr)58
5359 for i := 0; i < 200; i++ {
60 id.generator(&cs, &gerr)
61
62 if cs != nil || gerr != nil {
63 goto SuccessMaybe
64 }
65 time.Sleep(600 * time.Millisecond)
66 }
67 return errors.New("whoopsie_identifier_generate still bad after 2m; giving up")
68
69SuccessMaybe:
54 if gerr != nil {70 if gerr != nil {
55 return errors.New(C.GoString((*C.char)(gerr.message)))71 return errors.New(C.GoString((*C.char)(gerr.message)))
56 } else {72 } else {
5773
=== modified file 'whoopsie/identifier/identifier_test.go'
--- whoopsie/identifier/identifier_test.go 2014-01-15 15:51:50 +0000
+++ whoopsie/identifier/identifier_test.go 2014-04-28 14:37:38 +0000
@@ -41,3 +41,18 @@
41func (s *IdentifierSuite) TestIdentifierInterface(c *C) {41func (s *IdentifierSuite) TestIdentifierInterface(c *C) {
42 _ = []Id{New()}42 _ = []Id{New()}
43}43}
44
45// TestFailure checks that Identifier survives whoopsie shenanigans
46func (s *IdentifierSuite) TestIdentifierSurvivesShenanigans(c *C) {
47 count := 0
48 // using _Ctype* as a workaround for gocheck also having a C
49 gen := func(csp **_Ctype_char, errp **_Ctype_GError) {
50 count++
51 if count > 3 {
52 generator(csp, errp)
53 }
54 }
55 id := &Identifier{generator: gen}
56 id.Generate()
57 c.Check(id.String(), HasLen, 128)
58}

Subscribers

People subscribed via source and target branches