Merge lp:~chipaca/ubuntu-push/endpoint-names into lp:ubuntu-push
- endpoint-names
- Merge into trunk
Proposed by
John Lenton
Status: | Superseded |
---|---|
Proposed branch: | lp:~chipaca/ubuntu-push/endpoint-names |
Merge into: | lp:ubuntu-push |
Diff against target: |
4098 lines (+1990/-393) 50 files modified
Makefile (+3/-0) README (+8/-2) bus/connectivity/connectivity.go (+8/-7) bus/connectivity/connectivity_test.go (+78/-3) bus/endpoint.go (+15/-1) bus/testing/testing_endpoint.go (+12/-1) bus/testing/testing_endpoint_test.go (+15/-0) client/client_test.go (+5/-2) client/session/session.go (+40/-12) client/session/session_test.go (+63/-5) debian/changelog (+6/-0) debian/control (+2/-0) debian/rules (+5/-1) debian/ubuntu-push-client.install (+1/-0) dependencies.tsv (+1/-0) protocol/messages.go (+56/-0) protocol/messages_test.go (+18/-2) protocol/state-diag-client.gv (+4/-1) protocol/state-diag-client.svg (+77/-49) protocol/state-diag-session.gv (+10/-0) protocol/state-diag-session.svg (+132/-74) server/acceptance/acceptance_test.go (+3/-0) server/acceptance/acceptanceclient.go (+22/-1) server/acceptance/suites/broadcast.go (+7/-6) server/acceptance/suites/suite.go (+12/-0) server/acceptance/suites/unicast.go (+96/-0) server/api/handlers.go (+103/-21) server/api/handlers_test.go (+214/-12) server/broker/broker.go (+7/-1) server/broker/exchanges.go (+73/-41) server/broker/exchanges_test.go (+127/-26) server/broker/exchg_impl_test.go (+19/-17) server/broker/simple/simple.go (+56/-11) server/broker/simple/simple_test.go (+5/-4) server/broker/simple/suite_test.go (+3/-0) server/broker/testing/impls.go (+13/-0) server/broker/testsuite/suite.go (+121/-34) server/session/session.go (+23/-11) server/session/session_test.go (+43/-14) server/session/tracker.go (+12/-5) server/session/tracker_test.go (+4/-4) server/store/inmemory.go (+52/-18) server/store/inmemory_test.go (+72/-3) server/store/store.go (+74/-3) server/store/store_test.go (+46/-1) signing-helper/CMakeLists.txt (+39/-0) signing-helper/signing-helper.cpp (+97/-0) signing-helper/signing.h (+76/-0) testing/helpers.go (+11/-0) ubuntu-push-client.go (+1/-0) |
To merge this branch: | bzr merge lp:~chipaca/ubuntu-push/endpoint-names |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Push Hackers | Pending | ||
Review via email: mp+219202@code.launchpad.net |
This proposal has been superseded by a proposal from 2014-05-12.
Commit message
Added endpoint.GrabName
Description of the change
Added endpoint.GrabName
To post a comment you must log in.
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' | |||
2 | --- Makefile 2014-03-31 17:58:54 +0000 | |||
3 | +++ Makefile 2014-05-12 14:11:46 +0000 | |||
4 | @@ -11,10 +11,12 @@ | |||
5 | 11 | GODEPS += launchpad.net/go-dbus/v1 | 11 | GODEPS += launchpad.net/go-dbus/v1 |
6 | 12 | GODEPS += launchpad.net/go-xdg/v0 | 12 | GODEPS += launchpad.net/go-xdg/v0 |
7 | 13 | GODEPS += code.google.com/p/gosqlite/sqlite3 | 13 | GODEPS += code.google.com/p/gosqlite/sqlite3 |
8 | 14 | GODEPS += launchpad.net/~ubuntu-push-hackers/ubuntu-push/go-uuid/uuid | ||
9 | 14 | 15 | ||
10 | 15 | TOTEST = $(shell env GOPATH=$(GOPATH) go list $(PROJECT)/...|grep -v acceptance|grep -v http13client ) | 16 | TOTEST = $(shell env GOPATH=$(GOPATH) go list $(PROJECT)/...|grep -v acceptance|grep -v http13client ) |
11 | 16 | 17 | ||
12 | 17 | bootstrap: | 18 | bootstrap: |
13 | 19 | $(RM) -r $(GOPATH)/pkg | ||
14 | 18 | mkdir -p $(GOPATH)/bin | 20 | mkdir -p $(GOPATH)/bin |
15 | 19 | mkdir -p $(GOPATH)/pkg | 21 | mkdir -p $(GOPATH)/pkg |
16 | 20 | go get -u launchpad.net/godeps | 22 | go get -u launchpad.net/godeps |
17 | @@ -33,6 +35,7 @@ | |||
18 | 33 | 35 | ||
19 | 34 | build-client: | 36 | build-client: |
20 | 35 | go build ubuntu-push-client.go | 37 | go build ubuntu-push-client.go |
21 | 38 | (cd signing-helper && cmake . && make) | ||
22 | 36 | 39 | ||
23 | 37 | build-server-dev: | 40 | build-server-dev: |
24 | 38 | go build -o push-server-dev launchpad.net/ubuntu-push/server/dev | 41 | go build -o push-server-dev launchpad.net/ubuntu-push/server/dev |
25 | 39 | 42 | ||
26 | === modified file 'README' | |||
27 | --- README 2014-03-31 17:58:54 +0000 | |||
28 | +++ README 2014-05-12 14:11:46 +0000 | |||
29 | @@ -6,11 +6,17 @@ | |||
30 | 6 | The code expects to be checked out as launchpad.net/ubuntu-push in a Go | 6 | The code expects to be checked out as launchpad.net/ubuntu-push in a Go |
31 | 7 | workspace, see "go help gopath". | 7 | workspace, see "go help gopath". |
32 | 8 | 8 | ||
34 | 9 | To setup Go dependencies, install libsqlite3-dev and run: | 9 | To setup Go dependencies, install the following dependencies: |
35 | 10 | |||
36 | 11 | build-essential | ||
37 | 12 | libsqlite3-dev | ||
38 | 13 | |||
39 | 14 | and run: | ||
40 | 10 | 15 | ||
41 | 11 | make bootstrap | 16 | make bootstrap |
42 | 12 | 17 | ||
44 | 13 | To run tests, install libgcrypt11-dev and libwhoopsie-dev and run: | 18 | To run tests, install libglib2.0-dev, libgcrypt11-dev, libwhoopsie-dev, |
45 | 19 | and run: | ||
46 | 14 | 20 | ||
47 | 15 | make check | 21 | make check |
48 | 16 | 22 | ||
49 | 17 | 23 | ||
50 | === modified file 'bus/connectivity/connectivity.go' | |||
51 | --- bus/connectivity/connectivity.go 2014-04-04 11:08:28 +0000 | |||
52 | +++ bus/connectivity/connectivity.go 2014-05-12 14:11:46 +0000 | |||
53 | @@ -72,19 +72,20 @@ | |||
54 | 72 | cs.connAttempts += ar.Redial() | 72 | cs.connAttempts += ar.Redial() |
55 | 73 | nm := networkmanager.New(cs.endp, cs.log) | 73 | nm := networkmanager.New(cs.endp, cs.log) |
56 | 74 | 74 | ||
57 | 75 | // set up the watch | ||
58 | 76 | stateCh, err = nm.WatchState() | ||
59 | 77 | if err != nil { | ||
60 | 78 | cs.log.Debugf("failed to set up the state watch: %s", err) | ||
61 | 79 | goto Continue | ||
62 | 80 | } | ||
63 | 81 | |||
64 | 75 | // Get the current state. | 82 | // Get the current state. |
65 | 76 | initial = nm.GetState() | 83 | initial = nm.GetState() |
66 | 77 | if initial == networkmanager.Unknown { | 84 | if initial == networkmanager.Unknown { |
67 | 78 | cs.log.Debugf("Failed to get state.") | 85 | cs.log.Debugf("Failed to get state.") |
68 | 79 | goto Continue | 86 | goto Continue |
69 | 80 | } | 87 | } |
77 | 81 | 88 | cs.log.Debugf("got initial state of %s", initial) | |
71 | 82 | // set up the watch | ||
72 | 83 | stateCh, err = nm.WatchState() | ||
73 | 84 | if err != nil { | ||
74 | 85 | cs.log.Debugf("failed to set up the state watch: %s", err) | ||
75 | 86 | goto Continue | ||
76 | 87 | } | ||
78 | 88 | 89 | ||
79 | 89 | primary = nm.GetPrimaryConnection() | 90 | primary = nm.GetPrimaryConnection() |
80 | 90 | cs.log.Debugf("primary connection starts as %#v", primary) | 91 | cs.log.Debugf("primary connection starts as %#v", primary) |
81 | 91 | 92 | ||
82 | === modified file 'bus/connectivity/connectivity_test.go' | |||
83 | --- bus/connectivity/connectivity_test.go 2014-04-04 12:01:42 +0000 | |||
84 | +++ bus/connectivity/connectivity_test.go 2014-05-12 14:11:46 +0000 | |||
85 | @@ -17,8 +17,15 @@ | |||
86 | 17 | package connectivity | 17 | package connectivity |
87 | 18 | 18 | ||
88 | 19 | import ( | 19 | import ( |
89 | 20 | "net/http/httptest" | ||
90 | 21 | "sync" | ||
91 | 22 | "testing" | ||
92 | 23 | "time" | ||
93 | 24 | |||
94 | 20 | "launchpad.net/go-dbus/v1" | 25 | "launchpad.net/go-dbus/v1" |
95 | 21 | . "launchpad.net/gocheck" | 26 | . "launchpad.net/gocheck" |
96 | 27 | |||
97 | 28 | "launchpad.net/ubuntu-push/bus" | ||
98 | 22 | "launchpad.net/ubuntu-push/bus/networkmanager" | 29 | "launchpad.net/ubuntu-push/bus/networkmanager" |
99 | 23 | testingbus "launchpad.net/ubuntu-push/bus/testing" | 30 | testingbus "launchpad.net/ubuntu-push/bus/testing" |
100 | 24 | "launchpad.net/ubuntu-push/config" | 31 | "launchpad.net/ubuntu-push/config" |
101 | @@ -26,9 +33,6 @@ | |||
102 | 26 | helpers "launchpad.net/ubuntu-push/testing" | 33 | helpers "launchpad.net/ubuntu-push/testing" |
103 | 27 | "launchpad.net/ubuntu-push/testing/condition" | 34 | "launchpad.net/ubuntu-push/testing/condition" |
104 | 28 | "launchpad.net/ubuntu-push/util" | 35 | "launchpad.net/ubuntu-push/util" |
105 | 29 | "net/http/httptest" | ||
106 | 30 | "testing" | ||
107 | 31 | "time" | ||
108 | 32 | ) | 36 | ) |
109 | 33 | 37 | ||
110 | 34 | // hook up gocheck | 38 | // hook up gocheck |
111 | @@ -115,6 +119,77 @@ | |||
112 | 115 | c.Check(<-cs.networkStateCh, Equals, networkmanager.ConnectedGlobal) | 119 | c.Check(<-cs.networkStateCh, Equals, networkmanager.ConnectedGlobal) |
113 | 116 | } | 120 | } |
114 | 117 | 121 | ||
115 | 122 | // a racyEndpoint is an endpoint that behaves differently depending on | ||
116 | 123 | // how much time passes between getting the state and setting up the | ||
117 | 124 | // watch | ||
118 | 125 | type racyEndpoint struct { | ||
119 | 126 | stateGot bool | ||
120 | 127 | maxTime time.Time | ||
121 | 128 | delta time.Duration | ||
122 | 129 | lock sync.RWMutex | ||
123 | 130 | } | ||
124 | 131 | |||
125 | 132 | func (rep *racyEndpoint) GetProperty(prop string) (interface{}, error) { | ||
126 | 133 | switch prop { | ||
127 | 134 | case "state": | ||
128 | 135 | rep.lock.Lock() | ||
129 | 136 | defer rep.lock.Unlock() | ||
130 | 137 | rep.stateGot = true | ||
131 | 138 | rep.maxTime = time.Now().Add(rep.delta) | ||
132 | 139 | return uint32(networkmanager.Connecting), nil | ||
133 | 140 | case "PrimaryConnection": | ||
134 | 141 | return dbus.ObjectPath("/something"), nil | ||
135 | 142 | default: | ||
136 | 143 | return nil, nil | ||
137 | 144 | } | ||
138 | 145 | } | ||
139 | 146 | |||
140 | 147 | func (rep *racyEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error { | ||
141 | 148 | if member == "StateChanged" { | ||
142 | 149 | // we count never having gotten the state as happening "after" now. | ||
143 | 150 | rep.lock.RLock() | ||
144 | 151 | defer rep.lock.RUnlock() | ||
145 | 152 | ok := !rep.stateGot || time.Now().Before(rep.maxTime) | ||
146 | 153 | go func() { | ||
147 | 154 | if ok { | ||
148 | 155 | f(uint32(networkmanager.ConnectedGlobal)) | ||
149 | 156 | } | ||
150 | 157 | d() | ||
151 | 158 | }() | ||
152 | 159 | } | ||
153 | 160 | return nil | ||
154 | 161 | } | ||
155 | 162 | |||
156 | 163 | func (*racyEndpoint) Close() {} | ||
157 | 164 | func (*racyEndpoint) Dial() error { return nil } | ||
158 | 165 | func (*racyEndpoint) String() string { return "racyEndpoint" } | ||
159 | 166 | func (*racyEndpoint) Call(string, []interface{}, ...interface{}) error { return nil } | ||
160 | 167 | func (*racyEndpoint) GrabName(bool) <-chan error { return nil } | ||
161 | 168 | |||
162 | 169 | var _ bus.Endpoint = (*racyEndpoint)(nil) | ||
163 | 170 | |||
164 | 171 | // takeNext takes a value from given channel with a 1s timeout | ||
165 | 172 | func takeNext(ch <-chan networkmanager.State) networkmanager.State { | ||
166 | 173 | select { | ||
167 | 174 | case <-time.After(time.Second): | ||
168 | 175 | panic("channel stuck: too long waiting") | ||
169 | 176 | case v := <-ch: | ||
170 | 177 | return v | ||
171 | 178 | } | ||
172 | 179 | } | ||
173 | 180 | |||
174 | 181 | // test that if the nm state goes from connecting to connected very | ||
175 | 182 | // shortly after calling GetState, we don't lose the event. | ||
176 | 183 | func (s *ConnSuite) TestStartAvoidsRace(c *C) { | ||
177 | 184 | for delta := time.Second; delta > 1; delta /= 2 { | ||
178 | 185 | rep := &racyEndpoint{delta: delta} | ||
179 | 186 | cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: rep} | ||
180 | 187 | f := Commentf("when delta=%s", delta) | ||
181 | 188 | c.Assert(cs.start(), Equals, networkmanager.Connecting, f) | ||
182 | 189 | c.Assert(takeNext(cs.networkStateCh), Equals, networkmanager.ConnectedGlobal, f) | ||
183 | 190 | } | ||
184 | 191 | } | ||
185 | 192 | |||
186 | 118 | /* | 193 | /* |
187 | 119 | tests for connectedStateStep() | 194 | tests for connectedStateStep() |
188 | 120 | */ | 195 | */ |
189 | 121 | 196 | ||
190 | === modified file 'bus/endpoint.go' | |||
191 | --- bus/endpoint.go 2014-04-02 08:23:15 +0000 | |||
192 | +++ bus/endpoint.go 2014-05-12 14:11:46 +0000 | |||
193 | @@ -31,6 +31,7 @@ | |||
194 | 31 | 31 | ||
195 | 32 | // bus.Endpoint represents the DBus connection itself. | 32 | // bus.Endpoint represents the DBus connection itself. |
196 | 33 | type Endpoint interface { | 33 | type Endpoint interface { |
197 | 34 | GrabName(allowReplacement bool) <-chan error | ||
198 | 34 | WatchSignal(member string, f func(...interface{}), d func()) error | 35 | WatchSignal(member string, f func(...interface{}), d func()) error |
199 | 35 | Call(member string, args []interface{}, rvs ...interface{}) error | 36 | Call(member string, args []interface{}, rvs ...interface{}) error |
200 | 36 | GetProperty(property string) (interface{}, error) | 37 | GetProperty(property string) (interface{}, error) |
201 | @@ -53,7 +54,7 @@ | |||
202 | 53 | } | 54 | } |
203 | 54 | 55 | ||
204 | 55 | // ensure endpoint implements Endpoint | 56 | // ensure endpoint implements Endpoint |
206 | 56 | var _ Endpoint = &endpoint{} | 57 | var _ Endpoint = (*endpoint)(nil) |
207 | 57 | 58 | ||
208 | 58 | /* | 59 | /* |
209 | 59 | public methods | 60 | public methods |
210 | @@ -173,6 +174,19 @@ | |||
211 | 173 | return fmt.Sprintf("<Connection to %s %#v>", endp.bus, endp.addr) | 174 | return fmt.Sprintf("<Connection to %s %#v>", endp.bus, endp.addr) |
212 | 174 | } | 175 | } |
213 | 175 | 176 | ||
214 | 177 | // GrabName(...) takes over the name on the bus, reporting errors over the | ||
215 | 178 | // returned channel. | ||
216 | 179 | // | ||
217 | 180 | // While the first result will be nil on success, successive results would | ||
218 | 181 | // typically indicate another process trying to take over the name. | ||
219 | 182 | func (endp *endpoint) GrabName(allowReplacement bool) <-chan error { | ||
220 | 183 | flags := dbus.NameFlagAllowReplacement | dbus.NameFlagReplaceExisting | ||
221 | 184 | if !allowReplacement { | ||
222 | 185 | flags = 0 | ||
223 | 186 | } | ||
224 | 187 | return endp.bus.RequestName(endp.addr.Name, flags).C | ||
225 | 188 | } | ||
226 | 189 | |||
227 | 176 | /* | 190 | /* |
228 | 177 | private methods | 191 | private methods |
229 | 178 | */ | 192 | */ |
230 | 179 | 193 | ||
231 | === modified file 'bus/testing/testing_endpoint.go' | |||
232 | --- bus/testing/testing_endpoint.go 2014-04-04 11:08:28 +0000 | |||
233 | +++ bus/testing/testing_endpoint.go 2014-05-12 14:11:46 +0000 | |||
234 | @@ -169,5 +169,16 @@ | |||
235 | 169 | // see Endpoint's Close. This one does nothing. | 169 | // see Endpoint's Close. This one does nothing. |
236 | 170 | func (tc *testingEndpoint) Close() {} | 170 | func (tc *testingEndpoint) Close() {} |
237 | 171 | 171 | ||
238 | 172 | func (tc *testingEndpoint) GrabName(allowReplacement bool) <-chan error { | ||
239 | 173 | tc.callArgsLck.Lock() | ||
240 | 174 | defer tc.callArgsLck.Unlock() | ||
241 | 175 | |||
242 | 176 | args := callArgs{Member: "::GrabName"} | ||
243 | 177 | args.Args = append(args.Args, allowReplacement) | ||
244 | 178 | tc.callArgs = append(tc.callArgs, args) | ||
245 | 179 | |||
246 | 180 | return nil | ||
247 | 181 | } | ||
248 | 182 | |||
249 | 172 | // ensure testingEndpoint implements bus.Endpoint | 183 | // ensure testingEndpoint implements bus.Endpoint |
251 | 173 | var _ bus.Endpoint = &testingEndpoint{} | 184 | var _ bus.Endpoint = (*testingEndpoint)(nil) |
252 | 174 | 185 | ||
253 | === modified file 'bus/testing/testing_endpoint_test.go' | |||
254 | --- bus/testing/testing_endpoint_test.go 2014-04-02 08:23:15 +0000 | |||
255 | +++ bus/testing/testing_endpoint_test.go 2014-05-12 14:11:46 +0000 | |||
256 | @@ -173,3 +173,18 @@ | |||
257 | 173 | endp := NewTestingEndpoint(condition.Fail2Work(2), nil, "hello there") | 173 | endp := NewTestingEndpoint(condition.Fail2Work(2), nil, "hello there") |
258 | 174 | c.Check(endp.String(), Matches, ".*Still Broken.*hello there.*") | 174 | c.Check(endp.String(), Matches, ".*Still Broken.*hello there.*") |
259 | 175 | } | 175 | } |
260 | 176 | |||
261 | 177 | // Test that GrabName updates callArgs | ||
262 | 178 | func (s *TestingEndpointSuite) TestGrabNameUpdatesCallArgs(c *C) { | ||
263 | 179 | endp := NewTestingEndpoint(nil, condition.Work(true)) | ||
264 | 180 | endp.GrabName(false) | ||
265 | 181 | endp.GrabName(true) | ||
266 | 182 | c.Check(GetCallArgs(endp), DeepEquals, []callArgs{ | ||
267 | 183 | { | ||
268 | 184 | Member: "::GrabName", | ||
269 | 185 | Args: []interface{}{false}, | ||
270 | 186 | }, { | ||
271 | 187 | Member: "::GrabName", | ||
272 | 188 | Args: []interface{}{true}, | ||
273 | 189 | }}) | ||
274 | 190 | } | ||
275 | 176 | 191 | ||
276 | === modified file 'client/client_test.go' | |||
277 | --- client/client_test.go 2014-04-18 16:31:04 +0000 | |||
278 | +++ client/client_test.go 2014-05-12 14:11:46 +0000 | |||
279 | @@ -265,8 +265,9 @@ | |||
280 | 265 | ExchangeTimeout: 10 * time.Millisecond, | 265 | ExchangeTimeout: 10 * time.Millisecond, |
281 | 266 | HostsCachingExpiryTime: 1 * time.Hour, | 266 | HostsCachingExpiryTime: 1 * time.Hour, |
282 | 267 | ExpectAllRepairedTime: 30 * time.Minute, | 267 | ExpectAllRepairedTime: 30 * time.Minute, |
285 | 268 | PEM: cli.pem, | 268 | PEM: cli.pem, |
286 | 269 | Info: info, | 269 | Info: info, |
287 | 270 | AuthHelper: []string{}, | ||
288 | 270 | } | 271 | } |
289 | 271 | // sanity check that we are looking at all fields | 272 | // sanity check that we are looking at all fields |
290 | 272 | vExpected := reflect.ValueOf(expected) | 273 | vExpected := reflect.ValueOf(expected) |
291 | @@ -276,6 +277,8 @@ | |||
292 | 276 | // field isn't empty/zero | 277 | // field isn't empty/zero |
293 | 277 | c.Assert(fv.Interface(), Not(DeepEquals), reflect.Zero(fv.Type()).Interface(), Commentf("forgot about: %s", vExpected.Type().Field(i).Name)) | 278 | c.Assert(fv.Interface(), Not(DeepEquals), reflect.Zero(fv.Type()).Interface(), Commentf("forgot about: %s", vExpected.Type().Field(i).Name)) |
294 | 278 | } | 279 | } |
295 | 280 | // but AuthHelper really should be nil for now | ||
296 | 281 | expected.AuthHelper = nil | ||
297 | 279 | // finally compare | 282 | // finally compare |
298 | 280 | conf := cli.deriveSessionConfig(info) | 283 | conf := cli.deriveSessionConfig(info) |
299 | 281 | c.Check(conf, DeepEquals, expected) | 284 | c.Check(conf, DeepEquals, expected) |
300 | 282 | 285 | ||
301 | === modified file 'client/session/session.go' | |||
302 | --- client/session/session.go 2014-04-18 16:37:31 +0000 | |||
303 | +++ client/session/session.go 2014-05-12 14:11:46 +0000 | |||
304 | @@ -26,6 +26,7 @@ | |||
305 | 26 | "fmt" | 26 | "fmt" |
306 | 27 | "math/rand" | 27 | "math/rand" |
307 | 28 | "net" | 28 | "net" |
308 | 29 | "os/exec" | ||
309 | 29 | "strings" | 30 | "strings" |
310 | 30 | "sync" | 31 | "sync" |
311 | 31 | "sync/atomic" | 32 | "sync/atomic" |
312 | @@ -38,7 +39,9 @@ | |||
313 | 38 | "launchpad.net/ubuntu-push/util" | 39 | "launchpad.net/ubuntu-push/util" |
314 | 39 | ) | 40 | ) |
315 | 40 | 41 | ||
317 | 41 | var wireVersionBytes = []byte{protocol.ProtocolWireVersion} | 42 | var ( |
318 | 43 | wireVersionBytes = []byte{protocol.ProtocolWireVersion} | ||
319 | 44 | ) | ||
320 | 42 | 45 | ||
321 | 43 | type Notification struct { | 46 | type Notification struct { |
322 | 44 | TopLevel int64 | 47 | TopLevel int64 |
323 | @@ -84,6 +87,7 @@ | |||
324 | 84 | ExpectAllRepairedTime time.Duration | 87 | ExpectAllRepairedTime time.Duration |
325 | 85 | PEM []byte | 88 | PEM []byte |
326 | 86 | Info map[string]interface{} | 89 | Info map[string]interface{} |
327 | 90 | AuthHelper []string | ||
328 | 87 | } | 91 | } |
329 | 88 | 92 | ||
330 | 89 | // ClientSession holds a client<->server session and its configuration. | 93 | // ClientSession holds a client<->server session and its configuration. |
331 | @@ -115,6 +119,8 @@ | |||
332 | 115 | stateP *uint32 | 119 | stateP *uint32 |
333 | 116 | ErrCh chan error | 120 | ErrCh chan error |
334 | 117 | MsgCh chan *Notification | 121 | MsgCh chan *Notification |
335 | 122 | // authorization | ||
336 | 123 | auth string | ||
337 | 118 | // autoredial knobs | 124 | // autoredial knobs |
338 | 119 | shouldDelayP *uint32 | 125 | shouldDelayP *uint32 |
339 | 120 | lastAutoRedial time.Time | 126 | lastAutoRedial time.Time |
340 | @@ -234,6 +240,27 @@ | |||
341 | 234 | return nil | 240 | return nil |
342 | 235 | } | 241 | } |
343 | 236 | 242 | ||
344 | 243 | // addAuthorization gets the authorization blob to send to the server | ||
345 | 244 | // and adds it to the session. | ||
346 | 245 | func (sess *ClientSession) addAuthorization() error { | ||
347 | 246 | sess.Log.Debugf("adding authorization") | ||
348 | 247 | // using a helper, for now at least | ||
349 | 248 | if len(sess.AuthHelper) == 0 { | ||
350 | 249 | // do nothing if helper is unset or empty | ||
351 | 250 | return nil | ||
352 | 251 | } | ||
353 | 252 | |||
354 | 253 | auth, err := exec.Command(sess.AuthHelper[0], sess.AuthHelper[1:]...).Output() | ||
355 | 254 | if err != nil { | ||
356 | 255 | // For now we just log the error, as we don't want to block unauthorized users | ||
357 | 256 | sess.Log.Errorf("unable to get the authorization token from the account: %v", err) | ||
358 | 257 | } else { | ||
359 | 258 | sess.auth = strings.TrimSpace(string(auth)) | ||
360 | 259 | } | ||
361 | 260 | |||
362 | 261 | return nil | ||
363 | 262 | } | ||
364 | 263 | |||
365 | 237 | func (sess *ClientSession) resetHosts() { | 264 | func (sess *ClientSession) resetHosts() { |
366 | 238 | sess.deliveryHosts = nil | 265 | sess.deliveryHosts = nil |
367 | 239 | } | 266 | } |
368 | @@ -457,10 +484,9 @@ | |||
369 | 457 | return err | 484 | return err |
370 | 458 | } | 485 | } |
371 | 459 | err = proto.WriteMessage(protocol.ConnectMsg{ | 486 | err = proto.WriteMessage(protocol.ConnectMsg{ |
376 | 460 | Type: "connect", | 487 | Type: "connect", |
377 | 461 | DeviceId: sess.DeviceId, | 488 | DeviceId: sess.DeviceId, |
378 | 462 | // xxx get the SSO Authorization string from the phone | 489 | Authorization: sess.auth, |
375 | 463 | Authorization: "", | ||
379 | 464 | Levels: levels, | 490 | Levels: levels, |
380 | 465 | Info: sess.Info, | 491 | Info: sess.Info, |
381 | 466 | }) | 492 | }) |
382 | @@ -495,13 +521,15 @@ | |||
383 | 495 | 521 | ||
384 | 496 | // run calls connect, and if it works it calls start, and if it works | 522 | // run calls connect, and if it works it calls start, and if it works |
385 | 497 | // it runs loop in a goroutine, and ships its return value over ErrCh. | 523 | // it runs loop in a goroutine, and ships its return value over ErrCh. |
387 | 498 | func (sess *ClientSession) run(closer func(), hostGetter, connecter, starter, looper func() error) error { | 524 | func (sess *ClientSession) run(closer func(), authChecker, hostGetter, connecter, starter, looper func() error) error { |
388 | 499 | closer() | 525 | closer() |
394 | 500 | err := hostGetter() | 526 | if err := authChecker(); err != nil { |
395 | 501 | if err != nil { | 527 | return err |
396 | 502 | return err | 528 | } |
397 | 503 | } | 529 | if err := hostGetter(); err != nil { |
398 | 504 | err = connecter() | 530 | return err |
399 | 531 | } | ||
400 | 532 | err := connecter() | ||
401 | 505 | if err == nil { | 533 | if err == nil { |
402 | 506 | err = starter() | 534 | err = starter() |
403 | 507 | if err == nil { | 535 | if err == nil { |
404 | @@ -531,7 +559,7 @@ | |||
405 | 531 | // keep on trying. | 559 | // keep on trying. |
406 | 532 | panic("can't Dial() without a protocol constructor.") | 560 | panic("can't Dial() without a protocol constructor.") |
407 | 533 | } | 561 | } |
409 | 534 | return sess.run(sess.doClose, sess.getHosts, sess.connect, sess.start, sess.loop) | 562 | return sess.run(sess.doClose, sess.addAuthorization, sess.getHosts, sess.connect, sess.start, sess.loop) |
410 | 535 | } | 563 | } |
411 | 536 | 564 | ||
412 | 537 | func init() { | 565 | func init() { |
413 | 538 | 566 | ||
414 | === modified file 'client/session/session_test.go' | |||
415 | --- client/session/session_test.go 2014-04-18 16:37:31 +0000 | |||
416 | +++ client/session/session_test.go 2014-05-12 14:11:46 +0000 | |||
417 | @@ -34,7 +34,6 @@ | |||
418 | 34 | 34 | ||
419 | 35 | "launchpad.net/ubuntu-push/client/gethosts" | 35 | "launchpad.net/ubuntu-push/client/gethosts" |
420 | 36 | "launchpad.net/ubuntu-push/client/session/levelmap" | 36 | "launchpad.net/ubuntu-push/client/session/levelmap" |
421 | 37 | "launchpad.net/ubuntu-push/logger" | ||
422 | 38 | "launchpad.net/ubuntu-push/protocol" | 37 | "launchpad.net/ubuntu-push/protocol" |
423 | 39 | helpers "launchpad.net/ubuntu-push/testing" | 38 | helpers "launchpad.net/ubuntu-push/testing" |
424 | 40 | "launchpad.net/ubuntu-push/testing/condition" | 39 | "launchpad.net/ubuntu-push/testing/condition" |
425 | @@ -166,7 +165,7 @@ | |||
426 | 166 | ///// | 165 | ///// |
427 | 167 | 166 | ||
428 | 168 | type clientSessionSuite struct { | 167 | type clientSessionSuite struct { |
430 | 169 | log logger.Logger | 168 | log *helpers.TestLogger |
431 | 170 | lvls func() (levelmap.LevelMap, error) | 169 | lvls func() (levelmap.LevelMap, error) |
432 | 171 | } | 170 | } |
433 | 172 | 171 | ||
434 | @@ -347,6 +346,43 @@ | |||
435 | 347 | } | 346 | } |
436 | 348 | 347 | ||
437 | 349 | /**************************************************************** | 348 | /**************************************************************** |
438 | 349 | addAuthorization() tests | ||
439 | 350 | ****************************************************************/ | ||
440 | 351 | |||
441 | 352 | func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) { | ||
442 | 353 | sess := &ClientSession{Log: cs.log} | ||
443 | 354 | sess.AuthHelper = []string{"echo", "some auth"} | ||
444 | 355 | c.Assert(sess.auth, Equals, "") | ||
445 | 356 | err := sess.addAuthorization() | ||
446 | 357 | c.Assert(err, IsNil) | ||
447 | 358 | c.Check(sess.auth, Equals, "some auth") | ||
448 | 359 | } | ||
449 | 360 | |||
450 | 361 | func (cs *clientSessionSuite) TestAddAuthorizationIgnoresErrors(c *C) { | ||
451 | 362 | sess := &ClientSession{Log: cs.log} | ||
452 | 363 | sess.AuthHelper = []string{"sh", "-c", "echo hello; false"} | ||
453 | 364 | |||
454 | 365 | c.Assert(sess.auth, Equals, "") | ||
455 | 366 | err := sess.addAuthorization() | ||
456 | 367 | c.Assert(err, IsNil) | ||
457 | 368 | c.Check(sess.auth, Equals, "") | ||
458 | 369 | } | ||
459 | 370 | |||
460 | 371 | func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnsetOrNil(c *C) { | ||
461 | 372 | sess := &ClientSession{Log: cs.log} | ||
462 | 373 | sess.AuthHelper = nil | ||
463 | 374 | c.Assert(sess.auth, Equals, "") | ||
464 | 375 | err := sess.addAuthorization() | ||
465 | 376 | c.Assert(err, IsNil) | ||
466 | 377 | c.Check(sess.auth, Equals, "") | ||
467 | 378 | |||
468 | 379 | sess.AuthHelper = []string{} | ||
469 | 380 | err = sess.addAuthorization() | ||
470 | 381 | c.Assert(err, IsNil) | ||
471 | 382 | c.Check(sess.auth, Equals, "") | ||
472 | 383 | } | ||
473 | 384 | |||
474 | 385 | /**************************************************************** | ||
475 | 350 | startConnectionAttempt()/nextHostToTry()/started tests | 386 | startConnectionAttempt()/nextHostToTry()/started tests |
476 | 351 | ****************************************************************/ | 387 | ****************************************************************/ |
477 | 352 | 388 | ||
478 | @@ -931,9 +967,10 @@ | |||
479 | 931 | 967 | ||
480 | 932 | c.Check(takeNext(downCh), Equals, "deadline 0") | 968 | c.Check(takeNext(downCh), Equals, "deadline 0") |
481 | 933 | c.Check(takeNext(downCh), DeepEquals, protocol.ConnectMsg{ | 969 | c.Check(takeNext(downCh), DeepEquals, protocol.ConnectMsg{ |
485 | 934 | Type: "connect", | 970 | Type: "connect", |
486 | 935 | DeviceId: sess.DeviceId, | 971 | DeviceId: sess.DeviceId, |
487 | 936 | Levels: map[string]int64{}, | 972 | Levels: map[string]int64{}, |
488 | 973 | Authorization: "", | ||
489 | 937 | }) | 974 | }) |
490 | 938 | upCh <- errors.New("Overflow error in /dev/null") | 975 | upCh <- errors.New("Overflow error in /dev/null") |
491 | 939 | err = <-errCh | 976 | err = <-errCh |
492 | @@ -1038,6 +1075,7 @@ | |||
493 | 1038 | msg, ok := takeNext(downCh).(protocol.ConnectMsg) | 1075 | msg, ok := takeNext(downCh).(protocol.ConnectMsg) |
494 | 1039 | c.Check(ok, Equals, true) | 1076 | c.Check(ok, Equals, true) |
495 | 1040 | c.Check(msg.DeviceId, Equals, "wah") | 1077 | c.Check(msg.DeviceId, Equals, "wah") |
496 | 1078 | c.Check(msg.Authorization, Equals, "") | ||
497 | 1041 | c.Check(msg.Info, DeepEquals, info) | 1079 | c.Check(msg.Info, DeepEquals, info) |
498 | 1042 | upCh <- nil // no error | 1080 | upCh <- nil // no error |
499 | 1043 | upCh <- protocol.ConnAckMsg{ | 1081 | upCh <- protocol.ConnAckMsg{ |
500 | @@ -1054,6 +1092,22 @@ | |||
501 | 1054 | run() tests | 1092 | run() tests |
502 | 1055 | ****************************************************************/ | 1093 | ****************************************************************/ |
503 | 1056 | 1094 | ||
504 | 1095 | func (cs *clientSessionSuite) TestRunBailsIfAuthCheckFails(c *C) { | ||
505 | 1096 | sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) | ||
506 | 1097 | c.Assert(err, IsNil) | ||
507 | 1098 | failure := errors.New("TestRunBailsIfAuthCheckFails") | ||
508 | 1099 | has_closed := false | ||
509 | 1100 | err = sess.run( | ||
510 | 1101 | func() { has_closed = true }, | ||
511 | 1102 | func() error { return failure }, | ||
512 | 1103 | nil, | ||
513 | 1104 | nil, | ||
514 | 1105 | nil, | ||
515 | 1106 | nil) | ||
516 | 1107 | c.Check(err, Equals, failure) | ||
517 | 1108 | c.Check(has_closed, Equals, true) | ||
518 | 1109 | } | ||
519 | 1110 | |||
520 | 1057 | func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) { | 1111 | func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) { |
521 | 1058 | sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) | 1112 | sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
522 | 1059 | c.Assert(err, IsNil) | 1113 | c.Assert(err, IsNil) |
523 | @@ -1061,6 +1115,7 @@ | |||
524 | 1061 | has_closed := false | 1115 | has_closed := false |
525 | 1062 | err = sess.run( | 1116 | err = sess.run( |
526 | 1063 | func() { has_closed = true }, | 1117 | func() { has_closed = true }, |
527 | 1118 | func() error { return nil }, | ||
528 | 1064 | func() error { return failure }, | 1119 | func() error { return failure }, |
529 | 1065 | nil, | 1120 | nil, |
530 | 1066 | nil, | 1121 | nil, |
531 | @@ -1076,6 +1131,7 @@ | |||
532 | 1076 | err = sess.run( | 1131 | err = sess.run( |
533 | 1077 | func() {}, | 1132 | func() {}, |
534 | 1078 | func() error { return nil }, | 1133 | func() error { return nil }, |
535 | 1134 | func() error { return nil }, | ||
536 | 1079 | func() error { return failure }, | 1135 | func() error { return failure }, |
537 | 1080 | nil, | 1136 | nil, |
538 | 1081 | nil) | 1137 | nil) |
539 | @@ -1090,6 +1146,7 @@ | |||
540 | 1090 | func() {}, | 1146 | func() {}, |
541 | 1091 | func() error { return nil }, | 1147 | func() error { return nil }, |
542 | 1092 | func() error { return nil }, | 1148 | func() error { return nil }, |
543 | 1149 | func() error { return nil }, | ||
544 | 1093 | func() error { return failure }, | 1150 | func() error { return failure }, |
545 | 1094 | nil) | 1151 | nil) |
546 | 1095 | c.Check(err, Equals, failure) | 1152 | c.Check(err, Equals, failure) |
547 | @@ -1109,6 +1166,7 @@ | |||
548 | 1109 | func() error { return nil }, | 1166 | func() error { return nil }, |
549 | 1110 | func() error { return nil }, | 1167 | func() error { return nil }, |
550 | 1111 | func() error { return nil }, | 1168 | func() error { return nil }, |
551 | 1169 | func() error { return nil }, | ||
552 | 1112 | func() error { sess.MsgCh <- notf; return <-failureCh }) | 1170 | func() error { sess.MsgCh <- notf; return <-failureCh }) |
553 | 1113 | c.Check(err, Equals, nil) | 1171 | c.Check(err, Equals, nil) |
554 | 1114 | // if run doesn't error it sets up the channels | 1172 | // if run doesn't error it sets up the channels |
555 | 1115 | 1173 | ||
556 | === modified file 'debian/changelog' | |||
557 | --- debian/changelog 2014-04-23 11:54:00 +0000 | |||
558 | +++ debian/changelog 2014-05-12 14:11:46 +0000 | |||
559 | @@ -1,3 +1,9 @@ | |||
560 | 1 | ubuntu-push (0.21-0.ubuntu1) UNRELEASED; urgency=medium | ||
561 | 2 | |||
562 | 3 | * New upstream release: first auth bits, and Qt dependency. | ||
563 | 4 | |||
564 | 5 | -- John Lenton <john.lenton@canonical.com> Tue, 15 Apr 2014 14:04:35 +0100 | ||
565 | 6 | |||
566 | 1 | ubuntu-push (0.2.1+14.04.20140423.1-0ubuntu1) trusty; urgency=high | 7 | ubuntu-push (0.2.1+14.04.20140423.1-0ubuntu1) trusty; urgency=high |
567 | 2 | 8 | ||
568 | 3 | [ Samuele Pedroni ] | 9 | [ Samuele Pedroni ] |
569 | 4 | 10 | ||
570 | === modified file 'debian/control' | |||
571 | --- debian/control 2014-03-25 16:26:20 +0000 | |||
572 | +++ debian/control 2014-05-12 14:11:46 +0000 | |||
573 | @@ -14,6 +14,8 @@ | |||
574 | 14 | libgcrypt11-dev, | 14 | libgcrypt11-dev, |
575 | 15 | libglib2.0-dev (>= 2.31.6), | 15 | libglib2.0-dev (>= 2.31.6), |
576 | 16 | libwhoopsie-dev, | 16 | libwhoopsie-dev, |
577 | 17 | libubuntuoneauth-2.0-dev, | ||
578 | 18 | cmake, | ||
579 | 17 | Standards-Version: 3.9.5 | 19 | Standards-Version: 3.9.5 |
580 | 18 | Homepage: http://launchpad.net/ubuntu-push | 20 | Homepage: http://launchpad.net/ubuntu-push |
581 | 19 | Vcs-Bzr: lp:ubuntu-push | 21 | Vcs-Bzr: lp:ubuntu-push |
582 | 20 | 22 | ||
583 | === modified file 'debian/rules' | |||
584 | --- debian/rules 2014-03-24 12:22:55 +0000 | |||
585 | +++ debian/rules 2014-05-12 14:11:46 +0000 | |||
586 | @@ -2,9 +2,13 @@ | |||
587 | 2 | # -*- makefile -*- | 2 | # -*- makefile -*- |
588 | 3 | 3 | ||
589 | 4 | export DH_GOPKG := launchpad.net/ubuntu-push | 4 | export DH_GOPKG := launchpad.net/ubuntu-push |
590 | 5 | export DEB_BUILD_OPTIONS := nostrip | ||
591 | 6 | export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR) | 5 | export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR) |
592 | 7 | 6 | ||
593 | 7 | override_dh_auto_build: | ||
594 | 8 | cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1) | ||
595 | 9 | dh_auto_build --buildsystem=golang | ||
596 | 10 | (cd signing-helper && cmake . && make) | ||
597 | 11 | |||
598 | 8 | override_dh_install: | 12 | override_dh_install: |
599 | 9 | dh_install -Xusr/bin/cmd -Xusr/bin/dev --fail-missing | 13 | dh_install -Xusr/bin/cmd -Xusr/bin/dev --fail-missing |
600 | 10 | 14 | ||
601 | 11 | 15 | ||
602 | === modified file 'debian/ubuntu-push-client.install' | |||
603 | --- debian/ubuntu-push-client.install 2014-03-26 16:27:19 +0000 | |||
604 | +++ debian/ubuntu-push-client.install 2014-05-12 14:11:46 +0000 | |||
605 | @@ -1,4 +1,5 @@ | |||
606 | 1 | #!/usr/bin/dh-exec | 1 | #!/usr/bin/dh-exec |
607 | 2 | debian/config.json /etc/xdg/ubuntu-push-client | 2 | debian/config.json /etc/xdg/ubuntu-push-client |
608 | 3 | debian/ubuntu-push-client.conf /usr/share/upstart/sessions | 3 | debian/ubuntu-push-client.conf /usr/share/upstart/sessions |
609 | 4 | signing-helper/signing-helper /usr/lib/ubuntu-push-client | ||
610 | 4 | usr/bin/ubuntu-push => /usr/lib/ubuntu-push-client/ubuntu-push-client | 5 | usr/bin/ubuntu-push => /usr/lib/ubuntu-push-client/ubuntu-push-client |
611 | 5 | 6 | ||
612 | === modified file 'dependencies.tsv' | |||
613 | --- dependencies.tsv 2014-03-12 13:23:26 +0000 | |||
614 | +++ dependencies.tsv 2014-05-12 14:11:46 +0000 | |||
615 | @@ -2,3 +2,4 @@ | |||
616 | 2 | launchpad.net/go-dbus/v1 bzr james@jamesh.id.au-20140206110213-pbzcr6ucaz3rqmnw 125 | 2 | launchpad.net/go-dbus/v1 bzr james@jamesh.id.au-20140206110213-pbzcr6ucaz3rqmnw 125 |
617 | 3 | launchpad.net/go-xdg/v0 bzr john.lenton@canonical.com-20140208094800-gubd5md7cro3mtxa 10 | 3 | launchpad.net/go-xdg/v0 bzr john.lenton@canonical.com-20140208094800-gubd5md7cro3mtxa 10 |
618 | 4 | launchpad.net/gocheck bzr gustavo@niemeyer.net-20140127131816-zshobk1qqme626xw 86 | 4 | launchpad.net/gocheck bzr gustavo@niemeyer.net-20140127131816-zshobk1qqme626xw 86 |
619 | 5 | launchpad.net/~ubuntu-push-hackers/ubuntu-push/go-uuid bzr samuele.pedroni@canonical.com-20140130122455-pm9h8etl4owp90lg 1 | ||
620 | 5 | 6 | ||
621 | === modified file 'protocol/messages.go' | |||
622 | --- protocol/messages.go 2014-04-04 13:54:45 +0000 | |||
623 | +++ protocol/messages.go 2014-05-12 14:11:46 +0000 | |||
624 | @@ -54,6 +54,14 @@ | |||
625 | 54 | Split() (done bool) | 54 | Split() (done bool) |
626 | 55 | } | 55 | } |
627 | 56 | 56 | ||
628 | 57 | // OnewayMsg are messages that are not to be followed by a response, | ||
629 | 58 | // after sending them the session either aborts or continues. | ||
630 | 59 | type OnewayMsg interface { | ||
631 | 60 | SplittableMsg | ||
632 | 61 | // continue session after the message? | ||
633 | 62 | OnewayContinue() bool | ||
634 | 63 | } | ||
635 | 64 | |||
636 | 57 | // CONNBROKEN message, server side is breaking the connection for reason. | 65 | // CONNBROKEN message, server side is breaking the connection for reason. |
637 | 58 | type ConnBrokenMsg struct { | 66 | type ConnBrokenMsg struct { |
638 | 59 | Type string `json:"T"` | 67 | Type string `json:"T"` |
639 | @@ -65,11 +73,35 @@ | |||
640 | 65 | return true | 73 | return true |
641 | 66 | } | 74 | } |
642 | 67 | 75 | ||
643 | 76 | func (m *ConnBrokenMsg) OnewayContinue() bool { | ||
644 | 77 | return false | ||
645 | 78 | } | ||
646 | 79 | |||
647 | 68 | // CONNBROKEN reasons | 80 | // CONNBROKEN reasons |
648 | 69 | const ( | 81 | const ( |
649 | 70 | BrokenHostMismatch = "host-mismatch" | 82 | BrokenHostMismatch = "host-mismatch" |
650 | 71 | ) | 83 | ) |
651 | 72 | 84 | ||
652 | 85 | // CONNWARN message, server side is warning about partial functionality | ||
653 | 86 | // because reason. | ||
654 | 87 | type ConnWarnMsg struct { | ||
655 | 88 | Type string `json:"T"` | ||
656 | 89 | // reason | ||
657 | 90 | Reason string | ||
658 | 91 | } | ||
659 | 92 | |||
660 | 93 | func (m *ConnWarnMsg) Split() bool { | ||
661 | 94 | return true | ||
662 | 95 | } | ||
663 | 96 | func (m *ConnWarnMsg) OnewayContinue() bool { | ||
664 | 97 | return true | ||
665 | 98 | } | ||
666 | 99 | |||
667 | 100 | // CONNWARN reasons | ||
668 | 101 | const ( | ||
669 | 102 | WarnUnauthorized = "unauthorized" | ||
670 | 103 | ) | ||
671 | 104 | |||
672 | 73 | // PING/PONG messages | 105 | // PING/PONG messages |
673 | 74 | type PingPongMsg struct { | 106 | type PingPongMsg struct { |
674 | 75 | Type string `json:"T"` | 107 | Type string `json:"T"` |
675 | @@ -122,6 +154,17 @@ | |||
676 | 122 | Notifications []Notification | 154 | Notifications []Notification |
677 | 123 | } | 155 | } |
678 | 124 | 156 | ||
679 | 157 | // Reset resets the splitting state if the message storage is to be | ||
680 | 158 | // reused. | ||
681 | 159 | func (m *NotificationsMsg) Reset() { | ||
682 | 160 | // xxx | ||
683 | 161 | } | ||
684 | 162 | |||
685 | 163 | func (m *NotificationsMsg) Split() bool { | ||
686 | 164 | // xxx | ||
687 | 165 | return true | ||
688 | 166 | } | ||
689 | 167 | |||
690 | 125 | // A single unicast notification | 168 | // A single unicast notification |
691 | 126 | type Notification struct { | 169 | type Notification struct { |
692 | 127 | AppId string `json:"A"` | 170 | AppId string `json:"A"` |
693 | @@ -130,6 +173,19 @@ | |||
694 | 130 | Payload json.RawMessage `json:"P"` | 173 | Payload json.RawMessage `json:"P"` |
695 | 131 | } | 174 | } |
696 | 132 | 175 | ||
697 | 176 | // ExtractPayloads gets only the payloads out of a slice of notications. | ||
698 | 177 | func ExtractPayloads(notifications []Notification) []json.RawMessage { | ||
699 | 178 | n := len(notifications) | ||
700 | 179 | if n == 0 { | ||
701 | 180 | return nil | ||
702 | 181 | } | ||
703 | 182 | payloads := make([]json.RawMessage, n) | ||
704 | 183 | for i := 0; i < n; i++ { | ||
705 | 184 | payloads[i] = notifications[i].Payload | ||
706 | 185 | } | ||
707 | 186 | return payloads | ||
708 | 187 | } | ||
709 | 188 | |||
710 | 133 | // ACKnowledgement message | 189 | // ACKnowledgement message |
711 | 134 | type AckMsg struct { | 190 | type AckMsg struct { |
712 | 135 | Type string `json:"T"` | 191 | Type string `json:"T"` |
713 | 136 | 192 | ||
714 | === modified file 'protocol/messages_test.go' | |||
715 | --- protocol/messages_test.go 2014-04-04 13:19:10 +0000 | |||
716 | +++ protocol/messages_test.go 2014-05-12 14:11:46 +0000 | |||
717 | @@ -104,6 +104,22 @@ | |||
718 | 104 | c.Check(b.splitting, Equals, 0) | 104 | c.Check(b.splitting, Equals, 0) |
719 | 105 | } | 105 | } |
720 | 106 | 106 | ||
723 | 107 | func (s *messagesSuite) TestSplitConnBrokenMsg(c *C) { | 107 | func (s *messagesSuite) TestConnBrokenMsg(c *C) { |
724 | 108 | c.Check((&ConnBrokenMsg{}).Split(), Equals, true) | 108 | m := &ConnBrokenMsg{} |
725 | 109 | c.Check(m.Split(), Equals, true) | ||
726 | 110 | c.Check(m.OnewayContinue(), Equals, false) | ||
727 | 111 | } | ||
728 | 112 | |||
729 | 113 | func (s *messagesSuite) TestConnWarnMsg(c *C) { | ||
730 | 114 | m := &ConnWarnMsg{} | ||
731 | 115 | c.Check(m.Split(), Equals, true) | ||
732 | 116 | c.Check(m.OnewayContinue(), Equals, true) | ||
733 | 117 | } | ||
734 | 118 | |||
735 | 119 | func (s *messagesSuite) TestExtractPayloads(c *C) { | ||
736 | 120 | c.Check(ExtractPayloads(nil), IsNil) | ||
737 | 121 | p1 := json.RawMessage(`{"a":1}`) | ||
738 | 122 | p2 := json.RawMessage(`{"b":2}`) | ||
739 | 123 | ns := []Notification{Notification{Payload: p1}, Notification{Payload: p2}} | ||
740 | 124 | c.Check(ExtractPayloads(ns), DeepEquals, []json.RawMessage{p1, p2}) | ||
741 | 109 | } | 125 | } |
742 | 110 | 126 | ||
743 | === modified file 'protocol/state-diag-client.gv' | |||
744 | --- protocol/state-diag-client.gv 2014-01-16 20:07:13 +0000 | |||
745 | +++ protocol/state-diag-client.gv 2014-05-12 14:11:46 +0000 | |||
746 | @@ -2,7 +2,7 @@ | |||
747 | 2 | label = "State diagram for client"; | 2 | label = "State diagram for client"; |
748 | 3 | size="12,6"; | 3 | size="12,6"; |
749 | 4 | rankdir=LR; | 4 | rankdir=LR; |
751 | 5 | node [shape = doublecircle]; pingTimeout; | 5 | node [shape = doublecircle]; pingTimeout; connBroken; |
752 | 6 | node [shape = circle]; | 6 | node [shape = circle]; |
753 | 7 | start1 -> start2 [ label = "Write wire version" ]; | 7 | start1 -> start2 [ label = "Write wire version" ]; |
754 | 8 | start2 -> start3 [ label = "Write CONNECT" ]; | 8 | start2 -> start3 [ label = "Write CONNECT" ]; |
755 | @@ -13,4 +13,7 @@ | |||
756 | 13 | broadcast -> loop [label = "Write ACK"]; | 13 | broadcast -> loop [label = "Write ACK"]; |
757 | 14 | loop -> pingTimeout [ | 14 | loop -> pingTimeout [ |
758 | 15 | label = "Elapsed ping interval + exchange interval"]; | 15 | label = "Elapsed ping interval + exchange interval"]; |
759 | 16 | loop -> connBroken [label = "Read CONNBROKEN"]; | ||
760 | 17 | loop -> warn [label = "Read CONNWARN"]; | ||
761 | 18 | warn -> loop; | ||
762 | 16 | } | 19 | } |
763 | 17 | 20 | ||
764 | === modified file 'protocol/state-diag-client.svg' | |||
765 | --- protocol/state-diag-client.svg 2014-01-16 19:37:57 +0000 | |||
766 | +++ protocol/state-diag-client.svg 2014-05-12 14:11:46 +0000 | |||
767 | @@ -4,95 +4,123 @@ | |||
768 | 4 | <!-- Generated by graphviz version 2.26.3 (20100126.1600) | 4 | <!-- Generated by graphviz version 2.26.3 (20100126.1600) |
769 | 5 | --> | 5 | --> |
770 | 6 | <!-- Title: state_diagram_client Pages: 1 --> | 6 | <!-- Title: state_diagram_client Pages: 1 --> |
774 | 7 | <svg width="864pt" height="279pt" | 7 | <svg width="822pt" height="432pt" |
775 | 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"> |
776 | 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)"> |
777 | 10 | <title>state_diagram_client</title> | 10 | <title>state_diagram_client</title> |
779 | 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"/> |
780 | 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> |
781 | 13 | <!-- pingTimeout --> | 13 | <!-- pingTimeout --> |
782 | 14 | <g id="node1" class="node"><title>pingTimeout</title> | 14 | <g id="node1" class="node"><title>pingTimeout</title> |
786 | 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"/> |
787 | 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"/> |
788 | 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> |
789 | 18 | </g> | ||
790 | 19 | <!-- connBroken --> | ||
791 | 20 | <g id="node2" class="node"><title>connBroken</title> | ||
792 | 21 | <ellipse fill="none" stroke="black" cx="1180" cy="-413" rx="68.8251" ry="69.2965"/> | ||
793 | 22 | <ellipse fill="none" stroke="black" cx="1180" cy="-413" rx="72.7978" ry="73.2965"/> | ||
794 | 23 | <text text-anchor="middle" x="1180" y="-409.4" font-family="Times Roman,serif" font-size="14.00">connBroken</text> | ||
795 | 18 | </g> | 24 | </g> |
796 | 19 | <!-- start1 --> | 25 | <!-- start1 --> |
800 | 20 | <g id="node2" class="node"><title>start1</title> | 26 | <g id="node3" class="node"><title>start1</title> |
801 | 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"/> |
802 | 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> |
803 | 23 | </g> | 29 | </g> |
804 | 24 | <!-- start2 --> | 30 | <!-- start2 --> |
808 | 25 | <g id="node4" class="node"><title>start2</title> | 31 | <g id="node5" class="node"><title>start2</title> |
809 | 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"/> |
810 | 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> |
811 | 28 | </g> | 34 | </g> |
812 | 29 | <!-- start1->start2 --> | 35 | <!-- start1->start2 --> |
813 | 30 | <g id="edge2" class="edge"><title>start1->start2</title> | 36 | <g id="edge2" class="edge"><title>start1->start2</title> |
817 | 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"/> |
818 | 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"/> |
819 | 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> |
820 | 34 | </g> | 40 | </g> |
821 | 35 | <!-- start3 --> | 41 | <!-- start3 --> |
825 | 36 | <g id="node6" class="node"><title>start3</title> | 42 | <g id="node7" class="node"><title>start3</title> |
826 | 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"/> |
827 | 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> |
828 | 39 | </g> | 45 | </g> |
829 | 40 | <!-- start2->start3 --> | 46 | <!-- start2->start3 --> |
830 | 41 | <g id="edge4" class="edge"><title>start2->start3</title> | 47 | <g id="edge4" class="edge"><title>start2->start3</title> |
834 | 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"/> |
835 | 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"/> |
836 | 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> |
837 | 45 | </g> | 51 | </g> |
838 | 46 | <!-- loop --> | 52 | <!-- loop --> |
842 | 47 | <g id="node8" class="node"><title>loop</title> | 53 | <g id="node9" class="node"><title>loop</title> |
843 | 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"/> |
844 | 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> |
845 | 50 | </g> | 56 | </g> |
846 | 51 | <!-- start3->loop --> | 57 | <!-- start3->loop --> |
847 | 52 | <g id="edge6" class="edge"><title>start3->loop</title> | 58 | <g id="edge6" class="edge"><title>start3->loop</title> |
851 | 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"/> |
852 | 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"/> |
853 | 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> |
854 | 56 | </g> | 62 | </g> |
855 | 57 | <!-- loop->pingTimeout --> | 63 | <!-- loop->pingTimeout --> |
856 | 58 | <g id="edge16" class="edge"><title>loop->pingTimeout</title> | 64 | <g id="edge16" class="edge"><title>loop->pingTimeout</title> |
860 | 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"/> |
861 | 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"/> |
862 | 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> |
863 | 68 | </g> | ||
864 | 69 | <!-- loop->connBroken --> | ||
865 | 70 | <g id="edge18" class="edge"><title>loop->connBroken</title> | ||
866 | 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"/> | ||
867 | 72 | <polygon fill="black" stroke="black" points="1096.19,-416.445 1106.33,-413.388 1096.5,-409.452 1096.19,-416.445"/> | ||
868 | 73 | <text text-anchor="middle" x="941" y="-417.4" font-family="Times Roman,serif" font-size="14.00">Read CONNBROKEN</text> | ||
869 | 62 | </g> | 74 | </g> |
870 | 63 | <!-- pong --> | 75 | <!-- pong --> |
874 | 64 | <g id="node10" class="node"><title>pong</title> | 76 | <g id="node11" class="node"><title>pong</title> |
875 | 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"/> |
876 | 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> |
877 | 67 | </g> | 79 | </g> |
878 | 68 | <!-- loop->pong --> | 80 | <!-- loop->pong --> |
879 | 69 | <g id="edge8" class="edge"><title>loop->pong</title> | 81 | <g id="edge8" class="edge"><title>loop->pong</title> |
883 | 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"/> |
884 | 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"/> |
885 | 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> |
886 | 73 | </g> | 85 | </g> |
887 | 74 | <!-- broadcast --> | 86 | <!-- broadcast --> |
891 | 75 | <g id="node12" class="node"><title>broadcast</title> | 87 | <g id="node13" class="node"><title>broadcast</title> |
892 | 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"/> |
893 | 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> |
894 | 78 | </g> | 90 | </g> |
895 | 79 | <!-- loop->broadcast --> | 91 | <!-- loop->broadcast --> |
896 | 80 | <g id="edge10" class="edge"><title>loop->broadcast</title> | 92 | <g id="edge10" class="edge"><title>loop->broadcast</title> |
900 | 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"/> |
901 | 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"/> |
902 | 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> |
903 | 96 | </g> | ||
904 | 97 | <!-- warn --> | ||
905 | 98 | <g id="node19" class="node"><title>warn</title> | ||
906 | 99 | <ellipse fill="none" stroke="black" cx="1180" cy="-63" rx="36.7696" ry="36.7696"/> | ||
907 | 100 | <text text-anchor="middle" x="1180" y="-59.4" font-family="Times Roman,serif" font-size="14.00">warn</text> | ||
908 | 101 | </g> | ||
909 | 102 | <!-- loop->warn --> | ||
910 | 103 | <g id="edge20" class="edge"><title>loop->warn</title> | ||
911 | 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"/> | ||
912 | 105 | <polygon fill="black" stroke="black" points="1135.26,-79.0068 1144.04,-73.0757 1133.48,-72.2376 1135.26,-79.0068"/> | ||
913 | 106 | <text text-anchor="middle" x="941" y="-136.4" font-family="Times Roman,serif" font-size="14.00">Read CONNWARN</text> | ||
914 | 84 | </g> | 107 | </g> |
915 | 85 | <!-- pong->loop --> | 108 | <!-- pong->loop --> |
916 | 86 | <g id="edge12" class="edge"><title>pong->loop</title> | 109 | <g id="edge12" class="edge"><title>pong->loop</title> |
920 | 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"/> |
921 | 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"/> |
922 | 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> |
923 | 90 | </g> | 113 | </g> |
924 | 91 | <!-- broadcast->loop --> | 114 | <!-- broadcast->loop --> |
925 | 92 | <g id="edge14" class="edge"><title>broadcast->loop</title> | 115 | <g id="edge14" class="edge"><title>broadcast->loop</title> |
929 | 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"/> |
930 | 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"/> |
931 | 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> |
932 | 119 | </g> | ||
933 | 120 | <!-- warn->loop --> | ||
934 | 121 | <g id="edge22" class="edge"><title>warn->loop</title> | ||
935 | 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"/> | ||
936 | 123 | <polygon fill="black" stroke="black" points="751.574,-188.904 752.686,-199.44 758.387,-190.51 751.574,-188.904"/> | ||
937 | 96 | </g> | 124 | </g> |
938 | 97 | </g> | 125 | </g> |
939 | 98 | </svg> | 126 | </svg> |
940 | 99 | 127 | ||
941 | === modified file 'protocol/state-diag-session.gv' | |||
942 | --- protocol/state-diag-session.gv 2014-01-16 20:07:13 +0000 | |||
943 | +++ protocol/state-diag-session.gv 2014-05-12 14:11:46 +0000 | |||
944 | @@ -2,6 +2,7 @@ | |||
945 | 2 | label = "State diagram for session"; | 2 | label = "State diagram for session"; |
946 | 3 | size="12,6"; | 3 | size="12,6"; |
947 | 4 | rankdir=LR; | 4 | rankdir=LR; |
948 | 5 | node [shape = doublecircle]; stop; | ||
949 | 5 | node [shape = circle]; | 6 | node [shape = circle]; |
950 | 6 | start1 -> start2 [ label = "Read wire version" ]; | 7 | start1 -> start2 [ label = "Read wire version" ]; |
951 | 7 | start2 -> start3 [ label = "Read CONNECT" ]; | 8 | start2 -> start3 [ label = "Read CONNECT" ]; |
952 | @@ -17,4 +18,13 @@ | |||
953 | 17 | split_broadcast -> split_ack_wait [label = "Write split BROADCAST"]; | 18 | split_broadcast -> split_ack_wait [label = "Write split BROADCAST"]; |
954 | 18 | split_ack_wait -> split_broadcast [label = "Read ACK"]; | 19 | split_ack_wait -> split_broadcast [label = "Read ACK"]; |
955 | 19 | split_broadcast -> loop [label = "All split msgs written"]; | 20 | split_broadcast -> loop [label = "All split msgs written"]; |
956 | 21 | // other | ||
957 | 22 | loop -> conn_broken [label = "Receive connbroken request"]; | ||
958 | 23 | loop -> conn_warn [label = "Receive connwarn request"]; | ||
959 | 24 | conn_broken -> stop [label = "Write CONNBROKEN"]; | ||
960 | 25 | conn_warn -> loop [label = "Write CONNWARN"]; | ||
961 | 26 | // timeouts | ||
962 | 27 | ack_wait -> stop [label = "Elapsed exhange timeout"]; | ||
963 | 28 | split_ack_wait -> stop [label = "Elapsed exhange timeout"]; | ||
964 | 29 | pong_wait -> stop [label = "Elapsed exhange timeout"]; | ||
965 | 20 | } | 30 | } |
966 | 21 | 31 | ||
967 | === modified file 'protocol/state-diag-session.svg' | |||
968 | --- protocol/state-diag-session.svg 2014-01-16 19:37:57 +0000 | |||
969 | +++ protocol/state-diag-session.svg 2014-05-12 14:11:46 +0000 | |||
970 | @@ -4,139 +4,197 @@ | |||
971 | 4 | <!-- Generated by graphviz version 2.26.3 (20100126.1600) | 4 | <!-- Generated by graphviz version 2.26.3 (20100126.1600) |
972 | 5 | --> | 5 | --> |
973 | 6 | <!-- Title: state_diagram_session Pages: 1 --> | 6 | <!-- Title: state_diagram_session Pages: 1 --> |
977 | 7 | <svg width="864pt" height="208pt" | 7 | <svg width="864pt" height="266pt" |
978 | 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"> |
979 | 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)"> |
980 | 10 | <title>state_diagram_session</title> | 10 | <title>state_diagram_session</title> |
983 | 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"/> |
984 | 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> |
985 | 13 | <!-- stop --> | ||
986 | 14 | <g id="node1" class="node"><title>stop</title> | ||
987 | 15 | <ellipse fill="none" stroke="black" cx="2309" cy="-335" rx="32.0813" ry="32.5269"/> | ||
988 | 16 | <ellipse fill="none" stroke="black" cx="2309" cy="-335" rx="36.0265" ry="36.5269"/> | ||
989 | 17 | <text text-anchor="middle" x="2309" y="-331.4" font-family="Times Roman,serif" font-size="14.00">stop</text> | ||
990 | 18 | </g> | ||
991 | 13 | <!-- start1 --> | 19 | <!-- start1 --> |
995 | 14 | <g id="node1" class="node"><title>start1</title> | 20 | <g id="node2" class="node"><title>start1</title> |
996 | 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"/> |
997 | 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> |
998 | 17 | </g> | 23 | </g> |
999 | 18 | <!-- start2 --> | 24 | <!-- start2 --> |
1003 | 19 | <g id="node3" class="node"><title>start2</title> | 25 | <g id="node4" class="node"><title>start2</title> |
1004 | 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"/> |
1005 | 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> |
1006 | 22 | </g> | 28 | </g> |
1007 | 23 | <!-- start1->start2 --> | 29 | <!-- start1->start2 --> |
1008 | 24 | <g id="edge2" class="edge"><title>start1->start2</title> | 30 | <g id="edge2" class="edge"><title>start1->start2</title> |
1012 | 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"/> |
1013 | 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"/> |
1014 | 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> |
1015 | 28 | </g> | 34 | </g> |
1016 | 29 | <!-- start3 --> | 35 | <!-- start3 --> |
1020 | 30 | <g id="node5" class="node"><title>start3</title> | 36 | <g id="node6" class="node"><title>start3</title> |
1021 | 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"/> |
1022 | 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> |
1023 | 33 | </g> | 39 | </g> |
1024 | 34 | <!-- start2->start3 --> | 40 | <!-- start2->start3 --> |
1025 | 35 | <g id="edge4" class="edge"><title>start2->start3</title> | 41 | <g id="edge4" class="edge"><title>start2->start3</title> |
1029 | 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"/> |
1030 | 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"/> |
1031 | 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> |
1032 | 39 | </g> | 45 | </g> |
1033 | 40 | <!-- loop --> | 46 | <!-- loop --> |
1037 | 41 | <g id="node7" class="node"><title>loop</title> | 47 | <g id="node8" class="node"><title>loop</title> |
1038 | 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"/> |
1039 | 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> |
1040 | 44 | </g> | 50 | </g> |
1041 | 45 | <!-- start3->loop --> | 51 | <!-- start3->loop --> |
1042 | 46 | <g id="edge6" class="edge"><title>start3->loop</title> | 52 | <g id="edge6" class="edge"><title>start3->loop</title> |
1046 | 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"/> |
1047 | 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"/> |
1048 | 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> |
1049 | 50 | </g> | 56 | </g> |
1050 | 51 | <!-- ping --> | 57 | <!-- ping --> |
1054 | 52 | <g id="node9" class="node"><title>ping</title> | 58 | <g id="node10" class="node"><title>ping</title> |
1055 | 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"/> |
1056 | 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> |
1057 | 55 | </g> | 61 | </g> |
1058 | 56 | <!-- loop->ping --> | 62 | <!-- loop->ping --> |
1059 | 57 | <g id="edge8" class="edge"><title>loop->ping</title> | 63 | <g id="edge8" class="edge"><title>loop->ping</title> |
1063 | 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"/> |
1064 | 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"/> |
1065 | 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> |
1066 | 61 | </g> | 67 | </g> |
1067 | 62 | <!-- broadcast --> | 68 | <!-- broadcast --> |
1071 | 63 | <g id="node11" class="node"><title>broadcast</title> | 69 | <g id="node12" class="node"><title>broadcast</title> |
1072 | 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"/> |
1073 | 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> |
1074 | 66 | </g> | 72 | </g> |
1075 | 67 | <!-- loop->broadcast --> | 73 | <!-- loop->broadcast --> |
1076 | 68 | <g id="edge10" class="edge"><title>loop->broadcast</title> | 74 | <g id="edge10" class="edge"><title>loop->broadcast</title> |
1080 | 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"/> |
1081 | 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"/> |
1082 | 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> |
1083 | 78 | </g> | ||
1084 | 79 | <!-- conn_broken --> | ||
1085 | 80 | <g id="node26" class="node"><title>conn_broken</title> | ||
1086 | 81 | <ellipse fill="none" stroke="black" cx="1361" cy="-99" rx="73.0388" ry="73.5391"/> | ||
1087 | 82 | <text text-anchor="middle" x="1361" y="-95.4" font-family="Times Roman,serif" font-size="14.00">conn_broken</text> | ||
1088 | 83 | </g> | ||
1089 | 84 | <!-- loop->conn_broken --> | ||
1090 | 85 | <g id="edge28" class="edge"><title>loop->conn_broken</title> | ||
1091 | 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"/> | ||
1092 | 87 | <polygon fill="black" stroke="black" points="1278.34,-85.2954 1288.79,-83.4998 1279.5,-78.392 1278.34,-85.2954"/> | ||
1093 | 88 | <text text-anchor="middle" x="946" y="-160.4" font-family="Times Roman,serif" font-size="14.00">Receive connbroken request</text> | ||
1094 | 89 | </g> | ||
1095 | 90 | <!-- conn_warn --> | ||
1096 | 91 | <g id="node28" class="node"><title>conn_warn</title> | ||
1097 | 92 | <ellipse fill="none" stroke="black" cx="1135" cy="-477" rx="65.7609" ry="65.7609"/> | ||
1098 | 93 | <text text-anchor="middle" x="1135" y="-473.4" font-family="Times Roman,serif" font-size="14.00">conn_warn</text> | ||
1099 | 94 | </g> | ||
1100 | 95 | <!-- loop->conn_warn --> | ||
1101 | 96 | <g id="edge30" class="edge"><title>loop->conn_warn</title> | ||
1102 | 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"/> | ||
1103 | 98 | <polygon fill="black" stroke="black" points="1058.71,-479.855 1068.85,-476.786 1059.01,-472.861 1058.71,-479.855"/> | ||
1104 | 99 | <text text-anchor="middle" x="946" y="-480.4" font-family="Times Roman,serif" font-size="14.00">Receive connwarn request</text> | ||
1105 | 72 | </g> | 100 | </g> |
1106 | 73 | <!-- pong_wait --> | 101 | <!-- pong_wait --> |
1110 | 74 | <g id="node13" class="node"><title>pong_wait</title> | 102 | <g id="node14" class="node"><title>pong_wait</title> |
1111 | 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"/> |
1112 | 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> |
1113 | 77 | </g> | 105 | </g> |
1114 | 78 | <!-- ping->pong_wait --> | 106 | <!-- ping->pong_wait --> |
1115 | 79 | <g id="edge12" class="edge"><title>ping->pong_wait</title> | 107 | <g id="edge12" class="edge"><title>ping->pong_wait</title> |
1119 | 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"/> |
1120 | 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"/> |
1121 | 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> |
1122 | 83 | </g> | 111 | </g> |
1123 | 84 | <!-- ack_wait --> | 112 | <!-- ack_wait --> |
1127 | 85 | <g id="node15" class="node"><title>ack_wait</title> | 113 | <g id="node16" class="node"><title>ack_wait</title> |
1128 | 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"/> |
1129 | 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> |
1130 | 88 | </g> | 116 | </g> |
1131 | 89 | <!-- broadcast->ack_wait --> | 117 | <!-- broadcast->ack_wait --> |
1132 | 90 | <g id="edge14" class="edge"><title>broadcast->ack_wait</title> | 118 | <g id="edge14" class="edge"><title>broadcast->ack_wait</title> |
1136 | 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"/> |
1137 | 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"/> |
1138 | 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> |
1139 | 94 | </g> | 122 | </g> |
1140 | 95 | <!-- split_broadcast --> | 123 | <!-- split_broadcast --> |
1144 | 96 | <g id="node17" class="node"><title>split_broadcast</title> | 124 | <g id="node18" class="node"><title>split_broadcast</title> |
1145 | 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"/> |
1146 | 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> |
1147 | 99 | </g> | 127 | </g> |
1148 | 100 | <!-- broadcast->split_broadcast --> | 128 | <!-- broadcast->split_broadcast --> |
1149 | 101 | <g id="edge16" class="edge"><title>broadcast->split_broadcast</title> | 129 | <g id="edge16" class="edge"><title>broadcast->split_broadcast</title> |
1153 | 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"/> |
1154 | 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"/> |
1155 | 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> |
1156 | 133 | </g> | ||
1157 | 134 | <!-- pong_wait->stop --> | ||
1158 | 135 | <g id="edge40" class="edge"><title>pong_wait->stop</title> | ||
1159 | 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"/> | ||
1160 | 137 | <polygon fill="black" stroke="black" points="2292.5,-378.343 2293.84,-367.834 2286.24,-375.212 2292.5,-378.343"/> | ||
1161 | 138 | <text text-anchor="middle" x="1361" y="-658.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text> | ||
1162 | 105 | </g> | 139 | </g> |
1163 | 106 | <!-- pong_wait->loop --> | 140 | <!-- pong_wait->loop --> |
1164 | 107 | <g id="edge18" class="edge"><title>pong_wait->loop</title> | 141 | <g id="edge18" class="edge"><title>pong_wait->loop</title> |
1168 | 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"/> |
1169 | 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"/> |
1170 | 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> |
1171 | 145 | </g> | ||
1172 | 146 | <!-- ack_wait->stop --> | ||
1173 | 147 | <g id="edge36" class="edge"><title>ack_wait->stop</title> | ||
1174 | 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"/> | ||
1175 | 149 | <polygon fill="black" stroke="black" points="2263.58,-346.389 2272.99,-341.517 2262.59,-339.459 2263.58,-346.389"/> | ||
1176 | 150 | <text text-anchor="middle" x="1972" y="-373.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text> | ||
1177 | 111 | </g> | 151 | </g> |
1178 | 112 | <!-- ack_wait->loop --> | 152 | <!-- ack_wait->loop --> |
1179 | 113 | <g id="edge20" class="edge"><title>ack_wait->loop</title> | 153 | <g id="edge20" class="edge"><title>ack_wait->loop</title> |
1183 | 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"/> |
1184 | 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"/> |
1185 | 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> |
1186 | 117 | </g> | 157 | </g> |
1187 | 118 | <!-- split_broadcast->loop --> | 158 | <!-- split_broadcast->loop --> |
1188 | 119 | <g id="edge26" class="edge"><title>split_broadcast->loop</title> | 159 | <g id="edge26" class="edge"><title>split_broadcast->loop</title> |
1192 | 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"/> |
1193 | 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"/> |
1194 | 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> |
1195 | 123 | </g> | 163 | </g> |
1196 | 124 | <!-- split_ack_wait --> | 164 | <!-- split_ack_wait --> |
1200 | 125 | <g id="node21" class="node"><title>split_ack_wait</title> | 165 | <g id="node22" class="node"><title>split_ack_wait</title> |
1201 | 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"/> |
1202 | 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> |
1203 | 128 | </g> | 168 | </g> |
1204 | 129 | <!-- split_broadcast->split_ack_wait --> | 169 | <!-- split_broadcast->split_ack_wait --> |
1205 | 130 | <g id="edge22" class="edge"><title>split_broadcast->split_ack_wait</title> | 170 | <g id="edge22" class="edge"><title>split_broadcast->split_ack_wait</title> |
1209 | 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"/> |
1210 | 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"/> |
1211 | 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> |
1212 | 174 | </g> | ||
1213 | 175 | <!-- split_ack_wait->stop --> | ||
1214 | 176 | <g id="edge38" class="edge"><title>split_ack_wait->stop</title> | ||
1215 | 177 | <path fill="none" stroke="black" d="M2050.59,-275.189C2116.55,-290.456 2208.51,-311.74 2263.08,-324.372"/> | ||
1216 | 178 | <polygon fill="black" stroke="black" points="2262.62,-327.857 2273.15,-326.702 2264.2,-321.037 2262.62,-327.857"/> | ||
1217 | 179 | <text text-anchor="middle" x="2166" y="-327.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text> | ||
1218 | 134 | </g> | 180 | </g> |
1219 | 135 | <!-- split_ack_wait->split_broadcast --> | 181 | <!-- split_ack_wait->split_broadcast --> |
1220 | 136 | <g id="edge24" class="edge"><title>split_ack_wait->split_broadcast</title> | 182 | <g id="edge24" class="edge"><title>split_ack_wait->split_broadcast</title> |
1224 | 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"/> |
1225 | 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"/> |
1226 | 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> |
1227 | 186 | </g> | ||
1228 | 187 | <!-- conn_broken->stop --> | ||
1229 | 188 | <g id="edge32" class="edge"><title>conn_broken->stop</title> | ||
1230 | 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"/> | ||
1231 | 190 | <polygon fill="black" stroke="black" points="2275.42,-302.808 2284.91,-307.517 2280.43,-297.917 2275.42,-302.808"/> | ||
1232 | 191 | <text text-anchor="middle" x="1783" y="-127.4" font-family="Times Roman,serif" font-size="14.00">Write CONNBROKEN</text> | ||
1233 | 192 | </g> | ||
1234 | 193 | <!-- conn_warn->loop --> | ||
1235 | 194 | <g id="edge34" class="edge"><title>conn_warn->loop</title> | ||
1236 | 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"/> | ||
1237 | 196 | <polygon fill="black" stroke="black" points="831.758,-388.346 821.959,-392.373 832.131,-395.337 831.758,-388.346"/> | ||
1238 | 197 | <text text-anchor="middle" x="946" y="-420.4" font-family="Times Roman,serif" font-size="14.00">Write CONNWARN</text> | ||
1239 | 140 | </g> | 198 | </g> |
1240 | 141 | </g> | 199 | </g> |
1241 | 142 | </svg> | 200 | </svg> |
1242 | 143 | 201 | ||
1243 | === modified file 'server/acceptance/acceptance_test.go' | |||
1244 | --- server/acceptance/acceptance_test.go 2014-04-07 19:39:19 +0000 | |||
1245 | +++ server/acceptance/acceptance_test.go 2014-05-12 14:11:46 +0000 | |||
1246 | @@ -59,3 +59,6 @@ | |||
1247 | 59 | 59 | ||
1248 | 60 | // broadcast | 60 | // broadcast |
1249 | 61 | var _ = Suite(&suites.BroadcastAcceptanceSuite{suites.AcceptanceSuite{StartServer: StartServer}}) | 61 | var _ = Suite(&suites.BroadcastAcceptanceSuite{suites.AcceptanceSuite{StartServer: StartServer}}) |
1250 | 62 | |||
1251 | 63 | // unicast | ||
1252 | 64 | var _ = Suite(&suites.UnicastAcceptanceSuite{suites.AcceptanceSuite{StartServer: StartServer}, nil}) | ||
1253 | 62 | 65 | ||
1254 | === modified file 'server/acceptance/acceptanceclient.go' | |||
1255 | --- server/acceptance/acceptanceclient.go 2014-04-09 19:30:53 +0000 | |||
1256 | +++ server/acceptance/acceptanceclient.go 2014-05-12 14:11:46 +0000 | |||
1257 | @@ -24,6 +24,7 @@ | |||
1258 | 24 | "errors" | 24 | "errors" |
1259 | 25 | "fmt" | 25 | "fmt" |
1260 | 26 | "net" | 26 | "net" |
1261 | 27 | "strings" | ||
1262 | 27 | "time" | 28 | "time" |
1263 | 28 | 29 | ||
1264 | 29 | "launchpad.net/ubuntu-push/protocol" | 30 | "launchpad.net/ubuntu-push/protocol" |
1265 | @@ -44,6 +45,7 @@ | |||
1266 | 44 | Levels map[string]int64 | 45 | Levels map[string]int64 |
1267 | 45 | Insecure bool // don't verify certs | 46 | Insecure bool // don't verify certs |
1268 | 46 | Prefix string // prefix for events | 47 | Prefix string // prefix for events |
1269 | 48 | Auth string | ||
1270 | 47 | // connection | 49 | // connection |
1271 | 48 | Connection net.Conn | 50 | Connection net.Conn |
1272 | 49 | } | 51 | } |
1273 | @@ -73,6 +75,7 @@ | |||
1274 | 73 | Type string `json:"T"` | 75 | Type string `json:"T"` |
1275 | 74 | protocol.BroadcastMsg | 76 | protocol.BroadcastMsg |
1276 | 75 | protocol.NotificationsMsg | 77 | protocol.NotificationsMsg |
1277 | 78 | protocol.ConnWarnMsg | ||
1278 | 76 | } | 79 | } |
1279 | 77 | 80 | ||
1280 | 78 | // Run the session with the server, emits a stream of events. | 81 | // Run the session with the server, emits a stream of events. |
1281 | @@ -93,6 +96,7 @@ | |||
1282 | 93 | "device": sess.Model, | 96 | "device": sess.Model, |
1283 | 94 | "channel": sess.ImageChannel, | 97 | "channel": sess.ImageChannel, |
1284 | 95 | }, | 98 | }, |
1285 | 99 | Authorization: sess.Auth, | ||
1286 | 96 | }) | 100 | }) |
1287 | 97 | if err != nil { | 101 | if err != nil { |
1288 | 98 | return err | 102 | return err |
1289 | @@ -125,9 +129,24 @@ | |||
1290 | 125 | if sess.ReportPings { | 129 | if sess.ReportPings { |
1291 | 126 | events <- sess.Prefix + "ping" | 130 | events <- sess.Prefix + "ping" |
1292 | 127 | } | 131 | } |
1293 | 132 | case "notifications": | ||
1294 | 133 | conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) | ||
1295 | 134 | err := proto.WriteMessage(protocol.AckMsg{Type: "ack"}) | ||
1296 | 135 | if err != nil { | ||
1297 | 136 | return err | ||
1298 | 137 | } | ||
1299 | 138 | parts := make([]string, len(recv.Notifications)) | ||
1300 | 139 | for i, notif := range recv.Notifications { | ||
1301 | 140 | pack, err := json.Marshal(¬if.Payload) | ||
1302 | 141 | if err != nil { | ||
1303 | 142 | return err | ||
1304 | 143 | } | ||
1305 | 144 | parts[i] = fmt.Sprintf("app:%v payload:%s;", notif.AppId, pack) | ||
1306 | 145 | } | ||
1307 | 146 | events <- fmt.Sprintf("%sunicast %s", sess.Prefix, strings.Join(parts, " ")) | ||
1308 | 128 | case "broadcast": | 147 | case "broadcast": |
1309 | 129 | conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) | 148 | conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) |
1311 | 130 | err := proto.WriteMessage(protocol.PingPongMsg{Type: "ack"}) | 149 | err := proto.WriteMessage(protocol.AckMsg{Type: "ack"}) |
1312 | 131 | if err != nil { | 150 | if err != nil { |
1313 | 132 | return err | 151 | return err |
1314 | 133 | } | 152 | } |
1315 | @@ -136,6 +155,8 @@ | |||
1316 | 136 | return err | 155 | return err |
1317 | 137 | } | 156 | } |
1318 | 138 | events <- fmt.Sprintf("%sbroadcast chan:%v app:%v topLevel:%d payloads:%s", sess.Prefix, recv.ChanId, recv.AppId, recv.TopLevel, pack) | 157 | events <- fmt.Sprintf("%sbroadcast chan:%v app:%v topLevel:%d payloads:%s", sess.Prefix, recv.ChanId, recv.AppId, recv.TopLevel, pack) |
1319 | 158 | case "warn": | ||
1320 | 159 | events <- fmt.Sprintf("%swarn %s", sess.Prefix, recv.Reason) | ||
1321 | 139 | } | 160 | } |
1322 | 140 | } | 161 | } |
1323 | 141 | return nil | 162 | return nil |
1324 | 142 | 163 | ||
1325 | === modified file 'server/acceptance/suites/broadcast.go' | |||
1326 | --- server/acceptance/suites/broadcast.go 2014-04-07 19:39:19 +0000 | |||
1327 | +++ server/acceptance/suites/broadcast.go 2014-05-12 14:11:46 +0000 | |||
1328 | @@ -29,14 +29,11 @@ | |||
1329 | 29 | "launchpad.net/ubuntu-push/server/api" | 29 | "launchpad.net/ubuntu-push/server/api" |
1330 | 30 | ) | 30 | ) |
1331 | 31 | 31 | ||
1333 | 32 | // BroadCastAcceptanceSuite has tests about broadcast. | 32 | // BroadcastAcceptanceSuite has tests about broadcast. |
1334 | 33 | type BroadcastAcceptanceSuite struct { | 33 | type BroadcastAcceptanceSuite struct { |
1335 | 34 | AcceptanceSuite | 34 | AcceptanceSuite |
1336 | 35 | } | 35 | } |
1337 | 36 | 36 | ||
1338 | 37 | // Long after the end of the tests. | ||
1339 | 38 | var future = time.Now().Add(9 * time.Hour).Format(time.RFC3339) | ||
1340 | 39 | |||
1341 | 40 | func (s *BroadcastAcceptanceSuite) TestBroadcastToConnected(c *C) { | 37 | func (s *BroadcastAcceptanceSuite) TestBroadcastToConnected(c *C) { |
1342 | 41 | events, errCh, stop := s.StartClient(c, "DEVB", nil) | 38 | events, errCh, stop := s.StartClient(c, "DEVB", nil) |
1343 | 42 | got, err := s.PostRequest("/broadcast", &api.Broadcast{ | 39 | got, err := s.PostRequest("/broadcast", &api.Broadcast{ |
1344 | @@ -265,7 +262,11 @@ | |||
1345 | 265 | 262 | ||
1346 | 266 | func (s *BroadcastAcceptanceSuite) TestGetHosts(c *C) { | 263 | func (s *BroadcastAcceptanceSuite) TestGetHosts(c *C) { |
1347 | 267 | gh := gethosts.New("", s.ServerAPIURL+"/delivery-hosts", 2*time.Second) | 264 | gh := gethosts.New("", s.ServerAPIURL+"/delivery-hosts", 2*time.Second) |
1349 | 268 | hosts, err := gh.Get() | 265 | host, err := gh.Get() |
1350 | 269 | c.Assert(err, IsNil) | 266 | c.Assert(err, IsNil) |
1352 | 270 | c.Check(hosts, DeepEquals, []string{s.ServerAddr}) | 267 | expected := &gethosts.Host{ |
1353 | 268 | Domain: "localhost", | ||
1354 | 269 | Hosts: []string{s.ServerAddr}, | ||
1355 | 270 | } | ||
1356 | 271 | c.Check(host, DeepEquals, expected) | ||
1357 | 271 | } | 272 | } |
1358 | 272 | 273 | ||
1359 | === modified file 'server/acceptance/suites/suite.go' | |||
1360 | --- server/acceptance/suites/suite.go 2014-04-03 16:47:47 +0000 | |||
1361 | +++ server/acceptance/suites/suite.go 2014-05-12 14:11:46 +0000 | |||
1362 | @@ -44,10 +44,19 @@ | |||
1363 | 44 | 44 | ||
1364 | 45 | // Start a client. | 45 | // Start a client. |
1365 | 46 | func (h *ServerHandle) StartClient(c *C, devId string, levels map[string]int64) (events <-chan string, errorCh <-chan error, stop func()) { | 46 | func (h *ServerHandle) StartClient(c *C, devId string, levels map[string]int64) (events <-chan string, errorCh <-chan error, stop func()) { |
1366 | 47 | return h.StartClientAuth(c, devId, levels, "") | ||
1367 | 48 | } | ||
1368 | 49 | |||
1369 | 50 | // Start a client with auth. | ||
1370 | 51 | func (h *ServerHandle) StartClientAuth(c *C, devId string, levels map[string]int64, auth string) (events <-chan string, errorCh <-chan error, stop func()) { | ||
1371 | 47 | errCh := make(chan error, 1) | 52 | errCh := make(chan error, 1) |
1372 | 48 | cliEvents := make(chan string, 10) | 53 | cliEvents := make(chan string, 10) |
1373 | 49 | sess := testClientSession(h.ServerAddr, devId, "m1", "img1", false) | 54 | sess := testClientSession(h.ServerAddr, devId, "m1", "img1", false) |
1374 | 50 | sess.Levels = levels | 55 | sess.Levels = levels |
1375 | 56 | sess.Auth = auth | ||
1376 | 57 | if auth != "" { | ||
1377 | 58 | sess.ExchangeTimeout = 5 * time.Second | ||
1378 | 59 | } | ||
1379 | 51 | err := sess.Dial() | 60 | err := sess.Dial() |
1380 | 52 | c.Assert(err, IsNil) | 61 | c.Assert(err, IsNil) |
1381 | 53 | clientShutdown := make(chan bool, 1) // abused as an atomic flag | 62 | clientShutdown := make(chan bool, 1) // abused as an atomic flag |
1382 | @@ -186,3 +195,6 @@ | |||
1383 | 186 | } | 195 | } |
1384 | 187 | return | 196 | return |
1385 | 188 | } | 197 | } |
1386 | 198 | |||
1387 | 199 | // Long after the end of the tests. | ||
1388 | 200 | var future = time.Now().Add(9 * time.Hour).Format(time.RFC3339) | ||
1389 | 189 | 201 | ||
1390 | === added file 'server/acceptance/suites/unicast.go' | |||
1391 | --- server/acceptance/suites/unicast.go 1970-01-01 00:00:00 +0000 | |||
1392 | +++ server/acceptance/suites/unicast.go 2014-05-12 14:11:46 +0000 | |||
1393 | @@ -0,0 +1,96 @@ | |||
1394 | 1 | /* | ||
1395 | 2 | Copyright 2013-2014 Canonical Ltd. | ||
1396 | 3 | |||
1397 | 4 | This program is free software: you can redistribute it and/or modify it | ||
1398 | 5 | under the terms of the GNU General Public License version 3, as published | ||
1399 | 6 | by the Free Software Foundation. | ||
1400 | 7 | |||
1401 | 8 | This program is distributed in the hope that it will be useful, but | ||
1402 | 9 | WITHOUT ANY WARRANTY; without even the implied warranties of | ||
1403 | 10 | MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
1404 | 11 | PURPOSE. See the GNU General Public License for more details. | ||
1405 | 12 | |||
1406 | 13 | You should have received a copy of the GNU General Public License along | ||
1407 | 14 | with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1408 | 15 | */ | ||
1409 | 16 | |||
1410 | 17 | package suites | ||
1411 | 18 | |||
1412 | 19 | import ( | ||
1413 | 20 | "encoding/json" | ||
1414 | 21 | //"fmt" | ||
1415 | 22 | //"strings" | ||
1416 | 23 | //"time" | ||
1417 | 24 | |||
1418 | 25 | . "launchpad.net/gocheck" | ||
1419 | 26 | |||
1420 | 27 | //"launchpad.net/ubuntu-push/protocol" | ||
1421 | 28 | "launchpad.net/ubuntu-push/server/api" | ||
1422 | 29 | ) | ||
1423 | 30 | |||
1424 | 31 | // UnicastAcceptanceSuite has tests about unicast. | ||
1425 | 32 | type UnicastAcceptanceSuite struct { | ||
1426 | 33 | AcceptanceSuite | ||
1427 | 34 | AssociatedAuth func(string) (string, string) | ||
1428 | 35 | } | ||
1429 | 36 | |||
1430 | 37 | func (s *UnicastAcceptanceSuite) associatedAuth(deviceId string) (userId string, auth string) { | ||
1431 | 38 | if s.AssociatedAuth != nil { | ||
1432 | 39 | return s.AssociatedAuth(deviceId) | ||
1433 | 40 | } | ||
1434 | 41 | return deviceId, "" | ||
1435 | 42 | } | ||
1436 | 43 | |||
1437 | 44 | func (s *UnicastAcceptanceSuite) TestUnicastToConnected(c *C) { | ||
1438 | 45 | userId, auth := s.associatedAuth("DEV1") | ||
1439 | 46 | events, errCh, stop := s.StartClientAuth(c, "DEV1", nil, auth) | ||
1440 | 47 | got, err := s.PostRequest("/notify", &api.Unicast{ | ||
1441 | 48 | UserId: userId, | ||
1442 | 49 | DeviceId: "DEV1", | ||
1443 | 50 | AppId: "app1", | ||
1444 | 51 | ExpireOn: future, | ||
1445 | 52 | Data: json.RawMessage(`{"a": 42}`), | ||
1446 | 53 | }) | ||
1447 | 54 | c.Assert(err, IsNil) | ||
1448 | 55 | c.Assert(got, Matches, ".*ok.*") | ||
1449 | 56 | c.Check(NextEvent(events, errCh), Equals, `unicast app:app1 payload:{"a":42};`) | ||
1450 | 57 | stop() | ||
1451 | 58 | c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) | ||
1452 | 59 | c.Check(len(errCh), Equals, 0) | ||
1453 | 60 | } | ||
1454 | 61 | |||
1455 | 62 | func (s *UnicastAcceptanceSuite) TestUnicastCorrectDistribution(c *C) { | ||
1456 | 63 | userId1, auth1 := s.associatedAuth("DEV1") | ||
1457 | 64 | userId2, auth2 := s.associatedAuth("DEV2") | ||
1458 | 65 | // start 1st client | ||
1459 | 66 | events1, errCh1, stop1 := s.StartClientAuth(c, "DEV1", nil, auth1) | ||
1460 | 67 | // start 2nd client | ||
1461 | 68 | events2, errCh2, stop2 := s.StartClientAuth(c, "DEV2", nil, auth2) | ||
1462 | 69 | // unicast to one and the other | ||
1463 | 70 | got, err := s.PostRequest("/notify", &api.Unicast{ | ||
1464 | 71 | UserId: userId1, | ||
1465 | 72 | DeviceId: "DEV1", | ||
1466 | 73 | AppId: "app1", | ||
1467 | 74 | ExpireOn: future, | ||
1468 | 75 | Data: json.RawMessage(`{"to": 1}`), | ||
1469 | 76 | }) | ||
1470 | 77 | c.Assert(err, IsNil) | ||
1471 | 78 | c.Assert(got, Matches, ".*ok.*") | ||
1472 | 79 | got, err = s.PostRequest("/notify", &api.Unicast{ | ||
1473 | 80 | UserId: userId2, | ||
1474 | 81 | DeviceId: "DEV2", | ||
1475 | 82 | AppId: "app1", | ||
1476 | 83 | ExpireOn: future, | ||
1477 | 84 | Data: json.RawMessage(`{"to": 2}`), | ||
1478 | 85 | }) | ||
1479 | 86 | c.Assert(err, IsNil) | ||
1480 | 87 | c.Assert(got, Matches, ".*ok.*") | ||
1481 | 88 | c.Check(NextEvent(events1, errCh1), Equals, `unicast app:app1 payload:{"to":1};`) | ||
1482 | 89 | c.Check(NextEvent(events2, errCh2), Equals, `unicast app:app1 payload:{"to":2};`) | ||
1483 | 90 | stop1() | ||
1484 | 91 | stop2() | ||
1485 | 92 | c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) | ||
1486 | 93 | c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) | ||
1487 | 94 | c.Check(len(errCh1), Equals, 0) | ||
1488 | 95 | c.Check(len(errCh2), Equals, 0) | ||
1489 | 96 | } | ||
1490 | 0 | 97 | ||
1491 | === modified file 'server/api/handlers.go' | |||
1492 | --- server/api/handlers.go 2014-02-20 17:09:03 +0000 | |||
1493 | +++ server/api/handlers.go 2014-05-12 14:11:46 +0000 | |||
1494 | @@ -19,12 +19,15 @@ | |||
1495 | 19 | package api | 19 | package api |
1496 | 20 | 20 | ||
1497 | 21 | import ( | 21 | import ( |
1498 | 22 | "encoding/base64" | ||
1499 | 22 | "encoding/json" | 23 | "encoding/json" |
1500 | 23 | "fmt" | 24 | "fmt" |
1501 | 24 | "io" | 25 | "io" |
1502 | 25 | "net/http" | 26 | "net/http" |
1503 | 26 | "time" | 27 | "time" |
1504 | 27 | 28 | ||
1505 | 29 | "launchpad.net/~ubuntu-push-hackers/ubuntu-push/go-uuid/uuid" | ||
1506 | 30 | |||
1507 | 28 | "launchpad.net/ubuntu-push/logger" | 31 | "launchpad.net/ubuntu-push/logger" |
1508 | 29 | "launchpad.net/ubuntu-push/server/broker" | 32 | "launchpad.net/ubuntu-push/server/broker" |
1509 | 30 | "launchpad.net/ubuntu-push/server/store" | 33 | "launchpad.net/ubuntu-push/server/store" |
1510 | @@ -93,6 +96,11 @@ | |||
1511 | 93 | ioError, | 96 | ioError, |
1512 | 94 | "Could not read request body", | 97 | "Could not read request body", |
1513 | 95 | } | 98 | } |
1514 | 99 | ErrMissingIdField = &APIError{ | ||
1515 | 100 | http.StatusBadRequest, | ||
1516 | 101 | invalidRequest, | ||
1517 | 102 | "Missing id field", | ||
1518 | 103 | } | ||
1519 | 96 | ErrMissingData = &APIError{ | 104 | ErrMissingData = &APIError{ |
1520 | 97 | http.StatusBadRequest, | 105 | http.StatusBadRequest, |
1521 | 98 | invalidRequest, | 106 | invalidRequest, |
1522 | @@ -130,10 +138,17 @@ | |||
1523 | 130 | } | 138 | } |
1524 | 131 | ) | 139 | ) |
1525 | 132 | 140 | ||
1530 | 133 | type Message struct { | 141 | type castCommon struct { |
1531 | 134 | Registration string `json:"registration"` | 142 | } |
1532 | 135 | CoalesceTag string `json:"coalesce_tag"` | 143 | |
1533 | 136 | Data json.RawMessage `json:"data"` | 144 | type Unicast struct { |
1534 | 145 | UserId string `json:"userid"` | ||
1535 | 146 | DeviceId string `json:"deviceid"` | ||
1536 | 147 | AppId string `json:"appid"` | ||
1537 | 148 | //Registration string `json:"registration"` | ||
1538 | 149 | //CoalesceTag string `json:"coalesce_tag"` | ||
1539 | 150 | ExpireOn string `json:"expire_on"` | ||
1540 | 151 | Data json.RawMessage `json:"data"` | ||
1541 | 137 | } | 152 | } |
1542 | 138 | 153 | ||
1543 | 139 | // Broadcast request JSON object. | 154 | // Broadcast request JSON object. |
1544 | @@ -198,11 +213,11 @@ | |||
1545 | 198 | 213 | ||
1546 | 199 | var zeroTime = time.Time{} | 214 | var zeroTime = time.Time{} |
1547 | 200 | 215 | ||
1550 | 201 | func checkBroadcast(bcast *Broadcast) (time.Time, *APIError) { | 216 | func checkCastCommon(data json.RawMessage, expireOn string) (time.Time, *APIError) { |
1551 | 202 | if len(bcast.Data) == 0 { | 217 | if len(data) == 0 { |
1552 | 203 | return zeroTime, ErrMissingData | 218 | return zeroTime, ErrMissingData |
1553 | 204 | } | 219 | } |
1555 | 205 | expire, err := time.Parse(time.RFC3339, bcast.ExpireOn) | 220 | expire, err := time.Parse(time.RFC3339, expireOn) |
1556 | 206 | if err != nil { | 221 | if err != nil { |
1557 | 207 | return zeroTime, ErrInvalidExpiration | 222 | return zeroTime, ErrInvalidExpiration |
1558 | 208 | } | 223 | } |
1559 | @@ -212,6 +227,10 @@ | |||
1560 | 212 | return expire, nil | 227 | return expire, nil |
1561 | 213 | } | 228 | } |
1562 | 214 | 229 | ||
1563 | 230 | func checkBroadcast(bcast *Broadcast) (time.Time, *APIError) { | ||
1564 | 231 | return checkCastCommon(bcast.Data, bcast.ExpireOn) | ||
1565 | 232 | } | ||
1566 | 233 | |||
1567 | 215 | type StoreForRequest func(w http.ResponseWriter, request *http.Request) (store.PendingStore, error) | 234 | type StoreForRequest func(w http.ResponseWriter, request *http.Request) (store.PendingStore, error) |
1568 | 216 | 235 | ||
1569 | 217 | // context holds the interfaces to delegate to serving requests | 236 | // context holds the interfaces to delegate to serving requests |
1570 | @@ -234,6 +253,20 @@ | |||
1571 | 234 | return sto, nil | 253 | return sto, nil |
1572 | 235 | } | 254 | } |
1573 | 236 | 255 | ||
1574 | 256 | func (ctx *context) prepare(w http.ResponseWriter, request *http.Request, reqObj interface{}) (store.PendingStore, *APIError) { | ||
1575 | 257 | body, apiErr := ReadBody(request, MaxRequestBodyBytes) | ||
1576 | 258 | if apiErr != nil { | ||
1577 | 259 | return nil, apiErr | ||
1578 | 260 | } | ||
1579 | 261 | |||
1580 | 262 | err := json.Unmarshal(body, reqObj) | ||
1581 | 263 | if err != nil { | ||
1582 | 264 | return nil, ErrMalformedJSONObject | ||
1583 | 265 | } | ||
1584 | 266 | |||
1585 | 267 | return ctx.getStore(w, request) | ||
1586 | 268 | } | ||
1587 | 269 | |||
1588 | 237 | type BroadcastHandler struct { | 270 | type BroadcastHandler struct { |
1589 | 238 | *context | 271 | *context |
1590 | 239 | } | 272 | } |
1591 | @@ -270,23 +303,13 @@ | |||
1592 | 270 | } | 303 | } |
1593 | 271 | }() | 304 | }() |
1594 | 272 | 305 | ||
1595 | 273 | body, apiErr := ReadBody(request, MaxRequestBodyBytes) | ||
1596 | 274 | if apiErr != nil { | ||
1597 | 275 | return | ||
1598 | 276 | } | ||
1599 | 277 | |||
1600 | 278 | sto, apiErr := h.getStore(writer, request) | ||
1601 | 279 | if apiErr != nil { | ||
1602 | 280 | return | ||
1603 | 281 | } | ||
1604 | 282 | defer sto.Close() | ||
1605 | 283 | |||
1606 | 284 | broadcast := &Broadcast{} | 306 | broadcast := &Broadcast{} |
1610 | 285 | err := json.Unmarshal(body, broadcast) | 307 | |
1611 | 286 | if err != nil { | 308 | sto, apiErr := h.prepare(writer, request, broadcast) |
1612 | 287 | apiErr = ErrMalformedJSONObject | 309 | if apiErr != nil { |
1613 | 288 | return | 310 | return |
1614 | 289 | } | 311 | } |
1615 | 312 | defer sto.Close() | ||
1616 | 290 | 313 | ||
1617 | 291 | apiErr = h.doBroadcast(sto, broadcast) | 314 | apiErr = h.doBroadcast(sto, broadcast) |
1618 | 292 | if apiErr != nil { | 315 | if apiErr != nil { |
1619 | @@ -297,6 +320,64 @@ | |||
1620 | 297 | fmt.Fprintf(writer, `{"ok":true}`) | 320 | fmt.Fprintf(writer, `{"ok":true}`) |
1621 | 298 | } | 321 | } |
1622 | 299 | 322 | ||
1623 | 323 | type UnicastHandler struct { | ||
1624 | 324 | *context | ||
1625 | 325 | } | ||
1626 | 326 | |||
1627 | 327 | func checkUnicast(ucast *Unicast) (time.Time, *APIError) { | ||
1628 | 328 | if ucast.UserId == "" || ucast.DeviceId == "" || ucast.AppId == "" { | ||
1629 | 329 | return zeroTime, ErrMissingIdField | ||
1630 | 330 | } | ||
1631 | 331 | return checkCastCommon(ucast.Data, ucast.ExpireOn) | ||
1632 | 332 | } | ||
1633 | 333 | |||
1634 | 334 | // use a base64 encoded TimeUUID | ||
1635 | 335 | var generateMsgId = func() string { | ||
1636 | 336 | return base64.StdEncoding.EncodeToString(uuid.NewUUID()) | ||
1637 | 337 | } | ||
1638 | 338 | |||
1639 | 339 | func (h *UnicastHandler) doUnicast(sto store.PendingStore, ucast *Unicast) *APIError { | ||
1640 | 340 | expire, apiErr := checkUnicast(ucast) | ||
1641 | 341 | if apiErr != nil { | ||
1642 | 342 | return apiErr | ||
1643 | 343 | } | ||
1644 | 344 | chanId := store.UnicastInternalChannelId(ucast.UserId, ucast.DeviceId) | ||
1645 | 345 | msgId := generateMsgId() | ||
1646 | 346 | err := sto.AppendToUnicastChannel(chanId, ucast.AppId, ucast.Data, msgId, expire) | ||
1647 | 347 | if err != nil { | ||
1648 | 348 | h.logger.Errorf("could not store notification: %v", err) | ||
1649 | 349 | return ErrCouldNotStoreNotification | ||
1650 | 350 | } | ||
1651 | 351 | |||
1652 | 352 | h.broker.Unicast(chanId) | ||
1653 | 353 | return nil | ||
1654 | 354 | } | ||
1655 | 355 | |||
1656 | 356 | func (h *UnicastHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { | ||
1657 | 357 | var apiErr *APIError | ||
1658 | 358 | defer func() { | ||
1659 | 359 | if apiErr != nil { | ||
1660 | 360 | RespondError(writer, apiErr) | ||
1661 | 361 | } | ||
1662 | 362 | }() | ||
1663 | 363 | |||
1664 | 364 | unicast := &Unicast{} | ||
1665 | 365 | |||
1666 | 366 | sto, apiErr := h.prepare(writer, request, unicast) | ||
1667 | 367 | if apiErr != nil { | ||
1668 | 368 | return | ||
1669 | 369 | } | ||
1670 | 370 | defer sto.Close() | ||
1671 | 371 | |||
1672 | 372 | apiErr = h.doUnicast(sto, unicast) | ||
1673 | 373 | if apiErr != nil { | ||
1674 | 374 | return | ||
1675 | 375 | } | ||
1676 | 376 | |||
1677 | 377 | writer.Header().Set("Content-Type", "application/json") | ||
1678 | 378 | fmt.Fprintf(writer, `{"ok":true}`) | ||
1679 | 379 | } | ||
1680 | 380 | |||
1681 | 300 | // MakeHandlersMux makes a handler that dispatches for the various API endpoints. | 381 | // MakeHandlersMux makes a handler that dispatches for the various API endpoints. |
1682 | 301 | func MakeHandlersMux(storeForRequest StoreForRequest, broker broker.BrokerSending, logger logger.Logger) *http.ServeMux { | 382 | func MakeHandlersMux(storeForRequest StoreForRequest, broker broker.BrokerSending, logger logger.Logger) *http.ServeMux { |
1683 | 302 | ctx := &context{ | 383 | ctx := &context{ |
1684 | @@ -306,5 +387,6 @@ | |||
1685 | 306 | } | 387 | } |
1686 | 307 | mux := http.NewServeMux() | 388 | mux := http.NewServeMux() |
1687 | 308 | mux.Handle("/broadcast", &BroadcastHandler{context: ctx}) | 389 | mux.Handle("/broadcast", &BroadcastHandler{context: ctx}) |
1688 | 390 | mux.Handle("/notify", &UnicastHandler{context: ctx}) | ||
1689 | 309 | return mux | 391 | return mux |
1690 | 310 | } | 392 | } |
1691 | 311 | 393 | ||
1692 | === modified file 'server/api/handlers_test.go' | |||
1693 | --- server/api/handlers_test.go 2014-02-21 11:32:38 +0000 | |||
1694 | +++ server/api/handlers_test.go 2014-05-12 14:11:46 +0000 | |||
1695 | @@ -18,6 +18,7 @@ | |||
1696 | 18 | 18 | ||
1697 | 19 | import ( | 19 | import ( |
1698 | 20 | "bytes" | 20 | "bytes" |
1699 | 21 | "encoding/base64" | ||
1700 | 21 | "encoding/json" | 22 | "encoding/json" |
1701 | 22 | "errors" | 23 | "errors" |
1702 | 23 | "fmt" | 24 | "fmt" |
1703 | @@ -30,8 +31,9 @@ | |||
1704 | 30 | 31 | ||
1705 | 31 | . "launchpad.net/gocheck" | 32 | . "launchpad.net/gocheck" |
1706 | 32 | 33 | ||
1707 | 34 | "launchpad.net/ubuntu-push/protocol" | ||
1708 | 33 | "launchpad.net/ubuntu-push/server/store" | 35 | "launchpad.net/ubuntu-push/server/store" |
1710 | 34 | helpers "launchpad.net/ubuntu-push/testing" | 36 | help "launchpad.net/ubuntu-push/testing" |
1711 | 35 | ) | 37 | ) |
1712 | 36 | 38 | ||
1713 | 37 | func TestHandlers(t *testing.T) { TestingT(t) } | 39 | func TestHandlers(t *testing.T) { TestingT(t) } |
1714 | @@ -41,14 +43,14 @@ | |||
1715 | 41 | json string | 43 | json string |
1716 | 42 | client *http.Client | 44 | client *http.Client |
1717 | 43 | c *C | 45 | c *C |
1719 | 44 | testlog *helpers.TestLogger | 46 | testlog *help.TestLogger |
1720 | 45 | } | 47 | } |
1721 | 46 | 48 | ||
1722 | 47 | var _ = Suite(&handlersSuite{}) | 49 | var _ = Suite(&handlersSuite{}) |
1723 | 48 | 50 | ||
1724 | 49 | func (s *handlersSuite) SetUpTest(c *C) { | 51 | func (s *handlersSuite) SetUpTest(c *C) { |
1725 | 50 | s.client = &http.Client{} | 52 | s.client = &http.Client{} |
1727 | 51 | s.testlog = helpers.NewTestLogger(c, "error") | 53 | s.testlog = help.NewTestLogger(c, "error") |
1728 | 52 | } | 54 | } |
1729 | 53 | 55 | ||
1730 | 54 | func (s *handlersSuite) TestAPIError(c *C) { | 56 | func (s *handlersSuite) TestAPIError(c *C) { |
1731 | @@ -98,7 +100,7 @@ | |||
1732 | 98 | 100 | ||
1733 | 99 | var future = time.Now().Add(4 * time.Hour).Format(time.RFC3339) | 101 | var future = time.Now().Add(4 * time.Hour).Format(time.RFC3339) |
1734 | 100 | 102 | ||
1736 | 101 | func (s *handlersSuite) TestCheckBroadcast(c *C) { | 103 | func (s *handlersSuite) TestCheckCastBroadcastAndCommon(c *C) { |
1737 | 102 | payload := json.RawMessage(`{"foo":"bar"}`) | 104 | payload := json.RawMessage(`{"foo":"bar"}`) |
1738 | 103 | broadcast := &Broadcast{ | 105 | broadcast := &Broadcast{ |
1739 | 104 | Channel: "system", | 106 | Channel: "system", |
1740 | @@ -134,19 +136,27 @@ | |||
1741 | 134 | } | 136 | } |
1742 | 135 | 137 | ||
1743 | 136 | type checkBrokerSending struct { | 138 | type checkBrokerSending struct { |
1749 | 137 | store store.PendingStore | 139 | store store.PendingStore |
1750 | 138 | chanId store.InternalChannelId | 140 | chanId store.InternalChannelId |
1751 | 139 | err error | 141 | err error |
1752 | 140 | top int64 | 142 | top int64 |
1753 | 141 | payloads []json.RawMessage | 143 | notifications []protocol.Notification |
1754 | 142 | } | 144 | } |
1755 | 143 | 145 | ||
1756 | 144 | func (cbsend *checkBrokerSending) Broadcast(chanId store.InternalChannelId) { | 146 | func (cbsend *checkBrokerSending) Broadcast(chanId store.InternalChannelId) { |
1758 | 145 | top, payloads, err := cbsend.store.GetChannelSnapshot(chanId) | 147 | top, notifications, err := cbsend.store.GetChannelSnapshot(chanId) |
1759 | 146 | cbsend.err = err | 148 | cbsend.err = err |
1760 | 147 | cbsend.chanId = chanId | 149 | cbsend.chanId = chanId |
1761 | 148 | cbsend.top = top | 150 | cbsend.top = top |
1763 | 149 | cbsend.payloads = payloads | 151 | cbsend.notifications = notifications |
1764 | 152 | } | ||
1765 | 153 | |||
1766 | 154 | func (cbsend *checkBrokerSending) Unicast(chanIds ...store.InternalChannelId) { | ||
1767 | 155 | // for now | ||
1768 | 156 | if len(chanIds) != 1 { | ||
1769 | 157 | panic("not expecting many chan ids for now") | ||
1770 | 158 | } | ||
1771 | 159 | cbsend.Broadcast(chanIds[0]) | ||
1772 | 150 | } | 160 | } |
1773 | 151 | 161 | ||
1774 | 152 | func (s *handlersSuite) TestDoBroadcast(c *C) { | 162 | func (s *handlersSuite) TestDoBroadcast(c *C) { |
1775 | @@ -163,7 +173,7 @@ | |||
1776 | 163 | c.Check(bsend.err, IsNil) | 173 | c.Check(bsend.err, IsNil) |
1777 | 164 | c.Check(bsend.chanId, Equals, store.SystemInternalChannelId) | 174 | c.Check(bsend.chanId, Equals, store.SystemInternalChannelId) |
1778 | 165 | c.Check(bsend.top, Equals, int64(1)) | 175 | c.Check(bsend.top, Equals, int64(1)) |
1780 | 166 | c.Check(bsend.payloads, DeepEquals, []json.RawMessage{payload}) | 176 | c.Check(bsend.notifications, DeepEquals, help.Ns(payload)) |
1781 | 167 | } | 177 | } |
1782 | 168 | 178 | ||
1783 | 169 | func (s *handlersSuite) TestDoBroadcastUnknownChannel(c *C) { | 179 | func (s *handlersSuite) TestDoBroadcastUnknownChannel(c *C) { |
1784 | @@ -192,6 +202,11 @@ | |||
1785 | 192 | return isto.intercept("AppendToChannel", err) | 202 | return isto.intercept("AppendToChannel", err) |
1786 | 193 | } | 203 | } |
1787 | 194 | 204 | ||
1788 | 205 | func (isto *interceptInMemoryPendingStore) AppendToUnicastChannel(chanId store.InternalChannelId, appId string, payload json.RawMessage, msgId string, expiration time.Time) error { | ||
1789 | 206 | err := isto.InMemoryPendingStore.AppendToUnicastChannel(chanId, appId, payload, msgId, expiration) | ||
1790 | 207 | return isto.intercept("AppendToUnicastChannel", err) | ||
1791 | 208 | } | ||
1792 | 209 | |||
1793 | 195 | func (s *handlersSuite) TestDoBroadcastUnknownError(c *C) { | 210 | func (s *handlersSuite) TestDoBroadcastUnknownError(c *C) { |
1794 | 196 | sto := &interceptInMemoryPendingStore{ | 211 | sto := &interceptInMemoryPendingStore{ |
1795 | 197 | store.NewInMemoryPendingStore(), | 212 | store.NewInMemoryPendingStore(), |
1796 | @@ -229,6 +244,115 @@ | |||
1797 | 229 | c.Check(s.testlog.Captured(), Equals, "ERROR could not store notification: fail\n") | 244 | c.Check(s.testlog.Captured(), Equals, "ERROR could not store notification: fail\n") |
1798 | 230 | } | 245 | } |
1799 | 231 | 246 | ||
1800 | 247 | func (s *handlersSuite) TestCheckUnicast(c *C) { | ||
1801 | 248 | payload := json.RawMessage(`{"foo":"bar"}`) | ||
1802 | 249 | unicast := func() *Unicast { | ||
1803 | 250 | return &Unicast{ | ||
1804 | 251 | UserId: "user1", | ||
1805 | 252 | DeviceId: "DEV1", | ||
1806 | 253 | AppId: "app1", | ||
1807 | 254 | ExpireOn: future, | ||
1808 | 255 | Data: payload, | ||
1809 | 256 | } | ||
1810 | 257 | } | ||
1811 | 258 | u := unicast() | ||
1812 | 259 | expire, apiErr := checkUnicast(u) | ||
1813 | 260 | c.Assert(apiErr, IsNil) | ||
1814 | 261 | c.Check(expire.Format(time.RFC3339), Equals, future) | ||
1815 | 262 | |||
1816 | 263 | u = unicast() | ||
1817 | 264 | u.UserId = "" | ||
1818 | 265 | expire, apiErr = checkUnicast(u) | ||
1819 | 266 | c.Check(apiErr, Equals, ErrMissingIdField) | ||
1820 | 267 | |||
1821 | 268 | u = unicast() | ||
1822 | 269 | u.AppId = "" | ||
1823 | 270 | expire, apiErr = checkUnicast(u) | ||
1824 | 271 | c.Check(apiErr, Equals, ErrMissingIdField) | ||
1825 | 272 | |||
1826 | 273 | u = unicast() | ||
1827 | 274 | u.DeviceId = "" | ||
1828 | 275 | expire, apiErr = checkUnicast(u) | ||
1829 | 276 | c.Check(apiErr, Equals, ErrMissingIdField) | ||
1830 | 277 | |||
1831 | 278 | u = unicast() | ||
1832 | 279 | u.Data = json.RawMessage(nil) | ||
1833 | 280 | expire, apiErr = checkUnicast(u) | ||
1834 | 281 | c.Check(apiErr, Equals, ErrMissingData) | ||
1835 | 282 | } | ||
1836 | 283 | |||
1837 | 284 | func (s *handlersSuite) TestGenerateMsgId(c *C) { | ||
1838 | 285 | msgId := generateMsgId() | ||
1839 | 286 | decoded, err := base64.StdEncoding.DecodeString(msgId) | ||
1840 | 287 | c.Assert(err, IsNil) | ||
1841 | 288 | c.Check(decoded, HasLen, 16) | ||
1842 | 289 | } | ||
1843 | 290 | |||
1844 | 291 | func (s *handlersSuite) TestDoUnicast(c *C) { | ||
1845 | 292 | prevGenMsgId := generateMsgId | ||
1846 | 293 | defer func() { | ||
1847 | 294 | generateMsgId = prevGenMsgId | ||
1848 | 295 | }() | ||
1849 | 296 | generateMsgId = func() string { | ||
1850 | 297 | return "MSG-ID" | ||
1851 | 298 | } | ||
1852 | 299 | sto := store.NewInMemoryPendingStore() | ||
1853 | 300 | bsend := &checkBrokerSending{store: sto} | ||
1854 | 301 | bh := &UnicastHandler{&context{nil, bsend, nil}} | ||
1855 | 302 | payload := json.RawMessage(`{"a": 1}`) | ||
1856 | 303 | apiErr := bh.doUnicast(sto, &Unicast{ | ||
1857 | 304 | UserId: "user1", | ||
1858 | 305 | DeviceId: "DEV1", | ||
1859 | 306 | AppId: "app1", | ||
1860 | 307 | ExpireOn: future, | ||
1861 | 308 | Data: payload, | ||
1862 | 309 | }) | ||
1863 | 310 | c.Check(apiErr, IsNil) | ||
1864 | 311 | c.Check(bsend.err, IsNil) | ||
1865 | 312 | c.Check(bsend.chanId, Equals, store.UnicastInternalChannelId("user1", "DEV1")) | ||
1866 | 313 | c.Check(bsend.top, Equals, int64(0)) | ||
1867 | 314 | c.Check(bsend.notifications, DeepEquals, []protocol.Notification{ | ||
1868 | 315 | protocol.Notification{ | ||
1869 | 316 | AppId: "app1", | ||
1870 | 317 | MsgId: "MSG-ID", | ||
1871 | 318 | Payload: payload, | ||
1872 | 319 | }, | ||
1873 | 320 | }) | ||
1874 | 321 | } | ||
1875 | 322 | |||
1876 | 323 | func (s *handlersSuite) TestDoUnicastMissingIdField(c *C) { | ||
1877 | 324 | sto := store.NewInMemoryPendingStore() | ||
1878 | 325 | bh := &UnicastHandler{} | ||
1879 | 326 | apiErr := bh.doUnicast(sto, &Unicast{ | ||
1880 | 327 | ExpireOn: future, | ||
1881 | 328 | Data: json.RawMessage(`{"a": 1}`), | ||
1882 | 329 | }) | ||
1883 | 330 | c.Check(apiErr, Equals, ErrMissingIdField) | ||
1884 | 331 | } | ||
1885 | 332 | |||
1886 | 333 | func (s *handlersSuite) TestDoUnicastCouldNotStoreNotification(c *C) { | ||
1887 | 334 | sto := &interceptInMemoryPendingStore{ | ||
1888 | 335 | store.NewInMemoryPendingStore(), | ||
1889 | 336 | func(meth string, err error) error { | ||
1890 | 337 | if meth == "AppendToUnicastChannel" { | ||
1891 | 338 | return errors.New("fail") | ||
1892 | 339 | } | ||
1893 | 340 | return err | ||
1894 | 341 | }, | ||
1895 | 342 | } | ||
1896 | 343 | ctx := &context{logger: s.testlog} | ||
1897 | 344 | bh := &UnicastHandler{ctx} | ||
1898 | 345 | apiErr := bh.doUnicast(sto, &Unicast{ | ||
1899 | 346 | UserId: "user1", | ||
1900 | 347 | DeviceId: "DEV1", | ||
1901 | 348 | AppId: "app1", | ||
1902 | 349 | ExpireOn: future, | ||
1903 | 350 | Data: json.RawMessage(`{"a": 1}`), | ||
1904 | 351 | }) | ||
1905 | 352 | c.Check(apiErr, Equals, ErrCouldNotStoreNotification) | ||
1906 | 353 | c.Check(s.testlog.Captured(), Equals, "ERROR could not store notification: fail\n") | ||
1907 | 354 | } | ||
1908 | 355 | |||
1909 | 232 | func newPostRequest(path string, message interface{}, server *httptest.Server) *http.Request { | 356 | func newPostRequest(path string, message interface{}, server *httptest.Server) *http.Request { |
1910 | 233 | packedMessage, err := json.Marshal(message) | 357 | packedMessage, err := json.Marshal(message) |
1911 | 234 | if err != nil { | 358 | if err != nil { |
1912 | @@ -268,6 +392,14 @@ | |||
1913 | 268 | bsend.chanId <- chanId | 392 | bsend.chanId <- chanId |
1914 | 269 | } | 393 | } |
1915 | 270 | 394 | ||
1916 | 395 | func (bsend testBrokerSending) Unicast(chanIds ...store.InternalChannelId) { | ||
1917 | 396 | // for now | ||
1918 | 397 | if len(chanIds) != 1 { | ||
1919 | 398 | panic("not expecting many chan ids for now") | ||
1920 | 399 | } | ||
1921 | 400 | bsend.chanId <- chanIds[0] | ||
1922 | 401 | } | ||
1923 | 402 | |||
1924 | 271 | func (s *handlersSuite) TestRespondsToBasicSystemBroadcast(c *C) { | 403 | func (s *handlersSuite) TestRespondsToBasicSystemBroadcast(c *C) { |
1925 | 272 | sto := store.NewInMemoryPendingStore() | 404 | sto := store.NewInMemoryPendingStore() |
1926 | 273 | stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) { | 405 | stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) { |
1927 | @@ -480,3 +612,73 @@ | |||
1928 | 480 | 612 | ||
1929 | 481 | checkError(c, response, ErrWrongRequestMethod) | 613 | checkError(c, response, ErrWrongRequestMethod) |
1930 | 482 | } | 614 | } |
1931 | 615 | |||
1932 | 616 | func (s *handlersSuite) TestRespondsUnicast(c *C) { | ||
1933 | 617 | sto := store.NewInMemoryPendingStore() | ||
1934 | 618 | stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) { | ||
1935 | 619 | return sto, nil | ||
1936 | 620 | } | ||
1937 | 621 | bsend := testBrokerSending{make(chan store.InternalChannelId, 1)} | ||
1938 | 622 | testServer := httptest.NewServer(MakeHandlersMux(stoForReq, bsend, nil)) | ||
1939 | 623 | defer testServer.Close() | ||
1940 | 624 | |||
1941 | 625 | payload := json.RawMessage(`{"foo":"bar"}`) | ||
1942 | 626 | |||
1943 | 627 | request := newPostRequest("/notify", &Unicast{ | ||
1944 | 628 | UserId: "user2", | ||
1945 | 629 | DeviceId: "dev3", | ||
1946 | 630 | AppId: "app2", | ||
1947 | 631 | ExpireOn: future, | ||
1948 | 632 | Data: payload, | ||
1949 | 633 | }, testServer) | ||
1950 | 634 | |||
1951 | 635 | response, err := s.client.Do(request) | ||
1952 | 636 | c.Assert(err, IsNil) | ||
1953 | 637 | |||
1954 | 638 | c.Check(response.StatusCode, Equals, http.StatusOK) | ||
1955 | 639 | c.Check(response.Header.Get("Content-Type"), Equals, "application/json") | ||
1956 | 640 | body, err := getResponseBody(response) | ||
1957 | 641 | c.Assert(err, IsNil) | ||
1958 | 642 | c.Check(string(body), Matches, ".*ok.*") | ||
1959 | 643 | |||
1960 | 644 | chanId := store.UnicastInternalChannelId("user2", "dev3") | ||
1961 | 645 | c.Check(<-bsend.chanId, Equals, chanId) | ||
1962 | 646 | top, notifications, err := sto.GetChannelSnapshot(chanId) | ||
1963 | 647 | c.Assert(err, IsNil) | ||
1964 | 648 | c.Check(top, Equals, int64(0)) | ||
1965 | 649 | c.Check(notifications, HasLen, 1) | ||
1966 | 650 | } | ||
1967 | 651 | |||
1968 | 652 | func (s *handlersSuite) TestCannotUnicastTooBigMessages(c *C) { | ||
1969 | 653 | testServer := httptest.NewServer(&UnicastHandler{}) | ||
1970 | 654 | defer testServer.Close() | ||
1971 | 655 | |||
1972 | 656 | bigString := strings.Repeat("a", MaxRequestBodyBytes) | ||
1973 | 657 | dataString := fmt.Sprintf(`"%v"`, bigString) | ||
1974 | 658 | |||
1975 | 659 | request := newPostRequest("/", &Unicast{ | ||
1976 | 660 | ExpireOn: future, | ||
1977 | 661 | Data: json.RawMessage([]byte(dataString)), | ||
1978 | 662 | }, testServer) | ||
1979 | 663 | |||
1980 | 664 | response, err := s.client.Do(request) | ||
1981 | 665 | c.Assert(err, IsNil) | ||
1982 | 666 | checkError(c, response, ErrRequestBodyTooLarge) | ||
1983 | 667 | } | ||
1984 | 668 | |||
1985 | 669 | func (s *handlersSuite) TestCannotUnicastWithMissingFields(c *C) { | ||
1986 | 670 | stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) { | ||
1987 | 671 | return store.NewInMemoryPendingStore(), nil | ||
1988 | 672 | } | ||
1989 | 673 | ctx := &context{stoForReq, nil, nil} | ||
1990 | 674 | testServer := httptest.NewServer(&UnicastHandler{ctx}) | ||
1991 | 675 | defer testServer.Close() | ||
1992 | 676 | |||
1993 | 677 | request := newPostRequest("/", &Unicast{ | ||
1994 | 678 | Data: json.RawMessage(`{"foo":"bar"}`), | ||
1995 | 679 | }, testServer) | ||
1996 | 680 | |||
1997 | 681 | response, err := s.client.Do(request) | ||
1998 | 682 | c.Assert(err, IsNil) | ||
1999 | 683 | checkError(c, response, ErrMissingIdField) | ||
2000 | 684 | } | ||
2001 | 483 | 685 | ||
2002 | === modified file 'server/broker/broker.go' | |||
2003 | --- server/broker/broker.go 2014-04-04 09:58:34 +0000 | |||
2004 | +++ server/broker/broker.go 2014-05-12 14:11:46 +0000 | |||
2005 | @@ -30,7 +30,7 @@ | |||
2006 | 30 | // through them. | 30 | // through them. |
2007 | 31 | type Broker interface { | 31 | type Broker interface { |
2008 | 32 | // Register the session. | 32 | // Register the session. |
2010 | 33 | Register(*protocol.ConnectMsg) (BrokerSession, error) | 33 | Register(connMsg *protocol.ConnectMsg, sessionId string) (BrokerSession, error) |
2011 | 34 | // Unregister the session. | 34 | // Unregister the session. |
2012 | 35 | Unregister(BrokerSession) | 35 | Unregister(BrokerSession) |
2013 | 36 | } | 36 | } |
2014 | @@ -39,6 +39,8 @@ | |||
2015 | 39 | type BrokerSending interface { | 39 | type BrokerSending interface { |
2016 | 40 | // Broadcast channel. | 40 | // Broadcast channel. |
2017 | 41 | Broadcast(chanId store.InternalChannelId) | 41 | Broadcast(chanId store.InternalChannelId) |
2018 | 42 | // Unicast over channels. | ||
2019 | 43 | Unicast(chanIds ...store.InternalChannelId) | ||
2020 | 42 | } | 44 | } |
2021 | 43 | 45 | ||
2022 | 44 | // Exchange leads the session through performing an exchange, typically delivery. | 46 | // Exchange leads the session through performing an exchange, typically delivery. |
2023 | @@ -81,6 +83,10 @@ | |||
2024 | 81 | Levels() LevelsMap | 83 | Levels() LevelsMap |
2025 | 82 | // ExchangeScratchArea returns the scratch area for exchanges. | 84 | // ExchangeScratchArea returns the scratch area for exchanges. |
2026 | 83 | ExchangeScratchArea() *ExchangesScratchArea | 85 | ExchangeScratchArea() *ExchangesScratchArea |
2027 | 86 | // Get gets the content of the channel with chanId. | ||
2028 | 87 | Get(chanId store.InternalChannelId, cachedOk bool) (int64, []protocol.Notification, error) | ||
2029 | 88 | // DropByMsgId drops notifications from the channel chanId by message id. | ||
2030 | 89 | DropByMsgId(chanId store.InternalChannelId, targets []protocol.Notification) error | ||
2031 | 84 | } | 90 | } |
2032 | 85 | 91 | ||
2033 | 86 | // Session aborted error. | 92 | // Session aborted error. |
2034 | 87 | 93 | ||
2035 | === modified file 'server/broker/exchanges.go' | |||
2036 | --- server/broker/exchanges.go 2014-04-04 13:19:10 +0000 | |||
2037 | +++ server/broker/exchanges.go 2014-05-12 14:11:46 +0000 | |||
2038 | @@ -28,18 +28,18 @@ | |||
2039 | 28 | 28 | ||
2040 | 29 | // Scratch area for exchanges, sessions should hold one of these. | 29 | // Scratch area for exchanges, sessions should hold one of these. |
2041 | 30 | type ExchangesScratchArea struct { | 30 | type ExchangesScratchArea struct { |
2045 | 31 | broadcastMsg protocol.BroadcastMsg | 31 | broadcastMsg protocol.BroadcastMsg |
2046 | 32 | ackMsg protocol.AckMsg | 32 | notificationsMsg protocol.NotificationsMsg |
2047 | 33 | connBrokenMsg protocol.ConnBrokenMsg | 33 | ackMsg protocol.AckMsg |
2048 | 34 | } | 34 | } |
2049 | 35 | 35 | ||
2050 | 36 | // BroadcastExchange leads a session through delivering a BROADCAST. | 36 | // BroadcastExchange leads a session through delivering a BROADCAST. |
2051 | 37 | // For simplicity it is fully public. | 37 | // For simplicity it is fully public. |
2052 | 38 | type BroadcastExchange struct { | 38 | type BroadcastExchange struct { |
2057 | 39 | ChanId store.InternalChannelId | 39 | ChanId store.InternalChannelId |
2058 | 40 | TopLevel int64 | 40 | TopLevel int64 |
2059 | 41 | NotificationPayloads []json.RawMessage | 41 | Notifications []protocol.Notification |
2060 | 42 | Decoded []map[string]interface{} | 42 | Decoded []map[string]interface{} |
2061 | 43 | } | 43 | } |
2062 | 44 | 44 | ||
2063 | 45 | // check interface already here | 45 | // check interface already here |
2064 | @@ -47,18 +47,18 @@ | |||
2065 | 47 | 47 | ||
2066 | 48 | // Init ensures the BroadcastExchange is fully initialized for the sessions. | 48 | // Init ensures the BroadcastExchange is fully initialized for the sessions. |
2067 | 49 | func (sbe *BroadcastExchange) Init() { | 49 | func (sbe *BroadcastExchange) Init() { |
2069 | 50 | decoded := make([]map[string]interface{}, len(sbe.NotificationPayloads)) | 50 | decoded := make([]map[string]interface{}, len(sbe.Notifications)) |
2070 | 51 | sbe.Decoded = decoded | 51 | sbe.Decoded = decoded |
2073 | 52 | for i, p := range sbe.NotificationPayloads { | 52 | for i, notif := range sbe.Notifications { |
2074 | 53 | err := json.Unmarshal(p, &decoded[i]) | 53 | err := json.Unmarshal(notif.Payload, &decoded[i]) |
2075 | 54 | if err != nil { | 54 | if err != nil { |
2076 | 55 | decoded[i] = nil | 55 | decoded[i] = nil |
2077 | 56 | } | 56 | } |
2078 | 57 | } | 57 | } |
2079 | 58 | } | 58 | } |
2080 | 59 | 59 | ||
2083 | 60 | func filterByLevel(clientLevel, topLevel int64, payloads []json.RawMessage) []json.RawMessage { | 60 | func filterByLevel(clientLevel, topLevel int64, notifs []protocol.Notification) []protocol.Notification { |
2084 | 61 | c := int64(len(payloads)) | 61 | c := int64(len(notifs)) |
2085 | 62 | if c == 0 { | 62 | if c == 0 { |
2086 | 63 | return nil | 63 | return nil |
2087 | 64 | } | 64 | } |
2088 | @@ -67,32 +67,32 @@ | |||
2089 | 67 | delta = 1 | 67 | delta = 1 |
2090 | 68 | } | 68 | } |
2091 | 69 | if delta < c { | 69 | if delta < c { |
2093 | 70 | return payloads[c-delta:] | 70 | return notifs[c-delta:] |
2094 | 71 | } else { | 71 | } else { |
2096 | 72 | return payloads | 72 | return notifs |
2097 | 73 | } | 73 | } |
2098 | 74 | } | 74 | } |
2099 | 75 | 75 | ||
2103 | 76 | func channelFilter(tag string, chanId store.InternalChannelId, payloads []json.RawMessage, decoded []map[string]interface{}) []json.RawMessage { | 76 | func channelFilter(tag string, chanId store.InternalChannelId, notifs []protocol.Notification, decoded []map[string]interface{}) []json.RawMessage { |
2104 | 77 | if len(payloads) != 0 && chanId == store.SystemInternalChannelId { | 77 | if len(notifs) != 0 && chanId == store.SystemInternalChannelId { |
2105 | 78 | decoded := decoded[len(decoded)-len(payloads):] | 78 | decoded := decoded[len(decoded)-len(notifs):] |
2106 | 79 | filtered := make([]json.RawMessage, 0) | 79 | filtered := make([]json.RawMessage, 0) |
2107 | 80 | for i, decoded1 := range decoded { | 80 | for i, decoded1 := range decoded { |
2108 | 81 | if _, ok := decoded1[tag]; ok { | 81 | if _, ok := decoded1[tag]; ok { |
2110 | 82 | filtered = append(filtered, payloads[i]) | 82 | filtered = append(filtered, notifs[i].Payload) |
2111 | 83 | } | 83 | } |
2112 | 84 | } | 84 | } |
2114 | 85 | payloads = filtered | 85 | return filtered |
2115 | 86 | } | 86 | } |
2117 | 87 | return payloads | 87 | return protocol.ExtractPayloads(notifs) |
2118 | 88 | } | 88 | } |
2119 | 89 | 89 | ||
2120 | 90 | // Prepare session for a BROADCAST. | 90 | // Prepare session for a BROADCAST. |
2121 | 91 | func (sbe *BroadcastExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) { | 91 | func (sbe *BroadcastExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) { |
2122 | 92 | clientLevel := sess.Levels()[sbe.ChanId] | 92 | clientLevel := sess.Levels()[sbe.ChanId] |
2124 | 93 | payloads := filterByLevel(clientLevel, sbe.TopLevel, sbe.NotificationPayloads) | 93 | notifs := filterByLevel(clientLevel, sbe.TopLevel, sbe.Notifications) |
2125 | 94 | tag := fmt.Sprintf("%s/%s", sess.DeviceImageChannel(), sess.DeviceImageModel()) | 94 | tag := fmt.Sprintf("%s/%s", sess.DeviceImageChannel(), sess.DeviceImageModel()) |
2127 | 95 | payloads = channelFilter(tag, sbe.ChanId, payloads, sbe.Decoded) | 95 | payloads := channelFilter(tag, sbe.ChanId, notifs, sbe.Decoded) |
2128 | 96 | if len(payloads) == 0 && sbe.TopLevel >= clientLevel { | 96 | if len(payloads) == 0 && sbe.TopLevel >= clientLevel { |
2129 | 97 | // empty and don't need to force resync => do nothing | 97 | // empty and don't need to force resync => do nothing |
2130 | 98 | return nil, nil, ErrNop | 98 | return nil, nil, ErrNop |
2131 | @@ -119,23 +119,55 @@ | |||
2132 | 119 | return nil | 119 | return nil |
2133 | 120 | } | 120 | } |
2134 | 121 | 121 | ||
2154 | 122 | // ConnBrokenExchange breaks a session giving a reason. | 122 | // ConnMetaExchange allows to send a CONNBROKEN or CONNWARN message. |
2155 | 123 | type ConnBrokenExchange struct { | 123 | type ConnMetaExchange struct { |
2156 | 124 | Reason string | 124 | Msg protocol.OnewayMsg |
2157 | 125 | } | 125 | } |
2158 | 126 | 126 | ||
2159 | 127 | // check interface already here | 127 | // check interface already here |
2160 | 128 | var _ Exchange = (*ConnBrokenExchange)(nil) | 128 | var _ Exchange = (*ConnMetaExchange)(nil) |
2161 | 129 | 129 | ||
2162 | 130 | // Prepare session for a CONNBROKEN. | 130 | // Prepare session for a CONNBROKEN/WARN. |
2163 | 131 | func (cbe *ConnBrokenExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) { | 131 | func (cbe *ConnMetaExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) { |
2164 | 132 | scratchArea := sess.ExchangeScratchArea() | 132 | return cbe.Msg, nil, nil |
2165 | 133 | scratchArea.connBrokenMsg.Type = "connbroken" | 133 | } |
2166 | 134 | scratchArea.connBrokenMsg.Reason = cbe.Reason | 134 | |
2167 | 135 | return &scratchArea.connBrokenMsg, nil, nil | 135 | // CONNBROKEN/WARN aren't acked. |
2168 | 136 | } | 136 | func (cbe *ConnMetaExchange) Acked(sess BrokerSession, done bool) error { |
2169 | 137 | 137 | panic("Acked should not get invoked on ConnMetaExchange") | |
2170 | 138 | // CONNBROKEN isn't acked | 138 | } |
2171 | 139 | func (cbe *ConnBrokenExchange) Acked(sess BrokerSession, done bool) error { | 139 | |
2172 | 140 | panic("Acked should not get invoked on ConnBrokenExchange") | 140 | // UnicastExchange leads a session through delivering a NOTIFICATIONS message. |
2173 | 141 | // For simplicity it is fully public. | ||
2174 | 142 | type UnicastExchange struct { | ||
2175 | 143 | ChanId store.InternalChannelId | ||
2176 | 144 | } | ||
2177 | 145 | |||
2178 | 146 | // check interface already here | ||
2179 | 147 | var _ Exchange = (*UnicastExchange)(nil) | ||
2180 | 148 | |||
2181 | 149 | // Prepare session for a NOTIFICATIONS. | ||
2182 | 150 | func (sue *UnicastExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) { | ||
2183 | 151 | _, notifs, err := sess.Get(sue.ChanId, false) | ||
2184 | 152 | if err != nil { | ||
2185 | 153 | return nil, nil, err | ||
2186 | 154 | } | ||
2187 | 155 | scratchArea := sess.ExchangeScratchArea() | ||
2188 | 156 | scratchArea.notificationsMsg.Reset() | ||
2189 | 157 | scratchArea.notificationsMsg.Type = "notifications" | ||
2190 | 158 | scratchArea.notificationsMsg.Notifications = notifs | ||
2191 | 159 | return &scratchArea.notificationsMsg, &scratchArea.ackMsg, nil | ||
2192 | 160 | } | ||
2193 | 161 | |||
2194 | 162 | // Acked deals with an ACK for a NOTIFICATIONS. | ||
2195 | 163 | func (sue *UnicastExchange) Acked(sess BrokerSession, done bool) error { | ||
2196 | 164 | scratchArea := sess.ExchangeScratchArea() | ||
2197 | 165 | if scratchArea.ackMsg.Type != "ack" { | ||
2198 | 166 | return &ErrAbort{"expected ACK message"} | ||
2199 | 167 | } | ||
2200 | 168 | err := sess.DropByMsgId(sue.ChanId, scratchArea.notificationsMsg.Notifications) | ||
2201 | 169 | if err != nil { | ||
2202 | 170 | return err | ||
2203 | 171 | } | ||
2204 | 172 | return nil | ||
2205 | 141 | } | 173 | } |
2206 | 142 | 174 | ||
2207 | === modified file 'server/broker/exchanges_test.go' | |||
2208 | --- server/broker/exchanges_test.go 2014-04-04 13:19:10 +0000 | |||
2209 | +++ server/broker/exchanges_test.go 2014-05-12 14:11:46 +0000 | |||
2210 | @@ -18,15 +18,18 @@ | |||
2211 | 18 | 18 | ||
2212 | 19 | import ( | 19 | import ( |
2213 | 20 | "encoding/json" | 20 | "encoding/json" |
2214 | 21 | "errors" | ||
2215 | 21 | "fmt" | 22 | "fmt" |
2216 | 22 | "strings" | 23 | "strings" |
2217 | 23 | stdtesting "testing" | 24 | stdtesting "testing" |
2218 | 24 | 25 | ||
2219 | 25 | . "launchpad.net/gocheck" | 26 | . "launchpad.net/gocheck" |
2220 | 26 | 27 | ||
2221 | 28 | "launchpad.net/ubuntu-push/protocol" | ||
2222 | 27 | "launchpad.net/ubuntu-push/server/broker" | 29 | "launchpad.net/ubuntu-push/server/broker" |
2223 | 28 | "launchpad.net/ubuntu-push/server/broker/testing" | 30 | "launchpad.net/ubuntu-push/server/broker/testing" |
2224 | 29 | "launchpad.net/ubuntu-push/server/store" | 31 | "launchpad.net/ubuntu-push/server/store" |
2225 | 32 | help "launchpad.net/ubuntu-push/testing" | ||
2226 | 30 | ) | 33 | ) |
2227 | 31 | 34 | ||
2228 | 32 | func TestBroker(t *stdtesting.T) { TestingT(t) } | 35 | func TestBroker(t *stdtesting.T) { TestingT(t) } |
2229 | @@ -39,11 +42,11 @@ | |||
2230 | 39 | exchg := &broker.BroadcastExchange{ | 42 | exchg := &broker.BroadcastExchange{ |
2231 | 40 | ChanId: store.SystemInternalChannelId, | 43 | ChanId: store.SystemInternalChannelId, |
2232 | 41 | TopLevel: 3, | 44 | TopLevel: 3, |
2234 | 42 | NotificationPayloads: []json.RawMessage{ | 45 | Notifications: help.Ns( |
2235 | 43 | json.RawMessage(`{"a":"x"}`), | 46 | json.RawMessage(`{"a":"x"}`), |
2236 | 44 | json.RawMessage(`[]`), | 47 | json.RawMessage(`[]`), |
2237 | 45 | json.RawMessage(`{"a":"y"}`), | 48 | json.RawMessage(`{"a":"y"}`), |
2239 | 46 | }, | 49 | ), |
2240 | 47 | } | 50 | } |
2241 | 48 | exchg.Init() | 51 | exchg.Init() |
2242 | 49 | c.Check(exchg.Decoded, DeepEquals, []map[string]interface{}{ | 52 | c.Check(exchg.Decoded, DeepEquals, []map[string]interface{}{ |
2243 | @@ -62,10 +65,10 @@ | |||
2244 | 62 | exchg := &broker.BroadcastExchange{ | 65 | exchg := &broker.BroadcastExchange{ |
2245 | 63 | ChanId: store.SystemInternalChannelId, | 66 | ChanId: store.SystemInternalChannelId, |
2246 | 64 | TopLevel: 3, | 67 | TopLevel: 3, |
2248 | 65 | NotificationPayloads: []json.RawMessage{ | 68 | Notifications: help.Ns( |
2249 | 66 | json.RawMessage(`{"img1/m1":100}`), | 69 | json.RawMessage(`{"img1/m1":100}`), |
2250 | 67 | json.RawMessage(`{"img2/m2":200}`), | 70 | json.RawMessage(`{"img2/m2":200}`), |
2252 | 68 | }, | 71 | ), |
2253 | 69 | } | 72 | } |
2254 | 70 | exchg.Init() | 73 | exchg.Init() |
2255 | 71 | outMsg, inMsg, err := exchg.Prepare(sess) | 74 | outMsg, inMsg, err := exchg.Prepare(sess) |
2256 | @@ -88,9 +91,9 @@ | |||
2257 | 88 | ImageChannel: "img1", | 91 | ImageChannel: "img1", |
2258 | 89 | } | 92 | } |
2259 | 90 | exchg := &broker.BroadcastExchange{ | 93 | exchg := &broker.BroadcastExchange{ |
2263 | 91 | ChanId: store.SystemInternalChannelId, | 94 | ChanId: store.SystemInternalChannelId, |
2264 | 92 | TopLevel: 3, | 95 | TopLevel: 3, |
2265 | 93 | NotificationPayloads: []json.RawMessage{}, | 96 | Notifications: []protocol.Notification{}, |
2266 | 94 | } | 97 | } |
2267 | 95 | exchg.Init() | 98 | exchg.Init() |
2268 | 96 | outMsg, inMsg, err := exchg.Prepare(sess) | 99 | outMsg, inMsg, err := exchg.Prepare(sess) |
2269 | @@ -108,9 +111,9 @@ | |||
2270 | 108 | ImageChannel: "img1", | 111 | ImageChannel: "img1", |
2271 | 109 | } | 112 | } |
2272 | 110 | exchg := &broker.BroadcastExchange{ | 113 | exchg := &broker.BroadcastExchange{ |
2276 | 111 | ChanId: store.SystemInternalChannelId, | 114 | ChanId: store.SystemInternalChannelId, |
2277 | 112 | TopLevel: 3, | 115 | TopLevel: 3, |
2278 | 113 | NotificationPayloads: []json.RawMessage{}, | 116 | Notifications: []protocol.Notification{}, |
2279 | 114 | } | 117 | } |
2280 | 115 | exchg.Init() | 118 | exchg.Init() |
2281 | 116 | outMsg, inMsg, err := exchg.Prepare(sess) | 119 | outMsg, inMsg, err := exchg.Prepare(sess) |
2282 | @@ -133,9 +136,9 @@ | |||
2283 | 133 | 136 | ||
2284 | 134 | topLevel := int64(len(needsSplitting)) | 137 | topLevel := int64(len(needsSplitting)) |
2285 | 135 | exchg := &broker.BroadcastExchange{ | 138 | exchg := &broker.BroadcastExchange{ |
2289 | 136 | ChanId: store.SystemInternalChannelId, | 139 | ChanId: store.SystemInternalChannelId, |
2290 | 137 | TopLevel: topLevel, | 140 | TopLevel: topLevel, |
2291 | 138 | NotificationPayloads: needsSplitting, | 141 | Notifications: help.Ns(needsSplitting...), |
2292 | 139 | } | 142 | } |
2293 | 140 | exchg.Init() | 143 | exchg.Init() |
2294 | 141 | outMsg, _, err := exchg.Prepare(sess) | 144 | outMsg, _, err := exchg.Prepare(sess) |
2295 | @@ -152,10 +155,10 @@ | |||
2296 | 152 | exchg = &broker.BroadcastExchange{ | 155 | exchg = &broker.BroadcastExchange{ |
2297 | 153 | ChanId: store.SystemInternalChannelId, | 156 | ChanId: store.SystemInternalChannelId, |
2298 | 154 | TopLevel: topLevel + 2, | 157 | TopLevel: topLevel + 2, |
2300 | 155 | NotificationPayloads: []json.RawMessage{ | 158 | Notifications: help.Ns( |
2301 | 156 | json.RawMessage(`{"img1/m1":"x"}`), | 159 | json.RawMessage(`{"img1/m1":"x"}`), |
2302 | 157 | json.RawMessage(`{"img1/m1":"y"}`), | 160 | json.RawMessage(`{"img1/m1":"y"}`), |
2304 | 158 | }, | 161 | ), |
2305 | 159 | } | 162 | } |
2306 | 160 | exchg.Init() | 163 | exchg.Init() |
2307 | 161 | outMsg, _, err = exchg.Prepare(sess) | 164 | outMsg, _, err = exchg.Prepare(sess) |
2308 | @@ -173,9 +176,9 @@ | |||
2309 | 173 | exchg := &broker.BroadcastExchange{ | 176 | exchg := &broker.BroadcastExchange{ |
2310 | 174 | ChanId: store.SystemInternalChannelId, | 177 | ChanId: store.SystemInternalChannelId, |
2311 | 175 | TopLevel: 3, | 178 | TopLevel: 3, |
2313 | 176 | NotificationPayloads: []json.RawMessage{ | 179 | Notifications: help.Ns( |
2314 | 177 | json.RawMessage(`{"img2/m1":1}`), | 180 | json.RawMessage(`{"img2/m1":1}`), |
2316 | 178 | }, | 181 | ), |
2317 | 179 | } | 182 | } |
2318 | 180 | exchg.Init() | 183 | exchg.Init() |
2319 | 181 | outMsg, inMsg, err := exchg.Prepare(sess) | 184 | outMsg, inMsg, err := exchg.Prepare(sess) |
2320 | @@ -202,10 +205,10 @@ | |||
2321 | 202 | exchg := &broker.BroadcastExchange{ | 205 | exchg := &broker.BroadcastExchange{ |
2322 | 203 | ChanId: store.SystemInternalChannelId, | 206 | ChanId: store.SystemInternalChannelId, |
2323 | 204 | TopLevel: 3, | 207 | TopLevel: 3, |
2325 | 205 | NotificationPayloads: []json.RawMessage{ | 208 | Notifications: help.Ns( |
2326 | 206 | json.RawMessage(`{"img1/m1":100}`), | 209 | json.RawMessage(`{"img1/m1":100}`), |
2327 | 207 | json.RawMessage(`{"img1/m1":101}`), | 210 | json.RawMessage(`{"img1/m1":101}`), |
2329 | 208 | }, | 211 | ), |
2330 | 209 | } | 212 | } |
2331 | 210 | exchg.Init() | 213 | exchg.Init() |
2332 | 211 | outMsg, inMsg, err := exchg.Prepare(sess) | 214 | outMsg, inMsg, err := exchg.Prepare(sess) |
2333 | @@ -229,11 +232,11 @@ | |||
2334 | 229 | exchg := &broker.BroadcastExchange{ | 232 | exchg := &broker.BroadcastExchange{ |
2335 | 230 | ChanId: store.SystemInternalChannelId, | 233 | ChanId: store.SystemInternalChannelId, |
2336 | 231 | TopLevel: 5, | 234 | TopLevel: 5, |
2338 | 232 | NotificationPayloads: []json.RawMessage{ | 235 | Notifications: help.Ns( |
2339 | 233 | json.RawMessage(`{"img1/m1":100}`), | 236 | json.RawMessage(`{"img1/m1":100}`), |
2340 | 234 | json.RawMessage(`{"img2/m2":200}`), | 237 | json.RawMessage(`{"img2/m2":200}`), |
2341 | 235 | json.RawMessage(`{"img1/m1":101}`), | 238 | json.RawMessage(`{"img1/m1":101}`), |
2343 | 236 | }, | 239 | ), |
2344 | 237 | } | 240 | } |
2345 | 238 | exchg.Init() | 241 | exchg.Init() |
2346 | 239 | outMsg, inMsg, err := exchg.Prepare(sess) | 242 | outMsg, inMsg, err := exchg.Prepare(sess) |
2347 | @@ -249,16 +252,114 @@ | |||
2348 | 249 | c.Check(sess.LevelsMap[store.SystemInternalChannelId], Equals, int64(5)) | 252 | c.Check(sess.LevelsMap[store.SystemInternalChannelId], Equals, int64(5)) |
2349 | 250 | } | 253 | } |
2350 | 251 | 254 | ||
2352 | 252 | func (s *exchangesSuite) TestConnBrokenExchange(c *C) { | 255 | func (s *exchangesSuite) TestConnMetaExchange(c *C) { |
2353 | 253 | sess := &testing.TestBrokerSession{} | 256 | sess := &testing.TestBrokerSession{} |
2355 | 254 | cbe := &broker.ConnBrokenExchange{"REASON"} | 257 | var msg protocol.OnewayMsg = &protocol.ConnWarnMsg{"connwarn", "REASON"} |
2356 | 258 | cbe := &broker.ConnMetaExchange{msg} | ||
2357 | 255 | outMsg, inMsg, err := cbe.Prepare(sess) | 259 | outMsg, inMsg, err := cbe.Prepare(sess) |
2358 | 256 | c.Assert(err, IsNil) | 260 | c.Assert(err, IsNil) |
2359 | 261 | c.Check(msg, Equals, outMsg) | ||
2360 | 257 | c.Check(inMsg, IsNil) // no answer is expected | 262 | c.Check(inMsg, IsNil) // no answer is expected |
2361 | 258 | // check | 263 | // check |
2362 | 259 | marshalled, err := json.Marshal(outMsg) | 264 | marshalled, err := json.Marshal(outMsg) |
2363 | 260 | c.Assert(err, IsNil) | 265 | c.Assert(err, IsNil) |
2367 | 261 | c.Check(string(marshalled), Equals, `{"T":"connbroken","Reason":"REASON"}`) | 266 | c.Check(string(marshalled), Equals, `{"T":"connwarn","Reason":"REASON"}`) |
2368 | 262 | 267 | ||
2369 | 263 | c.Check(func() { cbe.Acked(nil, true) }, PanicMatches, "Acked should not get invoked on ConnBrokenExchange") | 268 | c.Check(func() { cbe.Acked(nil, true) }, PanicMatches, "Acked should not get invoked on ConnMetaExchange") |
2370 | 269 | } | ||
2371 | 270 | |||
2372 | 271 | func (s *exchangesSuite) TestUnicastExchange(c *C) { | ||
2373 | 272 | chanId1 := store.UnicastInternalChannelId("u1", "d1") | ||
2374 | 273 | notifs := []protocol.Notification{ | ||
2375 | 274 | protocol.Notification{ | ||
2376 | 275 | MsgId: "msg1", | ||
2377 | 276 | AppId: "app1", | ||
2378 | 277 | Payload: json.RawMessage(`{"m": 1}`), | ||
2379 | 278 | }, | ||
2380 | 279 | protocol.Notification{ | ||
2381 | 280 | MsgId: "msg2", | ||
2382 | 281 | AppId: "app2", | ||
2383 | 282 | Payload: json.RawMessage(`{"m": 2}`), | ||
2384 | 283 | }, | ||
2385 | 284 | } | ||
2386 | 285 | dropped := make(chan []protocol.Notification, 2) | ||
2387 | 286 | sess := &testing.TestBrokerSession{ | ||
2388 | 287 | DoGet: func(chanId store.InternalChannelId, cachedOk bool) (int64, []protocol.Notification, error) { | ||
2389 | 288 | c.Check(chanId, Equals, chanId1) | ||
2390 | 289 | c.Check(cachedOk, Equals, false) | ||
2391 | 290 | return 0, notifs, nil | ||
2392 | 291 | }, | ||
2393 | 292 | DoDropByMsgId: func(chanId store.InternalChannelId, targets []protocol.Notification) error { | ||
2394 | 293 | c.Check(chanId, Equals, chanId1) | ||
2395 | 294 | dropped <- targets | ||
2396 | 295 | return nil | ||
2397 | 296 | }, | ||
2398 | 297 | } | ||
2399 | 298 | exchg := &broker.UnicastExchange{chanId1} | ||
2400 | 299 | outMsg, inMsg, err := exchg.Prepare(sess) | ||
2401 | 300 | c.Assert(err, IsNil) | ||
2402 | 301 | // check | ||
2403 | 302 | marshalled, err := json.Marshal(outMsg) | ||
2404 | 303 | c.Assert(err, IsNil) | ||
2405 | 304 | c.Check(string(marshalled), Equals, `{"T":"notifications","Notifications":[{"A":"app1","M":"msg1","P":{"m":1}},{"A":"app2","M":"msg2","P":{"m":2}}]}`) | ||
2406 | 305 | err = json.Unmarshal([]byte(`{"T":"ack"}`), inMsg) | ||
2407 | 306 | c.Assert(err, IsNil) | ||
2408 | 307 | err = exchg.Acked(sess, true) | ||
2409 | 308 | c.Assert(err, IsNil) | ||
2410 | 309 | c.Check(dropped, HasLen, 1) | ||
2411 | 310 | c.Check(<-dropped, DeepEquals, notifs) | ||
2412 | 311 | } | ||
2413 | 312 | |||
2414 | 313 | func (s *exchangesSuite) TestUnicastExchangeAckMismatch(c *C) { | ||
2415 | 314 | notifs := []protocol.Notification{} | ||
2416 | 315 | dropped := make(chan []protocol.Notification, 2) | ||
2417 | 316 | sess := &testing.TestBrokerSession{ | ||
2418 | 317 | DoGet: func(chanId store.InternalChannelId, cachedOk bool) (int64, []protocol.Notification, error) { | ||
2419 | 318 | return 0, notifs, nil | ||
2420 | 319 | }, | ||
2421 | 320 | DoDropByMsgId: func(chanId store.InternalChannelId, targets []protocol.Notification) error { | ||
2422 | 321 | dropped <- targets | ||
2423 | 322 | return nil | ||
2424 | 323 | }, | ||
2425 | 324 | } | ||
2426 | 325 | exchg := &broker.UnicastExchange{} | ||
2427 | 326 | _, inMsg, err := exchg.Prepare(sess) | ||
2428 | 327 | c.Assert(err, IsNil) | ||
2429 | 328 | err = json.Unmarshal([]byte(`{}`), inMsg) | ||
2430 | 329 | c.Assert(err, IsNil) | ||
2431 | 330 | err = exchg.Acked(sess, true) | ||
2432 | 331 | c.Assert(err, Not(IsNil)) | ||
2433 | 332 | c.Check(dropped, HasLen, 0) | ||
2434 | 333 | } | ||
2435 | 334 | |||
2436 | 335 | func (s *exchangesSuite) TestUnicastExchangeErrorOnPrepare(c *C) { | ||
2437 | 336 | fail := errors.New("fail") | ||
2438 | 337 | sess := &testing.TestBrokerSession{ | ||
2439 | 338 | DoGet: func(chanId store.InternalChannelId, cachedOk bool) (int64, []protocol.Notification, error) { | ||
2440 | 339 | return 0, nil, fail | ||
2441 | 340 | }, | ||
2442 | 341 | } | ||
2443 | 342 | exchg := &broker.UnicastExchange{} | ||
2444 | 343 | _, _, err := exchg.Prepare(sess) | ||
2445 | 344 | c.Assert(err, Equals, fail) | ||
2446 | 345 | } | ||
2447 | 346 | |||
2448 | 347 | func (s *exchangesSuite) TestUnicastExchangeErrorOnAcked(c *C) { | ||
2449 | 348 | notifs := []protocol.Notification{} | ||
2450 | 349 | fail := errors.New("fail") | ||
2451 | 350 | sess := &testing.TestBrokerSession{ | ||
2452 | 351 | DoGet: func(chanId store.InternalChannelId, cachedOk bool) (int64, []protocol.Notification, error) { | ||
2453 | 352 | return 0, notifs, nil | ||
2454 | 353 | }, | ||
2455 | 354 | DoDropByMsgId: func(chanId store.InternalChannelId, targets []protocol.Notification) error { | ||
2456 | 355 | return fail | ||
2457 | 356 | }, | ||
2458 | 357 | } | ||
2459 | 358 | exchg := &broker.UnicastExchange{} | ||
2460 | 359 | _, inMsg, err := exchg.Prepare(sess) | ||
2461 | 360 | c.Assert(err, IsNil) | ||
2462 | 361 | err = json.Unmarshal([]byte(`{"T":"ack"}`), inMsg) | ||
2463 | 362 | c.Assert(err, IsNil) | ||
2464 | 363 | err = exchg.Acked(sess, true) | ||
2465 | 364 | c.Assert(err, Equals, fail) | ||
2466 | 264 | } | 365 | } |
2467 | 265 | 366 | ||
2468 | === modified file 'server/broker/exchg_impl_test.go' | |||
2469 | --- server/broker/exchg_impl_test.go 2014-04-03 16:00:53 +0000 | |||
2470 | +++ server/broker/exchg_impl_test.go 2014-05-12 14:11:46 +0000 | |||
2471 | @@ -22,6 +22,7 @@ | |||
2472 | 22 | . "launchpad.net/gocheck" | 22 | . "launchpad.net/gocheck" |
2473 | 23 | 23 | ||
2474 | 24 | "launchpad.net/ubuntu-push/server/store" | 24 | "launchpad.net/ubuntu-push/server/store" |
2475 | 25 | help "launchpad.net/ubuntu-push/testing" | ||
2476 | 25 | ) | 26 | ) |
2477 | 26 | 27 | ||
2478 | 27 | type exchangesImplSuite struct{} | 28 | type exchangesImplSuite struct{} |
2479 | @@ -29,27 +30,27 @@ | |||
2480 | 29 | var _ = Suite(&exchangesImplSuite{}) | 30 | var _ = Suite(&exchangesImplSuite{}) |
2481 | 30 | 31 | ||
2482 | 31 | func (s *exchangesImplSuite) TestFilterByLevel(c *C) { | 32 | func (s *exchangesImplSuite) TestFilterByLevel(c *C) { |
2484 | 32 | payloads := []json.RawMessage{ | 33 | notifs := help.Ns( |
2485 | 33 | json.RawMessage(`{"a": 3}`), | 34 | json.RawMessage(`{"a": 3}`), |
2486 | 34 | json.RawMessage(`{"a": 4}`), | 35 | json.RawMessage(`{"a": 4}`), |
2487 | 35 | json.RawMessage(`{"a": 5}`), | 36 | json.RawMessage(`{"a": 5}`), |
2490 | 36 | } | 37 | ) |
2491 | 37 | res := filterByLevel(5, 5, payloads) | 38 | res := filterByLevel(5, 5, notifs) |
2492 | 38 | c.Check(len(res), Equals, 0) | 39 | c.Check(len(res), Equals, 0) |
2494 | 39 | res = filterByLevel(4, 5, payloads) | 40 | res = filterByLevel(4, 5, notifs) |
2495 | 40 | c.Check(len(res), Equals, 1) | 41 | c.Check(len(res), Equals, 1) |
2498 | 41 | c.Check(res[0], DeepEquals, json.RawMessage(`{"a": 5}`)) | 42 | c.Check(res[0].Payload, DeepEquals, json.RawMessage(`{"a": 5}`)) |
2499 | 42 | res = filterByLevel(3, 5, payloads) | 43 | res = filterByLevel(3, 5, notifs) |
2500 | 43 | c.Check(len(res), Equals, 2) | 44 | c.Check(len(res), Equals, 2) |
2503 | 44 | c.Check(res[0], DeepEquals, json.RawMessage(`{"a": 4}`)) | 45 | c.Check(res[0].Payload, DeepEquals, json.RawMessage(`{"a": 4}`)) |
2504 | 45 | res = filterByLevel(2, 5, payloads) | 46 | res = filterByLevel(2, 5, notifs) |
2505 | 46 | c.Check(len(res), Equals, 3) | 47 | c.Check(len(res), Equals, 3) |
2507 | 47 | res = filterByLevel(1, 5, payloads) | 48 | res = filterByLevel(1, 5, notifs) |
2508 | 48 | c.Check(len(res), Equals, 3) | 49 | c.Check(len(res), Equals, 3) |
2509 | 49 | // too ahead, pick only last | 50 | // too ahead, pick only last |
2511 | 50 | res = filterByLevel(10, 5, payloads) | 51 | res = filterByLevel(10, 5, notifs) |
2512 | 51 | c.Check(len(res), Equals, 1) | 52 | c.Check(len(res), Equals, 1) |
2514 | 52 | c.Check(res[0], DeepEquals, json.RawMessage(`{"a": 5}`)) | 53 | c.Check(res[0].Payload, DeepEquals, json.RawMessage(`{"a": 5}`)) |
2515 | 53 | } | 54 | } |
2516 | 54 | 55 | ||
2517 | 55 | func (s *exchangesImplSuite) TestFilterByLevelEmpty(c *C) { | 56 | func (s *exchangesImplSuite) TestFilterByLevelEmpty(c *C) { |
2518 | @@ -71,18 +72,19 @@ | |||
2519 | 71 | err := json.Unmarshal(p, &decoded[i]) | 72 | err := json.Unmarshal(p, &decoded[i]) |
2520 | 72 | c.Assert(err, IsNil) | 73 | c.Assert(err, IsNil) |
2521 | 73 | } | 74 | } |
2522 | 75 | notifs := help.Ns(payloads...) | ||
2523 | 74 | 76 | ||
2524 | 75 | other := store.InternalChannelId("1") | 77 | other := store.InternalChannelId("1") |
2525 | 76 | 78 | ||
2526 | 77 | c.Check(channelFilter("", store.SystemInternalChannelId, nil, nil), IsNil) | 79 | c.Check(channelFilter("", store.SystemInternalChannelId, nil, nil), IsNil) |
2528 | 78 | c.Check(channelFilter("", other, payloads[1:], decoded), DeepEquals, payloads[1:]) | 80 | c.Check(channelFilter("", other, notifs[1:], decoded), DeepEquals, payloads[1:]) |
2529 | 79 | 81 | ||
2530 | 80 | // use tag when channel is the sytem channel | 82 | // use tag when channel is the sytem channel |
2531 | 81 | 83 | ||
2537 | 82 | c.Check(channelFilter("c/z", store.SystemInternalChannelId, payloads, decoded), HasLen, 0) | 84 | c.Check(channelFilter("c/z", store.SystemInternalChannelId, notifs, decoded), HasLen, 0) |
2538 | 83 | 85 | ||
2539 | 84 | c.Check(channelFilter("a/x", store.SystemInternalChannelId, payloads, decoded), DeepEquals, []json.RawMessage{payloads[0], payloads[3]}) | 86 | c.Check(channelFilter("a/x", store.SystemInternalChannelId, notifs, decoded), DeepEquals, []json.RawMessage{payloads[0], payloads[3]}) |
2540 | 85 | 87 | ||
2541 | 86 | c.Check(channelFilter("a/x", store.SystemInternalChannelId, payloads[1:], decoded), DeepEquals, []json.RawMessage{payloads[3]}) | 88 | c.Check(channelFilter("a/x", store.SystemInternalChannelId, notifs[1:], decoded), DeepEquals, []json.RawMessage{payloads[3]}) |
2542 | 87 | 89 | ||
2543 | 88 | } | 90 | } |
2544 | 89 | 91 | ||
2545 | === modified file 'server/broker/simple/simple.go' | |||
2546 | --- server/broker/simple/simple.go 2014-04-11 08:47:18 +0000 | |||
2547 | +++ server/broker/simple/simple.go 2014-05-12 14:11:46 +0000 | |||
2548 | @@ -46,6 +46,7 @@ | |||
2549 | 46 | 46 | ||
2550 | 47 | // simpleBrokerSession represents a session in the broker. | 47 | // simpleBrokerSession represents a session in the broker. |
2551 | 48 | type simpleBrokerSession struct { | 48 | type simpleBrokerSession struct { |
2552 | 49 | broker *SimpleBroker | ||
2553 | 49 | registered bool | 50 | registered bool |
2554 | 50 | deviceId string | 51 | deviceId string |
2555 | 51 | model string | 52 | model string |
2556 | @@ -61,6 +62,7 @@ | |||
2557 | 61 | 62 | ||
2558 | 62 | const ( | 63 | const ( |
2559 | 63 | broadcastDelivery deliveryKind = iota | 64 | broadcastDelivery deliveryKind = iota |
2560 | 65 | unicastDelivery | ||
2561 | 64 | ) | 66 | ) |
2562 | 65 | 67 | ||
2563 | 66 | // delivery holds all the information to request a delivery | 68 | // delivery holds all the information to request a delivery |
2564 | @@ -93,6 +95,14 @@ | |||
2565 | 93 | return &sess.exchgScratch | 95 | return &sess.exchgScratch |
2566 | 94 | } | 96 | } |
2567 | 95 | 97 | ||
2568 | 98 | func (sess *simpleBrokerSession) Get(chanId store.InternalChannelId, cachedOk bool) (int64, []protocol.Notification, error) { | ||
2569 | 99 | return sess.broker.get(chanId, cachedOk) | ||
2570 | 100 | } | ||
2571 | 101 | |||
2572 | 102 | func (sess *simpleBrokerSession) DropByMsgId(chanId store.InternalChannelId, targets []protocol.Notification) error { | ||
2573 | 103 | return sess.broker.drop(chanId, targets) | ||
2574 | 104 | } | ||
2575 | 105 | |||
2576 | 96 | // NewSimpleBroker makes a new SimpleBroker. | 106 | // NewSimpleBroker makes a new SimpleBroker. |
2577 | 97 | func NewSimpleBroker(sto store.PendingStore, cfg broker.BrokerConfig, logger logger.Logger) *SimpleBroker { | 107 | func NewSimpleBroker(sto store.PendingStore, cfg broker.BrokerConfig, logger logger.Logger) *SimpleBroker { |
2578 | 98 | sessionCh := make(chan *simpleBrokerSession, cfg.BrokerQueueSize()) | 108 | sessionCh := make(chan *simpleBrokerSession, cfg.BrokerQueueSize()) |
2579 | @@ -144,7 +154,7 @@ | |||
2580 | 144 | // find relevant channels, for now only system | 154 | // find relevant channels, for now only system |
2581 | 145 | channels := []store.InternalChannelId{store.SystemInternalChannelId} | 155 | channels := []store.InternalChannelId{store.SystemInternalChannelId} |
2582 | 146 | for _, chanId := range channels { | 156 | for _, chanId := range channels { |
2584 | 147 | topLevel, payloads, err := b.sto.GetChannelSnapshot(chanId) | 157 | topLevel, notifications, err := b.sto.GetChannelSnapshot(chanId) |
2585 | 148 | if err != nil { | 158 | if err != nil { |
2586 | 149 | // next broadcast will try again | 159 | // next broadcast will try again |
2587 | 150 | b.logger.Errorf("unsuccessful feed pending, get channel snapshot for %v: %v", chanId, err) | 160 | b.logger.Errorf("unsuccessful feed pending, get channel snapshot for %v: %v", chanId, err) |
2588 | @@ -153,9 +163,9 @@ | |||
2589 | 153 | clientLevel := sess.levels[chanId] | 163 | clientLevel := sess.levels[chanId] |
2590 | 154 | if clientLevel != topLevel { | 164 | if clientLevel != topLevel { |
2591 | 155 | broadcastExchg := &broker.BroadcastExchange{ | 165 | broadcastExchg := &broker.BroadcastExchange{ |
2595 | 156 | ChanId: chanId, | 166 | ChanId: chanId, |
2596 | 157 | TopLevel: topLevel, | 167 | TopLevel: topLevel, |
2597 | 158 | NotificationPayloads: payloads, | 168 | Notifications: notifications, |
2598 | 159 | } | 169 | } |
2599 | 160 | broadcastExchg.Init() | 170 | broadcastExchg.Init() |
2600 | 161 | sess.exchanges <- broadcastExchg | 171 | sess.exchanges <- broadcastExchg |
2601 | @@ -166,7 +176,7 @@ | |||
2602 | 166 | 176 | ||
2603 | 167 | // Register registers a session with the broker. It feeds the session | 177 | // Register registers a session with the broker. It feeds the session |
2604 | 168 | // pending notifications as well. | 178 | // pending notifications as well. |
2606 | 169 | func (b *SimpleBroker) Register(connect *protocol.ConnectMsg) (broker.BrokerSession, error) { | 179 | func (b *SimpleBroker) Register(connect *protocol.ConnectMsg, sessionId string) (broker.BrokerSession, error) { |
2607 | 170 | // xxx sanity check DeviceId | 180 | // xxx sanity check DeviceId |
2608 | 171 | model, err := broker.GetInfoString(connect, "device", "?") | 181 | model, err := broker.GetInfoString(connect, "device", "?") |
2609 | 172 | if err != nil { | 182 | if err != nil { |
2610 | @@ -185,6 +195,7 @@ | |||
2611 | 185 | levels[id] = v | 195 | levels[id] = v |
2612 | 186 | } | 196 | } |
2613 | 187 | sess := &simpleBrokerSession{ | 197 | sess := &simpleBrokerSession{ |
2614 | 198 | broker: b, | ||
2615 | 188 | deviceId: connect.DeviceId, | 199 | deviceId: connect.DeviceId, |
2616 | 189 | model: model, | 200 | model: model, |
2617 | 190 | imageChannel: imageChannel, | 201 | imageChannel: imageChannel, |
2618 | @@ -207,6 +218,24 @@ | |||
2619 | 207 | b.sessionCh <- sess | 218 | b.sessionCh <- sess |
2620 | 208 | } | 219 | } |
2621 | 209 | 220 | ||
2622 | 221 | func (b *SimpleBroker) get(chanId store.InternalChannelId, cachedOk bool) (int64, []protocol.Notification, error) { | ||
2623 | 222 | topLevel, notifications, err := b.sto.GetChannelSnapshot(chanId) | ||
2624 | 223 | if err != nil { | ||
2625 | 224 | b.logger.Errorf("unsuccessful, get channel snapshot for %v (cachedOk=%v): %v", chanId, cachedOk, err) | ||
2626 | 225 | } | ||
2627 | 226 | return topLevel, notifications, err | ||
2628 | 227 | |||
2629 | 228 | } | ||
2630 | 229 | |||
2631 | 230 | func (b *SimpleBroker) drop(chanId store.InternalChannelId, targets []protocol.Notification) error { | ||
2632 | 231 | err := b.sto.DropByMsgId(chanId, targets) | ||
2633 | 232 | if err != nil { | ||
2634 | 233 | b.logger.Errorf("unsuccessful, drop from channel %v: %v", chanId, err) | ||
2635 | 234 | } | ||
2636 | 235 | return err | ||
2637 | 236 | |||
2638 | 237 | } | ||
2639 | 238 | |||
2640 | 210 | // run runs the agent logic of the broker. | 239 | // run runs the agent logic of the broker. |
2641 | 211 | func (b *SimpleBroker) run() { | 240 | func (b *SimpleBroker) run() { |
2642 | 212 | Loop: | 241 | Loop: |
2643 | @@ -224,7 +253,7 @@ | |||
2644 | 224 | } else { // register | 253 | } else { // register |
2645 | 225 | prev := b.registry[sess.deviceId] | 254 | prev := b.registry[sess.deviceId] |
2646 | 226 | if prev != nil { // kick it | 255 | if prev != nil { // kick it |
2648 | 227 | close(prev.exchanges) | 256 | prev.exchanges <- nil |
2649 | 228 | } | 257 | } |
2650 | 229 | b.registry[sess.deviceId] = sess | 258 | b.registry[sess.deviceId] = sess |
2651 | 230 | sess.registered = true | 259 | sess.registered = true |
2652 | @@ -233,21 +262,27 @@ | |||
2653 | 233 | case delivery := <-b.deliveryCh: | 262 | case delivery := <-b.deliveryCh: |
2654 | 234 | switch delivery.kind { | 263 | switch delivery.kind { |
2655 | 235 | case broadcastDelivery: | 264 | case broadcastDelivery: |
2657 | 236 | topLevel, payloads, err := b.sto.GetChannelSnapshot(delivery.chanId) | 265 | topLevel, notifications, err := b.get(delivery.chanId, false) |
2658 | 237 | if err != nil { | 266 | if err != nil { |
2659 | 238 | // next broadcast will try again | 267 | // next broadcast will try again |
2660 | 239 | b.logger.Errorf("unsuccessful broadcast, get channel snapshot for %v: %v", delivery.chanId, err) | ||
2661 | 240 | continue Loop | 268 | continue Loop |
2662 | 241 | } | 269 | } |
2663 | 242 | broadcastExchg := &broker.BroadcastExchange{ | 270 | broadcastExchg := &broker.BroadcastExchange{ |
2667 | 243 | ChanId: delivery.chanId, | 271 | ChanId: delivery.chanId, |
2668 | 244 | TopLevel: topLevel, | 272 | TopLevel: topLevel, |
2669 | 245 | NotificationPayloads: payloads, | 273 | Notifications: notifications, |
2670 | 246 | } | 274 | } |
2671 | 247 | broadcastExchg.Init() | 275 | broadcastExchg.Init() |
2672 | 248 | for _, sess := range b.registry { | 276 | for _, sess := range b.registry { |
2673 | 249 | sess.exchanges <- broadcastExchg | 277 | sess.exchanges <- broadcastExchg |
2674 | 250 | } | 278 | } |
2675 | 279 | case unicastDelivery: | ||
2676 | 280 | chanId := delivery.chanId | ||
2677 | 281 | _, devId := chanId.UnicastUserAndDevice() | ||
2678 | 282 | sess := b.registry[devId] | ||
2679 | 283 | if sess != nil { | ||
2680 | 284 | sess.exchanges <- &broker.UnicastExchange{chanId} | ||
2681 | 285 | } | ||
2682 | 251 | } | 286 | } |
2683 | 252 | } | 287 | } |
2684 | 253 | } | 288 | } |
2685 | @@ -260,3 +295,13 @@ | |||
2686 | 260 | chanId: chanId, | 295 | chanId: chanId, |
2687 | 261 | } | 296 | } |
2688 | 262 | } | 297 | } |
2689 | 298 | |||
2690 | 299 | // Unicast requests unicast for the channels. | ||
2691 | 300 | func (b *SimpleBroker) Unicast(chanIds ...store.InternalChannelId) { | ||
2692 | 301 | for _, chanId := range chanIds { | ||
2693 | 302 | b.deliveryCh <- &delivery{ | ||
2694 | 303 | kind: unicastDelivery, | ||
2695 | 304 | chanId: chanId, | ||
2696 | 305 | } | ||
2697 | 306 | } | ||
2698 | 307 | } | ||
2699 | 263 | 308 | ||
2700 | === modified file 'server/broker/simple/simple_test.go' | |||
2701 | --- server/broker/simple/simple_test.go 2014-04-03 16:00:53 +0000 | |||
2702 | +++ server/broker/simple/simple_test.go 2014-05-12 14:11:46 +0000 | |||
2703 | @@ -26,6 +26,7 @@ | |||
2704 | 26 | "launchpad.net/ubuntu-push/server/broker" | 26 | "launchpad.net/ubuntu-push/server/broker" |
2705 | 27 | "launchpad.net/ubuntu-push/server/broker/testing" | 27 | "launchpad.net/ubuntu-push/server/broker/testing" |
2706 | 28 | "launchpad.net/ubuntu-push/server/store" | 28 | "launchpad.net/ubuntu-push/server/store" |
2707 | 29 | help "launchpad.net/ubuntu-push/testing" | ||
2708 | 29 | ) | 30 | ) |
2709 | 30 | 31 | ||
2710 | 31 | func TestSimple(t *stdtesting.T) { TestingT(t) } | 32 | func TestSimple(t *stdtesting.T) { TestingT(t) } |
2711 | @@ -58,10 +59,10 @@ | |||
2712 | 58 | c.Assert(len(sess.exchanges), Equals, 1) | 59 | c.Assert(len(sess.exchanges), Equals, 1) |
2713 | 59 | exchg1 := <-sess.exchanges | 60 | exchg1 := <-sess.exchanges |
2714 | 60 | c.Check(exchg1, DeepEquals, &broker.BroadcastExchange{ | 61 | c.Check(exchg1, DeepEquals, &broker.BroadcastExchange{ |
2719 | 61 | ChanId: store.SystemInternalChannelId, | 62 | ChanId: store.SystemInternalChannelId, |
2720 | 62 | TopLevel: 1, | 63 | TopLevel: 1, |
2721 | 63 | NotificationPayloads: []json.RawMessage{notification1}, | 64 | Notifications: help.Ns(notification1), |
2722 | 64 | Decoded: []map[string]interface{}{decoded1}, | 65 | Decoded: []map[string]interface{}{decoded1}, |
2723 | 65 | }) | 66 | }) |
2724 | 66 | } | 67 | } |
2725 | 67 | 68 | ||
2726 | 68 | 69 | ||
2727 | === modified file 'server/broker/simple/suite_test.go' | |||
2728 | --- server/broker/simple/suite_test.go 2014-02-10 23:19:08 +0000 | |||
2729 | +++ server/broker/simple/suite_test.go 2014-05-12 14:11:46 +0000 | |||
2730 | @@ -42,4 +42,7 @@ | |||
2731 | 42 | RevealBroadcastExchange: func(exchg broker.Exchange) *broker.BroadcastExchange { | 42 | RevealBroadcastExchange: func(exchg broker.Exchange) *broker.BroadcastExchange { |
2732 | 43 | return exchg.(*broker.BroadcastExchange) | 43 | return exchg.(*broker.BroadcastExchange) |
2733 | 44 | }, | 44 | }, |
2734 | 45 | RevealUnicastExchange: func(exchg broker.Exchange) *broker.UnicastExchange { | ||
2735 | 46 | return exchg.(*broker.UnicastExchange) | ||
2736 | 47 | }, | ||
2737 | 45 | }}) | 48 | }}) |
2738 | 46 | 49 | ||
2739 | === modified file 'server/broker/testing/impls.go' | |||
2740 | --- server/broker/testing/impls.go 2014-04-03 14:31:10 +0000 | |||
2741 | +++ server/broker/testing/impls.go 2014-05-12 14:11:46 +0000 | |||
2742 | @@ -18,7 +18,9 @@ | |||
2743 | 18 | package testing | 18 | package testing |
2744 | 19 | 19 | ||
2745 | 20 | import ( | 20 | import ( |
2746 | 21 | "launchpad.net/ubuntu-push/protocol" | ||
2747 | 21 | "launchpad.net/ubuntu-push/server/broker" | 22 | "launchpad.net/ubuntu-push/server/broker" |
2748 | 23 | "launchpad.net/ubuntu-push/server/store" | ||
2749 | 22 | ) | 24 | ) |
2750 | 23 | 25 | ||
2751 | 24 | // Test implementation of BrokerSession. | 26 | // Test implementation of BrokerSession. |
2752 | @@ -29,6 +31,9 @@ | |||
2753 | 29 | Exchanges chan broker.Exchange | 31 | Exchanges chan broker.Exchange |
2754 | 30 | LevelsMap broker.LevelsMap | 32 | LevelsMap broker.LevelsMap |
2755 | 31 | exchgScratch broker.ExchangesScratchArea | 33 | exchgScratch broker.ExchangesScratchArea |
2756 | 34 | // hooks | ||
2757 | 35 | DoGet func(store.InternalChannelId, bool) (int64, []protocol.Notification, error) | ||
2758 | 36 | DoDropByMsgId func(store.InternalChannelId, []protocol.Notification) error | ||
2759 | 32 | } | 37 | } |
2760 | 33 | 38 | ||
2761 | 34 | func (tbs *TestBrokerSession) DeviceIdentifier() string { | 39 | func (tbs *TestBrokerSession) DeviceIdentifier() string { |
2762 | @@ -55,6 +60,14 @@ | |||
2763 | 55 | return &tbs.exchgScratch | 60 | return &tbs.exchgScratch |
2764 | 56 | } | 61 | } |
2765 | 57 | 62 | ||
2766 | 63 | func (tbs *TestBrokerSession) Get(chanId store.InternalChannelId, cachedOk bool) (int64, []protocol.Notification, error) { | ||
2767 | 64 | return tbs.DoGet(chanId, cachedOk) | ||
2768 | 65 | } | ||
2769 | 66 | |||
2770 | 67 | func (tbs *TestBrokerSession) DropByMsgId(chanId store.InternalChannelId, targets []protocol.Notification) error { | ||
2771 | 68 | return tbs.DoDropByMsgId(chanId, targets) | ||
2772 | 69 | } | ||
2773 | 70 | |||
2774 | 58 | // Test implementation of BrokerConfig. | 71 | // Test implementation of BrokerConfig. |
2775 | 59 | type TestBrokerConfig struct { | 72 | type TestBrokerConfig struct { |
2776 | 60 | ConfigSessionQueueSize uint | 73 | ConfigSessionQueueSize uint |
2777 | 61 | 74 | ||
2778 | === modified file 'server/broker/testsuite/suite.go' | |||
2779 | --- server/broker/testsuite/suite.go 2014-04-11 08:47:18 +0000 | |||
2780 | +++ server/broker/testsuite/suite.go 2014-05-12 14:11:46 +0000 | |||
2781 | @@ -30,7 +30,7 @@ | |||
2782 | 30 | "launchpad.net/ubuntu-push/server/broker" | 30 | "launchpad.net/ubuntu-push/server/broker" |
2783 | 31 | "launchpad.net/ubuntu-push/server/broker/testing" | 31 | "launchpad.net/ubuntu-push/server/broker/testing" |
2784 | 32 | "launchpad.net/ubuntu-push/server/store" | 32 | "launchpad.net/ubuntu-push/server/store" |
2786 | 33 | helpers "launchpad.net/ubuntu-push/testing" | 33 | help "launchpad.net/ubuntu-push/testing" |
2787 | 34 | ) | 34 | ) |
2788 | 35 | 35 | ||
2789 | 36 | // The expected interface for tested brokers. | 36 | // The expected interface for tested brokers. |
2790 | @@ -50,12 +50,14 @@ | |||
2791 | 50 | RevealSession func(broker.Broker, string) broker.BrokerSession | 50 | RevealSession func(broker.Broker, string) broker.BrokerSession |
2792 | 51 | // Let us get to a broker.BroadcastExchange from an Exchange. | 51 | // Let us get to a broker.BroadcastExchange from an Exchange. |
2793 | 52 | RevealBroadcastExchange func(broker.Exchange) *broker.BroadcastExchange | 52 | RevealBroadcastExchange func(broker.Exchange) *broker.BroadcastExchange |
2794 | 53 | // Let us get to a broker.UnicastExchange from an Exchange. | ||
2795 | 54 | RevealUnicastExchange func(broker.Exchange) *broker.UnicastExchange | ||
2796 | 53 | // private | 55 | // private |
2798 | 54 | testlog *helpers.TestLogger | 56 | testlog *help.TestLogger |
2799 | 55 | } | 57 | } |
2800 | 56 | 58 | ||
2801 | 57 | func (s *CommonBrokerSuite) SetUpTest(c *C) { | 59 | func (s *CommonBrokerSuite) SetUpTest(c *C) { |
2803 | 58 | s.testlog = helpers.NewTestLogger(c, "error") | 60 | s.testlog = help.NewTestLogger(c, "error") |
2804 | 59 | } | 61 | } |
2805 | 60 | 62 | ||
2806 | 61 | var testBrokerConfig = &testing.TestBrokerConfig{10, 5} | 63 | var testBrokerConfig = &testing.TestBrokerConfig{10, 5} |
2807 | @@ -89,7 +91,7 @@ | |||
2808 | 89 | "device": "model", | 91 | "device": "model", |
2809 | 90 | "channel": "daily", | 92 | "channel": "daily", |
2810 | 91 | }, | 93 | }, |
2812 | 92 | }) | 94 | }, "s1") |
2813 | 93 | c.Assert(err, IsNil) | 95 | c.Assert(err, IsNil) |
2814 | 94 | c.Assert(s.RevealSession(b, "dev-1"), Equals, sess) | 96 | c.Assert(s.RevealSession(b, "dev-1"), Equals, sess) |
2815 | 95 | c.Assert(sess.DeviceIdentifier(), Equals, "dev-1") | 97 | c.Assert(sess.DeviceIdentifier(), Equals, "dev-1") |
2816 | @@ -101,7 +103,7 @@ | |||
2817 | 101 | })) | 103 | })) |
2818 | 102 | b.Unregister(sess) | 104 | b.Unregister(sess) |
2819 | 103 | // just to make sure the unregister was processed | 105 | // just to make sure the unregister was processed |
2821 | 104 | _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}) | 106 | _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}, "s2") |
2822 | 105 | c.Assert(err, IsNil) | 107 | c.Assert(err, IsNil) |
2823 | 106 | c.Check(s.RevealSession(b, "dev-1"), IsNil) | 108 | c.Check(s.RevealSession(b, "dev-1"), IsNil) |
2824 | 107 | } | 109 | } |
2825 | @@ -111,7 +113,7 @@ | |||
2826 | 111 | b := s.MakeBroker(sto, testBrokerConfig, nil) | 113 | b := s.MakeBroker(sto, testBrokerConfig, nil) |
2827 | 112 | b.Start() | 114 | b.Start() |
2828 | 113 | defer b.Stop() | 115 | defer b.Stop() |
2830 | 114 | _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1", Levels: map[string]int64{"z": 5}}) | 116 | _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1", Levels: map[string]int64{"z": 5}}, "s1") |
2831 | 115 | c.Check(err, FitsTypeOf, &broker.ErrAbort{}) | 117 | c.Check(err, FitsTypeOf, &broker.ErrAbort{}) |
2832 | 116 | } | 118 | } |
2833 | 117 | 119 | ||
2834 | @@ -123,11 +125,11 @@ | |||
2835 | 123 | info := map[string]interface{}{ | 125 | info := map[string]interface{}{ |
2836 | 124 | "device": -1, | 126 | "device": -1, |
2837 | 125 | } | 127 | } |
2839 | 126 | _, err := b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}) | 128 | _, err := b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}, "s1") |
2840 | 127 | c.Check(err, Equals, broker.ErrUnexpectedValue) | 129 | c.Check(err, Equals, broker.ErrUnexpectedValue) |
2841 | 128 | info["device"] = "m" | 130 | info["device"] = "m" |
2842 | 129 | info["channel"] = -1 | 131 | info["channel"] = -1 |
2844 | 130 | _, err = b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}) | 132 | _, err = b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}, "s2") |
2845 | 131 | c.Check(err, Equals, broker.ErrUnexpectedValue) | 133 | c.Check(err, Equals, broker.ErrUnexpectedValue) |
2846 | 132 | } | 134 | } |
2847 | 133 | 135 | ||
2848 | @@ -139,7 +141,7 @@ | |||
2849 | 139 | b := s.MakeBroker(sto, testBrokerConfig, nil) | 141 | b := s.MakeBroker(sto, testBrokerConfig, nil) |
2850 | 140 | b.Start() | 142 | b.Start() |
2851 | 141 | defer b.Stop() | 143 | defer b.Stop() |
2853 | 142 | sess, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) | 144 | sess, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1") |
2854 | 143 | c.Assert(err, IsNil) | 145 | c.Assert(err, IsNil) |
2855 | 144 | c.Check(len(sess.SessionChannel()), Equals, 1) | 146 | c.Check(len(sess.SessionChannel()), Equals, 1) |
2856 | 145 | } | 147 | } |
2857 | @@ -149,7 +151,7 @@ | |||
2858 | 149 | b := s.MakeBroker(sto, testBrokerConfig, s.testlog) | 151 | b := s.MakeBroker(sto, testBrokerConfig, s.testlog) |
2859 | 150 | b.Start() | 152 | b.Start() |
2860 | 151 | defer b.Stop() | 153 | defer b.Stop() |
2862 | 152 | _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) | 154 | _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1") |
2863 | 153 | c.Assert(err, IsNil) | 155 | c.Assert(err, IsNil) |
2864 | 154 | // but | 156 | // but |
2865 | 155 | c.Check(s.testlog.Captured(), Matches, "ERROR unsuccessful feed pending, get channel snapshot for 0: get channel snapshot fail\n") | 157 | c.Check(s.testlog.Captured(), Matches, "ERROR unsuccessful feed pending, get channel snapshot for 0: get channel snapshot fail\n") |
2866 | @@ -160,22 +162,25 @@ | |||
2867 | 160 | b := s.MakeBroker(sto, testBrokerConfig, nil) | 162 | b := s.MakeBroker(sto, testBrokerConfig, nil) |
2868 | 161 | b.Start() | 163 | b.Start() |
2869 | 162 | defer b.Stop() | 164 | defer b.Stop() |
2876 | 163 | sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) | 165 | sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1") |
2877 | 164 | c.Assert(err, IsNil) | 166 | c.Assert(err, IsNil) |
2878 | 165 | sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) | 167 | sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s2") |
2879 | 166 | c.Assert(err, IsNil) | 168 | c.Assert(err, IsNil) |
2880 | 167 | checkAndFalse := false | 169 | // previous session got signaled by sending nil on its channel |
2881 | 168 | // previous session got signaled by closing its channel | 170 | var sentinel broker.Exchange |
2882 | 171 | got := false | ||
2883 | 169 | select { | 172 | select { |
2887 | 170 | case _, ok := <-sess1.SessionChannel(): | 173 | case sentinel = <-sess1.SessionChannel(): |
2888 | 171 | checkAndFalse = ok == false | 174 | got = true |
2889 | 172 | default: | 175 | case <-time.After(5 * time.Second): |
2890 | 176 | c.Fatal("taking too long to get sentinel") | ||
2891 | 173 | } | 177 | } |
2893 | 174 | c.Check(checkAndFalse, Equals, true) | 178 | c.Check(got, Equals, true) |
2894 | 179 | c.Check(sentinel, IsNil) | ||
2895 | 175 | c.Assert(s.RevealSession(b, "dev-1"), Equals, sess2) | 180 | c.Assert(s.RevealSession(b, "dev-1"), Equals, sess2) |
2896 | 176 | b.Unregister(sess1) | 181 | b.Unregister(sess1) |
2897 | 177 | // just to make sure the unregister was processed | 182 | // just to make sure the unregister was processed |
2899 | 178 | _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}) | 183 | _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}, "s3") |
2900 | 179 | c.Assert(err, IsNil) | 184 | c.Assert(err, IsNil) |
2901 | 180 | c.Check(s.RevealSession(b, "dev-1"), Equals, sess2) | 185 | c.Check(s.RevealSession(b, "dev-1"), Equals, sess2) |
2902 | 181 | } | 186 | } |
2903 | @@ -187,9 +192,9 @@ | |||
2904 | 187 | b := s.MakeBroker(sto, testBrokerConfig, nil) | 192 | b := s.MakeBroker(sto, testBrokerConfig, nil) |
2905 | 188 | b.Start() | 193 | b.Start() |
2906 | 189 | defer b.Stop() | 194 | defer b.Stop() |
2908 | 190 | sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) | 195 | sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1") |
2909 | 191 | c.Assert(err, IsNil) | 196 | c.Assert(err, IsNil) |
2911 | 192 | sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-2"}) | 197 | sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-2"}, "s2") |
2912 | 193 | c.Assert(err, IsNil) | 198 | c.Assert(err, IsNil) |
2913 | 194 | // add notification to channel *after* the registrations | 199 | // add notification to channel *after* the registrations |
2914 | 195 | muchLater := time.Now().Add(10 * time.Minute) | 200 | muchLater := time.Now().Add(10 * time.Minute) |
2915 | @@ -200,10 +205,10 @@ | |||
2916 | 200 | c.Fatal("taking too long to get broadcast exchange") | 205 | c.Fatal("taking too long to get broadcast exchange") |
2917 | 201 | case exchg1 := <-sess1.SessionChannel(): | 206 | case exchg1 := <-sess1.SessionChannel(): |
2918 | 202 | c.Check(s.RevealBroadcastExchange(exchg1), DeepEquals, &broker.BroadcastExchange{ | 207 | c.Check(s.RevealBroadcastExchange(exchg1), DeepEquals, &broker.BroadcastExchange{ |
2923 | 203 | ChanId: store.SystemInternalChannelId, | 208 | ChanId: store.SystemInternalChannelId, |
2924 | 204 | TopLevel: 1, | 209 | TopLevel: 1, |
2925 | 205 | NotificationPayloads: []json.RawMessage{notification1}, | 210 | Notifications: help.Ns(notification1), |
2926 | 206 | Decoded: []map[string]interface{}{decoded1}, | 211 | Decoded: []map[string]interface{}{decoded1}, |
2927 | 207 | }) | 212 | }) |
2928 | 208 | } | 213 | } |
2929 | 209 | select { | 214 | select { |
2930 | @@ -211,10 +216,10 @@ | |||
2931 | 211 | c.Fatal("taking too long to get broadcast exchange") | 216 | c.Fatal("taking too long to get broadcast exchange") |
2932 | 212 | case exchg2 := <-sess2.SessionChannel(): | 217 | case exchg2 := <-sess2.SessionChannel(): |
2933 | 213 | c.Check(s.RevealBroadcastExchange(exchg2), DeepEquals, &broker.BroadcastExchange{ | 218 | c.Check(s.RevealBroadcastExchange(exchg2), DeepEquals, &broker.BroadcastExchange{ |
2938 | 214 | ChanId: store.SystemInternalChannelId, | 219 | ChanId: store.SystemInternalChannelId, |
2939 | 215 | TopLevel: 1, | 220 | TopLevel: 1, |
2940 | 216 | NotificationPayloads: []json.RawMessage{notification1}, | 221 | Notifications: help.Ns(notification1), |
2941 | 217 | Decoded: []map[string]interface{}{decoded1}, | 222 | Decoded: []map[string]interface{}{decoded1}, |
2942 | 218 | }) | 223 | }) |
2943 | 219 | } | 224 | } |
2944 | 220 | } | 225 | } |
2945 | @@ -224,7 +229,7 @@ | |||
2946 | 224 | countdownToFail int | 229 | countdownToFail int |
2947 | 225 | } | 230 | } |
2948 | 226 | 231 | ||
2950 | 227 | func (sto *testFailingStore) GetChannelSnapshot(chanId store.InternalChannelId) (int64, []json.RawMessage, error) { | 232 | func (sto *testFailingStore) GetChannelSnapshot(chanId store.InternalChannelId) (int64, []protocol.Notification, error) { |
2951 | 228 | if sto.countdownToFail == 0 { | 233 | if sto.countdownToFail == 0 { |
2952 | 229 | return 0, nil, errors.New("get channel snapshot fail") | 234 | return 0, nil, errors.New("get channel snapshot fail") |
2953 | 230 | } | 235 | } |
2954 | @@ -232,6 +237,10 @@ | |||
2955 | 232 | return 0, nil, nil | 237 | return 0, nil, nil |
2956 | 233 | } | 238 | } |
2957 | 234 | 239 | ||
2958 | 240 | func (sto *testFailingStore) DropByMsgId(chanId store.InternalChannelId, targets []protocol.Notification) error { | ||
2959 | 241 | return errors.New("drop fail") | ||
2960 | 242 | } | ||
2961 | 243 | |||
2962 | 235 | func (s *CommonBrokerSuite) TestBroadcastFail(c *C) { | 244 | func (s *CommonBrokerSuite) TestBroadcastFail(c *C) { |
2963 | 236 | logged := make(chan bool, 1) | 245 | logged := make(chan bool, 1) |
2964 | 237 | s.testlog.SetLogEventCb(func(string) { | 246 | s.testlog.SetLogEventCb(func(string) { |
2965 | @@ -241,7 +250,7 @@ | |||
2966 | 241 | b := s.MakeBroker(sto, testBrokerConfig, s.testlog) | 250 | b := s.MakeBroker(sto, testBrokerConfig, s.testlog) |
2967 | 242 | b.Start() | 251 | b.Start() |
2968 | 243 | defer b.Stop() | 252 | defer b.Stop() |
2970 | 244 | _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) | 253 | _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}, "s1") |
2971 | 245 | c.Assert(err, IsNil) | 254 | c.Assert(err, IsNil) |
2972 | 246 | b.Broadcast(store.SystemInternalChannelId) | 255 | b.Broadcast(store.SystemInternalChannelId) |
2973 | 247 | select { | 256 | select { |
2974 | @@ -249,5 +258,83 @@ | |||
2975 | 249 | c.Fatal("taking too long to log error") | 258 | c.Fatal("taking too long to log error") |
2976 | 250 | case <-logged: | 259 | case <-logged: |
2977 | 251 | } | 260 | } |
2979 | 252 | c.Check(s.testlog.Captured(), Matches, "ERROR unsuccessful broadcast, get channel snapshot for 0: get channel snapshot fail\n") | 261 | c.Check(s.testlog.Captured(), Matches, "ERROR.*: get channel snapshot fail\n") |
2980 | 262 | } | ||
2981 | 263 | |||
2982 | 264 | func (s *CommonBrokerSuite) TestUnicast(c *C) { | ||
2983 | 265 | sto := store.NewInMemoryPendingStore() | ||
2984 | 266 | notification1 := json.RawMessage(`{"m": "M1"}`) | ||
2985 | 267 | notification2 := json.RawMessage(`{"m": "M2"}`) | ||
2986 | 268 | chanId1 := store.UnicastInternalChannelId("dev1", "dev1") | ||
2987 | 269 | chanId2 := store.UnicastInternalChannelId("dev2", "dev2") | ||
2988 | 270 | b := s.MakeBroker(sto, testBrokerConfig, nil) | ||
2989 | 271 | b.Start() | ||
2990 | 272 | defer b.Stop() | ||
2991 | 273 | sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev1"}, "s1") | ||
2992 | 274 | c.Assert(err, IsNil) | ||
2993 | 275 | sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev2"}, "s2") | ||
2994 | 276 | c.Assert(err, IsNil) | ||
2995 | 277 | // add notification to channel *after* the registrations | ||
2996 | 278 | muchLater := time.Now().Add(10 * time.Minute) | ||
2997 | 279 | sto.AppendToUnicastChannel(chanId1, "app1", notification1, "msg1", muchLater) | ||
2998 | 280 | sto.AppendToUnicastChannel(chanId2, "app2", notification2, "msg2", muchLater) | ||
2999 | 281 | b.Unicast(chanId2, chanId1) | ||
3000 | 282 | select { | ||
3001 | 283 | case <-time.After(5 * time.Second): | ||
3002 | 284 | c.Fatal("taking too long to get unicast exchange") | ||
3003 | 285 | case exchg1 := <-sess1.SessionChannel(): | ||
3004 | 286 | u1 := s.RevealUnicastExchange(exchg1) | ||
3005 | 287 | c.Check(u1.ChanId, Equals, chanId1) | ||
3006 | 288 | } | ||
3007 | 289 | select { | ||
3008 | 290 | case <-time.After(5 * time.Second): | ||
3009 | 291 | c.Fatal("taking too long to get unicast exchange") | ||
3010 | 292 | case exchg2 := <-sess2.SessionChannel(): | ||
3011 | 293 | u2 := s.RevealUnicastExchange(exchg2) | ||
3012 | 294 | c.Check(u2.ChanId, Equals, chanId2) | ||
3013 | 295 | } | ||
3014 | 296 | } | ||
3015 | 297 | |||
3016 | 298 | func (s *CommonBrokerSuite) TestGetAndDrop(c *C) { | ||
3017 | 299 | sto := store.NewInMemoryPendingStore() | ||
3018 | 300 | notification1 := json.RawMessage(`{"m": "M1"}`) | ||
3019 | 301 | chanId1 := store.UnicastInternalChannelId("dev3", "dev3") | ||
3020 | 302 | b := s.MakeBroker(sto, testBrokerConfig, nil) | ||
3021 | 303 | b.Start() | ||
3022 | 304 | defer b.Stop() | ||
3023 | 305 | sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev3"}, "s1") | ||
3024 | 306 | c.Assert(err, IsNil) | ||
3025 | 307 | muchLater := time.Now().Add(10 * time.Minute) | ||
3026 | 308 | sto.AppendToUnicastChannel(chanId1, "app1", notification1, "msg1", muchLater) | ||
3027 | 309 | _, expected, err := sto.GetChannelSnapshot(chanId1) | ||
3028 | 310 | c.Assert(err, IsNil) | ||
3029 | 311 | _, notifs, err := sess1.Get(chanId1, false) | ||
3030 | 312 | c.Check(notifs, HasLen, 1) | ||
3031 | 313 | c.Check(notifs, DeepEquals, expected) | ||
3032 | 314 | err = sess1.DropByMsgId(chanId1, notifs) | ||
3033 | 315 | c.Assert(err, IsNil) | ||
3034 | 316 | _, notifs, err = sess1.Get(chanId1, true) | ||
3035 | 317 | c.Check(notifs, HasLen, 0) | ||
3036 | 318 | _, expected, err = sto.GetChannelSnapshot(chanId1) | ||
3037 | 319 | c.Assert(err, IsNil) | ||
3038 | 320 | c.Check(expected, HasLen, 0) | ||
3039 | 321 | |||
3040 | 322 | } | ||
3041 | 323 | |||
3042 | 324 | func (s *CommonBrokerSuite) TestGetAndDropErrors(c *C) { | ||
3043 | 325 | chanId1 := store.UnicastInternalChannelId("dev3", "dev3") | ||
3044 | 326 | sto := &testFailingStore{countdownToFail: 1} | ||
3045 | 327 | b := s.MakeBroker(sto, testBrokerConfig, s.testlog) | ||
3046 | 328 | b.Start() | ||
3047 | 329 | defer b.Stop() | ||
3048 | 330 | sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev3"}, "s1") | ||
3049 | 331 | c.Assert(err, IsNil) | ||
3050 | 332 | _, _, err = sess1.Get(chanId1, false) | ||
3051 | 333 | c.Assert(err, ErrorMatches, "get channel snapshot fail") | ||
3052 | 334 | c.Check(s.testlog.Captured(), Matches, "ERROR unsuccessful, get channel snapshot for Udev3:dev3 \\(cachedOk=false\\): get channel snapshot fail\n") | ||
3053 | 335 | s.testlog.ResetCapture() | ||
3054 | 336 | |||
3055 | 337 | err = sess1.DropByMsgId(chanId1, nil) | ||
3056 | 338 | c.Assert(err, ErrorMatches, "drop fail") | ||
3057 | 339 | c.Check(s.testlog.Captured(), Matches, "ERROR unsuccessful, drop from channel Udev3:dev3: drop fail\n") | ||
3058 | 253 | } | 340 | } |
3059 | 254 | 341 | ||
3060 | === modified file 'server/session/session.go' | |||
3061 | --- server/session/session.go 2014-04-11 08:47:18 +0000 | |||
3062 | +++ server/session/session.go 2014-05-12 14:11:46 +0000 | |||
3063 | @@ -18,6 +18,7 @@ | |||
3064 | 18 | package session | 18 | package session |
3065 | 19 | 19 | ||
3066 | 20 | import ( | 20 | import ( |
3067 | 21 | "errors" | ||
3068 | 21 | "net" | 22 | "net" |
3069 | 22 | "time" | 23 | "time" |
3070 | 23 | 24 | ||
3071 | @@ -35,7 +36,7 @@ | |||
3072 | 35 | } | 36 | } |
3073 | 36 | 37 | ||
3074 | 37 | // sessionStart manages the start of the protocol session. | 38 | // sessionStart manages the start of the protocol session. |
3076 | 38 | func sessionStart(proto protocol.Protocol, brkr broker.Broker, cfg SessionConfig) (broker.BrokerSession, error) { | 39 | func sessionStart(proto protocol.Protocol, brkr broker.Broker, cfg SessionConfig, sessionId string) (broker.BrokerSession, error) { |
3077 | 39 | var connMsg protocol.ConnectMsg | 40 | var connMsg protocol.ConnectMsg |
3078 | 40 | proto.SetDeadline(time.Now().Add(cfg.ExchangeTimeout())) | 41 | proto.SetDeadline(time.Now().Add(cfg.ExchangeTimeout())) |
3079 | 41 | err := proto.ReadMessage(&connMsg) | 42 | err := proto.ReadMessage(&connMsg) |
3080 | @@ -52,9 +53,11 @@ | |||
3081 | 52 | if err != nil { | 53 | if err != nil { |
3082 | 53 | return nil, err | 54 | return nil, err |
3083 | 54 | } | 55 | } |
3085 | 55 | return brkr.Register(&connMsg) | 56 | return brkr.Register(&connMsg, sessionId) |
3086 | 56 | } | 57 | } |
3087 | 57 | 58 | ||
3088 | 59 | var errOneway = errors.New("oneway") | ||
3089 | 60 | |||
3090 | 58 | // exchange writes outMsg message, reads answer in inMsg | 61 | // exchange writes outMsg message, reads answer in inMsg |
3091 | 59 | func exchange(proto protocol.Protocol, outMsg, inMsg interface{}, exchangeTimeout time.Duration) error { | 62 | func exchange(proto protocol.Protocol, outMsg, inMsg interface{}, exchangeTimeout time.Duration) error { |
3092 | 60 | proto.SetDeadline(time.Now().Add(exchangeTimeout)) | 63 | proto.SetDeadline(time.Now().Add(exchangeTimeout)) |
3093 | @@ -62,7 +65,10 @@ | |||
3094 | 62 | if err != nil { | 65 | if err != nil { |
3095 | 63 | return err | 66 | return err |
3096 | 64 | } | 67 | } |
3098 | 65 | if inMsg == nil { // no answer expected, breaking connection | 68 | if inMsg == nil { // no answer expected |
3099 | 69 | if outMsg.(protocol.OnewayMsg).OnewayContinue() { | ||
3100 | 70 | return errOneway | ||
3101 | 71 | } | ||
3102 | 66 | return &broker.ErrAbort{"session broken for reason"} | 72 | return &broker.ErrAbort{"session broken for reason"} |
3103 | 67 | } | 73 | } |
3104 | 68 | err = proto.ReadMessage(inMsg) | 74 | err = proto.ReadMessage(inMsg) |
3105 | @@ -78,6 +84,10 @@ | |||
3106 | 78 | exchangeTimeout := cfg.ExchangeTimeout() | 84 | exchangeTimeout := cfg.ExchangeTimeout() |
3107 | 79 | pingTimer := time.NewTimer(pingInterval) | 85 | pingTimer := time.NewTimer(pingInterval) |
3108 | 80 | intervalStart := time.Now() | 86 | intervalStart := time.Now() |
3109 | 87 | pingTimerReset := func() { | ||
3110 | 88 | pingTimer.Reset(pingInterval) | ||
3111 | 89 | intervalStart = time.Now() | ||
3112 | 90 | } | ||
3113 | 81 | ch := sess.SessionChannel() | 91 | ch := sess.SessionChannel() |
3114 | 82 | Loop: | 92 | Loop: |
3115 | 83 | for { | 93 | for { |
3116 | @@ -93,16 +103,15 @@ | |||
3117 | 93 | if pongMsg.Type != "pong" { | 103 | if pongMsg.Type != "pong" { |
3118 | 94 | return &broker.ErrAbort{"expected PONG message"} | 104 | return &broker.ErrAbort{"expected PONG message"} |
3119 | 95 | } | 105 | } |
3122 | 96 | pingTimer.Reset(pingInterval) | 106 | pingTimerReset() |
3123 | 97 | case exchg, ok := <-ch: | 107 | case exchg := <-ch: |
3124 | 98 | pingTimer.Stop() | 108 | pingTimer.Stop() |
3126 | 99 | if !ok { | 109 | if exchg == nil { |
3127 | 100 | return &broker.ErrAbort{"terminated"} | 110 | return &broker.ErrAbort{"terminated"} |
3128 | 101 | } | 111 | } |
3129 | 102 | outMsg, inMsg, err := exchg.Prepare(sess) | 112 | outMsg, inMsg, err := exchg.Prepare(sess) |
3130 | 103 | if err == broker.ErrNop { // nothing to do | 113 | if err == broker.ErrNop { // nothing to do |
3133 | 104 | pingTimer.Reset(pingInterval) | 114 | pingTimerReset() |
3132 | 105 | intervalStart = time.Now() | ||
3134 | 106 | continue Loop | 115 | continue Loop |
3135 | 107 | } | 116 | } |
3136 | 108 | if err != nil { | 117 | if err != nil { |
3137 | @@ -111,12 +120,15 @@ | |||
3138 | 111 | for { | 120 | for { |
3139 | 112 | done := outMsg.Split() | 121 | done := outMsg.Split() |
3140 | 113 | err = exchange(proto, outMsg, inMsg, exchangeTimeout) | 122 | err = exchange(proto, outMsg, inMsg, exchangeTimeout) |
3141 | 123 | if err == errOneway { | ||
3142 | 124 | pingTimerReset() | ||
3143 | 125 | continue Loop | ||
3144 | 126 | } | ||
3145 | 114 | if err != nil { | 127 | if err != nil { |
3146 | 115 | return err | 128 | return err |
3147 | 116 | } | 129 | } |
3148 | 117 | if done { | 130 | if done { |
3151 | 118 | pingTimer.Reset(pingInterval) | 131 | pingTimerReset() |
3150 | 119 | intervalStart = time.Now() | ||
3152 | 120 | } | 132 | } |
3153 | 121 | err = exchg.Acked(sess, done) | 133 | err = exchg.Acked(sess, done) |
3154 | 122 | if err != nil { | 134 | if err != nil { |
3155 | @@ -142,7 +154,7 @@ | |||
3156 | 142 | return track.End(&broker.ErrAbort{"unexpected wire format version"}) | 154 | return track.End(&broker.ErrAbort{"unexpected wire format version"}) |
3157 | 143 | } | 155 | } |
3158 | 144 | proto := protocol.NewProtocol0(conn) | 156 | proto := protocol.NewProtocol0(conn) |
3160 | 145 | sess, err := sessionStart(proto, brkr, cfg) | 157 | sess, err := sessionStart(proto, brkr, cfg, track.SessionId()) |
3161 | 146 | if err != nil { | 158 | if err != nil { |
3162 | 147 | return track.End(err) | 159 | return track.End(err) |
3163 | 148 | } | 160 | } |
3164 | 149 | 161 | ||
3165 | === modified file 'server/session/session_test.go' | |||
3166 | --- server/session/session_test.go 2014-04-11 08:47:18 +0000 | |||
3167 | +++ server/session/session_test.go 2014-05-12 14:11:46 +0000 | |||
3168 | @@ -130,8 +130,8 @@ | |||
3169 | 130 | return &testBroker{registration: make(chan interface{}, 2)} | 130 | return &testBroker{registration: make(chan interface{}, 2)} |
3170 | 131 | } | 131 | } |
3171 | 132 | 132 | ||
3174 | 133 | func (tb *testBroker) Register(connect *protocol.ConnectMsg) (broker.BrokerSession, error) { | 133 | func (tb *testBroker) Register(connect *protocol.ConnectMsg, sessionId string) (broker.BrokerSession, error) { |
3175 | 134 | tb.registration <- "register " + connect.DeviceId | 134 | tb.registration <- fmt.Sprintf("register %s %s", connect.DeviceId, sessionId) |
3176 | 135 | return &testing.TestBrokerSession{DeviceId: connect.DeviceId}, tb.err | 135 | return &testing.TestBrokerSession{DeviceId: connect.DeviceId}, tb.err |
3177 | 136 | } | 136 | } |
3178 | 137 | 137 | ||
3179 | @@ -148,7 +148,7 @@ | |||
3180 | 148 | brkr := newTestBroker() | 148 | brkr := newTestBroker() |
3181 | 149 | go func() { | 149 | go func() { |
3182 | 150 | var err error | 150 | var err error |
3184 | 151 | sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout) | 151 | sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout, "s1") |
3185 | 152 | errCh <- err | 152 | errCh <- err |
3186 | 153 | }() | 153 | }() |
3187 | 154 | c.Check(takeNext(down), Equals, "deadline 5ms") | 154 | c.Check(takeNext(down), Equals, "deadline 5ms") |
3188 | @@ -160,7 +160,7 @@ | |||
3189 | 160 | up <- nil // no write error | 160 | up <- nil // no write error |
3190 | 161 | err := <-errCh | 161 | err := <-errCh |
3191 | 162 | c.Check(err, IsNil) | 162 | c.Check(err, IsNil) |
3193 | 163 | c.Check(takeNext(brkr.registration), Equals, "register dev-1") | 163 | c.Check(takeNext(brkr.registration), Equals, "register dev-1 s1") |
3194 | 164 | c.Check(sess.DeviceIdentifier(), Equals, "dev-1") | 164 | c.Check(sess.DeviceIdentifier(), Equals, "dev-1") |
3195 | 165 | } | 165 | } |
3196 | 166 | 166 | ||
3197 | @@ -175,7 +175,7 @@ | |||
3198 | 175 | brkr.err = errRegister | 175 | brkr.err = errRegister |
3199 | 176 | go func() { | 176 | go func() { |
3200 | 177 | var err error | 177 | var err error |
3202 | 178 | sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout) | 178 | sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout, "s2") |
3203 | 179 | errCh <- err | 179 | errCh <- err |
3204 | 180 | }() | 180 | }() |
3205 | 181 | up <- protocol.ConnectMsg{Type: "connect", ClientVer: "1", DeviceId: "dev-1"} | 181 | up <- protocol.ConnectMsg{Type: "connect", ClientVer: "1", DeviceId: "dev-1"} |
3206 | @@ -190,7 +190,7 @@ | |||
3207 | 190 | down := make(chan interface{}, 5) | 190 | down := make(chan interface{}, 5) |
3208 | 191 | tp := &testProtocol{up, down} | 191 | tp := &testProtocol{up, down} |
3209 | 192 | up <- io.ErrUnexpectedEOF | 192 | up <- io.ErrUnexpectedEOF |
3211 | 193 | _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout) | 193 | _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s3") |
3212 | 194 | c.Check(err, Equals, io.ErrUnexpectedEOF) | 194 | c.Check(err, Equals, io.ErrUnexpectedEOF) |
3213 | 195 | } | 195 | } |
3214 | 196 | 196 | ||
3215 | @@ -200,7 +200,7 @@ | |||
3216 | 200 | tp := &testProtocol{up, down} | 200 | tp := &testProtocol{up, down} |
3217 | 201 | up <- protocol.ConnectMsg{Type: "connect"} | 201 | up <- protocol.ConnectMsg{Type: "connect"} |
3218 | 202 | up <- io.ErrUnexpectedEOF | 202 | up <- io.ErrUnexpectedEOF |
3220 | 203 | _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout) | 203 | _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s4") |
3221 | 204 | c.Check(err, Equals, io.ErrUnexpectedEOF) | 204 | c.Check(err, Equals, io.ErrUnexpectedEOF) |
3222 | 205 | // sanity | 205 | // sanity |
3223 | 206 | c.Check(takeNext(down), Matches, "deadline.*") | 206 | c.Check(takeNext(down), Matches, "deadline.*") |
3224 | @@ -212,7 +212,7 @@ | |||
3225 | 212 | down := make(chan interface{}, 5) | 212 | down := make(chan interface{}, 5) |
3226 | 213 | tp := &testProtocol{up, down} | 213 | tp := &testProtocol{up, down} |
3227 | 214 | up <- protocol.ConnectMsg{Type: "what"} | 214 | up <- protocol.ConnectMsg{Type: "what"} |
3229 | 215 | _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout) | 215 | _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout, "s5") |
3230 | 216 | c.Check(err, DeepEquals, &broker.ErrAbort{"expected CONNECT message"}) | 216 | c.Check(err, DeepEquals, &broker.ErrAbort{"expected CONNECT message"}) |
3231 | 217 | } | 217 | } |
3232 | 218 | 218 | ||
3233 | @@ -222,14 +222,14 @@ | |||
3234 | 222 | } | 222 | } |
3235 | 223 | 223 | ||
3236 | 224 | func (s *sessionSuite) TestSessionLoop(c *C) { | 224 | func (s *sessionSuite) TestSessionLoop(c *C) { |
3238 | 225 | nopTrack := NewTracker(s.testlog) | 225 | track := &testTracker{NewTracker(s.testlog), make(chan interface{}, 2)} |
3239 | 226 | errCh := make(chan error, 1) | 226 | errCh := make(chan error, 1) |
3240 | 227 | up := make(chan interface{}, 5) | 227 | up := make(chan interface{}, 5) |
3241 | 228 | down := make(chan interface{}, 5) | 228 | down := make(chan interface{}, 5) |
3242 | 229 | tp := &testProtocol{up, down} | 229 | tp := &testProtocol{up, down} |
3243 | 230 | sess := &testing.TestBrokerSession{} | 230 | sess := &testing.TestBrokerSession{} |
3244 | 231 | go func() { | 231 | go func() { |
3246 | 232 | errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) | 232 | errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, track) |
3247 | 233 | }() | 233 | }() |
3248 | 234 | c.Check(takeNext(down), Equals, "deadline 2ms") | 234 | c.Check(takeNext(down), Equals, "deadline 2ms") |
3249 | 235 | c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"}) | 235 | c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"}) |
3250 | @@ -241,6 +241,9 @@ | |||
3251 | 241 | up <- io.ErrUnexpectedEOF | 241 | up <- io.ErrUnexpectedEOF |
3252 | 242 | err := <-errCh | 242 | err := <-errCh |
3253 | 243 | c.Check(err, Equals, io.ErrUnexpectedEOF) | 243 | c.Check(err, Equals, io.ErrUnexpectedEOF) |
3254 | 244 | c.Check(track.interval, HasLen, 2) | ||
3255 | 245 | c.Check((<-track.interval).(time.Duration) <= 8*time.Millisecond, Equals, true) | ||
3256 | 246 | c.Check((<-track.interval).(time.Duration) <= 8*time.Millisecond, Equals, true) | ||
3257 | 244 | } | 247 | } |
3258 | 245 | 248 | ||
3259 | 246 | func (s *sessionSuite) TestSessionLoopWriteError(c *C) { | 249 | func (s *sessionSuite) TestSessionLoopWriteError(c *C) { |
3260 | @@ -357,7 +360,7 @@ | |||
3261 | 357 | go func() { | 360 | go func() { |
3262 | 358 | errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) | 361 | errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) |
3263 | 359 | }() | 362 | }() |
3265 | 360 | close(exchanges) | 363 | exchanges <- nil |
3266 | 361 | err := <-errCh | 364 | err := <-errCh |
3267 | 362 | c.Check(err, DeepEquals, &broker.ErrAbort{"terminated"}) | 365 | c.Check(err, DeepEquals, &broker.ErrAbort{"terminated"}) |
3268 | 363 | } | 366 | } |
3269 | @@ -477,18 +480,44 @@ | |||
3270 | 477 | down := make(chan interface{}, 5) | 480 | down := make(chan interface{}, 5) |
3271 | 478 | tp := &testProtocol{up, down} | 481 | tp := &testProtocol{up, down} |
3272 | 479 | exchanges := make(chan broker.Exchange, 1) | 482 | exchanges := make(chan broker.Exchange, 1) |
3274 | 480 | exchanges <- &broker.ConnBrokenExchange{"REASON"} | 483 | msg := &protocol.ConnBrokenMsg{"connbroken", "BREASON"} |
3275 | 484 | exchanges <- &broker.ConnMetaExchange{msg} | ||
3276 | 481 | sess := &testing.TestBrokerSession{Exchanges: exchanges} | 485 | sess := &testing.TestBrokerSession{Exchanges: exchanges} |
3277 | 482 | go func() { | 486 | go func() { |
3278 | 483 | errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) | 487 | errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) |
3279 | 484 | }() | 488 | }() |
3280 | 485 | c.Check(takeNext(down), Equals, "deadline 2ms") | 489 | c.Check(takeNext(down), Equals, "deadline 2ms") |
3282 | 486 | c.Check(takeNext(down), DeepEquals, protocol.ConnBrokenMsg{"connbroken", "REASON"}) | 490 | c.Check(takeNext(down), DeepEquals, protocol.ConnBrokenMsg{"connbroken", "BREASON"}) |
3283 | 487 | up <- nil // no write error | 491 | up <- nil // no write error |
3284 | 488 | err := <-errCh | 492 | err := <-errCh |
3285 | 489 | c.Check(err, DeepEquals, &broker.ErrAbort{"session broken for reason"}) | 493 | c.Check(err, DeepEquals, &broker.ErrAbort{"session broken for reason"}) |
3286 | 490 | } | 494 | } |
3287 | 491 | 495 | ||
3288 | 496 | func (s *sessionSuite) TestSessionLoopConnWarnExchange(c *C) { | ||
3289 | 497 | nopTrack := NewTracker(s.testlog) | ||
3290 | 498 | errCh := make(chan error, 1) | ||
3291 | 499 | up := make(chan interface{}, 5) | ||
3292 | 500 | down := make(chan interface{}, 5) | ||
3293 | 501 | tp := &testProtocol{up, down} | ||
3294 | 502 | exchanges := make(chan broker.Exchange, 1) | ||
3295 | 503 | msg := &protocol.ConnWarnMsg{"connwarn", "WREASON"} | ||
3296 | 504 | exchanges <- &broker.ConnMetaExchange{msg} | ||
3297 | 505 | sess := &testing.TestBrokerSession{Exchanges: exchanges} | ||
3298 | 506 | go func() { | ||
3299 | 507 | errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) | ||
3300 | 508 | }() | ||
3301 | 509 | c.Check(takeNext(down), Equals, "deadline 2ms") | ||
3302 | 510 | c.Check(takeNext(down), DeepEquals, protocol.ConnWarnMsg{"connwarn", "WREASON"}) | ||
3303 | 511 | up <- nil // no write error | ||
3304 | 512 | // session continues | ||
3305 | 513 | c.Check(takeNext(down), Equals, "deadline 2ms") | ||
3306 | 514 | c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"}) | ||
3307 | 515 | up <- nil // no write error | ||
3308 | 516 | up <- io.EOF | ||
3309 | 517 | err := <-errCh | ||
3310 | 518 | c.Check(err, Equals, io.EOF) | ||
3311 | 519 | } | ||
3312 | 520 | |||
3313 | 492 | type testTracker struct { | 521 | type testTracker struct { |
3314 | 493 | SessionTracker | 522 | SessionTracker |
3315 | 494 | interval chan interface{} | 523 | interval chan interface{} |
3316 | @@ -593,7 +622,7 @@ | |||
3317 | 593 | msg, err = downStream.ReadBytes(byte('}')) | 622 | msg, err = downStream.ReadBytes(byte('}')) |
3318 | 594 | c.Check(err, IsNil) | 623 | c.Check(err, IsNil) |
3319 | 595 | c.Check(msg, DeepEquals, []byte("\x00\x0c{\"T\":\"ping\"}")) | 624 | c.Check(msg, DeepEquals, []byte("\x00\x0c{\"T\":\"ping\"}")) |
3321 | 596 | c.Check(takeNext(brkr.registration), Equals, "register DEV") | 625 | c.Check(takeNext(brkr.registration), Equals, "register DEV "+track.SessionId()) |
3322 | 597 | c.Check(len(brkr.registration), Equals, 0) // not yet unregistered | 626 | c.Check(len(brkr.registration), Equals, 0) // not yet unregistered |
3323 | 598 | cli.Close() | 627 | cli.Close() |
3324 | 599 | err = <-errCh | 628 | err = <-errCh |
3325 | 600 | 629 | ||
3326 | === modified file 'server/session/tracker.go' | |||
3327 | --- server/session/tracker.go 2014-02-10 23:19:08 +0000 | |||
3328 | +++ server/session/tracker.go 2014-05-12 14:11:46 +0000 | |||
3329 | @@ -17,6 +17,7 @@ | |||
3330 | 17 | package session | 17 | package session |
3331 | 18 | 18 | ||
3332 | 19 | import ( | 19 | import ( |
3333 | 20 | "fmt" | ||
3334 | 20 | "net" | 21 | "net" |
3335 | 21 | "time" | 22 | "time" |
3336 | 22 | 23 | ||
3337 | @@ -29,6 +30,8 @@ | |||
3338 | 29 | logger.Logger | 30 | logger.Logger |
3339 | 30 | // Session got started. | 31 | // Session got started. |
3340 | 31 | Start(WithRemoteAddr) | 32 | Start(WithRemoteAddr) |
3341 | 33 | // SessionId | ||
3342 | 34 | SessionId() string | ||
3343 | 32 | // Session got registered with broker as sess BrokerSession. | 35 | // Session got registered with broker as sess BrokerSession. |
3344 | 33 | Registered(sess broker.BrokerSession) | 36 | Registered(sess broker.BrokerSession) |
3345 | 34 | // Report effective elapsed ping interval. | 37 | // Report effective elapsed ping interval. |
3346 | @@ -47,7 +50,7 @@ | |||
3347 | 47 | // Tracker implements SessionTracker simply. | 50 | // Tracker implements SessionTracker simply. |
3348 | 48 | type tracker struct { | 51 | type tracker struct { |
3349 | 49 | logger.Logger | 52 | logger.Logger |
3351 | 50 | sessionId int64 // xxx use timeuuid later | 53 | sessionId string |
3352 | 51 | } | 54 | } |
3353 | 52 | 55 | ||
3354 | 53 | func NewTracker(logger logger.Logger) SessionTracker { | 56 | func NewTracker(logger logger.Logger) SessionTracker { |
3355 | @@ -55,18 +58,22 @@ | |||
3356 | 55 | } | 58 | } |
3357 | 56 | 59 | ||
3358 | 57 | func (trk *tracker) Start(conn WithRemoteAddr) { | 60 | func (trk *tracker) Start(conn WithRemoteAddr) { |
3361 | 58 | trk.sessionId = time.Now().UnixNano() - sessionsEpoch | 61 | trk.sessionId = fmt.Sprintf("%x", time.Now().UnixNano()-sessionsEpoch) |
3362 | 59 | trk.Debugf("session(%x) connected %v", trk.sessionId, conn.RemoteAddr()) | 62 | trk.Debugf("session(%s) connected %v", trk.sessionId, conn.RemoteAddr()) |
3363 | 63 | } | ||
3364 | 64 | |||
3365 | 65 | func (trk *tracker) SessionId() string { | ||
3366 | 66 | return trk.sessionId | ||
3367 | 60 | } | 67 | } |
3368 | 61 | 68 | ||
3369 | 62 | func (trk *tracker) Registered(sess broker.BrokerSession) { | 69 | func (trk *tracker) Registered(sess broker.BrokerSession) { |
3371 | 63 | trk.Infof("session(%x) registered %v", trk.sessionId, sess.DeviceIdentifier()) | 70 | trk.Infof("session(%s) registered %v", trk.sessionId, sess.DeviceIdentifier()) |
3372 | 64 | } | 71 | } |
3373 | 65 | 72 | ||
3374 | 66 | func (trk *tracker) EffectivePingInterval(time.Duration) { | 73 | func (trk *tracker) EffectivePingInterval(time.Duration) { |
3375 | 67 | } | 74 | } |
3376 | 68 | 75 | ||
3377 | 69 | func (trk *tracker) End(err error) error { | 76 | func (trk *tracker) End(err error) error { |
3379 | 70 | trk.Debugf("session(%x) ended with: %v", trk.sessionId, err) | 77 | trk.Debugf("session(%s) ended with: %v", trk.sessionId, err) |
3380 | 71 | return err | 78 | return err |
3381 | 72 | } | 79 | } |
3382 | 73 | 80 | ||
3383 | === modified file 'server/session/tracker_test.go' | |||
3384 | --- server/session/tracker_test.go 2014-02-10 23:19:08 +0000 | |||
3385 | +++ server/session/tracker_test.go 2014-05-12 14:11:46 +0000 | |||
3386 | @@ -46,8 +46,8 @@ | |||
3387 | 46 | func (s *trackerSuite) TestSessionTrackStart(c *C) { | 46 | func (s *trackerSuite) TestSessionTrackStart(c *C) { |
3388 | 47 | track := NewTracker(s.testlog) | 47 | track := NewTracker(s.testlog) |
3389 | 48 | track.Start(&testRemoteAddrable{}) | 48 | track.Start(&testRemoteAddrable{}) |
3392 | 49 | c.Check(track.(*tracker).sessionId, Not(Equals), 0) | 49 | c.Check(track.SessionId(), Not(Equals), "") |
3393 | 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()) |
3394 | 51 | c.Check(s.testlog.Captured(), Matches, regExpected) | 51 | c.Check(s.testlog.Captured(), Matches, regExpected) |
3395 | 52 | } | 52 | } |
3396 | 53 | 53 | ||
3397 | @@ -55,7 +55,7 @@ | |||
3398 | 55 | track := NewTracker(s.testlog) | 55 | track := NewTracker(s.testlog) |
3399 | 56 | track.Start(&testRemoteAddrable{}) | 56 | track.Start(&testRemoteAddrable{}) |
3400 | 57 | track.Registered(&testing.TestBrokerSession{DeviceId: "DEV-ID"}) | 57 | track.Registered(&testing.TestBrokerSession{DeviceId: "DEV-ID"}) |
3402 | 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()) |
3403 | 59 | c.Check(s.testlog.Captured(), Matches, regExpected) | 59 | c.Check(s.testlog.Captured(), Matches, regExpected) |
3404 | 60 | } | 60 | } |
3405 | 61 | 61 | ||
3406 | @@ -63,6 +63,6 @@ | |||
3407 | 63 | track := NewTracker(s.testlog) | 63 | track := NewTracker(s.testlog) |
3408 | 64 | track.Start(&testRemoteAddrable{}) | 64 | track.Start(&testRemoteAddrable{}) |
3409 | 65 | track.End(&broker.ErrAbort{}) | 65 | track.End(&broker.ErrAbort{}) |
3411 | 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()) |
3412 | 67 | c.Check(s.testlog.Captured(), Matches, regExpected) | 67 | c.Check(s.testlog.Captured(), Matches, regExpected) |
3413 | 68 | } | 68 | } |
3414 | 69 | 69 | ||
3415 | === modified file 'server/store/inmemory.go' | |||
3416 | --- server/store/inmemory.go 2014-02-18 14:19:05 +0000 | |||
3417 | +++ server/store/inmemory.go 2014-05-12 14:11:46 +0000 | |||
3418 | @@ -20,18 +20,15 @@ | |||
3419 | 20 | "encoding/json" | 20 | "encoding/json" |
3420 | 21 | "sync" | 21 | "sync" |
3421 | 22 | "time" | 22 | "time" |
3422 | 23 | |||
3423 | 24 | "launchpad.net/ubuntu-push/protocol" | ||
3424 | 23 | ) | 25 | ) |
3425 | 24 | 26 | ||
3426 | 25 | // one stored notification | ||
3427 | 26 | type notification struct { | ||
3428 | 27 | payload json.RawMessage | ||
3429 | 28 | expiration time.Time | ||
3430 | 29 | } | ||
3431 | 30 | |||
3432 | 31 | // one stored channel | 27 | // one stored channel |
3433 | 32 | type channel struct { | 28 | type channel struct { |
3434 | 33 | topLevel int64 | 29 | topLevel int64 |
3436 | 34 | notifications []notification | 30 | notifications []protocol.Notification |
3437 | 31 | expirations []time.Time | ||
3438 | 35 | } | 32 | } |
3439 | 36 | 33 | ||
3440 | 37 | // InMemoryPendingStore is a basic in-memory pending notification store. | 34 | // InMemoryPendingStore is a basic in-memory pending notification store. |
3441 | @@ -54,23 +51,35 @@ | |||
3442 | 54 | return InternalChannelId(""), ErrUnknownChannel | 51 | return InternalChannelId(""), ErrUnknownChannel |
3443 | 55 | } | 52 | } |
3444 | 56 | 53 | ||
3446 | 57 | func (sto *InMemoryPendingStore) AppendToChannel(chanId InternalChannelId, notificationPayload json.RawMessage, expiration time.Time) error { | 54 | func (sto *InMemoryPendingStore) appendToChannel(chanId InternalChannelId, newNotification protocol.Notification, inc int64, expiration time.Time) error { |
3447 | 58 | sto.lock.Lock() | 55 | sto.lock.Lock() |
3448 | 59 | defer sto.lock.Unlock() | 56 | defer sto.lock.Unlock() |
3449 | 60 | prev := sto.store[chanId] | 57 | prev := sto.store[chanId] |
3450 | 61 | if prev == nil { | 58 | if prev == nil { |
3451 | 62 | prev = &channel{} | 59 | prev = &channel{} |
3452 | 63 | } | 60 | } |
3458 | 64 | prev.topLevel++ | 61 | prev.topLevel += inc |
3459 | 65 | prev.notifications = append(prev.notifications, notification{ | 62 | prev.notifications = append(prev.notifications, newNotification) |
3460 | 66 | payload: notificationPayload, | 63 | prev.expirations = append(prev.expirations, expiration) |
3456 | 67 | expiration: expiration, | ||
3457 | 68 | }) | ||
3461 | 69 | sto.store[chanId] = prev | 64 | sto.store[chanId] = prev |
3462 | 70 | return nil | 65 | return nil |
3463 | 71 | } | 66 | } |
3464 | 72 | 67 | ||
3466 | 73 | func (sto *InMemoryPendingStore) GetChannelSnapshot(chanId InternalChannelId) (int64, []json.RawMessage, error) { | 68 | func (sto *InMemoryPendingStore) AppendToChannel(chanId InternalChannelId, notificationPayload json.RawMessage, expiration time.Time) error { |
3467 | 69 | newNotification := protocol.Notification{Payload: notificationPayload} | ||
3468 | 70 | return sto.appendToChannel(chanId, newNotification, 1, expiration) | ||
3469 | 71 | } | ||
3470 | 72 | |||
3471 | 73 | func (sto *InMemoryPendingStore) AppendToUnicastChannel(chanId InternalChannelId, appId string, notificationPayload json.RawMessage, msgId string, expiration time.Time) error { | ||
3472 | 74 | newNotification := protocol.Notification{ | ||
3473 | 75 | Payload: notificationPayload, | ||
3474 | 76 | AppId: appId, | ||
3475 | 77 | MsgId: msgId, | ||
3476 | 78 | } | ||
3477 | 79 | return sto.appendToChannel(chanId, newNotification, 0, expiration) | ||
3478 | 80 | } | ||
3479 | 81 | |||
3480 | 82 | func (sto *InMemoryPendingStore) GetChannelSnapshot(chanId InternalChannelId) (int64, []protocol.Notification, error) { | ||
3481 | 74 | sto.lock.Lock() | 83 | sto.lock.Lock() |
3482 | 75 | defer sto.lock.Unlock() | 84 | defer sto.lock.Unlock() |
3483 | 76 | channel, ok := sto.store[chanId] | 85 | channel, ok := sto.store[chanId] |
3484 | @@ -79,14 +88,19 @@ | |||
3485 | 79 | } | 88 | } |
3486 | 80 | topLevel := channel.topLevel | 89 | topLevel := channel.topLevel |
3487 | 81 | n := len(channel.notifications) | 90 | n := len(channel.notifications) |
3489 | 82 | res := make([]json.RawMessage, 0, n) | 91 | res := make([]protocol.Notification, 0, n) |
3490 | 92 | exps := make([]time.Time, 0, n) | ||
3491 | 83 | now := time.Now() | 93 | now := time.Now() |
3494 | 84 | for _, notification := range channel.notifications { | 94 | for i, expiration := range channel.expirations { |
3495 | 85 | if notification.expiration.Before(now) { | 95 | if expiration.Before(now) { |
3496 | 86 | continue | 96 | continue |
3497 | 87 | } | 97 | } |
3499 | 88 | res = append(res, notification.payload) | 98 | res = append(res, channel.notifications[i]) |
3500 | 99 | exps = append(exps, expiration) | ||
3501 | 89 | } | 100 | } |
3502 | 101 | // store as well | ||
3503 | 102 | channel.notifications = res | ||
3504 | 103 | channel.expirations = exps | ||
3505 | 90 | return topLevel, res, nil | 104 | return topLevel, res, nil |
3506 | 91 | } | 105 | } |
3507 | 92 | 106 | ||
3508 | @@ -94,5 +108,25 @@ | |||
3509 | 94 | // ignored | 108 | // ignored |
3510 | 95 | } | 109 | } |
3511 | 96 | 110 | ||
3512 | 111 | func (sto *InMemoryPendingStore) DropByMsgId(chanId InternalChannelId, targets []protocol.Notification) error { | ||
3513 | 112 | sto.lock.Lock() | ||
3514 | 113 | defer sto.lock.Unlock() | ||
3515 | 114 | channel, ok := sto.store[chanId] | ||
3516 | 115 | if !ok { | ||
3517 | 116 | return nil | ||
3518 | 117 | } | ||
3519 | 118 | expById := make(map[string]time.Time, len(channel.notifications)) | ||
3520 | 119 | for i, notif := range channel.notifications { | ||
3521 | 120 | expById[notif.MsgId] = channel.expirations[i] | ||
3522 | 121 | } | ||
3523 | 122 | channel.notifications = FilterOutByMsgId(channel.notifications, targets) | ||
3524 | 123 | exps := make([]time.Time, len(channel.notifications)) | ||
3525 | 124 | for i, notif := range channel.notifications { | ||
3526 | 125 | exps[i] = expById[notif.MsgId] | ||
3527 | 126 | } | ||
3528 | 127 | channel.expirations = exps | ||
3529 | 128 | return nil | ||
3530 | 129 | } | ||
3531 | 130 | |||
3532 | 97 | // sanity check we implement the interface | 131 | // sanity check we implement the interface |
3533 | 98 | var _ PendingStore = (*InMemoryPendingStore)(nil) | 132 | var _ PendingStore = (*InMemoryPendingStore)(nil) |
3534 | 99 | 133 | ||
3535 | === modified file 'server/store/inmemory_test.go' | |||
3536 | --- server/store/inmemory_test.go 2014-02-14 12:38:38 +0000 | |||
3537 | +++ server/store/inmemory_test.go 2014-05-12 14:11:46 +0000 | |||
3538 | @@ -21,6 +21,9 @@ | |||
3539 | 21 | "time" | 21 | "time" |
3540 | 22 | 22 | ||
3541 | 23 | . "launchpad.net/gocheck" | 23 | . "launchpad.net/gocheck" |
3542 | 24 | |||
3543 | 25 | "launchpad.net/ubuntu-push/protocol" | ||
3544 | 26 | help "launchpad.net/ubuntu-push/testing" | ||
3545 | 24 | ) | 27 | ) |
3546 | 25 | 28 | ||
3547 | 26 | type inMemorySuite struct{} | 29 | type inMemorySuite struct{} |
3548 | @@ -45,7 +48,7 @@ | |||
3549 | 45 | top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId) | 48 | top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId) |
3550 | 46 | c.Assert(err, IsNil) | 49 | c.Assert(err, IsNil) |
3551 | 47 | c.Check(top, Equals, int64(0)) | 50 | c.Check(top, Equals, int64(0)) |
3553 | 48 | c.Check(res, DeepEquals, []json.RawMessage(nil)) | 51 | c.Check(res, DeepEquals, []protocol.Notification(nil)) |
3554 | 49 | } | 52 | } |
3555 | 50 | 53 | ||
3556 | 51 | func (s *inMemorySuite) TestAppendToChannelAndGetChannelSnapshot(c *C) { | 54 | func (s *inMemorySuite) TestAppendToChannelAndGetChannelSnapshot(c *C) { |
3557 | @@ -61,7 +64,33 @@ | |||
3558 | 61 | top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId) | 64 | top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId) |
3559 | 62 | c.Assert(err, IsNil) | 65 | c.Assert(err, IsNil) |
3560 | 63 | c.Check(top, Equals, int64(2)) | 66 | c.Check(top, Equals, int64(2)) |
3562 | 64 | c.Check(res, DeepEquals, []json.RawMessage{notification1, notification2}) | 67 | c.Check(res, DeepEquals, help.Ns(notification1, notification2)) |
3563 | 68 | } | ||
3564 | 69 | |||
3565 | 70 | func (s *inMemorySuite) TestAppendToUnicastChannelAndGetChannelSnapshot(c *C) { | ||
3566 | 71 | sto := NewInMemoryPendingStore() | ||
3567 | 72 | |||
3568 | 73 | chanId := UnicastInternalChannelId("user", "dev1") | ||
3569 | 74 | notification1 := json.RawMessage(`{"a":1}`) | ||
3570 | 75 | notification2 := json.RawMessage(`{"b":2}`) | ||
3571 | 76 | app1 := "app1" | ||
3572 | 77 | app2 := "app2" | ||
3573 | 78 | msg1 := "msg1" | ||
3574 | 79 | msg2 := "msg2" | ||
3575 | 80 | |||
3576 | 81 | muchLater := time.Now().Add(time.Minute) | ||
3577 | 82 | |||
3578 | 83 | err := sto.AppendToUnicastChannel(chanId, app1, notification1, msg1, muchLater) | ||
3579 | 84 | c.Assert(err, IsNil) | ||
3580 | 85 | err = sto.AppendToUnicastChannel(chanId, app2, notification2, msg2, muchLater) | ||
3581 | 86 | c.Assert(err, IsNil) | ||
3582 | 87 | top, res, err := sto.GetChannelSnapshot(chanId) | ||
3583 | 88 | c.Assert(err, IsNil) | ||
3584 | 89 | c.Check(res, DeepEquals, []protocol.Notification{ | ||
3585 | 90 | protocol.Notification{Payload: notification1, AppId: app1, MsgId: msg1}, | ||
3586 | 91 | protocol.Notification{Payload: notification2, AppId: app2, MsgId: msg2}, | ||
3587 | 92 | }) | ||
3588 | 93 | c.Check(top, Equals, int64(0)) | ||
3589 | 65 | } | 94 | } |
3590 | 66 | 95 | ||
3591 | 67 | func (s *inMemorySuite) TestAppendToChannelAndGetChannelSnapshotWithExpiration(c *C) { | 96 | func (s *inMemorySuite) TestAppendToChannelAndGetChannelSnapshotWithExpiration(c *C) { |
3592 | @@ -81,5 +110,45 @@ | |||
3593 | 81 | top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId) | 110 | top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId) |
3594 | 82 | c.Assert(err, IsNil) | 111 | c.Assert(err, IsNil) |
3595 | 83 | c.Check(top, Equals, int64(2)) | 112 | c.Check(top, Equals, int64(2)) |
3597 | 84 | c.Check(res, DeepEquals, []json.RawMessage{notification1}) | 113 | c.Check(res, DeepEquals, help.Ns(notification1)) |
3598 | 114 | } | ||
3599 | 115 | |||
3600 | 116 | func (s *inMemorySuite) TestDropByMsgId(c *C) { | ||
3601 | 117 | sto := NewInMemoryPendingStore() | ||
3602 | 118 | |||
3603 | 119 | chanId := UnicastInternalChannelId("user", "dev2") | ||
3604 | 120 | |||
3605 | 121 | // nothing to do is fine | ||
3606 | 122 | err := sto.DropByMsgId(chanId, nil) | ||
3607 | 123 | c.Assert(err, IsNil) | ||
3608 | 124 | |||
3609 | 125 | notification1 := json.RawMessage(`{"a":1}`) | ||
3610 | 126 | notification2 := json.RawMessage(`{"b":2}`) | ||
3611 | 127 | notification3 := json.RawMessage(`{"a":2}`) | ||
3612 | 128 | app1 := "app1" | ||
3613 | 129 | app2 := "app2" | ||
3614 | 130 | msg1 := "msg1" | ||
3615 | 131 | msg2 := "msg2" | ||
3616 | 132 | msg3 := "msg3" | ||
3617 | 133 | |||
3618 | 134 | muchLater := time.Now().Add(time.Minute) | ||
3619 | 135 | |||
3620 | 136 | err = sto.AppendToUnicastChannel(chanId, app1, notification1, msg1, muchLater) | ||
3621 | 137 | c.Assert(err, IsNil) | ||
3622 | 138 | err = sto.AppendToUnicastChannel(chanId, app2, notification2, msg2, muchLater) | ||
3623 | 139 | c.Assert(err, IsNil) | ||
3624 | 140 | err = sto.AppendToUnicastChannel(chanId, app1, notification3, msg3, muchLater) | ||
3625 | 141 | c.Assert(err, IsNil) | ||
3626 | 142 | _, res, err := sto.GetChannelSnapshot(chanId) | ||
3627 | 143 | c.Assert(err, IsNil) | ||
3628 | 144 | |||
3629 | 145 | err = sto.DropByMsgId(chanId, res[:2]) | ||
3630 | 146 | c.Assert(err, IsNil) | ||
3631 | 147 | |||
3632 | 148 | _, res, err = sto.GetChannelSnapshot(chanId) | ||
3633 | 149 | c.Assert(err, IsNil) | ||
3634 | 150 | c.Check(res, HasLen, 1) | ||
3635 | 151 | c.Check(res, DeepEquals, []protocol.Notification{ | ||
3636 | 152 | protocol.Notification{Payload: notification3, AppId: app1, MsgId: msg3}, | ||
3637 | 153 | }) | ||
3638 | 85 | } | 154 | } |
3639 | 86 | 155 | ||
3640 | === modified file 'server/store/store.go' | |||
3641 | --- server/store/store.go 2014-02-18 13:43:07 +0000 | |||
3642 | +++ server/store/store.go 2014-05-12 14:11:46 +0000 | |||
3643 | @@ -21,11 +21,36 @@ | |||
3644 | 21 | "encoding/hex" | 21 | "encoding/hex" |
3645 | 22 | "encoding/json" | 22 | "encoding/json" |
3646 | 23 | "errors" | 23 | "errors" |
3647 | 24 | "fmt" | ||
3648 | 25 | "strings" | ||
3649 | 24 | "time" | 26 | "time" |
3650 | 27 | |||
3651 | 28 | "launchpad.net/ubuntu-push/protocol" | ||
3652 | 25 | ) | 29 | ) |
3653 | 26 | 30 | ||
3654 | 27 | type InternalChannelId string | 31 | type InternalChannelId string |
3655 | 28 | 32 | ||
3656 | 33 | // BroadcastChannel returns whether the id represents a broadcast channel. | ||
3657 | 34 | func (icid InternalChannelId) BroadcastChannel() bool { | ||
3658 | 35 | marker := icid[0] | ||
3659 | 36 | return marker == 'B' || marker == '0' | ||
3660 | 37 | } | ||
3661 | 38 | |||
3662 | 39 | // UnicastChannel returns whether the id represents a unicast channel. | ||
3663 | 40 | func (icid InternalChannelId) UnicastChannel() bool { | ||
3664 | 41 | marker := icid[0] | ||
3665 | 42 | return marker == 'U' | ||
3666 | 43 | } | ||
3667 | 44 | |||
3668 | 45 | // UnicastUserAndDevice returns the user and device ids of a unicast channel. | ||
3669 | 46 | func (icid InternalChannelId) UnicastUserAndDevice() (userId, deviceId string) { | ||
3670 | 47 | if !icid.UnicastChannel() { | ||
3671 | 48 | panic("UnicastUserAndDevice is for unicast channels") | ||
3672 | 49 | } | ||
3673 | 50 | parts := strings.SplitN(string(icid)[1:], ":", 2) | ||
3674 | 51 | return parts[0], parts[1] | ||
3675 | 52 | } | ||
3676 | 53 | |||
3677 | 29 | var ErrUnknownChannel = errors.New("unknown channel name") | 54 | var ErrUnknownChannel = errors.New("unknown channel name") |
3678 | 30 | var ErrFull = errors.New("channel is full") | 55 | var ErrFull = errors.New("channel is full") |
3679 | 31 | var ErrExpected128BitsHexRepr = errors.New("expected 128 bits hex repr") | 56 | var ErrExpected128BitsHexRepr = errors.New("expected 128 bits hex repr") |
3680 | @@ -36,7 +61,10 @@ | |||
3681 | 36 | if chanId == SystemInternalChannelId { | 61 | if chanId == SystemInternalChannelId { |
3682 | 37 | return "0" | 62 | return "0" |
3683 | 38 | } | 63 | } |
3685 | 39 | panic("general InternalChannelIdToHex not implemeted yet") | 64 | if !chanId.BroadcastChannel() { |
3686 | 65 | panic("InternalChannelIdToHex is for broadcast channels") | ||
3687 | 66 | } | ||
3688 | 67 | return string(chanId)[1:] | ||
3689 | 40 | } | 68 | } |
3690 | 41 | 69 | ||
3691 | 42 | var zero128 [16]byte | 70 | var zero128 [16]byte |
3692 | @@ -58,7 +86,14 @@ | |||
3693 | 58 | if idbytes == zero128 { | 86 | if idbytes == zero128 { |
3694 | 59 | return SystemInternalChannelId, nil | 87 | return SystemInternalChannelId, nil |
3695 | 60 | } | 88 | } |
3697 | 61 | return InternalChannelId(idbytes[:]), nil | 89 | // mark with B(roadcast) prefix |
3698 | 90 | s := "B" + hexRepr | ||
3699 | 91 | return InternalChannelId(s), nil | ||
3700 | 92 | } | ||
3701 | 93 | |||
3702 | 94 | // UnicastInternalChannelId builds a channel id for the userId, deviceId pair. | ||
3703 | 95 | func UnicastInternalChannelId(userId, deviceId string) InternalChannelId { | ||
3704 | 96 | return InternalChannelId(fmt.Sprintf("U%s:%s", userId, deviceId)) | ||
3705 | 62 | } | 97 | } |
3706 | 63 | 98 | ||
3707 | 64 | // PendingStore let store notifications into channels. | 99 | // PendingStore let store notifications into channels. |
3708 | @@ -68,9 +103,45 @@ | |||
3709 | 68 | GetInternalChannelId(name string) (InternalChannelId, error) | 103 | GetInternalChannelId(name string) (InternalChannelId, error) |
3710 | 69 | // AppendToChannel appends a notification to the channel. | 104 | // AppendToChannel appends a notification to the channel. |
3711 | 70 | AppendToChannel(chanId InternalChannelId, notification json.RawMessage, expiration time.Time) error | 105 | AppendToChannel(chanId InternalChannelId, notification json.RawMessage, expiration time.Time) error |
3712 | 106 | // AppendToUnicastChannel appends a notification to the unicast channel. | ||
3713 | 71 | // GetChannelSnapshot gets all the current notifications and | 107 | // GetChannelSnapshot gets all the current notifications and |
3714 | 108 | AppendToUnicastChannel(chanId InternalChannelId, appId string, notification json.RawMessage, msgId string, expiration time.Time) error | ||
3715 | 72 | // current top level in the channel. | 109 | // current top level in the channel. |
3717 | 73 | GetChannelSnapshot(chanId InternalChannelId) (topLevel int64, payloads []json.RawMessage, err error) | 110 | GetChannelSnapshot(chanId InternalChannelId) (topLevel int64, notifications []protocol.Notification, err error) |
3718 | 111 | // DropByMsgId drops notifications from a unicast channel based on message ids. | ||
3719 | 112 | DropByMsgId(chanId InternalChannelId, targets []protocol.Notification) error | ||
3720 | 74 | // Close is to be called when done with the store. | 113 | // Close is to be called when done with the store. |
3721 | 75 | Close() | 114 | Close() |
3722 | 76 | } | 115 | } |
3723 | 116 | |||
3724 | 117 | // FilterOutByMsgId returns the notifications from orig whose msg id is not | ||
3725 | 118 | // mentioned in targets. | ||
3726 | 119 | func FilterOutByMsgId(orig, targets []protocol.Notification) []protocol.Notification { | ||
3727 | 120 | n := len(orig) | ||
3728 | 121 | t := len(targets) | ||
3729 | 122 | // common case, removing the continuous head | ||
3730 | 123 | if t > 0 && n >= t { | ||
3731 | 124 | if targets[0].MsgId == orig[0].MsgId { | ||
3732 | 125 | for i := t - 1; i >= 0; i-- { | ||
3733 | 126 | if i == 0 { | ||
3734 | 127 | return orig[t:] | ||
3735 | 128 | } | ||
3736 | 129 | if targets[i].MsgId != orig[i].MsgId { | ||
3737 | 130 | break | ||
3738 | 131 | } | ||
3739 | 132 | } | ||
3740 | 133 | } | ||
3741 | 134 | } | ||
3742 | 135 | // slow way | ||
3743 | 136 | ids := make(map[string]bool, t) | ||
3744 | 137 | for _, target := range targets { | ||
3745 | 138 | ids[target.MsgId] = true | ||
3746 | 139 | } | ||
3747 | 140 | acc := make([]protocol.Notification, 0, n) | ||
3748 | 141 | for _, notif := range orig { | ||
3749 | 142 | if !ids[notif.MsgId] { | ||
3750 | 143 | acc = append(acc, notif) | ||
3751 | 144 | } | ||
3752 | 145 | } | ||
3753 | 146 | return acc | ||
3754 | 147 | } | ||
3755 | 77 | 148 | ||
3756 | === modified file 'server/store/store_test.go' | |||
3757 | --- server/store/store_test.go 2014-02-10 23:19:08 +0000 | |||
3758 | +++ server/store/store_test.go 2014-05-12 14:11:46 +0000 | |||
3759 | @@ -33,6 +33,8 @@ | |||
3760 | 33 | 33 | ||
3761 | 34 | func (s *storeSuite) TestInternalChannelIdToHex(c *C) { | 34 | func (s *storeSuite) TestInternalChannelIdToHex(c *C) { |
3762 | 35 | c.Check(InternalChannelIdToHex(SystemInternalChannelId), Equals, protocol.SystemChannelId) | 35 | c.Check(InternalChannelIdToHex(SystemInternalChannelId), Equals, protocol.SystemChannelId) |
3763 | 36 | c.Check(InternalChannelIdToHex(InternalChannelId("Bf1c9bf7096084cb2a154979ce00c7f50")), Equals, "f1c9bf7096084cb2a154979ce00c7f50") | ||
3764 | 37 | c.Check(func() { InternalChannelIdToHex(InternalChannelId("U")) }, PanicMatches, "InternalChannelIdToHex is for broadcast channels") | ||
3765 | 36 | } | 38 | } |
3766 | 37 | 39 | ||
3767 | 38 | func (s *storeSuite) TestHexToInternalChannelId(c *C) { | 40 | func (s *storeSuite) TestHexToInternalChannelId(c *C) { |
3768 | @@ -42,9 +44,11 @@ | |||
3769 | 42 | i1, err := HexToInternalChannelId("00000000000000000000000000000000") | 44 | i1, err := HexToInternalChannelId("00000000000000000000000000000000") |
3770 | 43 | c.Check(err, IsNil) | 45 | c.Check(err, IsNil) |
3771 | 44 | c.Check(i1, Equals, SystemInternalChannelId) | 46 | c.Check(i1, Equals, SystemInternalChannelId) |
3772 | 47 | c.Check(i1.BroadcastChannel(), Equals, true) | ||
3773 | 45 | i2, err := HexToInternalChannelId("f1c9bf7096084cb2a154979ce00c7f50") | 48 | i2, err := HexToInternalChannelId("f1c9bf7096084cb2a154979ce00c7f50") |
3774 | 46 | c.Check(err, IsNil) | 49 | c.Check(err, IsNil) |
3776 | 47 | c.Check(i2, Equals, InternalChannelId("\xf1\xc9\xbf\x70\x96\x08\x4c\xb2\xa1\x54\x97\x9c\xe0\x0c\x7f\x50")) | 50 | c.Check(i2.BroadcastChannel(), Equals, true) |
3777 | 51 | c.Check(i2, Equals, InternalChannelId("Bf1c9bf7096084cb2a154979ce00c7f50")) | ||
3778 | 48 | _, err = HexToInternalChannelId("01") | 52 | _, err = HexToInternalChannelId("01") |
3779 | 49 | c.Check(err, Equals, ErrExpected128BitsHexRepr) | 53 | c.Check(err, Equals, ErrExpected128BitsHexRepr) |
3780 | 50 | _, err = HexToInternalChannelId("abceddddddddddddddddzeeeeeeeeeee") | 54 | _, err = HexToInternalChannelId("abceddddddddddddddddzeeeeeeeeeee") |
3781 | @@ -52,3 +56,44 @@ | |||
3782 | 52 | _, err = HexToInternalChannelId("f1c9bf7096084cb2a154979ce00c7f50ff") | 56 | _, err = HexToInternalChannelId("f1c9bf7096084cb2a154979ce00c7f50ff") |
3783 | 53 | c.Check(err, Equals, ErrExpected128BitsHexRepr) | 57 | c.Check(err, Equals, ErrExpected128BitsHexRepr) |
3784 | 54 | } | 58 | } |
3785 | 59 | |||
3786 | 60 | func (s *storeSuite) TestUnicastInternalChannelId(c *C) { | ||
3787 | 61 | chanId := UnicastInternalChannelId("user1", "dev2") | ||
3788 | 62 | c.Check(chanId.BroadcastChannel(), Equals, false) | ||
3789 | 63 | c.Check(chanId.UnicastChannel(), Equals, true) | ||
3790 | 64 | u, d := chanId.UnicastUserAndDevice() | ||
3791 | 65 | c.Check(u, Equals, "user1") | ||
3792 | 66 | c.Check(d, Equals, "dev2") | ||
3793 | 67 | c.Check(func() { SystemInternalChannelId.UnicastUserAndDevice() }, PanicMatches, "UnicastUserAndDevice is for unicast channels") | ||
3794 | 68 | } | ||
3795 | 69 | |||
3796 | 70 | func (s *storeSuite) TestFilterOutByMsgId(c *C) { | ||
3797 | 71 | orig := []protocol.Notification{ | ||
3798 | 72 | protocol.Notification{MsgId: "a"}, | ||
3799 | 73 | protocol.Notification{MsgId: "b"}, | ||
3800 | 74 | protocol.Notification{MsgId: "c"}, | ||
3801 | 75 | protocol.Notification{MsgId: "d"}, | ||
3802 | 76 | } | ||
3803 | 77 | // removing the continuous head | ||
3804 | 78 | res := FilterOutByMsgId(orig, orig[:3]) | ||
3805 | 79 | c.Check(res, DeepEquals, orig[3:]) | ||
3806 | 80 | |||
3807 | 81 | // random removal | ||
3808 | 82 | res = FilterOutByMsgId(orig, orig[1:2]) | ||
3809 | 83 | c.Check(res, DeepEquals, []protocol.Notification{ | ||
3810 | 84 | protocol.Notification{MsgId: "a"}, | ||
3811 | 85 | protocol.Notification{MsgId: "c"}, | ||
3812 | 86 | protocol.Notification{MsgId: "d"}, | ||
3813 | 87 | }) | ||
3814 | 88 | |||
3815 | 89 | // looks like removing the continuous head, but it isn't | ||
3816 | 90 | res = FilterOutByMsgId(orig, []protocol.Notification{ | ||
3817 | 91 | protocol.Notification{MsgId: "a"}, | ||
3818 | 92 | protocol.Notification{MsgId: "c"}, | ||
3819 | 93 | protocol.Notification{MsgId: "d"}, | ||
3820 | 94 | }) | ||
3821 | 95 | c.Check(res, DeepEquals, []protocol.Notification{ | ||
3822 | 96 | protocol.Notification{MsgId: "b"}, | ||
3823 | 97 | }) | ||
3824 | 98 | |||
3825 | 99 | } | ||
3826 | 55 | 100 | ||
3827 | === added directory 'signing-helper' | |||
3828 | === added file 'signing-helper/CMakeLists.txt' | |||
3829 | --- signing-helper/CMakeLists.txt 1970-01-01 00:00:00 +0000 | |||
3830 | +++ signing-helper/CMakeLists.txt 2014-05-12 14:11:46 +0000 | |||
3831 | @@ -0,0 +1,39 @@ | |||
3832 | 1 | cmake_minimum_required(VERSION 2.8) | ||
3833 | 2 | SET (EXAMPLES_TARGET ubuntuoneauth-examples) | ||
3834 | 3 | |||
3835 | 4 | SET (SIGNING_EXE "signing-helper") | ||
3836 | 5 | |||
3837 | 6 | find_package (PkgConfig REQUIRED) | ||
3838 | 7 | pkg_check_modules(UBUNTUONE REQUIRED ubuntuoneauth-2.0) | ||
3839 | 8 | add_definitions(${UBUNTUONE_CFLAGS} ${UBUNTUONE_CFLAGS_OTHER}) | ||
3840 | 9 | |||
3841 | 10 | |||
3842 | 11 | # Qt5 bits | ||
3843 | 12 | SET (CMAKE_INCLUDE_CURRENT_DIR ON) | ||
3844 | 13 | SET (CMAKE_AUTOMOC ON) | ||
3845 | 14 | find_package(Qt5Core REQUIRED) | ||
3846 | 15 | |||
3847 | 16 | FILE (GLOB SIGNING_SOURCES signing*.cpp) | ||
3848 | 17 | FILE (GLOB SIGNING_HEADERS signing*.h) | ||
3849 | 18 | |||
3850 | 19 | add_executable (${SIGNING_EXE} | ||
3851 | 20 | ${SIGNING_SOURCES} | ||
3852 | 21 | ${SIGNING_HEADERS}) | ||
3853 | 22 | qt5_use_modules (${SIGNING_EXE} DBus Network) | ||
3854 | 23 | |||
3855 | 24 | target_link_libraries (${SIGNING_EXE} | ||
3856 | 25 | ${UBUNTUONE_LDFLAGS}) | ||
3857 | 26 | |||
3858 | 27 | |||
3859 | 28 | |||
3860 | 29 | add_custom_target(examples-valgrind | ||
3861 | 30 | COMMAND "valgrind --tool=memcheck ${CMAKE_CURRENT_BINARY_DIR}/${SIGNING_EXE}" | ||
3862 | 31 | DEPENDS ${SIGNING_EXE} | ||
3863 | 32 | ) | ||
3864 | 33 | |||
3865 | 34 | add_custom_target(examples-valgrind-leaks | ||
3866 | 35 | COMMAND "valgrind --tool=memcheck --track-origins=yes --num-callers=40 --leak-resolution=high --leak-check=full ${CMAKE_CURRENT_BINARY_DIR}/${SIGNING_EXE}" | ||
3867 | 36 | DEPENDS ${SIGNING_EXE} | ||
3868 | 37 | ) | ||
3869 | 38 | |||
3870 | 39 | INSTALL_TARGETS( "lib/ubuntu-push-client/" ${SIGNING_EXE}) | ||
3871 | 0 | 40 | ||
3872 | === added file 'signing-helper/signing-helper.cpp' | |||
3873 | --- signing-helper/signing-helper.cpp 1970-01-01 00:00:00 +0000 | |||
3874 | +++ signing-helper/signing-helper.cpp 2014-05-12 14:11:46 +0000 | |||
3875 | @@ -0,0 +1,97 @@ | |||
3876 | 1 | /* | ||
3877 | 2 | * Copyright (C) 2013-2014 Canonical Ltd. | ||
3878 | 3 | * | ||
3879 | 4 | * This program is free software: you can redistribute it and/or modify it | ||
3880 | 5 | * under the terms of the GNU General Public License version 3, as published | ||
3881 | 6 | * by the Free Software Foundation. | ||
3882 | 7 | * | ||
3883 | 8 | * This program is distributed in the hope that it will be useful, but | ||
3884 | 9 | * WITHOUT ANY WARRANTY; without even the implied warranties of | ||
3885 | 10 | * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
3886 | 11 | * PURPOSE. See the GNU General Public License for more details. | ||
3887 | 12 | * | ||
3888 | 13 | * You should have received a copy of the GNU General Public License along | ||
3889 | 14 | * with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3890 | 15 | * | ||
3891 | 16 | * In addition, as a special exception, the copyright holders give | ||
3892 | 17 | * permission to link the code of portions of this program with the | ||
3893 | 18 | * OpenSSL library under certain conditions as described in each | ||
3894 | 19 | * individual source file, and distribute linked combinations | ||
3895 | 20 | * including the two. | ||
3896 | 21 | * You must obey the GNU General Public License in all respects | ||
3897 | 22 | * for all of the code used other than OpenSSL. If you modify | ||
3898 | 23 | * file(s) with this exception, you may extend this exception to your | ||
3899 | 24 | * version of the file(s), but you are not obligated to do so. If you | ||
3900 | 25 | * do not wish to do so, delete this exception statement from your | ||
3901 | 26 | * version. If you delete this exception statement from all source | ||
3902 | 27 | * files in the program, then also delete it here. | ||
3903 | 28 | */ | ||
3904 | 29 | |||
3905 | 30 | #include <iostream> | ||
3906 | 31 | #include <QCoreApplication> | ||
3907 | 32 | #include <QDebug> | ||
3908 | 33 | #include <QObject> | ||
3909 | 34 | #include <QString> | ||
3910 | 35 | #include <QTimer> | ||
3911 | 36 | |||
3912 | 37 | #include "ssoservice.h" | ||
3913 | 38 | #include "token.h" | ||
3914 | 39 | |||
3915 | 40 | #include "signing.h" | ||
3916 | 41 | |||
3917 | 42 | namespace UbuntuOne { | ||
3918 | 43 | |||
3919 | 44 | SigningExample::SigningExample(QObject *parent, QString url) : | ||
3920 | 45 | QObject(parent) | ||
3921 | 46 | { | ||
3922 | 47 | QObject::connect(&service, SIGNAL(credentialsFound(const Token&)), | ||
3923 | 48 | this, SLOT(handleCredentialsFound(Token))); | ||
3924 | 49 | QObject::connect(&service, SIGNAL(credentialsNotFound()), | ||
3925 | 50 | this, SLOT(handleCredentialsNotFound())); | ||
3926 | 51 | this->url = url; | ||
3927 | 52 | |||
3928 | 53 | } | ||
3929 | 54 | |||
3930 | 55 | SigningExample::~SigningExample(){ | ||
3931 | 56 | } | ||
3932 | 57 | |||
3933 | 58 | void SigningExample::doExample() | ||
3934 | 59 | { | ||
3935 | 60 | service.getCredentials(); | ||
3936 | 61 | } | ||
3937 | 62 | |||
3938 | 63 | void SigningExample::handleCredentialsFound(Token token) | ||
3939 | 64 | { | ||
3940 | 65 | qDebug() << "Credentials found, signing url."; | ||
3941 | 66 | |||
3942 | 67 | QString authHeader = token.signUrl(this->url, QStringLiteral("GET"), true); | ||
3943 | 68 | |||
3944 | 69 | std::cout << authHeader.toStdString() << "\n"; | ||
3945 | 70 | QCoreApplication::instance()->exit(0); | ||
3946 | 71 | |||
3947 | 72 | } | ||
3948 | 73 | |||
3949 | 74 | void SigningExample::handleCredentialsNotFound() | ||
3950 | 75 | { | ||
3951 | 76 | qDebug() << "No credentials were found."; | ||
3952 | 77 | QCoreApplication::instance()->exit(1); | ||
3953 | 78 | } | ||
3954 | 79 | |||
3955 | 80 | |||
3956 | 81 | } // namespace UbuntuOne | ||
3957 | 82 | |||
3958 | 83 | |||
3959 | 84 | int main(int argc, char *argv[]) | ||
3960 | 85 | { | ||
3961 | 86 | QCoreApplication a(argc, argv); | ||
3962 | 87 | |||
3963 | 88 | UbuntuOne::SigningExample *example = new UbuntuOne::SigningExample(&a); | ||
3964 | 89 | |||
3965 | 90 | QObject::connect(example, SIGNAL(finished()), &a, SLOT(quit())); | ||
3966 | 91 | |||
3967 | 92 | QTimer::singleShot(0, example, SLOT(doExample())); | ||
3968 | 93 | |||
3969 | 94 | return a.exec(); | ||
3970 | 95 | } | ||
3971 | 96 | |||
3972 | 97 | |||
3973 | 0 | 98 | ||
3974 | === added file 'signing-helper/signing.h' | |||
3975 | --- signing-helper/signing.h 1970-01-01 00:00:00 +0000 | |||
3976 | +++ signing-helper/signing.h 2014-05-12 14:11:46 +0000 | |||
3977 | @@ -0,0 +1,76 @@ | |||
3978 | 1 | /* | ||
3979 | 2 | * Copyright (C) 2013-2014 Canonical Ltd. | ||
3980 | 3 | * | ||
3981 | 4 | * This program is free software: you can redistribute it and/or modify it | ||
3982 | 5 | * under the terms of the GNU General Public License version 3, as published | ||
3983 | 6 | * by the Free Software Foundation. | ||
3984 | 7 | * | ||
3985 | 8 | * This program is distributed in the hope that it will be useful, but | ||
3986 | 9 | * WITHOUT ANY WARRANTY; without even the implied warranties of | ||
3987 | 10 | * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
3988 | 11 | * PURPOSE. See the GNU General Public License for more details. | ||
3989 | 12 | * | ||
3990 | 13 | * You should have received a copy of the GNU General Public License along | ||
3991 | 14 | * with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3992 | 15 | * | ||
3993 | 16 | * In addition, as a special exception, the copyright holders give | ||
3994 | 17 | * permission to link the code of portions of this program with the | ||
3995 | 18 | * OpenSSL library under certain conditions as described in each | ||
3996 | 19 | * individual source file, and distribute linked combinations | ||
3997 | 20 | * including the two. | ||
3998 | 21 | * You must obey the GNU General Public License in all respects | ||
3999 | 22 | * for all of the code used other than OpenSSL. If you modify | ||
4000 | 23 | * file(s) with this exception, you may extend this exception to your | ||
4001 | 24 | * version of the file(s), but you are not obligated to do so. If you | ||
4002 | 25 | * do not wish to do so, delete this exception statement from your | ||
4003 | 26 | * version. If you delete this exception statement from all source | ||
4004 | 27 | * files in the program, then also delete it here. | ||
4005 | 28 | */ | ||
4006 | 29 | |||
4007 | 30 | #ifndef _SIGNING_H_ | ||
4008 | 31 | #define _SIGNING_H_ | ||
4009 | 32 | |||
4010 | 33 | #include <QDebug> | ||
4011 | 34 | #include <QNetworkReply> | ||
4012 | 35 | #include <QObject> | ||
4013 | 36 | #include <QString> | ||
4014 | 37 | |||
4015 | 38 | #include "ssoservice.h" | ||
4016 | 39 | #include "token.h" | ||
4017 | 40 | #include "requests.h" | ||
4018 | 41 | #include "errormessages.h" | ||
4019 | 42 | |||
4020 | 43 | namespace UbuntuOne { | ||
4021 | 44 | |||
4022 | 45 | class SigningExample : public QObject | ||
4023 | 46 | { | ||
4024 | 47 | Q_OBJECT | ||
4025 | 48 | |||
4026 | 49 | public: | ||
4027 | 50 | |||
4028 | 51 | explicit SigningExample(QObject *parent = 0, QString url="https://one.ubuntu.com/api/account/"); | ||
4029 | 52 | ~SigningExample(); | ||
4030 | 53 | |||
4031 | 54 | public slots: | ||
4032 | 55 | |||
4033 | 56 | void doExample(); | ||
4034 | 57 | |||
4035 | 58 | signals: | ||
4036 | 59 | |||
4037 | 60 | void finished(); | ||
4038 | 61 | |||
4039 | 62 | private slots: | ||
4040 | 63 | |||
4041 | 64 | void handleCredentialsFound(Token token); | ||
4042 | 65 | void handleCredentialsNotFound(); | ||
4043 | 66 | |||
4044 | 67 | private: | ||
4045 | 68 | |||
4046 | 69 | SSOService service; | ||
4047 | 70 | QNetworkAccessManager nam; | ||
4048 | 71 | QString url; | ||
4049 | 72 | |||
4050 | 73 | }; | ||
4051 | 74 | |||
4052 | 75 | } | ||
4053 | 76 | #endif /* _SIGNING_H_ */ | ||
4054 | 0 | 77 | ||
4055 | === modified file 'testing/helpers.go' | |||
4056 | --- testing/helpers.go 2014-02-21 16:04:44 +0000 | |||
4057 | +++ testing/helpers.go 2014-05-12 14:11:46 +0000 | |||
4058 | @@ -18,6 +18,7 @@ | |||
4059 | 18 | package testing | 18 | package testing |
4060 | 19 | 19 | ||
4061 | 20 | import ( | 20 | import ( |
4062 | 21 | "encoding/json" | ||
4063 | 21 | "fmt" | 22 | "fmt" |
4064 | 22 | "os" | 23 | "os" |
4065 | 23 | "path/filepath" | 24 | "path/filepath" |
4066 | @@ -26,6 +27,7 @@ | |||
4067 | 26 | "sync" | 27 | "sync" |
4068 | 27 | 28 | ||
4069 | 28 | "launchpad.net/ubuntu-push/logger" | 29 | "launchpad.net/ubuntu-push/logger" |
4070 | 30 | "launchpad.net/ubuntu-push/protocol" | ||
4071 | 29 | ) | 31 | ) |
4072 | 30 | 32 | ||
4073 | 31 | type captureHelper struct { | 33 | type captureHelper struct { |
4074 | @@ -122,3 +124,12 @@ | |||
4075 | 122 | } | 124 | } |
4076 | 123 | return filepath.Join(dir, relativePath) | 125 | return filepath.Join(dir, relativePath) |
4077 | 124 | } | 126 | } |
4078 | 127 | |||
4079 | 128 | // Ns makes a []Notification from just payloads. | ||
4080 | 129 | func Ns(payloads ...json.RawMessage) []protocol.Notification { | ||
4081 | 130 | res := make([]protocol.Notification, len(payloads)) | ||
4082 | 131 | for i := 0; i < len(payloads); i++ { | ||
4083 | 132 | res[i].Payload = payloads[i] | ||
4084 | 133 | } | ||
4085 | 134 | return res | ||
4086 | 135 | } | ||
4087 | 125 | 136 | ||
4088 | === modified file 'ubuntu-push-client.go' | |||
4089 | --- ubuntu-push-client.go 2014-04-15 11:27:38 +0000 | |||
4090 | +++ ubuntu-push-client.go 2014-05-12 14:11:46 +0000 | |||
4091 | @@ -58,6 +58,7 @@ | |||
4092 | 58 | if err != nil { | 58 | if err != nil { |
4093 | 59 | log.Fatalf("unable to open the levels database: %v", err) | 59 | log.Fatalf("unable to open the levels database: %v", err) |
4094 | 60 | } | 60 | } |
4095 | 61 | |||
4096 | 61 | cli := client.NewPushClient(cfgFname, lvlFname) | 62 | cli := client.NewPushClient(cfgFname, lvlFname) |
4097 | 62 | err = cli.Start() | 63 | err = cli.Start() |
4098 | 63 | if err != nil { | 64 | if err != nil { |