Merge lp:~chipaca/ubuntu-push/coverage-on-bus-testing into lp:ubuntu-push
- coverage-on-bus-testing
- Merge into trunk
Proposed by
John Lenton
Status: | Superseded |
---|---|
Proposed branch: | lp:~chipaca/ubuntu-push/coverage-on-bus-testing |
Merge into: | lp:ubuntu-push |
Diff against target: |
7738 lines (+4222/-751) 71 files modified
.bzrignore (+9/-0) Makefile (+28/-3) PACKAGE_DEPS (+10/-0) README (+17/-5) bus/connectivity/connectivity.go (+8/-7) bus/connectivity/connectivity_test.go (+80/-3) bus/endpoint.go (+105/-1) bus/endpoint_test.go (+1/-1) bus/notifications/raw.go (+5/-0) bus/notifications/raw_test.go (+6/-0) bus/testing/testing_endpoint.go (+41/-3) bus/testing/testing_endpoint_test.go (+66/-1) client/client.go (+120/-36) client/client_test.go (+183/-50) client/service/service.go (+209/-0) client/service/service_test.go (+247/-0) client/session/seenstate/seenstate.go (+47/-21) client/session/seenstate/seenstate_test.go (+54/-23) client/session/seenstate/sqlseenstate.go (+46/-11) client/session/seenstate/sqlseenstate_test.go (+101/-23) client/session/session.go (+95/-29) client/session/session_test.go (+253/-33) config/config.go (+24/-7) config/config_test.go (+35/-7) debian/changelog (+6/-0) debian/control (+2/-0) debian/rules (+5/-1) debian/ubuntu-push-client.install (+1/-0) dependencies.tsv (+1/-0) nih/cnih/cnih.go (+28/-0) nih/nih.go (+68/-0) nih/nih_test.go (+57/-0) protocol/messages.go (+83/-1) protocol/messages_test.go (+92/-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) scripts/broadcast (+59/-0) scripts/deps.sh (+28/-0) scripts/unicast (+65/-0) server/acceptance/acceptance_test.go (+3/-0) server/acceptance/acceptanceclient.go (+22/-1) server/acceptance/cmd/acceptanceclient.go (+26/-18) server/acceptance/suites/broadcast.go (+19/-9) server/acceptance/suites/suite.go (+12/-0) server/acceptance/suites/unicast.go (+149/-0) server/api/handlers.go (+103/-21) server/api/handlers_test.go (+214/-12) server/broker/broker.go (+11/-1) server/broker/exchanges.go (+108/-42) server/broker/exchanges_test.go (+217/-26) server/broker/exchg_impl_test.go (+19/-17) server/broker/simple/simple.go (+61/-32) server/broker/simple/simple_test.go (+3/-38) server/broker/simple/suite_test.go (+3/-0) server/broker/testing/impls.go (+21/-0) server/broker/testsuite/suite.go (+153/-38) server/session/session.go (+23/-11) server/session/session_test.go (+63/-34) 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/-25) |
To merge this branch: | bzr merge lp:~chipaca/ubuntu-push/coverage-on-bus-testing |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Push Hackers | Pending | ||
Review via email: mp+221363@code.launchpad.net |
This proposal has been superseded by a proposal from 2014-05-29.
Commit message
100% coverage on bus/testing.
Description of the change
100% coverage on bus/testing, fwiw.
To post a comment you must log in.
- 173. By John Lenton
-
fixed comment on test
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2014-03-31 16:43:15 +0000 |
3 | +++ .bzrignore 2014-05-29 12:04:32 +0000 |
4 | @@ -13,3 +13,12 @@ |
5 | debian/*.substvars |
6 | ubuntu-push-client |
7 | push-server-dev |
8 | +signing-helper/CMakeCache.txt |
9 | +signing-helper/CMakeFiles |
10 | +signing-helper/Makefile |
11 | +signing-helper/cmake_install.cmake |
12 | +signing-helper/moc_signing.cpp |
13 | +signing-helper/signing-helper |
14 | +signing-helper/signing-helper_automoc.cpp |
15 | +.has-fetched-deps |
16 | +.*.deps |
17 | |
18 | === modified file 'Makefile' |
19 | --- Makefile 2014-03-31 17:58:54 +0000 |
20 | +++ Makefile 2014-05-29 12:04:32 +0000 |
21 | @@ -11,10 +11,21 @@ |
22 | GODEPS += launchpad.net/go-dbus/v1 |
23 | GODEPS += launchpad.net/go-xdg/v0 |
24 | GODEPS += code.google.com/p/gosqlite/sqlite3 |
25 | +GODEPS += launchpad.net/~ubuntu-push-hackers/ubuntu-push/go-uuid/uuid |
26 | |
27 | TOTEST = $(shell env GOPATH=$(GOPATH) go list $(PROJECT)/...|grep -v acceptance|grep -v http13client ) |
28 | |
29 | +fetchdeps: .has-fetched-deps |
30 | + |
31 | +.has-fetched-deps: PACKAGE_DEPS |
32 | + @$(MAKE) --no-print-directory refetchdeps |
33 | + @touch $@ |
34 | + |
35 | +refetchdeps: |
36 | + sudo apt-get install $$( grep -v '^#' PACKAGE_DEPS ) |
37 | + |
38 | bootstrap: |
39 | + $(RM) -r $(GOPATH)/pkg |
40 | mkdir -p $(GOPATH)/bin |
41 | mkdir -p $(GOPATH)/pkg |
42 | go get -u launchpad.net/godeps |
43 | @@ -31,8 +42,21 @@ |
44 | acceptance: |
45 | cd server/acceptance; ./acceptance.sh |
46 | |
47 | -build-client: |
48 | - go build ubuntu-push-client.go |
49 | +build-client: ubuntu-push-client signing-helper/signing-helper |
50 | + |
51 | +.%.deps: % |
52 | + $(SH) scripts/deps.sh $< |
53 | + |
54 | +%: %.go |
55 | + go build $< |
56 | + |
57 | +include .ubuntu-push-client.go.deps |
58 | + |
59 | +signing-helper/Makefile: signing-helper/CMakeLists.txt signing-helper/signing-helper.cpp signing-helper/signing.h |
60 | + cd signing-helper && (make clean || true) && cmake . |
61 | + |
62 | +signing-helper/signing-helper: signing-helper/Makefile signing-helper/signing-helper.cpp signing-helper/signing.h |
63 | + cd signing-helper && make |
64 | |
65 | build-server-dev: |
66 | go build -o push-server-dev launchpad.net/ubuntu-push/server/dev |
67 | @@ -68,4 +92,5 @@ |
68 | |
69 | .PHONY: bootstrap check check-race format check-format \ |
70 | acceptance build-client build server-dev run-server-dev \ |
71 | - coverage-summary coverage-html protocol-diagrams |
72 | + coverage-summary coverage-html protocol-diagrams \ |
73 | + fetchdeps refetchdeps |
74 | |
75 | === added file 'PACKAGE_DEPS' |
76 | --- PACKAGE_DEPS 1970-01-01 00:00:00 +0000 |
77 | +++ PACKAGE_DEPS 2014-05-29 12:04:32 +0000 |
78 | @@ -0,0 +1,10 @@ |
79 | +# See the README for what this file is and how to use it. |
80 | +build-essential |
81 | +cmake |
82 | +libdbus-1-dev |
83 | +libgcrypt11-dev |
84 | +libglib2.0-dev |
85 | +libnih-dbus-dev |
86 | +libsqlite3-dev |
87 | +libubuntuoneauth-2.0-dev |
88 | +libwhoopsie-dev |
89 | |
90 | === modified file 'README' |
91 | --- README 2014-03-31 17:58:54 +0000 |
92 | +++ README 2014-05-29 12:04:32 +0000 |
93 | @@ -6,11 +6,23 @@ |
94 | The code expects to be checked out as launchpad.net/ubuntu-push in a Go |
95 | workspace, see "go help gopath". |
96 | |
97 | -To setup Go dependencies, install libsqlite3-dev and run: |
98 | - |
99 | - make bootstrap |
100 | - |
101 | -To run tests, install libgcrypt11-dev and libwhoopsie-dev and run: |
102 | +You need a somewhat long list of dependencies, as well as a working Go |
103 | +development environment. THe Ubuntu packagenames for these are listed |
104 | +in the file PACKAGE_DEPS. |
105 | + |
106 | +On Ubuntu, if you have sudo, you can have all those installed for you |
107 | +by do doing |
108 | + |
109 | + make fetchdeps |
110 | + |
111 | +Once you have the packaged dependencies you can get the Go |
112 | +dependencies via |
113 | + |
114 | + make bootstrap |
115 | + |
116 | +and then you're set. Good luck! |
117 | + |
118 | +To run the tests: |
119 | |
120 | make check |
121 | |
122 | |
123 | === modified file 'bus/connectivity/connectivity.go' |
124 | --- bus/connectivity/connectivity.go 2014-04-04 11:08:28 +0000 |
125 | +++ bus/connectivity/connectivity.go 2014-05-29 12:04:32 +0000 |
126 | @@ -72,19 +72,20 @@ |
127 | cs.connAttempts += ar.Redial() |
128 | nm := networkmanager.New(cs.endp, cs.log) |
129 | |
130 | + // set up the watch |
131 | + stateCh, err = nm.WatchState() |
132 | + if err != nil { |
133 | + cs.log.Debugf("failed to set up the state watch: %s", err) |
134 | + goto Continue |
135 | + } |
136 | + |
137 | // Get the current state. |
138 | initial = nm.GetState() |
139 | if initial == networkmanager.Unknown { |
140 | cs.log.Debugf("Failed to get state.") |
141 | goto Continue |
142 | } |
143 | - |
144 | - // set up the watch |
145 | - stateCh, err = nm.WatchState() |
146 | - if err != nil { |
147 | - cs.log.Debugf("failed to set up the state watch: %s", err) |
148 | - goto Continue |
149 | - } |
150 | + cs.log.Debugf("got initial state of %s", initial) |
151 | |
152 | primary = nm.GetPrimaryConnection() |
153 | cs.log.Debugf("primary connection starts as %#v", primary) |
154 | |
155 | === modified file 'bus/connectivity/connectivity_test.go' |
156 | --- bus/connectivity/connectivity_test.go 2014-04-04 12:01:42 +0000 |
157 | +++ bus/connectivity/connectivity_test.go 2014-05-29 12:04:32 +0000 |
158 | @@ -17,8 +17,15 @@ |
159 | package connectivity |
160 | |
161 | import ( |
162 | + "net/http/httptest" |
163 | + "sync" |
164 | + "testing" |
165 | + "time" |
166 | + |
167 | "launchpad.net/go-dbus/v1" |
168 | . "launchpad.net/gocheck" |
169 | + |
170 | + "launchpad.net/ubuntu-push/bus" |
171 | "launchpad.net/ubuntu-push/bus/networkmanager" |
172 | testingbus "launchpad.net/ubuntu-push/bus/testing" |
173 | "launchpad.net/ubuntu-push/config" |
174 | @@ -26,9 +33,6 @@ |
175 | helpers "launchpad.net/ubuntu-push/testing" |
176 | "launchpad.net/ubuntu-push/testing/condition" |
177 | "launchpad.net/ubuntu-push/util" |
178 | - "net/http/httptest" |
179 | - "testing" |
180 | - "time" |
181 | ) |
182 | |
183 | // hook up gocheck |
184 | @@ -115,6 +119,79 @@ |
185 | c.Check(<-cs.networkStateCh, Equals, networkmanager.ConnectedGlobal) |
186 | } |
187 | |
188 | +// a racyEndpoint is an endpoint that behaves differently depending on |
189 | +// how much time passes between getting the state and setting up the |
190 | +// watch |
191 | +type racyEndpoint struct { |
192 | + stateGot bool |
193 | + maxTime time.Time |
194 | + delta time.Duration |
195 | + lock sync.RWMutex |
196 | +} |
197 | + |
198 | +func (rep *racyEndpoint) GetProperty(prop string) (interface{}, error) { |
199 | + switch prop { |
200 | + case "state": |
201 | + rep.lock.Lock() |
202 | + defer rep.lock.Unlock() |
203 | + rep.stateGot = true |
204 | + rep.maxTime = time.Now().Add(rep.delta) |
205 | + return uint32(networkmanager.Connecting), nil |
206 | + case "PrimaryConnection": |
207 | + return dbus.ObjectPath("/something"), nil |
208 | + default: |
209 | + return nil, nil |
210 | + } |
211 | +} |
212 | + |
213 | +func (rep *racyEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error { |
214 | + if member == "StateChanged" { |
215 | + // we count never having gotten the state as happening "after" now. |
216 | + rep.lock.RLock() |
217 | + defer rep.lock.RUnlock() |
218 | + ok := !rep.stateGot || time.Now().Before(rep.maxTime) |
219 | + go func() { |
220 | + if ok { |
221 | + f(uint32(networkmanager.ConnectedGlobal)) |
222 | + } |
223 | + d() |
224 | + }() |
225 | + } |
226 | + return nil |
227 | +} |
228 | + |
229 | +func (*racyEndpoint) Close() {} |
230 | +func (*racyEndpoint) Dial() error { return nil } |
231 | +func (*racyEndpoint) String() string { return "racyEndpoint" } |
232 | +func (*racyEndpoint) Call(string, []interface{}, ...interface{}) error { return nil } |
233 | +func (*racyEndpoint) GrabName(bool) <-chan error { return nil } |
234 | +func (*racyEndpoint) WatchMethod(bus.DispatchMap, ...interface{}) {} |
235 | +func (*racyEndpoint) Signal(member string, args []interface{}) error { return nil } |
236 | + |
237 | +var _ bus.Endpoint = (*racyEndpoint)(nil) |
238 | + |
239 | +// takeNext takes a value from given channel with a 1s timeout |
240 | +func takeNext(ch <-chan networkmanager.State) networkmanager.State { |
241 | + select { |
242 | + case <-time.After(time.Second): |
243 | + panic("channel stuck: too long waiting") |
244 | + case v := <-ch: |
245 | + return v |
246 | + } |
247 | +} |
248 | + |
249 | +// test that if the nm state goes from connecting to connected very |
250 | +// shortly after calling GetState, we don't lose the event. |
251 | +func (s *ConnSuite) TestStartAvoidsRace(c *C) { |
252 | + for delta := time.Second; delta > 1; delta /= 2 { |
253 | + rep := &racyEndpoint{delta: delta} |
254 | + cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: rep} |
255 | + f := Commentf("when delta=%s", delta) |
256 | + c.Assert(cs.start(), Equals, networkmanager.Connecting, f) |
257 | + c.Assert(takeNext(cs.networkStateCh), Equals, networkmanager.ConnectedGlobal, f) |
258 | + } |
259 | +} |
260 | + |
261 | /* |
262 | tests for connectedStateStep() |
263 | */ |
264 | |
265 | === modified file 'bus/endpoint.go' |
266 | --- bus/endpoint.go 2014-04-02 08:23:15 +0000 |
267 | +++ bus/endpoint.go 2014-05-29 12:04:32 +0000 |
268 | @@ -29,9 +29,15 @@ |
269 | * Endpoint (and its implementation) |
270 | */ |
271 | |
272 | +type BusMethod func([]interface{}, []interface{}) ([]interface{}, error) |
273 | +type DispatchMap map[string]BusMethod |
274 | + |
275 | // bus.Endpoint represents the DBus connection itself. |
276 | type Endpoint interface { |
277 | + GrabName(allowReplacement bool) <-chan error |
278 | WatchSignal(member string, f func(...interface{}), d func()) error |
279 | + WatchMethod(DispatchMap, ...interface{}) |
280 | + Signal(string, []interface{}) error |
281 | Call(member string, args []interface{}, rvs ...interface{}) error |
282 | GetProperty(property string) (interface{}, error) |
283 | Dial() error |
284 | @@ -53,13 +59,18 @@ |
285 | } |
286 | |
287 | // ensure endpoint implements Endpoint |
288 | -var _ Endpoint = &endpoint{} |
289 | +var _ Endpoint = (*endpoint)(nil) |
290 | |
291 | /* |
292 | public methods |
293 | + |
294 | +XXX: these are almost entirely untested, as that would need |
295 | +XXX: integration tests we are currently missing. |
296 | */ |
297 | |
298 | // Dial() (re)establishes the connection with dbus |
299 | +// |
300 | +// XXX: mostly untested |
301 | func (endp *endpoint) Dial() error { |
302 | bus, err := dbus.Connect(endp.busT.(concreteBus).dbusType()) |
303 | if err != nil { |
304 | @@ -106,6 +117,8 @@ |
305 | // with the unpacked value. If it's unable to set up the watch it returns an |
306 | // error. If the watch fails once established, d() is called. Typically f() |
307 | // sends the values over a channel, and d() would close the channel. |
308 | +// |
309 | +// XXX: untested |
310 | func (endp *endpoint) WatchSignal(member string, f func(...interface{}), d func()) error { |
311 | watch, err := endp.proxy.WatchSignal(endp.addr.Interface, member) |
312 | if err != nil { |
313 | @@ -122,6 +135,8 @@ |
314 | // interface provided when creating the endpoint). args can be built |
315 | // using bus.Args(...). The return value is unpacked into rvs before being |
316 | // returned. |
317 | +// |
318 | +// XXX: untested |
319 | func (endp *endpoint) Call(member string, args []interface{}, rvs ...interface{}) error { |
320 | msg, err := endp.proxy.Call(endp.addr.Interface, member, args...) |
321 | if err != nil { |
322 | @@ -138,6 +153,8 @@ |
323 | // to read a given property on the name, path and interface provided when |
324 | // creating the endpoint. The return value is unpacked into a dbus.Variant, |
325 | // and its value returned. |
326 | +// |
327 | +// XXX: untested |
328 | func (endp *endpoint) GetProperty(property string) (interface{}, error) { |
329 | msg, err := endp.proxy.Call("org.freedesktop.DBus.Properties", "Get", endp.addr.Interface, property) |
330 | if err != nil { |
331 | @@ -160,6 +177,8 @@ |
332 | } |
333 | |
334 | // Close the connection to dbus. |
335 | +// |
336 | +// XXX: untested |
337 | func (endp *endpoint) Close() { |
338 | if endp.bus != nil { |
339 | endp.bus.Close() |
340 | @@ -169,15 +188,98 @@ |
341 | } |
342 | |
343 | // String() performs advanced endpoint stringification |
344 | +// |
345 | +// XXX: untested |
346 | func (endp *endpoint) String() string { |
347 | return fmt.Sprintf("<Connection to %s %#v>", endp.bus, endp.addr) |
348 | } |
349 | |
350 | +// GrabName() takes over the name on the bus, reporting errors over the |
351 | +// returned channel. |
352 | +// |
353 | +// While the first result will be nil on success, successive results would |
354 | +// typically indicate another process trying to take over the name. |
355 | +// |
356 | +// XXX: untested |
357 | +func (endp *endpoint) GrabName(allowReplacement bool) <-chan error { |
358 | + flags := dbus.NameFlagAllowReplacement | dbus.NameFlagReplaceExisting |
359 | + if !allowReplacement { |
360 | + flags = 0 |
361 | + } |
362 | + return endp.bus.RequestName(endp.addr.Name, flags).C |
363 | +} |
364 | + |
365 | +// Signal() sends out a signal called <member> containing <args>. |
366 | +// |
367 | +// XXX: untested |
368 | +func (endp *endpoint) Signal(member string, args []interface{}) error { |
369 | + msg := dbus.NewSignalMessage(dbus.ObjectPath(endp.addr.Path), endp.addr.Interface, member) |
370 | + if args != nil { |
371 | + err := msg.AppendArgs(args...) |
372 | + if err != nil { |
373 | + endp.log.Errorf("unable to build dbus signal message: %v", err) |
374 | + return err |
375 | + } |
376 | + } |
377 | + err := endp.bus.Send(msg) |
378 | + if err != nil { |
379 | + endp.log.Errorf("unable to send dbus signal: %v", err) |
380 | + } else { |
381 | + endp.log.Debugf("sent dbus signal %s(%#v)", member, args) |
382 | + } |
383 | + return nil |
384 | +} |
385 | + |
386 | +// WatchMethod() uses the given DispatchMap to answer incoming method |
387 | +// calls. |
388 | +// |
389 | +// XXX: untested |
390 | +func (endp *endpoint) WatchMethod(dispatch DispatchMap, extra ...interface{}) { |
391 | + ch := make(chan *dbus.Message) |
392 | + go func() { |
393 | + var reply *dbus.Message |
394 | + |
395 | + err_iface := endp.addr.Interface + ".Error" |
396 | + |
397 | + for msg := range ch { |
398 | + meth, ok := dispatch[msg.Member] |
399 | + if !ok || msg.Interface != endp.addr.Interface { |
400 | + reply = dbus.NewErrorMessage(msg, |
401 | + "org.freedesktop.DBus.Error.UnknownMethod", "Unknown method") |
402 | + endp.log.Errorf("WatchMethod: unknown method %s", msg.Member) |
403 | + } else { |
404 | + args := msg.AllArgs() |
405 | + rvals, err := meth(args, extra) |
406 | + if err != nil { |
407 | + reply = dbus.NewErrorMessage(msg, err_iface, err.Error()) |
408 | + endp.log.Errorf("WatchMethod: %s(%#v, %#v) failure: %#v", msg.Member, args, extra, err) |
409 | + } else { |
410 | + endp.log.Debugf("WatchMethod: %s(%#v, %#v) success: %#v", msg.Member, args, extra, rvals) |
411 | + reply = dbus.NewMethodReturnMessage(msg) |
412 | + err = reply.AppendArgs(rvals...) |
413 | + if err != nil { |
414 | + endp.log.Errorf("WatchMethod: unable to build dbus response message: %v", err) |
415 | + reply = dbus.NewErrorMessage(msg, err_iface, err.Error()) |
416 | + } |
417 | + } |
418 | + } |
419 | + err := endp.bus.Send(reply) |
420 | + if err != nil { |
421 | + endp.log.Errorf("WatchMethod: unable to send reply: %v", err) |
422 | + } |
423 | + |
424 | + } |
425 | + }() |
426 | + endp.bus.RegisterObjectPath(dbus.ObjectPath(endp.addr.Path), ch) |
427 | +} |
428 | + |
429 | /* |
430 | private methods |
431 | */ |
432 | |
433 | // unpackOneMsg unpacks the value from the response msg |
434 | +// |
435 | +// XXX: untested |
436 | func (endp *endpoint) unpackOneMsg(msg *dbus.Message, member string) []interface{} { |
437 | var varmap map[string]dbus.Variant |
438 | if err := msg.Args(&varmap); err != nil { |
439 | @@ -187,6 +289,8 @@ |
440 | } |
441 | |
442 | // unpackMessages unpacks the value from the watch |
443 | +// |
444 | +// XXX: untested |
445 | func (endp *endpoint) unpackMessages(watch *dbus.SignalWatch, f func(...interface{}), d func(), member string) { |
446 | for { |
447 | msg, ok := <-watch.C |
448 | |
449 | === modified file 'bus/endpoint_test.go' |
450 | --- bus/endpoint_test.go 2014-02-06 09:57:49 +0000 |
451 | +++ bus/endpoint_test.go 2014-05-29 12:04:32 +0000 |
452 | @@ -37,7 +37,7 @@ |
453 | // testing amenities (already talked about it with jamesh) |
454 | |
455 | // Tests that we can connect to the *actual* system bus. |
456 | -// XXX maybe connect to a mock/fake/etc bus? |
457 | +// XXX: maybe connect to a mock/fake/etc bus? |
458 | func (s *EndpointSuite) TestDial(c *C) { |
459 | // if somebody's set up the env var, assume it's "live" |
460 | if os.Getenv("DBUS_SYSTEM_BUS_ADDRESS") == "" { |
461 | |
462 | === modified file 'bus/notifications/raw.go' |
463 | --- bus/notifications/raw.go 2014-04-02 08:23:15 +0000 |
464 | +++ bus/notifications/raw.go 2014-05-29 12:04:32 +0000 |
465 | @@ -22,6 +22,8 @@ |
466 | // this is the lower-level api |
467 | |
468 | import ( |
469 | + "errors" |
470 | + |
471 | "launchpad.net/go-dbus/v1" |
472 | "launchpad.net/ubuntu-push/bus" |
473 | "launchpad.net/ubuntu-push/logger" |
474 | @@ -68,6 +70,9 @@ |
475 | timeout int32) (uint32, error) { |
476 | // that's a long argument list! Take a breather. |
477 | // |
478 | + if raw.bus == nil { |
479 | + return 0, errors.New("unconfigured (missing bus)") |
480 | + } |
481 | var res uint32 |
482 | err := raw.bus.Call("Notify", bus.Args(app_name, reuse_id, icon, |
483 | summary, body, actions, hints, timeout), &res) |
484 | |
485 | === modified file 'bus/notifications/raw_test.go' |
486 | --- bus/notifications/raw_test.go 2014-02-05 18:17:26 +0000 |
487 | +++ bus/notifications/raw_test.go 2014-05-29 12:04:32 +0000 |
488 | @@ -57,6 +57,12 @@ |
489 | c.Check(err, NotNil) |
490 | } |
491 | |
492 | +func (s *RawSuite) TestNotifyFailsIfNoBus(c *C) { |
493 | + raw := Raw(nil, s.log) |
494 | + _, err := raw.Notify("", 0, "", "", "", nil, nil, 0) |
495 | + c.Check(err, ErrorMatches, `.*unconfigured .*`) |
496 | +} |
497 | + |
498 | func (s *RawSuite) TestNotifiesFailsWeirdly(c *C) { |
499 | endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{1, 2}) |
500 | raw := Raw(endp, s.log) |
501 | |
502 | === modified file 'bus/testing/testing_endpoint.go' |
503 | --- bus/testing/testing_endpoint.go 2014-04-04 11:08:28 +0000 |
504 | +++ bus/testing/testing_endpoint.go 2014-05-29 12:04:32 +0000 |
505 | @@ -166,8 +166,46 @@ |
506 | endp.dialCond, endp.callCond, endp.retvals) |
507 | } |
508 | |
509 | -// see Endpoint's Close. This one does nothing. |
510 | -func (tc *testingEndpoint) Close() {} |
511 | +// see Endpoint's Close. This one does nothing beyond registering |
512 | +// being called. |
513 | +func (tc *testingEndpoint) Close() { |
514 | + tc.callArgsLck.Lock() |
515 | + defer tc.callArgsLck.Unlock() |
516 | + |
517 | + args := callArgs{Member: "::Close"} |
518 | + tc.callArgs = append(tc.callArgs, args) |
519 | +} |
520 | + |
521 | +func (tc *testingEndpoint) GrabName(allowReplacement bool) <-chan error { |
522 | + tc.callArgsLck.Lock() |
523 | + defer tc.callArgsLck.Unlock() |
524 | + |
525 | + args := callArgs{Member: "::GrabName"} |
526 | + args.Args = append(args.Args, allowReplacement) |
527 | + tc.callArgs = append(tc.callArgs, args) |
528 | + |
529 | + return nil |
530 | +} |
531 | + |
532 | +func (tc *testingEndpoint) WatchMethod(dispatch bus.DispatchMap, extra ...interface{}) { |
533 | + tc.callArgsLck.Lock() |
534 | + defer tc.callArgsLck.Unlock() |
535 | + |
536 | + args := callArgs{Member: "::WatchMethod"} |
537 | + args.Args = append(args.Args, dispatch, extra) |
538 | + tc.callArgs = append(tc.callArgs, args) |
539 | +} |
540 | + |
541 | +func (tc *testingEndpoint) Signal(member string, args []interface{}) error { |
542 | + tc.callArgsLck.Lock() |
543 | + defer tc.callArgsLck.Unlock() |
544 | + |
545 | + callargs := callArgs{Member: "::Signal"} |
546 | + callargs.Args = append(callargs.Args, member, args) |
547 | + tc.callArgs = append(tc.callArgs, callargs) |
548 | + |
549 | + return nil |
550 | +} |
551 | |
552 | // ensure testingEndpoint implements bus.Endpoint |
553 | -var _ bus.Endpoint = &testingEndpoint{} |
554 | +var _ bus.Endpoint = (*testingEndpoint)(nil) |
555 | |
556 | === modified file 'bus/testing/testing_endpoint_test.go' |
557 | --- bus/testing/testing_endpoint_test.go 2014-04-02 08:23:15 +0000 |
558 | +++ bus/testing/testing_endpoint_test.go 2014-05-29 12:04:32 +0000 |
559 | @@ -76,6 +76,24 @@ |
560 | []callArgs{{"what", []interface{}{"is", "this", "thing"}}}) |
561 | } |
562 | |
563 | +// Test that Call() fails but does not explode when asked to return |
564 | +// values that can't be packed into a dbus message. |
565 | +func (s *TestingEndpointSuite) TestCallFailsOnBadRetval(c *C) { |
566 | + endp := NewTestingEndpoint(nil, condition.Work(true), Equals) |
567 | + var r uint32 |
568 | + e := endp.Call("what", bus.Args(), &r) |
569 | + c.Check(e, NotNil) |
570 | +} |
571 | + |
572 | +// Test that Call() fails but does not explode when given argument |
573 | +// that can't be packed into a dbus message. |
574 | +func (s *TestingEndpointSuite) TestCallFailsOnBadArg(c *C) { |
575 | + endp := NewTestingEndpoint(nil, condition.Work(true), 1) |
576 | + r := func() {} |
577 | + e := endp.Call("what", bus.Args(), &r) |
578 | + c.Check(e, NotNil) |
579 | +} |
580 | + |
581 | // Test that WatchSignal() with a positive condition sends the provided return |
582 | // values over the channel. |
583 | func (s *TestingEndpointSuite) TestWatch(c *C) { |
584 | @@ -102,7 +120,11 @@ |
585 | func (s *TestingEndpointSuite) TestCloser(c *C) { |
586 | endp := NewTestingEndpoint(nil, condition.Work(true)) |
587 | endp.Close() |
588 | - // ... yay? |
589 | + c.Check(GetCallArgs(endp), DeepEquals, []callArgs{ |
590 | + { |
591 | + Member: "::Close", |
592 | + Args: nil, |
593 | + }}) |
594 | } |
595 | |
596 | // Test that WatchSignal() with a negative condition returns an error. |
597 | @@ -173,3 +195,46 @@ |
598 | endp := NewTestingEndpoint(condition.Fail2Work(2), nil, "hello there") |
599 | c.Check(endp.String(), Matches, ".*Still Broken.*hello there.*") |
600 | } |
601 | + |
602 | +// Test that GrabName updates callArgs |
603 | +func (s *TestingEndpointSuite) TestGrabNameUpdatesCallArgs(c *C) { |
604 | + endp := NewTestingEndpoint(nil, condition.Work(true)) |
605 | + endp.GrabName(false) |
606 | + endp.GrabName(true) |
607 | + c.Check(GetCallArgs(endp), DeepEquals, []callArgs{ |
608 | + { |
609 | + Member: "::GrabName", |
610 | + Args: []interface{}{false}, |
611 | + }, { |
612 | + Member: "::GrabName", |
613 | + Args: []interface{}{true}, |
614 | + }}) |
615 | +} |
616 | + |
617 | +// Test that Signal updates callArgs |
618 | +func (s *TestingEndpointSuite) TestSignalUpdatesCallArgs(c *C) { |
619 | + endp := NewTestingEndpoint(nil, condition.Work(true)) |
620 | + endp.Signal("hello", []interface{}{"world"}) |
621 | + endp.Signal("hello", []interface{}{"there"}) |
622 | + c.Check(GetCallArgs(endp), DeepEquals, []callArgs{ |
623 | + { |
624 | + Member: "::Signal", |
625 | + Args: []interface{}{"hello", []interface{}{"world"}}, |
626 | + }, { |
627 | + Member: "::Signal", |
628 | + Args: []interface{}{"hello", []interface{}{"there"}}, |
629 | + }}) |
630 | +} |
631 | + |
632 | +// Test that WatchMethod updates callArgs |
633 | +func (s *TestingEndpointSuite) TestWatchMethodUpdatesCallArgs(c *C) { |
634 | + endp := NewTestingEndpoint(nil, condition.Work(true)) |
635 | + foo := func([]interface{}, []interface{}) ([]interface{}, error) { return nil, nil } |
636 | + foomp := bus.DispatchMap{"foo": foo} |
637 | + endp.WatchMethod(foomp) |
638 | + c.Check(GetCallArgs(endp), DeepEquals, []callArgs{ |
639 | + { |
640 | + Member: "::WatchMethod", |
641 | + Args: []interface{}{foomp, []interface{}(nil)}, |
642 | + }}) |
643 | +} |
644 | |
645 | === modified file 'client/client.go' |
646 | --- client/client.go 2014-04-18 09:35:59 +0000 |
647 | +++ client/client.go 2014-05-29 12:04:32 +0000 |
648 | @@ -19,6 +19,10 @@ |
649 | package client |
650 | |
651 | import ( |
652 | + "crypto/sha256" |
653 | + "encoding/base64" |
654 | + "encoding/hex" |
655 | + "encoding/json" |
656 | "encoding/pem" |
657 | "errors" |
658 | "fmt" |
659 | @@ -34,10 +38,12 @@ |
660 | "launchpad.net/ubuntu-push/bus/notifications" |
661 | "launchpad.net/ubuntu-push/bus/systemimage" |
662 | "launchpad.net/ubuntu-push/bus/urldispatcher" |
663 | + "launchpad.net/ubuntu-push/client/service" |
664 | "launchpad.net/ubuntu-push/client/session" |
665 | - "launchpad.net/ubuntu-push/client/session/levelmap" |
666 | + "launchpad.net/ubuntu-push/client/session/seenstate" |
667 | "launchpad.net/ubuntu-push/config" |
668 | "launchpad.net/ubuntu-push/logger" |
669 | + "launchpad.net/ubuntu-push/protocol" |
670 | "launchpad.net/ubuntu-push/util" |
671 | "launchpad.net/ubuntu-push/whoopsie/identifier" |
672 | ) |
673 | @@ -56,6 +62,8 @@ |
674 | ExpectAllRepairedTime config.ConfigTimeDuration `json:"expect_all_repaired"` // worth retrying all servers after |
675 | // The PEM-encoded server certificate |
676 | CertPEMFile string `json:"cert_pem_file"` |
677 | + // How to invoke the auth helper |
678 | + AuthHelper []string `json:"auth_helper"` |
679 | // The logging level (one of "debug", "info", "error") |
680 | LogLevel logger.ConfigLogLevel `json:"log_level"` |
681 | } |
682 | @@ -79,9 +87,15 @@ |
683 | actionsCh <-chan notifications.RawActionReply |
684 | session *session.ClientSession |
685 | sessionConnectedCh chan uint32 |
686 | + serviceEndpoint bus.Endpoint |
687 | + service *service.Service |
688 | } |
689 | |
690 | -var ACTION_ID_SNOWFLAKE = "::ubuntu-push-client::" |
691 | +var ( |
692 | + system_update_url = "settings:///system/system-update" |
693 | + ACTION_ID_SNOWFLAKE = "::ubuntu-push-client::" |
694 | + ACTION_ID_BROADCAST = ACTION_ID_SNOWFLAKE + system_update_url |
695 | +) |
696 | |
697 | // Creates a new Ubuntu Push Notifications client-side daemon that will use |
698 | // the given configuration file. |
699 | @@ -144,8 +158,9 @@ |
700 | ExchangeTimeout: client.config.ExchangeTimeout.TimeDuration(), |
701 | HostsCachingExpiryTime: client.config.HostsCachingExpiryTime.TimeDuration(), |
702 | ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(), |
703 | - PEM: client.pem, |
704 | - Info: info, |
705 | + PEM: client.pem, |
706 | + Info: info, |
707 | + AuthHelper: client.config.AuthHelper, |
708 | } |
709 | } |
710 | |
711 | @@ -155,7 +170,13 @@ |
712 | if err != nil { |
713 | return err |
714 | } |
715 | - client.deviceId = client.idder.String() |
716 | + baseId := client.idder.String() |
717 | + b, err := hex.DecodeString(baseId) |
718 | + if err != nil { |
719 | + return fmt.Errorf("whoopsie id should be hex: %v", err) |
720 | + } |
721 | + h := sha256.Sum224(b) |
722 | + client.deviceId = base64.StdEncoding.EncodeToString(h[:]) |
723 | return nil |
724 | } |
725 | |
726 | @@ -192,7 +213,7 @@ |
727 | } |
728 | sess, err := session.NewSession(client.config.Addr, |
729 | client.deriveSessionConfig(info), client.deviceId, |
730 | - client.levelMapFactory, client.log) |
731 | + client.seenStateFactory, client.log) |
732 | if err != nil { |
733 | return err |
734 | } |
735 | @@ -200,12 +221,12 @@ |
736 | return nil |
737 | } |
738 | |
739 | -// levelmapFactory returns a levelMap for the session |
740 | -func (client *PushClient) levelMapFactory() (levelmap.LevelMap, error) { |
741 | +// seenStateFactory returns a SeenState for the session |
742 | +func (client *PushClient) seenStateFactory() (seenstate.SeenState, error) { |
743 | if client.leveldbPath == "" { |
744 | - return levelmap.NewLevelMap() |
745 | + return seenstate.NewSeenState() |
746 | } else { |
747 | - return levelmap.NewSqliteLevelMap(client.leveldbPath) |
748 | + return seenstate.NewSqliteSeenState(client.leveldbPath) |
749 | } |
750 | } |
751 | |
752 | @@ -232,7 +253,7 @@ |
753 | } |
754 | } |
755 | |
756 | -// filterNotification finds out if the notification is about an actual |
757 | +// filterBroadcastNotification finds out if the notification is about an actual |
758 | // upgrade for the device. It expects msg.Decoded entries to look |
759 | // like: |
760 | // |
761 | @@ -240,7 +261,7 @@ |
762 | // "IMAGE-CHANNEL/DEVICE-MODEL": [BUILD-NUMBER, CHANNEL-ALIAS] |
763 | // ... |
764 | // } |
765 | -func (client *PushClient) filterNotification(msg *session.Notification) bool { |
766 | +func (client *PushClient) filterBroadcastNotification(msg *session.BroadcastNotification) bool { |
767 | n := len(msg.Decoded) |
768 | if n == 0 { |
769 | return false |
770 | @@ -275,26 +296,30 @@ |
771 | return false |
772 | } |
773 | |
774 | -// handleNotification deals with receiving a notification |
775 | -func (client *PushClient) handleNotification(msg *session.Notification) error { |
776 | - if !client.filterNotification(msg) { |
777 | - return nil |
778 | - } |
779 | - action_id := ACTION_ID_SNOWFLAKE |
780 | - a := []string{action_id, "Go get it!"} // action value not visible on the phone |
781 | +func (client *PushClient) sendNotification(action_id, icon, summary, body string) (uint32, error) { |
782 | + a := []string{action_id, "Switch to app"} // action value not visible on the phone |
783 | h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}} |
784 | nots := notifications.Raw(client.notificationsEndp, client.log) |
785 | - body := "Tap to open the system updater." |
786 | - not_id, err := nots.Notify( |
787 | - "ubuntu-push-client", // app name |
788 | - uint32(0), // id |
789 | - "update_manager_icon", // icon |
790 | - "There's an updated system image.", // summary |
791 | - body, // body |
792 | - a, // actions |
793 | - h, // hints |
794 | - int32(10*1000), // timeout (ms) |
795 | + return nots.Notify( |
796 | + "ubuntu-push-client", // app name |
797 | + uint32(0), // id |
798 | + icon, // icon |
799 | + summary, // summary |
800 | + body, // body |
801 | + a, // actions |
802 | + h, // hints |
803 | + int32(10*1000), // timeout (ms) |
804 | ) |
805 | +} |
806 | + |
807 | +// handleBroadcastNotification deals with receiving a broadcast notification |
808 | +func (client *PushClient) handleBroadcastNotification(msg *session.BroadcastNotification) error { |
809 | + if !client.filterBroadcastNotification(msg) { |
810 | + return nil |
811 | + } |
812 | + not_id, err := client.sendNotification(ACTION_ID_BROADCAST, |
813 | + "update_manager_icon", "There's an updated system image.", |
814 | + "Tap to open the system updater.") |
815 | if err != nil { |
816 | client.log.Errorf("showing notification: %s", err) |
817 | return err |
818 | @@ -303,26 +328,42 @@ |
819 | return nil |
820 | } |
821 | |
822 | +// handleUnicastNotification deals with receiving a unicast notification |
823 | +func (client *PushClient) handleUnicastNotification(msg *protocol.Notification) error { |
824 | + client.log.Debugf("sending notification %#v for %#v.", msg.MsgId, msg.AppId) |
825 | + return client.service.Inject(msg.AppId, string(msg.Payload)) |
826 | +} |
827 | + |
828 | // handleClick deals with the user clicking a notification |
829 | func (client *PushClient) handleClick(action_id string) error { |
830 | - if action_id != ACTION_ID_SNOWFLAKE { |
831 | + // “The string is a stark data structure and everywhere it is passed |
832 | + // there is much duplication of process. It is a perfect vehicle for |
833 | + // hiding information.” |
834 | + // |
835 | + // From ACM's SIGPLAN publication, (September, 1982), Article |
836 | + // "Epigrams in Programming", by Alan J. Perlis of Yale University. |
837 | + url := strings.TrimPrefix(action_id, ACTION_ID_SNOWFLAKE) |
838 | + if len(url) == len(action_id) || len(url) == 0 { |
839 | + // it didn't start with the prefix |
840 | return nil |
841 | } |
842 | // it doesn't get much simpler... |
843 | urld := urldispatcher.New(client.urlDispatcherEndp, client.log) |
844 | - return urld.DispatchURL("settings:///system/system-update") |
845 | + return urld.DispatchURL(url) |
846 | } |
847 | |
848 | // doLoop connects events with their handlers |
849 | -func (client *PushClient) doLoop(connhandler func(bool), clickhandler func(string) error, notifhandler func(*session.Notification) error, errhandler func(error)) { |
850 | +func (client *PushClient) doLoop(connhandler func(bool), clickhandler func(string) error, bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(*protocol.Notification) error, errhandler func(error)) { |
851 | for { |
852 | select { |
853 | case state := <-client.connCh: |
854 | connhandler(state) |
855 | case action := <-client.actionsCh: |
856 | clickhandler(action.ActionId) |
857 | - case msg := <-client.session.MsgCh: |
858 | - notifhandler(msg) |
859 | + case bcast := <-client.session.BroadcastCh: |
860 | + bcasthandler(bcast) |
861 | + case ucast := <-client.session.NotificationsCh: |
862 | + ucasthandler(ucast) |
863 | case err := <-client.session.ErrCh: |
864 | errhandler(err) |
865 | case count := <-client.sessionConnectedCh: |
866 | @@ -344,14 +385,57 @@ |
867 | |
868 | // Loop calls doLoop with the "real" handlers |
869 | func (client *PushClient) Loop() { |
870 | - client.doLoop(client.handleConnState, client.handleClick, |
871 | - client.handleNotification, client.handleErr) |
872 | + client.doLoop(client.handleConnState, |
873 | + client.handleClick, |
874 | + client.handleBroadcastNotification, |
875 | + client.handleUnicastNotification, |
876 | + client.handleErr) |
877 | +} |
878 | + |
879 | +// these are the currently supported fields of a unicast message |
880 | +type UnicastMessage struct { |
881 | + Icon string `json:"icon"` |
882 | + Body string `json:"body"` |
883 | + Summary string `json:"summary"` |
884 | + URL string `json:"url"` |
885 | + Blob json.RawMessage `json:"blob"` |
886 | +} |
887 | + |
888 | +func (client *PushClient) messageHandler(message []byte) error { |
889 | + var umsg = new(UnicastMessage) |
890 | + err := json.Unmarshal(message, &umsg) |
891 | + if err != nil { |
892 | + client.log.Errorf("unable to unmarshal message: %v", err) |
893 | + return err |
894 | + } |
895 | + |
896 | + not_id, err := client.sendNotification( |
897 | + ACTION_ID_SNOWFLAKE+umsg.URL, |
898 | + umsg.Icon, umsg.Summary, umsg.Body) |
899 | + |
900 | + if err != nil { |
901 | + client.log.Errorf("showing notification: %s", err) |
902 | + return err |
903 | + } |
904 | + client.log.Debugf("got notification id %d", not_id) |
905 | + return nil |
906 | +} |
907 | + |
908 | +func (client *PushClient) startService() error { |
909 | + if client.serviceEndpoint == nil { |
910 | + client.serviceEndpoint = bus.SessionBus.Endpoint(service.BusAddress, client.log) |
911 | + } |
912 | + |
913 | + client.service = service.NewService(client.serviceEndpoint, client.log) |
914 | + client.service.SetMessageHandler(client.messageHandler) |
915 | + return client.service.Start() |
916 | } |
917 | |
918 | // Start calls doStart with the "real" starters |
919 | func (client *PushClient) Start() error { |
920 | return client.doStart( |
921 | client.configure, |
922 | + client.startService, |
923 | client.getDeviceId, |
924 | client.takeTheBus, |
925 | client.initSession, |
926 | |
927 | === modified file 'client/client_test.go' |
928 | --- client/client_test.go 2014-04-18 16:31:04 +0000 |
929 | +++ client/client_test.go 2014-05-29 12:04:32 +0000 |
930 | @@ -38,8 +38,9 @@ |
931 | "launchpad.net/ubuntu-push/bus/systemimage" |
932 | testibus "launchpad.net/ubuntu-push/bus/testing" |
933 | "launchpad.net/ubuntu-push/client/session" |
934 | - "launchpad.net/ubuntu-push/client/session/levelmap" |
935 | + "launchpad.net/ubuntu-push/client/session/seenstate" |
936 | "launchpad.net/ubuntu-push/config" |
937 | + "launchpad.net/ubuntu-push/protocol" |
938 | helpers "launchpad.net/ubuntu-push/testing" |
939 | "launchpad.net/ubuntu-push/testing/condition" |
940 | "launchpad.net/ubuntu-push/util" |
941 | @@ -105,6 +106,7 @@ |
942 | "addr": ":0", |
943 | "cert_pem_file": pem_file, |
944 | "recheck_timeout": "3h", |
945 | + "auth_helper": []string{}, |
946 | "log_level": "debug", |
947 | } |
948 | for k, v := range overrides { |
949 | @@ -254,6 +256,9 @@ |
950 | ******************************************************************/ |
951 | |
952 | func (cs *clientSuite) TestDeriveSessionConfig(c *C) { |
953 | + cs.writeTestConfig(map[string]interface{}{ |
954 | + "auth_helper": []string{"auth", "helper"}, |
955 | + }) |
956 | info := map[string]interface{}{ |
957 | "foo": 1, |
958 | } |
959 | @@ -265,8 +270,9 @@ |
960 | ExchangeTimeout: 10 * time.Millisecond, |
961 | HostsCachingExpiryTime: 1 * time.Hour, |
962 | ExpectAllRepairedTime: 30 * time.Minute, |
963 | - PEM: cli.pem, |
964 | - Info: info, |
965 | + PEM: cli.pem, |
966 | + Info: info, |
967 | + AuthHelper: []string{"auth", "helper"}, |
968 | } |
969 | // sanity check that we are looking at all fields |
970 | vExpected := reflect.ValueOf(expected) |
971 | @@ -282,6 +288,35 @@ |
972 | } |
973 | |
974 | /***************************************************************** |
975 | + startService tests |
976 | +******************************************************************/ |
977 | + |
978 | +func (cs *clientSuite) TestStartServiceWorks(c *C) { |
979 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
980 | + cli.log = cs.log |
981 | + cli.serviceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil) |
982 | + c.Check(cli.service, IsNil) |
983 | + c.Check(cli.startService(), IsNil) |
984 | + c.Assert(cli.service, NotNil) |
985 | + c.Check(cli.service.IsRunning(), Equals, true) |
986 | + c.Check(cli.service.GetMessageHandler(), NotNil) |
987 | + cli.service.Stop() |
988 | +} |
989 | + |
990 | +func (cs *clientSuite) TestStartServiceErrorsOnNilLog(c *C) { |
991 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
992 | + c.Check(cli.log, IsNil) |
993 | + c.Check(cli.startService(), NotNil) |
994 | +} |
995 | + |
996 | +func (cs *clientSuite) TestStartServiceErrorsOnBusDialFail(c *C) { |
997 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
998 | + cli.log = cs.log |
999 | + cli.serviceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil) |
1000 | + c.Check(cli.startService(), NotNil) |
1001 | +} |
1002 | + |
1003 | +/***************************************************************** |
1004 | getDeviceId tests |
1005 | ******************************************************************/ |
1006 | |
1007 | @@ -291,7 +326,7 @@ |
1008 | cli.idder = identifier.New() |
1009 | c.Check(cli.deviceId, Equals, "") |
1010 | c.Check(cli.getDeviceId(), IsNil) |
1011 | - c.Check(cli.deviceId, HasLen, 128) |
1012 | + c.Check(cli.deviceId, HasLen, 40) |
1013 | } |
1014 | |
1015 | func (cs *clientSuite) TestGetDeviceIdCanFail(c *C) { |
1016 | @@ -302,6 +337,16 @@ |
1017 | c.Check(cli.getDeviceId(), NotNil) |
1018 | } |
1019 | |
1020 | +func (cs *clientSuite) TestGetDeviceIdWhoopsieDoesTheUnexpected(c *C) { |
1021 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1022 | + cli.log = cs.log |
1023 | + settable := idtesting.Settable() |
1024 | + cli.idder = settable |
1025 | + settable.Set("not-hex") |
1026 | + c.Check(cli.deviceId, Equals, "") |
1027 | + c.Check(cli.getDeviceId(), ErrorMatches, "whoopsie id should be hex: .*") |
1028 | +} |
1029 | + |
1030 | /***************************************************************** |
1031 | takeTheBus tests |
1032 | ******************************************************************/ |
1033 | @@ -389,21 +434,21 @@ |
1034 | } |
1035 | |
1036 | /***************************************************************** |
1037 | - levelmapFactory tests |
1038 | + seenStateFactory tests |
1039 | ******************************************************************/ |
1040 | |
1041 | -func (cs *clientSuite) TestLevelMapFactoryNoDbPath(c *C) { |
1042 | +func (cs *clientSuite) TestSeenStateFactoryNoDbPath(c *C) { |
1043 | cli := NewPushClient(cs.configPath, "") |
1044 | - ln, err := cli.levelMapFactory() |
1045 | + ln, err := cli.seenStateFactory() |
1046 | c.Assert(err, IsNil) |
1047 | - c.Check(fmt.Sprintf("%T", ln), Equals, "*levelmap.mapLevelMap") |
1048 | + c.Check(fmt.Sprintf("%T", ln), Equals, "*seenstate.memSeenState") |
1049 | } |
1050 | |
1051 | -func (cs *clientSuite) TestLevelMapFactoryWithDbPath(c *C) { |
1052 | +func (cs *clientSuite) TestSeenStateFactoryWithDbPath(c *C) { |
1053 | cli := NewPushClient(cs.configPath, ":memory:") |
1054 | - ln, err := cli.levelMapFactory() |
1055 | + ln, err := cli.seenStateFactory() |
1056 | c.Assert(err, IsNil) |
1057 | - c.Check(fmt.Sprintf("%T", ln), Equals, "*levelmap.sqliteLevelMap") |
1058 | + c.Check(fmt.Sprintf("%T", ln), Equals, "*seenstate.sqliteSeenState") |
1059 | } |
1060 | |
1061 | /***************************************************************** |
1062 | @@ -439,7 +484,7 @@ |
1063 | func (cs *clientSuite) TestHandleConnStateC2D(c *C) { |
1064 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1065 | cli.log = cs.log |
1066 | - cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, levelmap.NewLevelMap, cs.log) |
1067 | + cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, seenstate.NewSeenState, cs.log) |
1068 | cli.session.Dial() |
1069 | cli.hasConnectivity = true |
1070 | |
1071 | @@ -452,7 +497,7 @@ |
1072 | func (cs *clientSuite) TestHandleConnStateC2DPending(c *C) { |
1073 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1074 | cli.log = cs.log |
1075 | - cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, levelmap.NewLevelMap, cs.log) |
1076 | + cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, seenstate.NewSeenState, cs.log) |
1077 | cli.hasConnectivity = true |
1078 | |
1079 | cli.handleConnState(false) |
1080 | @@ -460,7 +505,7 @@ |
1081 | } |
1082 | |
1083 | /***************************************************************** |
1084 | - filterNotification tests |
1085 | + filterBroadcastNotification tests |
1086 | ******************************************************************/ |
1087 | |
1088 | var siInfoRes = &systemimage.InfoResult{ |
1089 | @@ -470,23 +515,23 @@ |
1090 | LastUpdate: "Unknown", |
1091 | } |
1092 | |
1093 | -func (cs *clientSuite) TestFilterNotification(c *C) { |
1094 | +func (cs *clientSuite) TestFilterBroadcastNotification(c *C) { |
1095 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1096 | cli.systemImageInfo = siInfoRes |
1097 | // empty |
1098 | - msg := &session.Notification{} |
1099 | - c.Check(cli.filterNotification(msg), Equals, false) |
1100 | + msg := &session.BroadcastNotification{} |
1101 | + c.Check(cli.filterBroadcastNotification(msg), Equals, false) |
1102 | // same build number |
1103 | - msg = &session.Notification{ |
1104 | + msg = &session.BroadcastNotification{ |
1105 | Decoded: []map[string]interface{}{ |
1106 | map[string]interface{}{ |
1107 | "daily/mako": []interface{}{float64(102), "tubular"}, |
1108 | }, |
1109 | }, |
1110 | } |
1111 | - c.Check(cli.filterNotification(msg), Equals, false) |
1112 | + c.Check(cli.filterBroadcastNotification(msg), Equals, false) |
1113 | // higher build number and pick last |
1114 | - msg = &session.Notification{ |
1115 | + msg = &session.BroadcastNotification{ |
1116 | Decoded: []map[string]interface{}{ |
1117 | map[string]interface{}{ |
1118 | "daily/mako": []interface{}{float64(102), "tubular"}, |
1119 | @@ -496,9 +541,9 @@ |
1120 | }, |
1121 | }, |
1122 | } |
1123 | - c.Check(cli.filterNotification(msg), Equals, true) |
1124 | + c.Check(cli.filterBroadcastNotification(msg), Equals, true) |
1125 | // going backward by a margin, assume switch of alias |
1126 | - msg = &session.Notification{ |
1127 | + msg = &session.BroadcastNotification{ |
1128 | Decoded: []map[string]interface{}{ |
1129 | map[string]interface{}{ |
1130 | "daily/mako": []interface{}{float64(102), "tubular"}, |
1131 | @@ -508,47 +553,47 @@ |
1132 | }, |
1133 | }, |
1134 | } |
1135 | - c.Check(cli.filterNotification(msg), Equals, true) |
1136 | + c.Check(cli.filterBroadcastNotification(msg), Equals, true) |
1137 | } |
1138 | |
1139 | -func (cs *clientSuite) TestFilterNotificationRobust(c *C) { |
1140 | +func (cs *clientSuite) TestFilterBroadcastNotificationRobust(c *C) { |
1141 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1142 | cli.systemImageInfo = siInfoRes |
1143 | - msg := &session.Notification{ |
1144 | + msg := &session.BroadcastNotification{ |
1145 | Decoded: []map[string]interface{}{ |
1146 | map[string]interface{}{}, |
1147 | }, |
1148 | } |
1149 | - c.Check(cli.filterNotification(msg), Equals, false) |
1150 | + c.Check(cli.filterBroadcastNotification(msg), Equals, false) |
1151 | for _, broken := range []interface{}{ |
1152 | 5, |
1153 | []interface{}{}, |
1154 | []interface{}{55}, |
1155 | } { |
1156 | - msg := &session.Notification{ |
1157 | + msg := &session.BroadcastNotification{ |
1158 | Decoded: []map[string]interface{}{ |
1159 | map[string]interface{}{ |
1160 | "daily/mako": broken, |
1161 | }, |
1162 | }, |
1163 | } |
1164 | - c.Check(cli.filterNotification(msg), Equals, false) |
1165 | + c.Check(cli.filterBroadcastNotification(msg), Equals, false) |
1166 | } |
1167 | } |
1168 | |
1169 | /***************************************************************** |
1170 | - handleNotification tests |
1171 | + handleBroadcastNotification tests |
1172 | ******************************************************************/ |
1173 | |
1174 | var ( |
1175 | - positiveNotification = &session.Notification{ |
1176 | + positiveBroadcastNotification = &session.BroadcastNotification{ |
1177 | Decoded: []map[string]interface{}{ |
1178 | map[string]interface{}{ |
1179 | "daily/mako": []interface{}{float64(103), "tubular"}, |
1180 | }, |
1181 | }, |
1182 | } |
1183 | - negativeNotification = &session.Notification{ |
1184 | + negativeBroadcastNotification = &session.BroadcastNotification{ |
1185 | Decoded: []map[string]interface{}{ |
1186 | map[string]interface{}{ |
1187 | "daily/mako": []interface{}{float64(102), "tubular"}, |
1188 | @@ -557,13 +602,13 @@ |
1189 | } |
1190 | ) |
1191 | |
1192 | -func (cs *clientSuite) TestHandleNotification(c *C) { |
1193 | +func (cs *clientSuite) TestHandleBroadcastNotification(c *C) { |
1194 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1195 | cli.systemImageInfo = siInfoRes |
1196 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
1197 | cli.notificationsEndp = endp |
1198 | cli.log = cs.log |
1199 | - c.Check(cli.handleNotification(positiveNotification), IsNil) |
1200 | + c.Check(cli.handleBroadcastNotification(positiveBroadcastNotification), IsNil) |
1201 | // check we sent the notification |
1202 | args := testibus.GetCallArgs(endp) |
1203 | c.Assert(args, HasLen, 1) |
1204 | @@ -571,26 +616,48 @@ |
1205 | c.Check(cs.log.Captured(), Matches, `.* got notification id \d+\s*`) |
1206 | } |
1207 | |
1208 | -func (cs *clientSuite) TestHandleNotificationNothingToDo(c *C) { |
1209 | +func (cs *clientSuite) TestHandleBroadcastNotificationNothingToDo(c *C) { |
1210 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1211 | cli.systemImageInfo = siInfoRes |
1212 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
1213 | cli.notificationsEndp = endp |
1214 | cli.log = cs.log |
1215 | - c.Check(cli.handleNotification(negativeNotification), IsNil) |
1216 | + c.Check(cli.handleBroadcastNotification(negativeBroadcastNotification), IsNil) |
1217 | // check we sent the notification |
1218 | args := testibus.GetCallArgs(endp) |
1219 | c.Assert(args, HasLen, 0) |
1220 | c.Check(cs.log.Captured(), Matches, "") |
1221 | } |
1222 | |
1223 | -func (cs *clientSuite) TestHandleNotificationFail(c *C) { |
1224 | +func (cs *clientSuite) TestHandleBroadcastNotificationFail(c *C) { |
1225 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1226 | cli.systemImageInfo = siInfoRes |
1227 | cli.log = cs.log |
1228 | endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
1229 | cli.notificationsEndp = endp |
1230 | - c.Check(cli.handleNotification(positiveNotification), NotNil) |
1231 | + c.Check(cli.handleBroadcastNotification(positiveBroadcastNotification), NotNil) |
1232 | +} |
1233 | + |
1234 | +/***************************************************************** |
1235 | + handleUnicastNotification tests |
1236 | +******************************************************************/ |
1237 | + |
1238 | +var notif = &protocol.Notification{AppId: "hello", Payload: []byte(`{"url": "xyzzy"}`), MsgId: "42"} |
1239 | + |
1240 | +func (cs *clientSuite) TestHandleUcastNotification(c *C) { |
1241 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1242 | + svcEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
1243 | + cli.log = cs.log |
1244 | + cli.serviceEndpoint = svcEndp |
1245 | + notsEndp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
1246 | + cli.notificationsEndp = notsEndp |
1247 | + c.Assert(cli.startService(), IsNil) |
1248 | + c.Check(cli.handleUnicastNotification(notif), IsNil) |
1249 | + // check we sent the notification |
1250 | + args := testibus.GetCallArgs(svcEndp) |
1251 | + c.Assert(len(args), Not(Equals), 0) |
1252 | + c.Check(args[len(args)-1].Member, Equals, "::Signal") |
1253 | + c.Check(cs.log.Captured(), Matches, `(?m).*sending notification "42" for "hello".*`) |
1254 | } |
1255 | |
1256 | /***************************************************************** |
1257 | @@ -608,18 +675,31 @@ |
1258 | args := testibus.GetCallArgs(endp) |
1259 | c.Assert(args, HasLen, 0) |
1260 | // check we worked with the right action id |
1261 | - c.Check(cli.handleClick(ACTION_ID_SNOWFLAKE), IsNil) |
1262 | + c.Check(cli.handleClick(ACTION_ID_BROADCAST), IsNil) |
1263 | // check we sent the notification |
1264 | args = testibus.GetCallArgs(endp) |
1265 | c.Assert(args, HasLen, 1) |
1266 | c.Check(args[0].Member, Equals, "DispatchURL") |
1267 | - c.Check(args[0].Args, DeepEquals, []interface{}{"settings:///system/system-update"}) |
1268 | + c.Check(args[0].Args, DeepEquals, []interface{}{system_update_url}) |
1269 | + // check we worked with the right action id |
1270 | + c.Check(cli.handleClick(ACTION_ID_SNOWFLAKE+"foo"), IsNil) |
1271 | + // check we sent the notification |
1272 | + args = testibus.GetCallArgs(endp) |
1273 | + c.Assert(args, HasLen, 2) |
1274 | + c.Check(args[1].Member, Equals, "DispatchURL") |
1275 | + c.Check(args[1].Args, DeepEquals, []interface{}{"foo"}) |
1276 | } |
1277 | |
1278 | /***************************************************************** |
1279 | doLoop tests |
1280 | ******************************************************************/ |
1281 | |
1282 | +var nopConn = func(bool) {} |
1283 | +var nopClick = func(string) error { return nil } |
1284 | +var nopBcast = func(*session.BroadcastNotification) error { return nil } |
1285 | +var nopUcast = func(*protocol.Notification) error { return nil } |
1286 | +var nopError = func(error) {} |
1287 | + |
1288 | func (cs *clientSuite) TestDoLoopConn(c *C) { |
1289 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1290 | cli.log = cs.log |
1291 | @@ -629,7 +709,7 @@ |
1292 | c.Assert(cli.initSession(), IsNil) |
1293 | |
1294 | ch := make(chan bool, 1) |
1295 | - go cli.doLoop(func(bool) { ch <- true }, func(_ string) error { return nil }, func(_ *session.Notification) error { return nil }, func(error) {}) |
1296 | + go cli.doLoop(func(bool) { ch <- true }, nopClick, nopBcast, nopUcast, nopError) |
1297 | c.Check(takeNextBool(ch), Equals, true) |
1298 | } |
1299 | |
1300 | @@ -643,7 +723,20 @@ |
1301 | cli.actionsCh = aCh |
1302 | |
1303 | ch := make(chan bool, 1) |
1304 | - go cli.doLoop(func(bool) {}, func(_ string) error { ch <- true; return nil }, func(_ *session.Notification) error { return nil }, func(error) {}) |
1305 | + go cli.doLoop(nopConn, func(_ string) error { ch <- true; return nil }, nopBcast, nopUcast, nopError) |
1306 | + c.Check(takeNextBool(ch), Equals, true) |
1307 | +} |
1308 | + |
1309 | +func (cs *clientSuite) TestDoLoopBroadcast(c *C) { |
1310 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1311 | + cli.log = cs.log |
1312 | + cli.systemImageInfo = siInfoRes |
1313 | + c.Assert(cli.initSession(), IsNil) |
1314 | + cli.session.BroadcastCh = make(chan *session.BroadcastNotification, 1) |
1315 | + cli.session.BroadcastCh <- &session.BroadcastNotification{} |
1316 | + |
1317 | + ch := make(chan bool, 1) |
1318 | + go cli.doLoop(nopConn, nopClick, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopError) |
1319 | c.Check(takeNextBool(ch), Equals, true) |
1320 | } |
1321 | |
1322 | @@ -652,11 +745,11 @@ |
1323 | cli.log = cs.log |
1324 | cli.systemImageInfo = siInfoRes |
1325 | c.Assert(cli.initSession(), IsNil) |
1326 | - cli.session.MsgCh = make(chan *session.Notification, 1) |
1327 | - cli.session.MsgCh <- &session.Notification{} |
1328 | + cli.session.NotificationsCh = make(chan *protocol.Notification, 1) |
1329 | + cli.session.NotificationsCh <- &protocol.Notification{} |
1330 | |
1331 | ch := make(chan bool, 1) |
1332 | - go cli.doLoop(func(bool) {}, func(_ string) error { return nil }, func(_ *session.Notification) error { ch <- true; return nil }, func(error) {}) |
1333 | + go cli.doLoop(nopConn, nopClick, nopBcast, func(*protocol.Notification) error { ch <- true; return nil }, nopError) |
1334 | c.Check(takeNextBool(ch), Equals, true) |
1335 | } |
1336 | |
1337 | @@ -669,7 +762,7 @@ |
1338 | cli.session.ErrCh <- nil |
1339 | |
1340 | ch := make(chan bool, 1) |
1341 | - go cli.doLoop(func(bool) {}, func(_ string) error { return nil }, func(_ *session.Notification) error { return nil }, func(error) { ch <- true }) |
1342 | + go cli.doLoop(nopConn, nopClick, nopBcast, nopUcast, func(error) { ch <- true }) |
1343 | c.Check(takeNextBool(ch), Equals, true) |
1344 | } |
1345 | |
1346 | @@ -719,7 +812,7 @@ |
1347 | cli.systemImageInfo = siInfoRes |
1348 | c.Assert(cli.initSession(), IsNil) |
1349 | |
1350 | - cli.session.MsgCh = make(chan *session.Notification) |
1351 | + cli.session.BroadcastCh = make(chan *session.BroadcastNotification) |
1352 | cli.session.ErrCh = make(chan error) |
1353 | |
1354 | // we use tick() to make sure things have been through the |
1355 | @@ -736,7 +829,7 @@ |
1356 | c.Check(cs.log.Captured(), Matches, "(?ms).*Session connected after 42 attempts$") |
1357 | |
1358 | // * actionsCh to the click handler/url dispatcher |
1359 | - aCh <- notifications.RawActionReply{ActionId: ACTION_ID_SNOWFLAKE} |
1360 | + aCh <- notifications.RawActionReply{ActionId: ACTION_ID_BROADCAST} |
1361 | tick() |
1362 | uargs := testibus.GetCallArgs(cli.urlDispatcherEndp) |
1363 | c.Assert(uargs, HasLen, 1) |
1364 | @@ -752,8 +845,8 @@ |
1365 | tick() |
1366 | c.Check(cli.hasConnectivity, Equals, false) |
1367 | |
1368 | - // * session.MsgCh to the notifications handler |
1369 | - cli.session.MsgCh <- positiveNotification |
1370 | + // * session.BroadcastCh to the notifications handler |
1371 | + cli.session.BroadcastCh <- positiveBroadcastNotification |
1372 | tick() |
1373 | nargs := testibus.GetCallArgs(cli.notificationsEndp) |
1374 | c.Check(nargs, HasLen, 1) |
1375 | @@ -785,6 +878,8 @@ |
1376 | |
1377 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1378 | // before start, everything sucks: |
1379 | + // no service, |
1380 | + c.Check(cli.service, IsNil) |
1381 | // no config, |
1382 | c.Check(string(cli.config.Addr), Equals, "") |
1383 | // no device id, |
1384 | @@ -803,12 +898,15 @@ |
1385 | // and now everthing is better! We have a config, |
1386 | c.Check(string(cli.config.Addr), Equals, ":0") |
1387 | // and a device id, |
1388 | - c.Check(cli.deviceId, HasLen, 128) |
1389 | + c.Check(cli.deviceId, HasLen, 40) |
1390 | // and a session, |
1391 | c.Check(cli.session, NotNil) |
1392 | // and a bus, |
1393 | c.Check(cli.notificationsEndp, NotNil) |
1394 | + // and a service, |
1395 | + c.Check(cli.service, NotNil) |
1396 | // and everthying us just peachy! |
1397 | + cli.service.Stop() // cleanup |
1398 | } |
1399 | |
1400 | func (cs *clientSuite) TestStartCanFail(c *C) { |
1401 | @@ -818,3 +916,38 @@ |
1402 | // and it works. Err. Doesn't. |
1403 | c.Check(err, NotNil) |
1404 | } |
1405 | + |
1406 | +func (cs *clientSuite) TestMessageHandler(c *C) { |
1407 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1408 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
1409 | + cli.notificationsEndp = endp |
1410 | + cli.log = cs.log |
1411 | + err := cli.messageHandler([]byte(`{"icon": "icon-value", "summary": "summary-value", "body": "body-value"}`)) |
1412 | + c.Assert(err, IsNil) |
1413 | + args := testibus.GetCallArgs(endp) |
1414 | + c.Assert(args, HasLen, 1) |
1415 | + c.Check(args[0].Member, Equals, "Notify") |
1416 | + c.Check(args[0].Args[0], Equals, "ubuntu-push-client") |
1417 | + c.Check(args[0].Args[2], Equals, "icon-value") |
1418 | + c.Check(args[0].Args[3], Equals, "summary-value") |
1419 | + c.Check(args[0].Args[4], Equals, "body-value") |
1420 | +} |
1421 | + |
1422 | +func (cs *clientSuite) TestMessageHandlerReportsUnmarshalErrors(c *C) { |
1423 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1424 | + cli.log = cs.log |
1425 | + |
1426 | + err := cli.messageHandler([]byte(`{"broken`)) |
1427 | + c.Check(err, NotNil) |
1428 | + c.Check(cs.log.Captured(), Matches, "(?msi).*unable to unmarshal message:.*") |
1429 | +} |
1430 | + |
1431 | +func (cs *clientSuite) TestMessageHandlerReportsFailedNotifies(c *C) { |
1432 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1433 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
1434 | + cli.notificationsEndp = endp |
1435 | + cli.log = cs.log |
1436 | + err := cli.messageHandler([]byte(`{}`)) |
1437 | + c.Assert(err, NotNil) |
1438 | + c.Check(cs.log.Captured(), Matches, "(?msi).*showing notification: no way$") |
1439 | +} |
1440 | |
1441 | === added directory 'client/service' |
1442 | === added file 'client/service/service.go' |
1443 | --- client/service/service.go 1970-01-01 00:00:00 +0000 |
1444 | +++ client/service/service.go 2014-05-29 12:04:32 +0000 |
1445 | @@ -0,0 +1,209 @@ |
1446 | +/* |
1447 | + Copyright 2013-2014 Canonical Ltd. |
1448 | + |
1449 | + This program is free software: you can redistribute it and/or modify it |
1450 | + under the terms of the GNU General Public License version 3, as published |
1451 | + by the Free Software Foundation. |
1452 | + |
1453 | + This program is distributed in the hope that it will be useful, but |
1454 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
1455 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1456 | + PURPOSE. See the GNU General Public License for more details. |
1457 | + |
1458 | + You should have received a copy of the GNU General Public License along |
1459 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
1460 | +*/ |
1461 | + |
1462 | +// package service implements the dbus-level service with which client |
1463 | +// applications are expected to interact. |
1464 | +package service |
1465 | + |
1466 | +import ( |
1467 | + "errors" |
1468 | + "os" |
1469 | + "sync" |
1470 | + |
1471 | + "launchpad.net/ubuntu-push/bus" |
1472 | + "launchpad.net/ubuntu-push/logger" |
1473 | +) |
1474 | + |
1475 | +// Service is the dbus api |
1476 | +type Service struct { |
1477 | + lock sync.RWMutex |
1478 | + state ServiceState |
1479 | + mbox map[string][]string |
1480 | + msgHandler func([]byte) error |
1481 | + Log logger.Logger |
1482 | + Bus bus.Endpoint |
1483 | +} |
1484 | + |
1485 | +// the service can be in a numnber of states |
1486 | +type ServiceState uint8 |
1487 | + |
1488 | +const ( |
1489 | + StateUnknown ServiceState = iota |
1490 | + StateRunning // Start() has been successfully called |
1491 | + StateFinished // Stop() has been successfully called |
1492 | +) |
1493 | + |
1494 | +var ( |
1495 | + NotConfigured = errors.New("not configured") |
1496 | + AlreadyStarted = errors.New("already started") |
1497 | + BusAddress = bus.Address{ |
1498 | + Interface: "com.ubuntu.PushNotifications", |
1499 | + Path: "/com/ubuntu/PushNotifications", |
1500 | + Name: "com.ubuntu.PushNotifications", |
1501 | + } |
1502 | +) |
1503 | + |
1504 | +// NewService() builds a new service and returns it. |
1505 | +func NewService(bus bus.Endpoint, log logger.Logger) *Service { |
1506 | + return &Service{Log: log, Bus: bus} |
1507 | +} |
1508 | + |
1509 | +// SetMessageHandler() sets the message-handling callback |
1510 | +func (svc *Service) SetMessageHandler(callback func([]byte) error) { |
1511 | + svc.lock.Lock() |
1512 | + defer svc.lock.Unlock() |
1513 | + svc.msgHandler = callback |
1514 | +} |
1515 | + |
1516 | +// GetMessageHandler() returns the (possibly nil) messaging handler callback |
1517 | +func (svc *Service) GetMessageHandler() func([]byte) error { |
1518 | + svc.lock.RLock() |
1519 | + defer svc.lock.RUnlock() |
1520 | + return svc.msgHandler |
1521 | +} |
1522 | + |
1523 | +// IsRunning() returns whether the service's state is StateRunning |
1524 | +func (svc *Service) IsRunning() bool { |
1525 | + svc.lock.RLock() |
1526 | + defer svc.lock.RUnlock() |
1527 | + return svc.state == StateRunning |
1528 | +} |
1529 | + |
1530 | +// Start() dials the bus, grab the name, and listens for method calls. |
1531 | +func (svc *Service) Start() error { |
1532 | + svc.lock.Lock() |
1533 | + defer svc.lock.Unlock() |
1534 | + if svc.state != StateUnknown { |
1535 | + return AlreadyStarted |
1536 | + } |
1537 | + if svc.Log == nil || svc.Bus == nil { |
1538 | + return NotConfigured |
1539 | + } |
1540 | + err := svc.Bus.Dial() |
1541 | + if err != nil { |
1542 | + return err |
1543 | + } |
1544 | + ch := svc.Bus.GrabName(true) |
1545 | + log := svc.Log |
1546 | + go func() { |
1547 | + for err := range ch { |
1548 | + if !svc.IsRunning() { |
1549 | + break |
1550 | + } |
1551 | + if err != nil { |
1552 | + log.Fatalf("name channel for %s got: %v", |
1553 | + BusAddress.Name, err) |
1554 | + } |
1555 | + } |
1556 | + }() |
1557 | + svc.Bus.WatchMethod(bus.DispatchMap{ |
1558 | + "Register": svc.register, |
1559 | + "Notifications": svc.notifications, |
1560 | + "Inject": svc.inject, |
1561 | + }, svc) |
1562 | + svc.state = StateRunning |
1563 | + return nil |
1564 | +} |
1565 | + |
1566 | +// Stop() closes the bus and sets the state to StateFinished |
1567 | +func (svc *Service) Stop() { |
1568 | + svc.lock.Lock() |
1569 | + defer svc.lock.Unlock() |
1570 | + if svc.Bus != nil { |
1571 | + svc.Bus.Close() |
1572 | + } |
1573 | + svc.state = StateFinished |
1574 | +} |
1575 | + |
1576 | +var ( |
1577 | + BadArgCount = errors.New("Wrong number of arguments") |
1578 | + BadArgType = errors.New("Bad argument type") |
1579 | +) |
1580 | + |
1581 | +func (svc *Service) register(args []interface{}, _ []interface{}) ([]interface{}, error) { |
1582 | + if len(args) != 1 { |
1583 | + return nil, BadArgCount |
1584 | + } |
1585 | + appname, ok := args[0].(string) |
1586 | + if !ok { |
1587 | + return nil, BadArgType |
1588 | + } |
1589 | + |
1590 | + rv := os.Getenv("PUSH_REG_" + appname) |
1591 | + if rv == "" { |
1592 | + rv = "this-is-an-opaque-block-of-random-bits-i-promise" |
1593 | + } |
1594 | + |
1595 | + return []interface{}{rv}, nil |
1596 | +} |
1597 | + |
1598 | +func (svc *Service) notifications(args []interface{}, _ []interface{}) ([]interface{}, error) { |
1599 | + if len(args) != 1 { |
1600 | + return nil, BadArgCount |
1601 | + } |
1602 | + appname, ok := args[0].(string) |
1603 | + if !ok { |
1604 | + return nil, BadArgType |
1605 | + } |
1606 | + |
1607 | + svc.lock.Lock() |
1608 | + defer svc.lock.Unlock() |
1609 | + |
1610 | + if svc.mbox == nil { |
1611 | + return []interface{}{[]string(nil)}, nil |
1612 | + } |
1613 | + msgs := svc.mbox[appname] |
1614 | + delete(svc.mbox, appname) |
1615 | + |
1616 | + return []interface{}{msgs}, nil |
1617 | +} |
1618 | + |
1619 | +func (svc *Service) inject(args []interface{}, _ []interface{}) ([]interface{}, error) { |
1620 | + if len(args) != 2 { |
1621 | + return nil, BadArgCount |
1622 | + } |
1623 | + appname, ok := args[0].(string) |
1624 | + if !ok { |
1625 | + return nil, BadArgType |
1626 | + } |
1627 | + notif, ok := args[1].(string) |
1628 | + if !ok { |
1629 | + return nil, BadArgType |
1630 | + } |
1631 | + |
1632 | + return nil, svc.Inject(appname, notif) |
1633 | +} |
1634 | + |
1635 | +// Inject() signals to an application over dbus that a notification |
1636 | +// has arrived. |
1637 | +func (svc *Service) Inject(appname string, notif string) error { |
1638 | + svc.lock.Lock() |
1639 | + defer svc.lock.Unlock() |
1640 | + if svc.mbox == nil { |
1641 | + svc.mbox = make(map[string][]string) |
1642 | + } |
1643 | + svc.mbox[appname] = append(svc.mbox[appname], notif) |
1644 | + if svc.msgHandler != nil { |
1645 | + err := svc.msgHandler([]byte(notif)) |
1646 | + if err != nil { |
1647 | + svc.Log.Errorf("msgHandler returned %v", err) |
1648 | + return err |
1649 | + } |
1650 | + svc.Log.Debugf("call to msgHandler successful") |
1651 | + } |
1652 | + |
1653 | + return svc.Bus.Signal("Notification", []interface{}{appname}) |
1654 | +} |
1655 | |
1656 | === added file 'client/service/service_test.go' |
1657 | --- client/service/service_test.go 1970-01-01 00:00:00 +0000 |
1658 | +++ client/service/service_test.go 2014-05-29 12:04:32 +0000 |
1659 | @@ -0,0 +1,247 @@ |
1660 | +/* |
1661 | + Copyright 2014 Canonical Ltd. |
1662 | + |
1663 | + This program is free software: you can redistribute it and/or modify it |
1664 | + under the terms of the GNU General Public License version 3, as published |
1665 | + by the Free Software Foundation. |
1666 | + |
1667 | + This program is distributed in the hope that it will be useful, but |
1668 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
1669 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1670 | + PURPOSE. See the GNU General Public License for more details. |
1671 | + |
1672 | + You should have received a copy of the GNU General Public License along |
1673 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
1674 | +*/ |
1675 | + |
1676 | +package service |
1677 | + |
1678 | +import ( |
1679 | + "errors" |
1680 | + "os" |
1681 | + "testing" |
1682 | + |
1683 | + . "launchpad.net/gocheck" |
1684 | + |
1685 | + "launchpad.net/ubuntu-push/bus" |
1686 | + testibus "launchpad.net/ubuntu-push/bus/testing" |
1687 | + "launchpad.net/ubuntu-push/logger" |
1688 | + helpers "launchpad.net/ubuntu-push/testing" |
1689 | + "launchpad.net/ubuntu-push/testing/condition" |
1690 | +) |
1691 | + |
1692 | +func TestService(t *testing.T) { TestingT(t) } |
1693 | + |
1694 | +type serviceSuite struct { |
1695 | + log logger.Logger |
1696 | + bus bus.Endpoint |
1697 | +} |
1698 | + |
1699 | +var _ = Suite(&serviceSuite{}) |
1700 | + |
1701 | +func (ss *serviceSuite) SetUpTest(c *C) { |
1702 | + ss.log = helpers.NewTestLogger(c, "debug") |
1703 | + ss.bus = testibus.NewTestingEndpoint(condition.Work(true), nil) |
1704 | +} |
1705 | + |
1706 | +func (ss *serviceSuite) TestStart(c *C) { |
1707 | + svc := NewService(ss.bus, ss.log) |
1708 | + c.Check(svc.IsRunning(), Equals, false) |
1709 | + c.Check(svc.Start(), IsNil) |
1710 | + c.Check(svc.IsRunning(), Equals, true) |
1711 | + svc.Stop() |
1712 | +} |
1713 | + |
1714 | +func (ss *serviceSuite) TestStartTwice(c *C) { |
1715 | + svc := NewService(ss.bus, ss.log) |
1716 | + c.Check(svc.Start(), IsNil) |
1717 | + c.Check(svc.Start(), Equals, AlreadyStarted) |
1718 | + svc.Stop() |
1719 | +} |
1720 | + |
1721 | +func (ss *serviceSuite) TestStartNoLog(c *C) { |
1722 | + svc := NewService(ss.bus, nil) |
1723 | + c.Check(svc.Start(), Equals, NotConfigured) |
1724 | +} |
1725 | + |
1726 | +func (ss *serviceSuite) TestStartNoBus(c *C) { |
1727 | + svc := NewService(nil, ss.log) |
1728 | + c.Check(svc.Start(), Equals, NotConfigured) |
1729 | +} |
1730 | + |
1731 | +func (ss *serviceSuite) TestStartFailsOnBusDialFailure(c *C) { |
1732 | + bus := testibus.NewTestingEndpoint(condition.Work(false), nil) |
1733 | + svc := NewService(bus, ss.log) |
1734 | + c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`) |
1735 | + svc.Stop() |
1736 | +} |
1737 | + |
1738 | +func (ss *serviceSuite) TestStartGrabsName(c *C) { |
1739 | + svc := NewService(ss.bus, ss.log) |
1740 | + c.Assert(svc.Start(), IsNil) |
1741 | + callArgs := testibus.GetCallArgs(ss.bus) |
1742 | + defer svc.Stop() |
1743 | + c.Assert(callArgs, NotNil) |
1744 | + c.Check(callArgs[0].Member, Equals, "::GrabName") |
1745 | +} |
1746 | + |
1747 | +func (ss *serviceSuite) TestStopClosesBus(c *C) { |
1748 | + svc := NewService(ss.bus, ss.log) |
1749 | + c.Assert(svc.Start(), IsNil) |
1750 | + svc.Stop() |
1751 | + callArgs := testibus.GetCallArgs(ss.bus) |
1752 | + c.Assert(callArgs, NotNil) |
1753 | + c.Check(callArgs[len(callArgs)-1].Member, Equals, "::Close") |
1754 | +} |
1755 | + |
1756 | +// registration tests |
1757 | + |
1758 | +func (ss *serviceSuite) TestRegistrationFailsIfBadArgs(c *C) { |
1759 | + for i, s := range []struct { |
1760 | + args []interface{} |
1761 | + errt error |
1762 | + }{ |
1763 | + {nil, BadArgCount}, // no args |
1764 | + {[]interface{}{}, BadArgCount}, // still no args |
1765 | + {[]interface{}{42}, BadArgType}, // bad arg type |
1766 | + {[]interface{}{1, 2}, BadArgCount}, // too many args |
1767 | + } { |
1768 | + reg, err := new(Service).register(s.args, nil) |
1769 | + c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
1770 | + c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
1771 | + } |
1772 | +} |
1773 | + |
1774 | +func (ss *serviceSuite) TestRegistrationWorks(c *C) { |
1775 | + reg, err := new(Service).register([]interface{}{"this"}, nil) |
1776 | + c.Assert(reg, HasLen, 1) |
1777 | + regs, ok := reg[0].(string) |
1778 | + c.Check(ok, Equals, true) |
1779 | + c.Check(regs, Not(Equals), "") |
1780 | + c.Check(err, IsNil) |
1781 | +} |
1782 | + |
1783 | +func (ss *serviceSuite) TestRegistrationOverrideWorks(c *C) { |
1784 | + os.Setenv("PUSH_REG_stuff", "42") |
1785 | + defer os.Setenv("PUSH_REG_stuff", "") |
1786 | + |
1787 | + reg, err := new(Service).register([]interface{}{"stuff"}, nil) |
1788 | + c.Assert(reg, HasLen, 1) |
1789 | + regs, ok := reg[0].(string) |
1790 | + c.Check(ok, Equals, true) |
1791 | + c.Check(regs, Equals, "42") |
1792 | + c.Check(err, IsNil) |
1793 | +} |
1794 | + |
1795 | +// |
1796 | +// Injection tests |
1797 | + |
1798 | +func (ss *serviceSuite) TestInjectWorks(c *C) { |
1799 | + svc := NewService(ss.bus, ss.log) |
1800 | + rvs, err := svc.inject([]interface{}{"hello", "world"}, nil) |
1801 | + c.Assert(err, IsNil) |
1802 | + c.Check(rvs, IsNil) |
1803 | + rvs, err = svc.inject([]interface{}{"hello", "there"}, nil) |
1804 | + c.Assert(err, IsNil) |
1805 | + c.Check(rvs, IsNil) |
1806 | + c.Assert(svc.mbox, HasLen, 1) |
1807 | + c.Assert(svc.mbox["hello"], HasLen, 2) |
1808 | + c.Check(svc.mbox["hello"][0], Equals, "world") |
1809 | + c.Check(svc.mbox["hello"][1], Equals, "there") |
1810 | + |
1811 | + // and check it fired the right signal (twice) |
1812 | + callArgs := testibus.GetCallArgs(ss.bus) |
1813 | + c.Assert(callArgs, HasLen, 2) |
1814 | + c.Check(callArgs[0].Member, Equals, "::Signal") |
1815 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Notification", []interface{}{"hello"}}) |
1816 | + c.Check(callArgs[1], DeepEquals, callArgs[0]) |
1817 | +} |
1818 | + |
1819 | +func (ss *serviceSuite) TestInjectFailsIfInjectFails(c *C) { |
1820 | + bus := testibus.NewTestingEndpoint(condition.Work(true), |
1821 | + condition.Work(false)) |
1822 | + svc := NewService(bus, ss.log) |
1823 | + svc.SetMessageHandler(func([]byte) error { return errors.New("fail") }) |
1824 | + _, err := svc.inject([]interface{}{"hello", "xyzzy"}, nil) |
1825 | + c.Check(err, NotNil) |
1826 | +} |
1827 | + |
1828 | +func (ss *serviceSuite) TestInjectFailsIfBadArgs(c *C) { |
1829 | + for i, s := range []struct { |
1830 | + args []interface{} |
1831 | + errt error |
1832 | + }{ |
1833 | + {nil, BadArgCount}, |
1834 | + {[]interface{}{}, BadArgCount}, |
1835 | + {[]interface{}{1}, BadArgCount}, |
1836 | + {[]interface{}{1, 2}, BadArgType}, |
1837 | + {[]interface{}{"1", 2}, BadArgType}, |
1838 | + {[]interface{}{1, "2"}, BadArgType}, |
1839 | + {[]interface{}{1, 2, 3}, BadArgCount}, |
1840 | + } { |
1841 | + reg, err := new(Service).inject(s.args, nil) |
1842 | + c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
1843 | + c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
1844 | + } |
1845 | +} |
1846 | + |
1847 | +// |
1848 | +// Notifications tests |
1849 | +func (ss *serviceSuite) TestNotificationsWorks(c *C) { |
1850 | + svc := NewService(ss.bus, ss.log) |
1851 | + nots, err := svc.notifications([]interface{}{"hello"}, nil) |
1852 | + c.Assert(err, IsNil) |
1853 | + c.Assert(nots, NotNil) |
1854 | + c.Assert(nots, HasLen, 1) |
1855 | + c.Check(nots[0], HasLen, 0) |
1856 | + if svc.mbox == nil { |
1857 | + svc.mbox = make(map[string][]string) |
1858 | + } |
1859 | + svc.mbox["hello"] = append(svc.mbox["hello"], "this", "thing") |
1860 | + nots, err = svc.notifications([]interface{}{"hello"}, nil) |
1861 | + c.Assert(err, IsNil) |
1862 | + c.Assert(nots, NotNil) |
1863 | + c.Assert(nots, HasLen, 1) |
1864 | + c.Check(nots[0], DeepEquals, []string{"this", "thing"}) |
1865 | +} |
1866 | + |
1867 | +func (ss *serviceSuite) TestNotificationsFailsIfBadArgs(c *C) { |
1868 | + for i, s := range []struct { |
1869 | + args []interface{} |
1870 | + errt error |
1871 | + }{ |
1872 | + {nil, BadArgCount}, // no args |
1873 | + {[]interface{}{}, BadArgCount}, // still no args |
1874 | + {[]interface{}{42}, BadArgType}, // bad arg type |
1875 | + {[]interface{}{1, 2}, BadArgCount}, // too many args |
1876 | + } { |
1877 | + reg, err := new(Service).notifications(s.args, nil) |
1878 | + c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
1879 | + c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
1880 | + } |
1881 | +} |
1882 | + |
1883 | +func (ss *serviceSuite) TestMessageHandler(c *C) { |
1884 | + svc := new(Service) |
1885 | + c.Assert(svc.msgHandler, IsNil) |
1886 | + var ext = []byte{} |
1887 | + e := errors.New("Hello") |
1888 | + f := func(s []byte) error { ext = s; return e } |
1889 | + c.Check(svc.GetMessageHandler(), IsNil) |
1890 | + svc.SetMessageHandler(f) |
1891 | + c.Check(svc.GetMessageHandler(), NotNil) |
1892 | + c.Check(svc.msgHandler([]byte("37")), Equals, e) |
1893 | + c.Check(ext, DeepEquals, []byte("37")) |
1894 | +} |
1895 | + |
1896 | +func (ss *serviceSuite) TestInjectCallsMessageHandler(c *C) { |
1897 | + var ext = []byte{} |
1898 | + svc := NewService(ss.bus, ss.log) |
1899 | + f := func(s []byte) error { ext = s; return nil } |
1900 | + svc.SetMessageHandler(f) |
1901 | + c.Check(svc.Inject("stuff", "{}"), IsNil) |
1902 | + c.Check(ext, DeepEquals, []byte("{}")) |
1903 | + err := errors.New("ouch") |
1904 | + svc.SetMessageHandler(func([]byte) error { return err }) |
1905 | + c.Check(svc.Inject("stuff", "{}"), Equals, err) |
1906 | +} |
1907 | |
1908 | === renamed directory 'client/session/levelmap' => 'client/session/seenstate' |
1909 | === renamed file 'client/session/levelmap/levelmap.go' => 'client/session/seenstate/seenstate.go' |
1910 | --- client/session/levelmap/levelmap.go 2014-02-21 16:17:28 +0000 |
1911 | +++ client/session/seenstate/seenstate.go 2014-05-29 12:04:32 +0000 |
1912 | @@ -14,31 +14,57 @@ |
1913 | with this program. If not, see <http://www.gnu.org/licenses/>. |
1914 | */ |
1915 | |
1916 | -// Package levelmap holds implementations of the LevelMap that the client |
1917 | +// Package seenstate holds implementations of the SeenState that the client |
1918 | // session uses to keep track of what messages it has seen. |
1919 | -package levelmap |
1920 | - |
1921 | -type LevelMap interface { |
1922 | +package seenstate |
1923 | + |
1924 | +import ( |
1925 | + "launchpad.net/ubuntu-push/protocol" |
1926 | +) |
1927 | + |
1928 | +type SeenState interface { |
1929 | // Set() (re)sets the given level to the given value. |
1930 | - Set(level string, top int64) error |
1931 | + SetLevel(level string, top int64) error |
1932 | // GetAll() returns a "simple" map of the current levels. |
1933 | - GetAll() (map[string]int64, error) |
1934 | -} |
1935 | - |
1936 | -type mapLevelMap map[string]int64 |
1937 | - |
1938 | -func (m *mapLevelMap) Set(level string, top int64) error { |
1939 | - (*m)[level] = top |
1940 | + GetAllLevels() (map[string]int64, error) |
1941 | + // FilterBySeen filters notifications already seen, keep track |
1942 | + // of them as well |
1943 | + FilterBySeen([]protocol.Notification) ([]protocol.Notification, error) |
1944 | +} |
1945 | + |
1946 | +type memSeenState struct { |
1947 | + levels map[string]int64 |
1948 | + seenMsgs map[string]bool |
1949 | +} |
1950 | + |
1951 | +func (m *memSeenState) SetLevel(level string, top int64) error { |
1952 | + m.levels[level] = top |
1953 | return nil |
1954 | } |
1955 | -func (m *mapLevelMap) GetAll() (map[string]int64, error) { |
1956 | - return map[string]int64(*m), nil |
1957 | -} |
1958 | - |
1959 | -var _ LevelMap = &mapLevelMap{} |
1960 | - |
1961 | -// NewLevelMap returns an implementation of LevelMap that is memory-based and |
1962 | +func (m *memSeenState) GetAllLevels() (map[string]int64, error) { |
1963 | + return m.levels, nil |
1964 | +} |
1965 | + |
1966 | +func (m *memSeenState) FilterBySeen(notifs []protocol.Notification) ([]protocol.Notification, error) { |
1967 | + acc := make([]protocol.Notification, 0, len(notifs)) |
1968 | + for _, notif := range notifs { |
1969 | + seen := m.seenMsgs[notif.MsgId] |
1970 | + if seen { |
1971 | + continue |
1972 | + } |
1973 | + m.seenMsgs[notif.MsgId] = true |
1974 | + acc = append(acc, notif) |
1975 | + } |
1976 | + return acc, nil |
1977 | +} |
1978 | + |
1979 | +var _ SeenState = (*memSeenState)(nil) |
1980 | + |
1981 | +// NewSeenState returns an implementation of SeenState that is memory-based and |
1982 | // does not save state. |
1983 | -func NewLevelMap() (LevelMap, error) { |
1984 | - return &mapLevelMap{}, nil |
1985 | +func NewSeenState() (SeenState, error) { |
1986 | + return &memSeenState{ |
1987 | + levels: make(map[string]int64), |
1988 | + seenMsgs: make(map[string]bool), |
1989 | + }, nil |
1990 | } |
1991 | |
1992 | === renamed file 'client/session/levelmap/levelmap_test.go' => 'client/session/seenstate/seenstate_test.go' |
1993 | --- client/session/levelmap/levelmap_test.go 2014-02-08 13:50:58 +0000 |
1994 | +++ client/session/seenstate/seenstate_test.go 2014-05-29 12:04:32 +0000 |
1995 | @@ -14,42 +14,73 @@ |
1996 | with this program. If not, see <http://www.gnu.org/licenses/>. |
1997 | */ |
1998 | |
1999 | -package levelmap |
2000 | +package seenstate |
2001 | |
2002 | import ( |
2003 | + "testing" |
2004 | + |
2005 | . "launchpad.net/gocheck" |
2006 | - "testing" |
2007 | + |
2008 | + "launchpad.net/ubuntu-push/protocol" |
2009 | ) |
2010 | |
2011 | -func TestLevelMap(t *testing.T) { TestingT(t) } |
2012 | - |
2013 | -type lmSuite struct { |
2014 | - constructor func() (LevelMap, error) |
2015 | -} |
2016 | - |
2017 | -var _ = Suite(&lmSuite{}) |
2018 | - |
2019 | -func (s *lmSuite) SetUpSuite(c *C) { |
2020 | - s.constructor = NewLevelMap |
2021 | -} |
2022 | - |
2023 | -func (s *lmSuite) TestAllTheThings(c *C) { |
2024 | +func TestSeenState(t *testing.T) { TestingT(t) } |
2025 | + |
2026 | +type ssSuite struct { |
2027 | + constructor func() (SeenState, error) |
2028 | +} |
2029 | + |
2030 | +var _ = Suite(&ssSuite{}) |
2031 | + |
2032 | +func (s *ssSuite) SetUpSuite(c *C) { |
2033 | + s.constructor = NewSeenState |
2034 | +} |
2035 | + |
2036 | +func (s *ssSuite) TestAllTheLevelThings(c *C) { |
2037 | var err error |
2038 | - var lm LevelMap |
2039 | - // checks NewLevelMap returns a LevelMap |
2040 | - lm, err = s.constructor() |
2041 | + var ss SeenState |
2042 | + // checks NewSeenState returns a SeenState |
2043 | + ss, err = s.constructor() |
2044 | // and that it works |
2045 | c.Assert(err, IsNil) |
2046 | // setting a couple of things, sets them |
2047 | - c.Check(lm.Set("this", 12), IsNil) |
2048 | - c.Check(lm.Set("that", 42), IsNil) |
2049 | - all, err := lm.GetAll() |
2050 | + c.Check(ss.SetLevel("this", 12), IsNil) |
2051 | + c.Check(ss.SetLevel("that", 42), IsNil) |
2052 | + all, err := ss.GetAllLevels() |
2053 | c.Check(err, IsNil) |
2054 | c.Check(all, DeepEquals, map[string]int64{"this": 12, "that": 42}) |
2055 | // re-setting one of them, resets it |
2056 | - c.Check(lm.Set("this", 999), IsNil) |
2057 | - all, err = lm.GetAll() |
2058 | + c.Check(ss.SetLevel("this", 999), IsNil) |
2059 | + all, err = ss.GetAllLevels() |
2060 | c.Check(err, IsNil) |
2061 | c.Check(all, DeepEquals, map[string]int64{"this": 999, "that": 42}) |
2062 | // huzzah |
2063 | } |
2064 | + |
2065 | +func (s *ssSuite) TestFilterBySeen(c *C) { |
2066 | + var err error |
2067 | + var ss SeenState |
2068 | + ss, err = s.constructor() |
2069 | + // and that it works |
2070 | + c.Assert(err, IsNil) |
2071 | + n1 := protocol.Notification{MsgId: "m1"} |
2072 | + n2 := protocol.Notification{MsgId: "m2"} |
2073 | + n3 := protocol.Notification{MsgId: "m3"} |
2074 | + n4 := protocol.Notification{MsgId: "m4"} |
2075 | + n5 := protocol.Notification{MsgId: "m5"} |
2076 | + |
2077 | + res, err := ss.FilterBySeen([]protocol.Notification{n1, n2, n3}) |
2078 | + c.Assert(err, IsNil) |
2079 | + // everything wasn't seen yet |
2080 | + c.Check(res, DeepEquals, []protocol.Notification{n1, n2, n3}) |
2081 | + |
2082 | + res, err = ss.FilterBySeen([]protocol.Notification{n1, n3, n4, n5}) |
2083 | + c.Assert(err, IsNil) |
2084 | + // already seen n1-n3 removed |
2085 | + c.Check(res, DeepEquals, []protocol.Notification{n4, n5}) |
2086 | + |
2087 | + // corner case |
2088 | + res, err = ss.FilterBySeen([]protocol.Notification{}) |
2089 | + c.Assert(err, IsNil) |
2090 | + c.Assert(res, HasLen, 0) |
2091 | +} |
2092 | |
2093 | === renamed file 'client/session/levelmap/sqlevelmap.go' => 'client/session/seenstate/sqlseenstate.go' |
2094 | --- client/session/levelmap/sqlevelmap.go 2014-02-12 13:52:19 +0000 |
2095 | +++ client/session/seenstate/sqlseenstate.go 2014-05-29 12:04:32 +0000 |
2096 | @@ -14,21 +14,25 @@ |
2097 | with this program. If not, see <http://www.gnu.org/licenses/>. |
2098 | */ |
2099 | |
2100 | -package levelmap |
2101 | +package seenstate |
2102 | |
2103 | import ( |
2104 | - _ "code.google.com/p/gosqlite/sqlite3" |
2105 | "database/sql" |
2106 | "fmt" |
2107 | + "strings" |
2108 | + |
2109 | + _ "code.google.com/p/gosqlite/sqlite3" |
2110 | + |
2111 | + "launchpad.net/ubuntu-push/protocol" |
2112 | ) |
2113 | |
2114 | -type sqliteLevelMap struct { |
2115 | +type sqliteSeenState struct { |
2116 | db *sql.DB |
2117 | } |
2118 | |
2119 | -// NewSqliteLevelMap returns an implementation of LevelMap that |
2120 | -// persists the map in an sqlite database. |
2121 | -func NewSqliteLevelMap(filename string) (LevelMap, error) { |
2122 | +// NewSqliteSeenState returns an implementation of SeenState that |
2123 | +// keeps and persists the state in an sqlite database. |
2124 | +func NewSqliteSeenState(filename string) (SeenState, error) { |
2125 | db, err := sql.Open("sqlite3", filename) |
2126 | if err != nil { |
2127 | return nil, fmt.Errorf("cannot open sqlite level map %#v: %v", filename, err) |
2128 | @@ -37,18 +41,22 @@ |
2129 | if err != nil { |
2130 | return nil, fmt.Errorf("cannot (re)create sqlite level map table: %v", err) |
2131 | } |
2132 | - return &sqliteLevelMap{db}, nil |
2133 | + _, err = db.Exec("CREATE TABLE IF NOT EXISTS seen_msgs (id text primary key)") |
2134 | + if err != nil { |
2135 | + return nil, fmt.Errorf("cannot (re)create sqlite seen msgs table: %v", err) |
2136 | + } |
2137 | + return &sqliteSeenState{db}, nil |
2138 | } |
2139 | |
2140 | -func (pm *sqliteLevelMap) Set(level string, top int64) error { |
2141 | - _, err := pm.db.Exec("REPLACE INTO level_map (level, top) VALUES (?, ?)", level, top) |
2142 | +func (ps *sqliteSeenState) SetLevel(level string, top int64) error { |
2143 | + _, err := ps.db.Exec("REPLACE INTO level_map (level, top) VALUES (?, ?)", level, top) |
2144 | if err != nil { |
2145 | return fmt.Errorf("cannot set %#v to %#v in level map: %v", level, top, err) |
2146 | } |
2147 | return nil |
2148 | } |
2149 | -func (pm *sqliteLevelMap) GetAll() (map[string]int64, error) { |
2150 | - rows, err := pm.db.Query("SELECT * FROM level_map") |
2151 | +func (ps *sqliteSeenState) GetAllLevels() (map[string]int64, error) { |
2152 | + rows, err := ps.db.Query("SELECT * FROM level_map") |
2153 | if err != nil { |
2154 | return nil, fmt.Errorf("cannot retrieve levels from sqlite level map: %v", err) |
2155 | } |
2156 | @@ -64,3 +72,30 @@ |
2157 | } |
2158 | return m, nil |
2159 | } |
2160 | + |
2161 | +func (ps *sqliteSeenState) dropPrevThan(msgId string) error { |
2162 | + _, err := ps.db.Exec("DELETE FROM seen_msgs WHERE rowid < (SELECT rowid FROM seen_msgs WHERE id = ?)", msgId) |
2163 | + return err |
2164 | +} |
2165 | + |
2166 | +func (ps *sqliteSeenState) FilterBySeen(notifs []protocol.Notification) ([]protocol.Notification, error) { |
2167 | + if len(notifs) == 0 { |
2168 | + return nil, nil |
2169 | + } |
2170 | + acc := make([]protocol.Notification, 0, len(notifs)) |
2171 | + for _, notif := range notifs { |
2172 | + _, err := ps.db.Exec("INSERT INTO seen_msgs (id) VALUES (?)", notif.MsgId) |
2173 | + if err != nil { |
2174 | + if strings.HasSuffix(err.Error(), "UNIQUE constraint failed: seen_msgs.id") { |
2175 | + continue |
2176 | + } |
2177 | + return nil, fmt.Errorf("cannot insert %#v in seen msgs: %v", notif.MsgId, err) |
2178 | + } |
2179 | + acc = append(acc, notif) |
2180 | + } |
2181 | + err := ps.dropPrevThan(notifs[0].MsgId) |
2182 | + if err != nil { |
2183 | + return nil, fmt.Errorf("cannot delete obsolete seen msgs: %v", err) |
2184 | + } |
2185 | + return acc, nil |
2186 | +} |
2187 | |
2188 | === renamed file 'client/session/levelmap/sqlevelmap_test.go' => 'client/session/seenstate/sqlseenstate_test.go' |
2189 | --- client/session/levelmap/sqlevelmap_test.go 2014-02-08 18:08:55 +0000 |
2190 | +++ client/session/seenstate/sqlseenstate_test.go 2014-05-29 12:04:32 +0000 |
2191 | @@ -14,29 +14,32 @@ |
2192 | with this program. If not, see <http://www.gnu.org/licenses/>. |
2193 | */ |
2194 | |
2195 | -package levelmap |
2196 | +package seenstate |
2197 | |
2198 | import ( |
2199 | + "database/sql" |
2200 | + |
2201 | _ "code.google.com/p/gosqlite/sqlite3" |
2202 | - "database/sql" |
2203 | . "launchpad.net/gocheck" |
2204 | + |
2205 | + "launchpad.net/ubuntu-push/protocol" |
2206 | ) |
2207 | |
2208 | -type sqlmSuite struct{ lmSuite } |
2209 | - |
2210 | -var _ = Suite(&sqlmSuite{}) |
2211 | - |
2212 | -func (s *sqlmSuite) SetUpSuite(c *C) { |
2213 | - s.constructor = func() (LevelMap, error) { return NewSqliteLevelMap(":memory:") } |
2214 | +type sqlsSuite struct{ ssSuite } |
2215 | + |
2216 | +var _ = Suite(&sqlsSuite{}) |
2217 | + |
2218 | +func (s *sqlsSuite) SetUpSuite(c *C) { |
2219 | + s.constructor = func() (SeenState, error) { return NewSqliteSeenState(":memory:") } |
2220 | } |
2221 | |
2222 | -func (s *sqlmSuite) TestNewCanFail(c *C) { |
2223 | - m, err := NewSqliteLevelMap("/does/not/exist") |
2224 | - c.Assert(m, IsNil) |
2225 | +func (s *sqlsSuite) TestNewCanFail(c *C) { |
2226 | + sqls, err := NewSqliteSeenState("/does/not/exist") |
2227 | + c.Assert(sqls, IsNil) |
2228 | c.Check(err, NotNil) |
2229 | } |
2230 | |
2231 | -func (s *sqlmSuite) TestSetCanFail(c *C) { |
2232 | +func (s *sqlsSuite) TestSetCanFail(c *C) { |
2233 | dir := c.MkDir() |
2234 | filename := dir + "test.db" |
2235 | db, err := sql.Open("sqlite3", filename) |
2236 | @@ -45,14 +48,14 @@ |
2237 | _, err = db.Exec("CREATE TABLE level_map (foo)") |
2238 | c.Assert(err, IsNil) |
2239 | // <evil laughter> |
2240 | - m, err := NewSqliteLevelMap(filename) |
2241 | + sqls, err := NewSqliteSeenState(filename) |
2242 | c.Check(err, IsNil) |
2243 | - c.Assert(m, NotNil) |
2244 | - err = m.Set("foo", 42) |
2245 | + c.Assert(sqls, NotNil) |
2246 | + err = sqls.SetLevel("foo", 42) |
2247 | c.Check(err, ErrorMatches, "cannot set .*") |
2248 | } |
2249 | |
2250 | -func (s *sqlmSuite) TestGetAllCanFail(c *C) { |
2251 | +func (s *sqlsSuite) TestGetAllCanFail(c *C) { |
2252 | dir := c.MkDir() |
2253 | filename := dir + "test.db" |
2254 | db, err := sql.Open("sqlite3", filename) |
2255 | @@ -61,15 +64,15 @@ |
2256 | _, err = db.Exec("CREATE TABLE level_map AS SELECT 'what'") |
2257 | c.Assert(err, IsNil) |
2258 | // <evil laughter> |
2259 | - m, err := NewSqliteLevelMap(filename) |
2260 | + sqls, err := NewSqliteSeenState(filename) |
2261 | c.Check(err, IsNil) |
2262 | - c.Assert(m, NotNil) |
2263 | - all, err := m.GetAll() |
2264 | + c.Assert(sqls, NotNil) |
2265 | + all, err := sqls.GetAllLevels() |
2266 | c.Check(all, IsNil) |
2267 | c.Check(err, ErrorMatches, "cannot read level .*") |
2268 | } |
2269 | |
2270 | -func (s *sqlmSuite) TestGetAllCanFailDifferently(c *C) { |
2271 | +func (s *sqlsSuite) TestGetAllCanFailDifferently(c *C) { |
2272 | dir := c.MkDir() |
2273 | filename := dir + "test.db" |
2274 | db, err := sql.Open("sqlite3", filename) |
2275 | @@ -83,10 +86,85 @@ |
2276 | _, err = db.Exec("DROP TABLE foo") |
2277 | c.Assert(err, IsNil) |
2278 | // <evil laughter> |
2279 | - m, err := NewSqliteLevelMap(filename) |
2280 | + sqls, err := NewSqliteSeenState(filename) |
2281 | c.Check(err, IsNil) |
2282 | - c.Assert(m, NotNil) |
2283 | - all, err := m.GetAll() |
2284 | + c.Assert(sqls, NotNil) |
2285 | + all, err := sqls.GetAllLevels() |
2286 | c.Check(all, IsNil) |
2287 | c.Check(err, ErrorMatches, "cannot retrieve levels .*") |
2288 | } |
2289 | + |
2290 | +func (s *sqlsSuite) TestFilterBySeenCanFail(c *C) { |
2291 | + dir := c.MkDir() |
2292 | + filename := dir + "test.db" |
2293 | + db, err := sql.Open("sqlite3", filename) |
2294 | + c.Assert(err, IsNil) |
2295 | + // create the wrong kind of table |
2296 | + _, err = db.Exec("CREATE TABLE seen_msgs AS SELECT 'what'") |
2297 | + c.Assert(err, IsNil) |
2298 | + // <evil laughter> |
2299 | + sqls, err := NewSqliteSeenState(filename) |
2300 | + c.Check(err, IsNil) |
2301 | + c.Assert(sqls, NotNil) |
2302 | + n1 := protocol.Notification{MsgId: "m1"} |
2303 | + res, err := sqls.FilterBySeen([]protocol.Notification{n1}) |
2304 | + c.Check(res, IsNil) |
2305 | + c.Check(err, ErrorMatches, "cannot insert .*") |
2306 | +} |
2307 | + |
2308 | +func (s *sqlsSuite) TestDropPrevThan(c *C) { |
2309 | + dir := c.MkDir() |
2310 | + filename := dir + "test.db" |
2311 | + db, err := sql.Open("sqlite3", filename) |
2312 | + c.Assert(err, IsNil) |
2313 | + sqls, err := NewSqliteSeenState(filename) |
2314 | + c.Check(err, IsNil) |
2315 | + c.Assert(sqls, NotNil) |
2316 | + |
2317 | + _, err = db.Exec("INSERT INTO seen_msgs (id) VALUES (?)", "m1") |
2318 | + c.Assert(err, IsNil) |
2319 | + _, err = db.Exec("INSERT INTO seen_msgs (id) VALUES (?)", "m2") |
2320 | + c.Assert(err, IsNil) |
2321 | + _, err = db.Exec("INSERT INTO seen_msgs (id) VALUES (?)", "m3") |
2322 | + c.Assert(err, IsNil) |
2323 | + _, err = db.Exec("INSERT INTO seen_msgs (id) VALUES (?)", "m4") |
2324 | + c.Assert(err, IsNil) |
2325 | + _, err = db.Exec("INSERT INTO seen_msgs (id) VALUES (?)", "m5") |
2326 | + c.Assert(err, IsNil) |
2327 | + |
2328 | + rows, err := db.Query("SELECT COUNT(*) FROM seen_msgs") |
2329 | + c.Assert(err, IsNil) |
2330 | + rows.Next() |
2331 | + var i int |
2332 | + err = rows.Scan(&i) |
2333 | + c.Assert(err, IsNil) |
2334 | + c.Check(i, Equals, 5) |
2335 | + rows.Close() |
2336 | + |
2337 | + err = sqls.(*sqliteSeenState).dropPrevThan("m3") |
2338 | + c.Assert(err, IsNil) |
2339 | + |
2340 | + rows, err = db.Query("SELECT COUNT(*) FROM seen_msgs") |
2341 | + c.Assert(err, IsNil) |
2342 | + rows.Next() |
2343 | + err = rows.Scan(&i) |
2344 | + c.Assert(err, IsNil) |
2345 | + c.Check(i, Equals, 3) |
2346 | + rows.Close() |
2347 | + |
2348 | + var msgId string |
2349 | + rows, err = db.Query("SELECT * FROM seen_msgs") |
2350 | + rows.Next() |
2351 | + err = rows.Scan(&msgId) |
2352 | + c.Assert(err, IsNil) |
2353 | + c.Check(msgId, Equals, "m3") |
2354 | + rows.Next() |
2355 | + err = rows.Scan(&msgId) |
2356 | + c.Assert(err, IsNil) |
2357 | + c.Check(msgId, Equals, "m4") |
2358 | + rows.Next() |
2359 | + err = rows.Scan(&msgId) |
2360 | + c.Assert(err, IsNil) |
2361 | + c.Check(msgId, Equals, "m5") |
2362 | + rows.Close() |
2363 | +} |
2364 | |
2365 | === modified file 'client/session/session.go' |
2366 | --- client/session/session.go 2014-04-18 16:37:31 +0000 |
2367 | +++ client/session/session.go 2014-05-29 12:04:32 +0000 |
2368 | @@ -26,21 +26,24 @@ |
2369 | "fmt" |
2370 | "math/rand" |
2371 | "net" |
2372 | + "os/exec" |
2373 | "strings" |
2374 | "sync" |
2375 | "sync/atomic" |
2376 | "time" |
2377 | |
2378 | "launchpad.net/ubuntu-push/client/gethosts" |
2379 | - "launchpad.net/ubuntu-push/client/session/levelmap" |
2380 | + "launchpad.net/ubuntu-push/client/session/seenstate" |
2381 | "launchpad.net/ubuntu-push/logger" |
2382 | "launchpad.net/ubuntu-push/protocol" |
2383 | "launchpad.net/ubuntu-push/util" |
2384 | ) |
2385 | |
2386 | -var wireVersionBytes = []byte{protocol.ProtocolWireVersion} |
2387 | +var ( |
2388 | + wireVersionBytes = []byte{protocol.ProtocolWireVersion} |
2389 | +) |
2390 | |
2391 | -type Notification struct { |
2392 | +type BroadcastNotification struct { |
2393 | TopLevel int64 |
2394 | Decoded []map[string]interface{} |
2395 | } |
2396 | @@ -84,6 +87,7 @@ |
2397 | ExpectAllRepairedTime time.Duration |
2398 | PEM []byte |
2399 | Info map[string]interface{} |
2400 | + AuthHelper []string |
2401 | } |
2402 | |
2403 | // ClientSession holds a client<->server session and its configuration. |
2404 | @@ -91,7 +95,7 @@ |
2405 | // configuration |
2406 | DeviceId string |
2407 | ClientSessionConfig |
2408 | - Levels levelmap.LevelMap |
2409 | + SeenState seenstate.SeenState |
2410 | Protocolator func(net.Conn) protocol.Protocol |
2411 | // hosts |
2412 | getHost hostGetter |
2413 | @@ -112,9 +116,12 @@ |
2414 | pingInterval time.Duration |
2415 | retrier util.AutoRedialer |
2416 | // status |
2417 | - stateP *uint32 |
2418 | - ErrCh chan error |
2419 | - MsgCh chan *Notification |
2420 | + stateP *uint32 |
2421 | + ErrCh chan error |
2422 | + BroadcastCh chan *BroadcastNotification |
2423 | + NotificationsCh chan *protocol.Notification |
2424 | + // authorization |
2425 | + auth string |
2426 | // autoredial knobs |
2427 | shouldDelayP *uint32 |
2428 | lastAutoRedial time.Time |
2429 | @@ -138,10 +145,10 @@ |
2430 | } |
2431 | |
2432 | func NewSession(serverAddrSpec string, conf ClientSessionConfig, |
2433 | - deviceId string, levelmapFactory func() (levelmap.LevelMap, error), |
2434 | + deviceId string, seenStateFactory func() (seenstate.SeenState, error), |
2435 | log logger.Logger) (*ClientSession, error) { |
2436 | state := uint32(Disconnected) |
2437 | - levels, err := levelmapFactory() |
2438 | + seenState, err := seenStateFactory() |
2439 | if err != nil { |
2440 | return nil, err |
2441 | } |
2442 | @@ -159,7 +166,7 @@ |
2443 | DeviceId: deviceId, |
2444 | Log: log, |
2445 | Protocolator: protocol.NewProtocol0, |
2446 | - Levels: levels, |
2447 | + SeenState: seenState, |
2448 | TLS: &tls.Config{}, |
2449 | stateP: &state, |
2450 | timeSince: time.Since, |
2451 | @@ -234,6 +241,27 @@ |
2452 | return nil |
2453 | } |
2454 | |
2455 | +// addAuthorization gets the authorization blob to send to the server |
2456 | +// and adds it to the session. |
2457 | +func (sess *ClientSession) addAuthorization() error { |
2458 | + sess.Log.Debugf("adding authorization") |
2459 | + // using a helper, for now at least |
2460 | + if len(sess.AuthHelper) == 0 { |
2461 | + // do nothing if helper is unset or empty |
2462 | + return nil |
2463 | + } |
2464 | + |
2465 | + auth, err := exec.Command(sess.AuthHelper[0], sess.AuthHelper[1:]...).Output() |
2466 | + if err != nil { |
2467 | + // For now we just log the error, as we don't want to block unauthorized users |
2468 | + sess.Log.Errorf("unable to get the authorization token from the account: %v", err) |
2469 | + } else { |
2470 | + sess.auth = strings.TrimSpace(string(auth)) |
2471 | + } |
2472 | + |
2473 | + return nil |
2474 | +} |
2475 | + |
2476 | func (sess *ClientSession) resetHosts() { |
2477 | sess.deliveryHosts = nil |
2478 | } |
2479 | @@ -343,7 +371,7 @@ |
2480 | return err |
2481 | } |
2482 | |
2483 | -func (sess *ClientSession) decodeBroadcast(bcast *serverMsg) *Notification { |
2484 | +func (sess *ClientSession) decodeBroadcast(bcast *serverMsg) *BroadcastNotification { |
2485 | decoded := make([]map[string]interface{}, 0) |
2486 | for _, p := range bcast.Payloads { |
2487 | var v map[string]interface{} |
2488 | @@ -354,7 +382,7 @@ |
2489 | } |
2490 | decoded = append(decoded, v) |
2491 | } |
2492 | - return &Notification{ |
2493 | + return &BroadcastNotification{ |
2494 | TopLevel: bcast.TopLevel, |
2495 | Decoded: decoded, |
2496 | } |
2497 | @@ -362,7 +390,7 @@ |
2498 | |
2499 | // handle "broadcast" messages |
2500 | func (sess *ClientSession) handleBroadcast(bcast *serverMsg) error { |
2501 | - err := sess.Levels.Set(bcast.ChanId, bcast.TopLevel) |
2502 | + err := sess.SeenState.SetLevel(bcast.ChanId, bcast.TopLevel) |
2503 | if err != nil { |
2504 | sess.setState(Error) |
2505 | sess.Log.Errorf("unable to set level: %v", err) |
2506 | @@ -382,15 +410,44 @@ |
2507 | bcast.ChanId, bcast.AppId, bcast.TopLevel, bcast.Payloads) |
2508 | if bcast.ChanId == protocol.SystemChannelId { |
2509 | // the system channel id, the only one we care about for now |
2510 | - sess.Log.Debugf("sending it over") |
2511 | - sess.MsgCh <- sess.decodeBroadcast(bcast) |
2512 | - sess.Log.Debugf("sent it over") |
2513 | + sess.Log.Debugf("sending bcast over") |
2514 | + sess.BroadcastCh <- sess.decodeBroadcast(bcast) |
2515 | + sess.Log.Debugf("sent bcast over") |
2516 | } else { |
2517 | sess.Log.Debugf("what is this weird channel, %#v?", bcast.ChanId) |
2518 | } |
2519 | return nil |
2520 | } |
2521 | |
2522 | +// handle "notifications" messages |
2523 | +func (sess *ClientSession) handleNotifications(ucast *serverMsg) error { |
2524 | + notifs, err := sess.SeenState.FilterBySeen(ucast.Notifications) |
2525 | + if err != nil { |
2526 | + sess.setState(Error) |
2527 | + sess.Log.Errorf("unable to record msgs seen: %v", err) |
2528 | + sess.proto.WriteMessage(protocol.AckMsg{"nak"}) |
2529 | + return err |
2530 | + } |
2531 | + // the server assumes if we ack the broadcast, we've updated |
2532 | + // our state. Hence the order. |
2533 | + err = sess.proto.WriteMessage(protocol.AckMsg{"ack"}) |
2534 | + if err != nil { |
2535 | + sess.setState(Error) |
2536 | + sess.Log.Errorf("unable to ack notifications: %s", err) |
2537 | + return err |
2538 | + } |
2539 | + sess.clearShouldDelay() |
2540 | + for i := range notifs { |
2541 | + notif := ¬ifs[i] |
2542 | + sess.Log.Debugf("unicast app:%v msg:%s payload:%s", |
2543 | + notif.AppId, notif.MsgId, notif.Payload) |
2544 | + sess.Log.Debugf("sending ucast over") |
2545 | + sess.NotificationsCh <- notif |
2546 | + sess.Log.Debugf("sent ucast over") |
2547 | + } |
2548 | + return nil |
2549 | +} |
2550 | + |
2551 | // handle "connbroken" messages |
2552 | func (sess *ClientSession) handleConnBroken(connBroken *serverMsg) error { |
2553 | sess.setState(Error) |
2554 | @@ -422,8 +479,15 @@ |
2555 | err = sess.handlePing() |
2556 | case "broadcast": |
2557 | err = sess.handleBroadcast(&recv) |
2558 | + case "notifications": |
2559 | + err = sess.handleNotifications(&recv) |
2560 | case "connbroken": |
2561 | err = sess.handleConnBroken(&recv) |
2562 | + case "warn": |
2563 | + // XXX: current message "warn" should be "connwarn" |
2564 | + fallthrough |
2565 | + case "connwarn": |
2566 | + sess.Log.Errorf("server sent warning: %s", recv.Reason) |
2567 | } |
2568 | if err != nil { |
2569 | return err |
2570 | @@ -450,17 +514,16 @@ |
2571 | } |
2572 | proto := sess.Protocolator(conn) |
2573 | proto.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) |
2574 | - levels, err := sess.Levels.GetAll() |
2575 | + levels, err := sess.SeenState.GetAllLevels() |
2576 | if err != nil { |
2577 | sess.setState(Error) |
2578 | sess.Log.Errorf("unable to start: get levels: %v", err) |
2579 | return err |
2580 | } |
2581 | err = proto.WriteMessage(protocol.ConnectMsg{ |
2582 | - Type: "connect", |
2583 | - DeviceId: sess.DeviceId, |
2584 | - // xxx get the SSO Authorization string from the phone |
2585 | - Authorization: "", |
2586 | + Type: "connect", |
2587 | + DeviceId: sess.DeviceId, |
2588 | + Authorization: sess.auth, |
2589 | Levels: levels, |
2590 | Info: sess.Info, |
2591 | }) |
2592 | @@ -495,18 +558,21 @@ |
2593 | |
2594 | // run calls connect, and if it works it calls start, and if it works |
2595 | // it runs loop in a goroutine, and ships its return value over ErrCh. |
2596 | -func (sess *ClientSession) run(closer func(), hostGetter, connecter, starter, looper func() error) error { |
2597 | +func (sess *ClientSession) run(closer func(), authChecker, hostGetter, connecter, starter, looper func() error) error { |
2598 | closer() |
2599 | - err := hostGetter() |
2600 | - if err != nil { |
2601 | - return err |
2602 | - } |
2603 | - err = connecter() |
2604 | + if err := authChecker(); err != nil { |
2605 | + return err |
2606 | + } |
2607 | + if err := hostGetter(); err != nil { |
2608 | + return err |
2609 | + } |
2610 | + err := connecter() |
2611 | if err == nil { |
2612 | err = starter() |
2613 | if err == nil { |
2614 | sess.ErrCh = make(chan error, 1) |
2615 | - sess.MsgCh = make(chan *Notification) |
2616 | + sess.BroadcastCh = make(chan *BroadcastNotification) |
2617 | + sess.NotificationsCh = make(chan *protocol.Notification) |
2618 | go func() { sess.ErrCh <- looper() }() |
2619 | } |
2620 | } |
2621 | @@ -531,7 +597,7 @@ |
2622 | // keep on trying. |
2623 | panic("can't Dial() without a protocol constructor.") |
2624 | } |
2625 | - return sess.run(sess.doClose, sess.getHosts, sess.connect, sess.start, sess.loop) |
2626 | + return sess.run(sess.doClose, sess.addAuthorization, sess.getHosts, sess.connect, sess.start, sess.loop) |
2627 | } |
2628 | |
2629 | func init() { |
2630 | |
2631 | === modified file 'client/session/session_test.go' |
2632 | --- client/session/session_test.go 2014-04-18 16:37:31 +0000 |
2633 | +++ client/session/session_test.go 2014-05-29 12:04:32 +0000 |
2634 | @@ -33,8 +33,7 @@ |
2635 | . "launchpad.net/gocheck" |
2636 | |
2637 | "launchpad.net/ubuntu-push/client/gethosts" |
2638 | - "launchpad.net/ubuntu-push/client/session/levelmap" |
2639 | - "launchpad.net/ubuntu-push/logger" |
2640 | + "launchpad.net/ubuntu-push/client/session/seenstate" |
2641 | "launchpad.net/ubuntu-push/protocol" |
2642 | helpers "launchpad.net/ubuntu-push/testing" |
2643 | "launchpad.net/ubuntu-push/testing/condition" |
2644 | @@ -157,17 +156,20 @@ |
2645 | return nil |
2646 | } |
2647 | |
2648 | -// brokenLevelMap is a LevelMap that always breaks |
2649 | -type brokenLevelMap struct{} |
2650 | +// brokenSeenState is a SeenState that always breaks |
2651 | +type brokenSeenState struct{} |
2652 | |
2653 | -func (*brokenLevelMap) Set(string, int64) error { return errors.New("broken.") } |
2654 | -func (*brokenLevelMap) GetAll() (map[string]int64, error) { return nil, errors.New("broken.") } |
2655 | +func (*brokenSeenState) SetLevel(string, int64) error { return errors.New("broken.") } |
2656 | +func (*brokenSeenState) GetAllLevels() (map[string]int64, error) { return nil, errors.New("broken.") } |
2657 | +func (*brokenSeenState) FilterBySeen([]protocol.Notification) ([]protocol.Notification, error) { |
2658 | + return nil, errors.New("broken.") |
2659 | +} |
2660 | |
2661 | ///// |
2662 | |
2663 | type clientSessionSuite struct { |
2664 | - log logger.Logger |
2665 | - lvls func() (levelmap.LevelMap, error) |
2666 | + log *helpers.TestLogger |
2667 | + lvls func() (seenstate.SeenState, error) |
2668 | } |
2669 | |
2670 | func (cs *clientSessionSuite) SetUpTest(c *C) { |
2671 | @@ -175,7 +177,7 @@ |
2672 | } |
2673 | |
2674 | // in-memory level map testing |
2675 | -var _ = Suite(&clientSessionSuite{lvls: levelmap.NewLevelMap}) |
2676 | +var _ = Suite(&clientSessionSuite{lvls: seenstate.NewSeenState}) |
2677 | |
2678 | // sqlite level map testing |
2679 | type clientSqlevelsSessionSuite struct{ clientSessionSuite } |
2680 | @@ -183,7 +185,7 @@ |
2681 | var _ = Suite(&clientSqlevelsSessionSuite{}) |
2682 | |
2683 | func (cs *clientSqlevelsSessionSuite) SetUpSuite(c *C) { |
2684 | - cs.lvls = func() (levelmap.LevelMap, error) { return levelmap.NewSqliteLevelMap(":memory:") } |
2685 | + cs.lvls = func() (seenstate.SeenState, error) { return seenstate.NewSqliteSeenState(":memory:") } |
2686 | } |
2687 | |
2688 | /**************************************************************** |
2689 | @@ -249,8 +251,8 @@ |
2690 | c.Check(err, NotNil) |
2691 | } |
2692 | |
2693 | -func (cs *clientSessionSuite) TestNewSessionBadLevelMapFails(c *C) { |
2694 | - ferr := func() (levelmap.LevelMap, error) { return nil, errors.New("Busted.") } |
2695 | +func (cs *clientSessionSuite) TestNewSessionBadSeenStateFails(c *C) { |
2696 | + ferr := func() (seenstate.SeenState, error) { return nil, errors.New("Busted.") } |
2697 | sess, err := NewSession("", dummyConf, "wah", ferr, cs.log) |
2698 | c.Check(sess, IsNil) |
2699 | c.Assert(err, NotNil) |
2700 | @@ -347,6 +349,43 @@ |
2701 | } |
2702 | |
2703 | /**************************************************************** |
2704 | + addAuthorization() tests |
2705 | +****************************************************************/ |
2706 | + |
2707 | +func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) { |
2708 | + sess := &ClientSession{Log: cs.log} |
2709 | + sess.AuthHelper = []string{"echo", "some auth"} |
2710 | + c.Assert(sess.auth, Equals, "") |
2711 | + err := sess.addAuthorization() |
2712 | + c.Assert(err, IsNil) |
2713 | + c.Check(sess.auth, Equals, "some auth") |
2714 | +} |
2715 | + |
2716 | +func (cs *clientSessionSuite) TestAddAuthorizationIgnoresErrors(c *C) { |
2717 | + sess := &ClientSession{Log: cs.log} |
2718 | + sess.AuthHelper = []string{"sh", "-c", "echo hello; false"} |
2719 | + |
2720 | + c.Assert(sess.auth, Equals, "") |
2721 | + err := sess.addAuthorization() |
2722 | + c.Assert(err, IsNil) |
2723 | + c.Check(sess.auth, Equals, "") |
2724 | +} |
2725 | + |
2726 | +func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnsetOrNil(c *C) { |
2727 | + sess := &ClientSession{Log: cs.log} |
2728 | + sess.AuthHelper = nil |
2729 | + c.Assert(sess.auth, Equals, "") |
2730 | + err := sess.addAuthorization() |
2731 | + c.Assert(err, IsNil) |
2732 | + c.Check(sess.auth, Equals, "") |
2733 | + |
2734 | + sess.AuthHelper = []string{} |
2735 | + err = sess.addAuthorization() |
2736 | + c.Assert(err, IsNil) |
2737 | + c.Check(sess.auth, Equals, "") |
2738 | +} |
2739 | + |
2740 | +/**************************************************************** |
2741 | startConnectionAttempt()/nextHostToTry()/started tests |
2742 | ****************************************************************/ |
2743 | |
2744 | @@ -601,7 +640,7 @@ |
2745 | conf := ClientSessionConfig{ |
2746 | ExchangeTimeout: time.Millisecond, |
2747 | } |
2748 | - s.sess, err = NewSession("", conf, "wah", levelmap.NewLevelMap, helpers.NewTestLogger(c, "debug")) |
2749 | + s.sess, err = NewSession("", conf, "wah", seenstate.NewSeenState, helpers.NewTestLogger(c, "debug")) |
2750 | c.Assert(err, IsNil) |
2751 | s.sess.Connection = &testConn{Name: "TestHandle*"} |
2752 | s.errCh = make(chan error, 1) |
2753 | @@ -609,7 +648,8 @@ |
2754 | s.downCh = make(chan interface{}, 5) |
2755 | s.sess.proto = &testProtocol{up: s.upCh, down: s.downCh} |
2756 | // make the message channel buffered |
2757 | - s.sess.MsgCh = make(chan *Notification, 5) |
2758 | + s.sess.BroadcastCh = make(chan *BroadcastNotification, 5) |
2759 | + s.sess.NotificationsCh = make(chan *protocol.Notification, 5) |
2760 | } |
2761 | |
2762 | func (s *msgSuite) TestHandlePingWorks(c *C) { |
2763 | @@ -668,8 +708,8 @@ |
2764 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
2765 | s.upCh <- nil // ack ok |
2766 | c.Check(<-s.errCh, Equals, nil) |
2767 | - c.Assert(len(s.sess.MsgCh), Equals, 1) |
2768 | - c.Check(<-s.sess.MsgCh, DeepEquals, &Notification{ |
2769 | + c.Assert(len(s.sess.BroadcastCh), Equals, 1) |
2770 | + c.Check(<-s.sess.BroadcastCh, DeepEquals, &BroadcastNotification{ |
2771 | TopLevel: 2, |
2772 | Decoded: []map[string]interface{}{ |
2773 | map[string]interface{}{ |
2774 | @@ -681,7 +721,7 @@ |
2775 | }, |
2776 | }) |
2777 | // and finally, the session keeps track of the levels |
2778 | - levels, err := s.sess.Levels.GetAll() |
2779 | + levels, err := s.sess.SeenState.GetAllLevels() |
2780 | c.Check(err, IsNil) |
2781 | c.Check(levels, DeepEquals, map[string]int64{"0": 2}) |
2782 | } |
2783 | @@ -716,11 +756,11 @@ |
2784 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
2785 | s.upCh <- nil // ack ok |
2786 | c.Check(<-s.errCh, IsNil) |
2787 | - c.Check(len(s.sess.MsgCh), Equals, 0) |
2788 | + c.Check(len(s.sess.BroadcastCh), Equals, 0) |
2789 | } |
2790 | |
2791 | -func (s *msgSuite) TestHandleBroadcastWrongBrokenLevelmap(c *C) { |
2792 | - s.sess.Levels = &brokenLevelMap{} |
2793 | +func (s *msgSuite) TestHandleBroadcastBrokenSeenState(c *C) { |
2794 | + s.sess.SeenState = &brokenSeenState{} |
2795 | msg := serverMsg{"broadcast", |
2796 | protocol.BroadcastMsg{ |
2797 | Type: "broadcast", |
2798 | @@ -733,8 +773,9 @@ |
2799 | s.upCh <- nil // ack ok |
2800 | // start returns with error |
2801 | c.Check(<-s.errCh, Not(Equals), nil) |
2802 | + c.Check(s.sess.State(), Equals, Error) |
2803 | // no message sent out |
2804 | - c.Check(len(s.sess.MsgCh), Equals, 0) |
2805 | + c.Check(len(s.sess.BroadcastCh), Equals, 0) |
2806 | // and nak'ed it |
2807 | c.Check(len(s.downCh), Equals, 1) |
2808 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"nak"}) |
2809 | @@ -767,6 +808,118 @@ |
2810 | } |
2811 | |
2812 | /**************************************************************** |
2813 | + handleNotifications() tests |
2814 | +****************************************************************/ |
2815 | + |
2816 | +func (s *msgSuite) TestHandleNotificationsWorks(c *C) { |
2817 | + s.sess.setShouldDelay() |
2818 | + n1 := protocol.Notification{ |
2819 | + AppId: "app1", |
2820 | + MsgId: "a", |
2821 | + Payload: json.RawMessage(`{"m": 1}`), |
2822 | + } |
2823 | + n2 := protocol.Notification{ |
2824 | + AppId: "app2", |
2825 | + MsgId: "b", |
2826 | + Payload: json.RawMessage(`{"m": 2}`), |
2827 | + } |
2828 | + msg := serverMsg{"notifications", |
2829 | + protocol.BroadcastMsg{}, |
2830 | + protocol.NotificationsMsg{ |
2831 | + Notifications: []protocol.Notification{n1, n2}, |
2832 | + }, protocol.ConnBrokenMsg{}} |
2833 | + go func() { s.errCh <- s.sess.handleNotifications(&msg) }() |
2834 | + c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
2835 | + s.upCh <- nil // ack ok |
2836 | + c.Check(<-s.errCh, Equals, nil) |
2837 | + c.Check(s.sess.ShouldDelay(), Equals, false) |
2838 | + c.Assert(len(s.sess.NotificationsCh), Equals, 2) |
2839 | + c.Check(<-s.sess.NotificationsCh, DeepEquals, &n1) |
2840 | + c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2) |
2841 | +} |
2842 | + |
2843 | +func (s *msgSuite) TestHandleNotificationsFiltersSeen(c *C) { |
2844 | + n1 := protocol.Notification{ |
2845 | + AppId: "app1", |
2846 | + MsgId: "a", |
2847 | + Payload: json.RawMessage(`{"m": 1}`), |
2848 | + } |
2849 | + n2 := protocol.Notification{ |
2850 | + AppId: "app2", |
2851 | + MsgId: "b", |
2852 | + Payload: json.RawMessage(`{"m": 2}`), |
2853 | + } |
2854 | + msg := serverMsg{"notifications", |
2855 | + protocol.BroadcastMsg{}, |
2856 | + protocol.NotificationsMsg{ |
2857 | + Notifications: []protocol.Notification{n1, n2}, |
2858 | + }, protocol.ConnBrokenMsg{}} |
2859 | + go func() { s.errCh <- s.sess.handleNotifications(&msg) }() |
2860 | + c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
2861 | + s.upCh <- nil // ack ok |
2862 | + c.Check(<-s.errCh, Equals, nil) |
2863 | + c.Assert(len(s.sess.NotificationsCh), Equals, 2) |
2864 | + c.Check(<-s.sess.NotificationsCh, DeepEquals, &n1) |
2865 | + c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2) |
2866 | + |
2867 | + // second time they get ignored |
2868 | + go func() { s.errCh <- s.sess.handleNotifications(&msg) }() |
2869 | + c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
2870 | + s.upCh <- nil // ack ok |
2871 | + c.Check(<-s.errCh, Equals, nil) |
2872 | + c.Assert(len(s.sess.NotificationsCh), Equals, 0) |
2873 | +} |
2874 | + |
2875 | +func (s *msgSuite) TestHandleNotificationsBadAckWrite(c *C) { |
2876 | + s.sess.setShouldDelay() |
2877 | + n1 := protocol.Notification{ |
2878 | + AppId: "app1", |
2879 | + MsgId: "a", |
2880 | + Payload: json.RawMessage(`{"m": 1}`), |
2881 | + } |
2882 | + msg := serverMsg{"notifications", |
2883 | + protocol.BroadcastMsg{}, |
2884 | + protocol.NotificationsMsg{ |
2885 | + Notifications: []protocol.Notification{n1}, |
2886 | + }, protocol.ConnBrokenMsg{}} |
2887 | + go func() { s.errCh <- s.sess.handleNotifications(&msg) }() |
2888 | + c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
2889 | + failure := errors.New("ACK ACK ACK") |
2890 | + s.upCh <- failure |
2891 | + c.Assert(<-s.errCh, Equals, failure) |
2892 | + c.Check(s.sess.State(), Equals, Error) |
2893 | + // didn't get to clear |
2894 | + c.Check(s.sess.ShouldDelay(), Equals, true) |
2895 | +} |
2896 | + |
2897 | +func (s *msgSuite) TestHandleNotificationsBrokenSeenState(c *C) { |
2898 | + s.sess.setShouldDelay() |
2899 | + s.sess.SeenState = &brokenSeenState{} |
2900 | + n1 := protocol.Notification{ |
2901 | + AppId: "app1", |
2902 | + MsgId: "a", |
2903 | + Payload: json.RawMessage(`{"m": 1}`), |
2904 | + } |
2905 | + msg := serverMsg{"notifications", |
2906 | + protocol.BroadcastMsg{}, |
2907 | + protocol.NotificationsMsg{ |
2908 | + Notifications: []protocol.Notification{n1}, |
2909 | + }, protocol.ConnBrokenMsg{}} |
2910 | + go func() { s.errCh <- s.sess.handleNotifications(&msg) }() |
2911 | + s.upCh <- nil // ack ok |
2912 | + // start returns with error |
2913 | + c.Check(<-s.errCh, Not(Equals), nil) |
2914 | + c.Check(s.sess.State(), Equals, Error) |
2915 | + // no message sent out |
2916 | + c.Check(len(s.sess.NotificationsCh), Equals, 0) |
2917 | + // and nak'ed it |
2918 | + c.Check(len(s.downCh), Equals, 1) |
2919 | + c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"nak"}) |
2920 | + // didn't get to clear |
2921 | + c.Check(s.sess.ShouldDelay(), Equals, true) |
2922 | +} |
2923 | + |
2924 | +/**************************************************************** |
2925 | handleConnBroken() tests |
2926 | ****************************************************************/ |
2927 | |
2928 | @@ -861,6 +1014,26 @@ |
2929 | c.Check(<-s.errCh, Equals, failure) |
2930 | } |
2931 | |
2932 | +func (s *loopSuite) TestLoopNotifications(c *C) { |
2933 | + c.Check(s.sess.State(), Equals, Running) |
2934 | + |
2935 | + n1 := protocol.Notification{ |
2936 | + AppId: "app1", |
2937 | + MsgId: "a", |
2938 | + Payload: json.RawMessage(`{"m": 1}`), |
2939 | + } |
2940 | + msg := &protocol.NotificationsMsg{ |
2941 | + Type: "notifications", |
2942 | + Notifications: []protocol.Notification{n1}, |
2943 | + } |
2944 | + c.Check(takeNext(s.downCh), Equals, "deadline 1ms") |
2945 | + s.upCh <- msg |
2946 | + c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
2947 | + failure := errors.New("ack") |
2948 | + s.upCh <- failure |
2949 | + c.Check(<-s.errCh, Equals, failure) |
2950 | +} |
2951 | + |
2952 | func (s *loopSuite) TestLoopConnBroken(c *C) { |
2953 | c.Check(s.sess.State(), Equals, Running) |
2954 | broken := protocol.ConnBrokenMsg{ |
2955 | @@ -872,6 +1045,31 @@ |
2956 | c.Check(<-s.errCh, NotNil) |
2957 | } |
2958 | |
2959 | +func (s *loopSuite) TestLoopConnWarn(c *C) { |
2960 | + warn := protocol.ConnWarnMsg{ |
2961 | + Type: "warn", |
2962 | + Reason: "XXX", |
2963 | + } |
2964 | + connwarn := protocol.ConnWarnMsg{ |
2965 | + Type: "connwarn", |
2966 | + Reason: "REASON", |
2967 | + } |
2968 | + failure := errors.New("warn") |
2969 | + log := s.sess.Log.(*helpers.TestLogger) |
2970 | + |
2971 | + c.Check(s.sess.State(), Equals, Running) |
2972 | + c.Check(takeNext(s.downCh), Equals, "deadline 1ms") |
2973 | + log.ResetCapture() |
2974 | + s.upCh <- warn |
2975 | + s.upCh <- connwarn |
2976 | + s.upCh <- failure |
2977 | + c.Check(<-s.errCh, Equals, failure) |
2978 | + c.Check(log.Captured(), |
2979 | + Matches, `(?ms).* warning: XXX$.*`) |
2980 | + c.Check(log.Captured(), |
2981 | + Matches, `(?ms).* warning: REASON$`) |
2982 | +} |
2983 | + |
2984 | /**************************************************************** |
2985 | start() tests |
2986 | ****************************************************************/ |
2987 | @@ -898,7 +1096,7 @@ |
2988 | func (cs *clientSessionSuite) TestStartFailsIfGetLevelsFails(c *C) { |
2989 | sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
2990 | c.Assert(err, IsNil) |
2991 | - sess.Levels = &brokenLevelMap{} |
2992 | + sess.SeenState = &brokenSeenState{} |
2993 | sess.Connection = &testConn{Name: "TestStartConnectMessageFails"} |
2994 | errCh := make(chan error, 1) |
2995 | upCh := make(chan interface{}, 5) |
2996 | @@ -931,9 +1129,10 @@ |
2997 | |
2998 | c.Check(takeNext(downCh), Equals, "deadline 0") |
2999 | c.Check(takeNext(downCh), DeepEquals, protocol.ConnectMsg{ |
3000 | - Type: "connect", |
3001 | - DeviceId: sess.DeviceId, |
3002 | - Levels: map[string]int64{}, |
3003 | + Type: "connect", |
3004 | + DeviceId: sess.DeviceId, |
3005 | + Levels: map[string]int64{}, |
3006 | + Authorization: "", |
3007 | }) |
3008 | upCh <- errors.New("Overflow error in /dev/null") |
3009 | err = <-errCh |
3010 | @@ -1038,6 +1237,7 @@ |
3011 | msg, ok := takeNext(downCh).(protocol.ConnectMsg) |
3012 | c.Check(ok, Equals, true) |
3013 | c.Check(msg.DeviceId, Equals, "wah") |
3014 | + c.Check(msg.Authorization, Equals, "") |
3015 | c.Check(msg.Info, DeepEquals, info) |
3016 | upCh <- nil // no error |
3017 | upCh <- protocol.ConnAckMsg{ |
3018 | @@ -1054,6 +1254,22 @@ |
3019 | run() tests |
3020 | ****************************************************************/ |
3021 | |
3022 | +func (cs *clientSessionSuite) TestRunBailsIfAuthCheckFails(c *C) { |
3023 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
3024 | + c.Assert(err, IsNil) |
3025 | + failure := errors.New("TestRunBailsIfAuthCheckFails") |
3026 | + has_closed := false |
3027 | + err = sess.run( |
3028 | + func() { has_closed = true }, |
3029 | + func() error { return failure }, |
3030 | + nil, |
3031 | + nil, |
3032 | + nil, |
3033 | + nil) |
3034 | + c.Check(err, Equals, failure) |
3035 | + c.Check(has_closed, Equals, true) |
3036 | +} |
3037 | + |
3038 | func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) { |
3039 | sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
3040 | c.Assert(err, IsNil) |
3041 | @@ -1061,6 +1277,7 @@ |
3042 | has_closed := false |
3043 | err = sess.run( |
3044 | func() { has_closed = true }, |
3045 | + func() error { return nil }, |
3046 | func() error { return failure }, |
3047 | nil, |
3048 | nil, |
3049 | @@ -1076,6 +1293,7 @@ |
3050 | err = sess.run( |
3051 | func() {}, |
3052 | func() error { return nil }, |
3053 | + func() error { return nil }, |
3054 | func() error { return failure }, |
3055 | nil, |
3056 | nil) |
3057 | @@ -1090,6 +1308,7 @@ |
3058 | func() {}, |
3059 | func() error { return nil }, |
3060 | func() error { return nil }, |
3061 | + func() error { return nil }, |
3062 | func() error { return failure }, |
3063 | nil) |
3064 | c.Check(err, Equals, failure) |
3065 | @@ -1098,23 +1317,24 @@ |
3066 | func (cs *clientSessionSuite) TestRunRunsEvenIfLoopFails(c *C) { |
3067 | sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
3068 | c.Assert(err, IsNil) |
3069 | - // just to make a point: until here we haven't set ErrCh & MsgCh (no |
3070 | + // just to make a point: until here we haven't set ErrCh & BroadcastCh (no |
3071 | // biggie if this stops being true) |
3072 | c.Check(sess.ErrCh, IsNil) |
3073 | - c.Check(sess.MsgCh, IsNil) |
3074 | + c.Check(sess.BroadcastCh, IsNil) |
3075 | failureCh := make(chan error) // must be unbuffered |
3076 | - notf := &Notification{} |
3077 | + notf := &BroadcastNotification{} |
3078 | err = sess.run( |
3079 | func() {}, |
3080 | func() error { return nil }, |
3081 | func() error { return nil }, |
3082 | func() error { return nil }, |
3083 | - func() error { sess.MsgCh <- notf; return <-failureCh }) |
3084 | + func() error { return nil }, |
3085 | + func() error { sess.BroadcastCh <- notf; return <-failureCh }) |
3086 | c.Check(err, Equals, nil) |
3087 | // if run doesn't error it sets up the channels |
3088 | c.Assert(sess.ErrCh, NotNil) |
3089 | - c.Assert(sess.MsgCh, NotNil) |
3090 | - c.Check(<-sess.MsgCh, Equals, notf) |
3091 | + c.Assert(sess.BroadcastCh, NotNil) |
3092 | + c.Check(<-sess.BroadcastCh, Equals, notf) |
3093 | failure := errors.New("TestRunRunsEvenIfLoopFails") |
3094 | failureCh <- failure |
3095 | c.Check(<-sess.ErrCh, Equals, failure) |
3096 | @@ -1317,9 +1537,9 @@ |
3097 | c.Check(takeNext(downCh), Equals, protocol.AckMsg{"ack"}) |
3098 | upCh <- nil |
3099 | // ...get bubbled up, |
3100 | - c.Check(<-sess.MsgCh, NotNil) |
3101 | + c.Check(<-sess.BroadcastCh, NotNil) |
3102 | // and their TopLevel remembered |
3103 | - levels, err := sess.Levels.GetAll() |
3104 | + levels, err := sess.SeenState.GetAllLevels() |
3105 | c.Check(err, IsNil) |
3106 | c.Check(levels, DeepEquals, map[string]int64{"0": 2}) |
3107 | |
3108 | |
3109 | === modified file 'config/config.go' |
3110 | --- config/config.go 2014-04-12 08:44:39 +0000 |
3111 | +++ config/config.go 2014-05-29 12:04:32 +0000 |
3112 | @@ -268,14 +268,16 @@ |
3113 | |
3114 | // used to implement -cfg@= |
3115 | type readConfigAtVal struct { |
3116 | + path string |
3117 | accu map[string]json.RawMessage |
3118 | } |
3119 | |
3120 | func (v *readConfigAtVal) String() string { |
3121 | - return "<config.json>" |
3122 | + return v.path |
3123 | } |
3124 | |
3125 | func (v *readConfigAtVal) Set(path string) error { |
3126 | + v.path = path |
3127 | return readOneConfig(v.accu, path) |
3128 | } |
3129 | |
3130 | @@ -292,7 +294,7 @@ |
3131 | help := destField.fld.Tag.Get("help") |
3132 | flag.Var(&val{destField, accu}, destField.configName(), help) |
3133 | } |
3134 | - flag.Var(&readConfigAtVal{accu}, "cfg@", "get config values from file") |
3135 | + flag.Var(&readConfigAtVal{"<config.json>", accu}, "cfg@", "get config values from file") |
3136 | flag.Parse() |
3137 | return nil |
3138 | } |
3139 | @@ -301,17 +303,25 @@ |
3140 | // command line was already parsed. |
3141 | var IgnoreParsedFlags = false |
3142 | |
3143 | -// ReadFiles reads configuration from a set of files. The string |
3144 | -// "<flags>" can be used as a pseudo file-path, it will consider |
3145 | -// command line flags, invoking flag.Parse(). Among those the flag |
3146 | -// -cfg@=FILE can be used to get further config values from FILE. |
3147 | -func ReadFiles(destConfig interface{}, cfgFpaths ...string) error { |
3148 | +// ReadFilesDefaults reads configuration from a set of files. The |
3149 | +// string "<flags>" can be used as a pseudo file-path, it will |
3150 | +// consider command line flags, invoking flag.Parse(). Among those the |
3151 | +// flag -cfg@=FILE can be used to get further config values from FILE. |
3152 | +// Defaults for fields can be given through a map[string]interface{}. |
3153 | +func ReadFilesDefaults(destConfig interface{}, defls map[string]interface{}, cfgFpaths ...string) error { |
3154 | destValue, err := checkDestConfig("destConfig", destConfig) |
3155 | if err != nil { |
3156 | return err |
3157 | } |
3158 | // do the parsing in two phases for better error handling |
3159 | p1 := make(map[string]json.RawMessage) |
3160 | + for field, value := range defls { |
3161 | + b, err := json.Marshal(value) |
3162 | + if err != nil { |
3163 | + return err |
3164 | + } |
3165 | + p1[field] = json.RawMessage(b) |
3166 | + } |
3167 | readOne := false |
3168 | for _, cfgPath := range cfgFpaths { |
3169 | if cfgPath == "<flags>" { |
3170 | @@ -336,6 +346,13 @@ |
3171 | return fillDestConfig(destValue, p1) |
3172 | } |
3173 | |
3174 | +// ReadFiles reads configuration from a set of files exactly like |
3175 | +// ReadFilesDefaults but no defaults can be given making all fields |
3176 | +// mandatory. |
3177 | +func ReadFiles(destConfig interface{}, cfgFpaths ...string) error { |
3178 | + return ReadFilesDefaults(destConfig, nil, cfgFpaths...) |
3179 | +} |
3180 | + |
3181 | // CompareConfigs compares the two given configuration structures. It returns a list of differing fields or nil if the config contents are the same. |
3182 | func CompareConfig(config1, config2 interface{}) ([]string, error) { |
3183 | v1, err := checkDestConfig("config1", config1) |
3184 | |
3185 | === modified file 'config/config_test.go' |
3186 | --- config/config_test.go 2014-04-12 08:43:32 +0000 |
3187 | +++ config/config_test.go 2014-05-29 12:04:32 +0000 |
3188 | @@ -173,6 +173,40 @@ |
3189 | c.Check(err, NotNil) |
3190 | } |
3191 | |
3192 | +type testConfig2 struct { |
3193 | + A int |
3194 | + B string |
3195 | + C []string `json:"c_list"` |
3196 | + D ConfigTimeDuration |
3197 | +} |
3198 | + |
3199 | +func (s *configSuite) TestReadFilesDefaults(c *C) { |
3200 | + var cfg testConfig2 |
3201 | + tmpDir := c.MkDir() |
3202 | + emptyCfgPath := filepath.Join(tmpDir, "e.json") |
3203 | + err := ioutil.WriteFile(emptyCfgPath, []byte("{}"), os.ModePerm) |
3204 | + c.Assert(err, IsNil) |
3205 | + err = ReadFilesDefaults(&cfg, map[string]interface{}{ |
3206 | + "a": 42, |
3207 | + "b": "foo", |
3208 | + "c_list": []string{"bar", "baz"}, |
3209 | + "d": "3s", |
3210 | + }, emptyCfgPath) |
3211 | + c.Check(err, IsNil) |
3212 | + c.Check(cfg.A, Equals, 42) |
3213 | + c.Check(cfg.B, Equals, "foo") |
3214 | + c.Check(cfg.C, DeepEquals, []string{"bar", "baz"}) |
3215 | + c.Check(cfg.D.TimeDuration(), Equals, 3*time.Second) |
3216 | +} |
3217 | + |
3218 | +func (s *configSuite) TestReadFilesDefaultsError(c *C) { |
3219 | + var cfg testConfig2 |
3220 | + err := ReadFilesDefaults(&cfg, map[string]interface{}{ |
3221 | + "a": make(chan int), |
3222 | + }) |
3223 | + c.Assert(err, NotNil) |
3224 | +} |
3225 | + |
3226 | type B struct { |
3227 | BFld int |
3228 | } |
3229 | @@ -193,13 +227,6 @@ |
3230 | c.Check(a, DeepEquals, A{1, B{2}, 0}) |
3231 | } |
3232 | |
3233 | -type testConfig2 struct { |
3234 | - A int |
3235 | - B string |
3236 | - C []string `json:"c_list"` |
3237 | - D ConfigTimeDuration |
3238 | -} |
3239 | - |
3240 | func (s *configSuite) TestCompareConfig(c *C) { |
3241 | var cfg1 = testConfig2{ |
3242 | A: 1, |
3243 | @@ -304,6 +331,7 @@ |
3244 | c.Check(cfg.A, Equals, 42) |
3245 | c.Check(cfg.B, Equals, "x") |
3246 | c.Check(cfg.C, DeepEquals, []string{"y", "z"}) |
3247 | + c.Check(flag.Lookup("cfg@").Value.String(), Equals, cfgPath) |
3248 | } |
3249 | |
3250 | func (s *configFlagsSuite) TestReadUsingFlagsHelp(c *C) { |
3251 | |
3252 | === modified file 'debian/changelog' |
3253 | --- debian/changelog 2014-04-23 11:54:00 +0000 |
3254 | +++ debian/changelog 2014-05-29 12:04:32 +0000 |
3255 | @@ -1,3 +1,9 @@ |
3256 | +ubuntu-push (0.21-0.ubuntu1) UNRELEASED; urgency=medium |
3257 | + |
3258 | + * New upstream release: first auth bits, and Qt dependency. |
3259 | + |
3260 | + -- John Lenton <john.lenton@canonical.com> Tue, 15 Apr 2014 14:04:35 +0100 |
3261 | + |
3262 | ubuntu-push (0.2.1+14.04.20140423.1-0ubuntu1) trusty; urgency=high |
3263 | |
3264 | [ Samuele Pedroni ] |
3265 | |
3266 | === modified file 'debian/control' |
3267 | --- debian/control 2014-03-25 16:26:20 +0000 |
3268 | +++ debian/control 2014-05-29 12:04:32 +0000 |
3269 | @@ -14,6 +14,8 @@ |
3270 | libgcrypt11-dev, |
3271 | libglib2.0-dev (>= 2.31.6), |
3272 | libwhoopsie-dev, |
3273 | + libubuntuoneauth-2.0-dev, |
3274 | + cmake, |
3275 | Standards-Version: 3.9.5 |
3276 | Homepage: http://launchpad.net/ubuntu-push |
3277 | Vcs-Bzr: lp:ubuntu-push |
3278 | |
3279 | === modified file 'debian/rules' |
3280 | --- debian/rules 2014-03-24 12:22:55 +0000 |
3281 | +++ debian/rules 2014-05-29 12:04:32 +0000 |
3282 | @@ -2,9 +2,13 @@ |
3283 | # -*- makefile -*- |
3284 | |
3285 | export DH_GOPKG := launchpad.net/ubuntu-push |
3286 | -export DEB_BUILD_OPTIONS := nostrip |
3287 | export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR) |
3288 | |
3289 | +override_dh_auto_build: |
3290 | + cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1) |
3291 | + dh_auto_build --buildsystem=golang |
3292 | + (cd signing-helper && cmake . && make) |
3293 | + |
3294 | override_dh_install: |
3295 | dh_install -Xusr/bin/cmd -Xusr/bin/dev --fail-missing |
3296 | |
3297 | |
3298 | === modified file 'debian/ubuntu-push-client.install' |
3299 | --- debian/ubuntu-push-client.install 2014-03-26 16:27:19 +0000 |
3300 | +++ debian/ubuntu-push-client.install 2014-05-29 12:04:32 +0000 |
3301 | @@ -1,4 +1,5 @@ |
3302 | #!/usr/bin/dh-exec |
3303 | debian/config.json /etc/xdg/ubuntu-push-client |
3304 | debian/ubuntu-push-client.conf /usr/share/upstart/sessions |
3305 | +signing-helper/signing-helper /usr/lib/ubuntu-push-client |
3306 | usr/bin/ubuntu-push => /usr/lib/ubuntu-push-client/ubuntu-push-client |
3307 | |
3308 | === modified file 'dependencies.tsv' |
3309 | --- dependencies.tsv 2014-03-12 13:23:26 +0000 |
3310 | +++ dependencies.tsv 2014-05-29 12:04:32 +0000 |
3311 | @@ -2,3 +2,4 @@ |
3312 | launchpad.net/go-dbus/v1 bzr james@jamesh.id.au-20140206110213-pbzcr6ucaz3rqmnw 125 |
3313 | launchpad.net/go-xdg/v0 bzr john.lenton@canonical.com-20140208094800-gubd5md7cro3mtxa 10 |
3314 | launchpad.net/gocheck bzr gustavo@niemeyer.net-20140127131816-zshobk1qqme626xw 86 |
3315 | +launchpad.net/~ubuntu-push-hackers/ubuntu-push/go-uuid bzr samuele.pedroni@canonical.com-20140130122455-pm9h8etl4owp90lg 1 |
3316 | |
3317 | === added directory 'nih' |
3318 | === added directory 'nih/cnih' |
3319 | === added file 'nih/cnih/cnih.go' |
3320 | --- nih/cnih/cnih.go 1970-01-01 00:00:00 +0000 |
3321 | +++ nih/cnih/cnih.go 2014-05-29 12:04:32 +0000 |
3322 | @@ -0,0 +1,28 @@ |
3323 | +package cnih |
3324 | + |
3325 | +/* |
3326 | +#cgo pkg-config: dbus-1 libnih libnih-dbus |
3327 | +#include <stdlib.h> |
3328 | +#include <nih/alloc.h> |
3329 | +#include <libnih-dbus.h> |
3330 | + |
3331 | +// a small wrapper because cgo doesn't handle varargs |
3332 | +char *cuote (const char *id) { |
3333 | + return nih_dbus_path (NULL, "", id, NULL); |
3334 | +} |
3335 | +*/ |
3336 | +import "C" |
3337 | + |
3338 | +import ( |
3339 | + "unsafe" |
3340 | +) |
3341 | + |
3342 | +func Quote(s []byte) string { |
3343 | + cs := C.CString(string(s)) |
3344 | + defer C.free(unsafe.Pointer(cs)) |
3345 | + |
3346 | + cq := C.cuote(cs) |
3347 | + defer C.nih_free(unsafe.Pointer(cq)) |
3348 | + |
3349 | + return C.GoString(cq)[1:] |
3350 | +} |
3351 | |
3352 | === added file 'nih/nih.go' |
3353 | --- nih/nih.go 1970-01-01 00:00:00 +0000 |
3354 | +++ nih/nih.go 2014-05-29 12:04:32 +0000 |
3355 | @@ -0,0 +1,68 @@ |
3356 | +/* |
3357 | + Copyright 2013-2014 Canonical Ltd. |
3358 | + |
3359 | + This program is free software: you can redistribute it and/or modify it |
3360 | + under the terms of the GNU General Public License version 3, as published |
3361 | + by the Free Software Foundation. |
3362 | + |
3363 | + This program is distributed in the hope that it will be useful, but |
3364 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
3365 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3366 | + PURPOSE. See the GNU General Public License for more details. |
3367 | + |
3368 | + You should have received a copy of the GNU General Public License along |
3369 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
3370 | +*/ |
3371 | + |
3372 | +// package nih reimplements libnih-dbus's nih_dbus_path's path element |
3373 | +// quoting. |
3374 | +// |
3375 | +// Reimplementing libnih is a wonderful exercise that everybody should persue |
3376 | +// at least thrice. |
3377 | +package nih |
3378 | + |
3379 | +import "strconv" |
3380 | + |
3381 | +// Quote() takes a byte slice and quotes it á la libnih. |
3382 | +func Quote(s []byte) []byte { |
3383 | + if len(s) == 0 { |
3384 | + return []byte{'_'} |
3385 | + } |
3386 | + out := make([]byte, 0, 2*len(s)) |
3387 | + for _, c := range s { |
3388 | + if ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') { |
3389 | + out = append(out, c) |
3390 | + } else { |
3391 | + if c < 16 { |
3392 | + out = append(out, '_', '0') |
3393 | + } else { |
3394 | + out = append(out, '_') |
3395 | + } |
3396 | + out = strconv.AppendUint(out, uint64(c), 16) |
3397 | + } |
3398 | + } |
3399 | + |
3400 | + return out |
3401 | +} |
3402 | + |
3403 | +// Quote() takes a byte slice and undoes the damage done to it by the quoting. |
3404 | +func Unquote(s []byte) []byte { |
3405 | + out := make([]byte, 0, len(s)) |
3406 | + |
3407 | + for i := 0; i < len(s); i++ { |
3408 | + if s[i] == '_' { |
3409 | + if len(s) < i+3 { |
3410 | + break |
3411 | + } |
3412 | + num, err := strconv.ParseUint(string(s[i+1:i+3]), 16, 8) |
3413 | + if err == nil { |
3414 | + out = append(out, byte(num)) |
3415 | + } |
3416 | + i += 2 |
3417 | + } else { |
3418 | + out = append(out, s[i]) |
3419 | + } |
3420 | + } |
3421 | + |
3422 | + return out |
3423 | +} |
3424 | |
3425 | === added file 'nih/nih_test.go' |
3426 | --- nih/nih_test.go 1970-01-01 00:00:00 +0000 |
3427 | +++ nih/nih_test.go 2014-05-29 12:04:32 +0000 |
3428 | @@ -0,0 +1,57 @@ |
3429 | +/* |
3430 | + Copyright 2013-2014 Canonical Ltd. |
3431 | + |
3432 | + This program is free software: you can redistribute it and/or modify it |
3433 | + under the terms of the GNU General Public License version 3, as published |
3434 | + by the Free Software Foundation. |
3435 | + |
3436 | + This program is distributed in the hope that it will be useful, but |
3437 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
3438 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3439 | + PURPOSE. See the GNU General Public License for more details. |
3440 | + |
3441 | + You should have received a copy of the GNU General Public License along |
3442 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
3443 | +*/ |
3444 | + |
3445 | +package nih |
3446 | + |
3447 | +import ( |
3448 | + "testing" |
3449 | + |
3450 | + . "launchpad.net/gocheck" |
3451 | + |
3452 | + "launchpad.net/ubuntu-push/nih/cnih" |
3453 | +) |
3454 | + |
3455 | +func TestNIH(t *testing.T) { TestingT(t) } |
3456 | + |
3457 | +type nihSuite struct{} |
3458 | + |
3459 | +var _ = Suite(&nihSuite{}) |
3460 | + |
3461 | +func (ns *nihSuite) TestQuote(c *C) { |
3462 | + for i, s := range []struct { |
3463 | + raw []byte |
3464 | + quoted []byte |
3465 | + }{ |
3466 | + {[]byte("test"), []byte("test")}, |
3467 | + {[]byte("foo/bar.baz"), []byte("foo_2fbar_2ebaz")}, |
3468 | + {[]byte("test_thing"), []byte("test_5fthing")}, |
3469 | + {[]byte("\x01\x0f\x10\xff"), []byte("_01_0f_10_ff")}, |
3470 | + {[]byte{}, []byte{'_'}}, |
3471 | + } { |
3472 | + c.Check(string(s.quoted), Equals, cnih.Quote(s.raw), Commentf("iter %d (%s)", i, string(s.quoted))) |
3473 | + c.Check(string(Quote(s.raw)), DeepEquals, string(s.quoted), Commentf("iter %d (%s)", i, string(s.quoted))) |
3474 | + c.Check(Unquote(s.quoted), DeepEquals, s.raw, Commentf("iter %d (%s)", i, string(s.quoted))) |
3475 | + c.Check(string(Quote(s.raw)), Equals, cnih.Quote(s.raw), Commentf("iter %d (%s)", i, string(s.quoted))) |
3476 | + } |
3477 | + |
3478 | + // check one cnih doesn't like |
3479 | + c.Check(Quote([]byte{0}), DeepEquals, []byte("_00")) |
3480 | + |
3481 | + // check we don't panic with some weird ones |
3482 | + for i, s := range []string{"foo_", "foo_a", "foo_zz"} { |
3483 | + c.Check(Unquote([]byte(s)), DeepEquals, []byte("foo"), Commentf("iter %d (%s)", i, s)) |
3484 | + } |
3485 | +} |
3486 | |
3487 | === modified file 'protocol/messages.go' |
3488 | --- protocol/messages.go 2014-04-04 13:54:45 +0000 |
3489 | +++ protocol/messages.go 2014-05-29 12:04:32 +0000 |
3490 | @@ -20,6 +20,7 @@ |
3491 | |
3492 | import ( |
3493 | "encoding/json" |
3494 | + "fmt" |
3495 | ) |
3496 | |
3497 | // System channel id using a shortened hex-encoded form for the NIL UUID. |
3498 | @@ -54,6 +55,14 @@ |
3499 | Split() (done bool) |
3500 | } |
3501 | |
3502 | +// OnewayMsg are messages that are not to be followed by a response, |
3503 | +// after sending them the session either aborts or continues. |
3504 | +type OnewayMsg interface { |
3505 | + SplittableMsg |
3506 | + // continue session after the message? |
3507 | + OnewayContinue() bool |
3508 | +} |
3509 | + |
3510 | // CONNBROKEN message, server side is breaking the connection for reason. |
3511 | type ConnBrokenMsg struct { |
3512 | Type string `json:"T"` |
3513 | @@ -65,11 +74,35 @@ |
3514 | return true |
3515 | } |
3516 | |
3517 | +func (m *ConnBrokenMsg) OnewayContinue() bool { |
3518 | + return false |
3519 | +} |
3520 | + |
3521 | // CONNBROKEN reasons |
3522 | const ( |
3523 | BrokenHostMismatch = "host-mismatch" |
3524 | ) |
3525 | |
3526 | +// CONNWARN message, server side is warning about partial functionality |
3527 | +// because reason. |
3528 | +type ConnWarnMsg struct { |
3529 | + Type string `json:"T"` |
3530 | + // reason |
3531 | + Reason string |
3532 | +} |
3533 | + |
3534 | +func (m *ConnWarnMsg) Split() bool { |
3535 | + return true |
3536 | +} |
3537 | +func (m *ConnWarnMsg) OnewayContinue() bool { |
3538 | + return true |
3539 | +} |
3540 | + |
3541 | +// CONNWARN reasons |
3542 | +const ( |
3543 | + WarnUnauthorized = "unauthorized" |
3544 | +) |
3545 | + |
3546 | // PING/PONG messages |
3547 | type PingPongMsg struct { |
3548 | Type string `json:"T"` |
3549 | @@ -111,8 +144,9 @@ |
3550 | } |
3551 | |
3552 | // Reset resets the splitting state if the message storage is to be |
3553 | -// reused. |
3554 | +// reused and sets the proper Type. |
3555 | func (b *BroadcastMsg) Reset() { |
3556 | + b.Type = "broadcast" |
3557 | b.splitting = 0 |
3558 | } |
3559 | |
3560 | @@ -120,6 +154,41 @@ |
3561 | type NotificationsMsg struct { |
3562 | Type string `json:"T"` |
3563 | Notifications []Notification |
3564 | + splitting int |
3565 | +} |
3566 | + |
3567 | +// Reset resets the splitting state if the message storage is to be |
3568 | +// reused and sets the proper Type. |
3569 | +func (m *NotificationsMsg) Reset() { |
3570 | + m.Type = "notifications" |
3571 | + m.splitting = 0 |
3572 | +} |
3573 | + |
3574 | +func (m *NotificationsMsg) Split() bool { |
3575 | + if m.splitting != 0 { |
3576 | + m.Notifications = m.Notifications[len(m.Notifications):m.splitting] |
3577 | + } |
3578 | + notifs := m.Notifications |
3579 | + var size int |
3580 | + for i, notif := range notifs { |
3581 | + size += len(notif.Payload) + len(notif.AppId) + len(notif.MsgId) + notificationOverhead |
3582 | + if size > maxPayloadSize { |
3583 | + m.splitting = len(notifs) |
3584 | + m.Notifications = notifs[:i] |
3585 | + return false |
3586 | + } |
3587 | + } |
3588 | + return true |
3589 | +} |
3590 | + |
3591 | +var notificationOverhead int |
3592 | + |
3593 | +func init() { |
3594 | + buf, err := json.Marshal(Notification{}) |
3595 | + if err != nil { |
3596 | + panic(fmt.Errorf("failed to compute Notification marshal overhead: %v", err)) |
3597 | + } |
3598 | + notificationOverhead = len(buf) - 4 // - 4 for the null from P(ayload) |
3599 | } |
3600 | |
3601 | // A single unicast notification |
3602 | @@ -130,6 +199,19 @@ |
3603 | Payload json.RawMessage `json:"P"` |
3604 | } |
3605 | |
3606 | +// ExtractPayloads gets only the payloads out of a slice of notications. |
3607 | +func ExtractPayloads(notifications []Notification) []json.RawMessage { |
3608 | + n := len(notifications) |
3609 | + if n == 0 { |
3610 | + return nil |
3611 | + } |
3612 | + payloads := make([]json.RawMessage, n) |
3613 | + for i := 0; i < n; i++ { |
3614 | + payloads[i] = notifications[i].Payload |
3615 | + } |
3616 | + return payloads |
3617 | +} |
3618 | + |
3619 | // ACKnowledgement message |
3620 | type AckMsg struct { |
3621 | Type string `json:"T"` |
3622 | |
3623 | === modified file 'protocol/messages_test.go' |
3624 | --- protocol/messages_test.go 2014-04-04 13:19:10 +0000 |
3625 | +++ protocol/messages_test.go 2014-05-29 12:04:32 +0000 |
3626 | @@ -100,10 +100,100 @@ |
3627 | c.Check(b.TopLevel, Equals, int64(n)) |
3628 | c.Check(n1+n2+n3, Equals, n) |
3629 | // reset |
3630 | + b.Type = "" |
3631 | b.Reset() |
3632 | + c.Check(b.Type, Equals, "broadcast") |
3633 | c.Check(b.splitting, Equals, 0) |
3634 | } |
3635 | |
3636 | -func (s *messagesSuite) TestSplitConnBrokenMsg(c *C) { |
3637 | - c.Check((&ConnBrokenMsg{}).Split(), Equals, true) |
3638 | +func (s *messagesSuite) TestConnBrokenMsg(c *C) { |
3639 | + m := &ConnBrokenMsg{} |
3640 | + c.Check(m.Split(), Equals, true) |
3641 | + c.Check(m.OnewayContinue(), Equals, false) |
3642 | +} |
3643 | + |
3644 | +func (s *messagesSuite) TestConnWarnMsg(c *C) { |
3645 | + m := &ConnWarnMsg{} |
3646 | + c.Check(m.Split(), Equals, true) |
3647 | + c.Check(m.OnewayContinue(), Equals, true) |
3648 | +} |
3649 | + |
3650 | +func (s *messagesSuite) TestExtractPayloads(c *C) { |
3651 | + c.Check(ExtractPayloads(nil), IsNil) |
3652 | + p1 := json.RawMessage(`{"a":1}`) |
3653 | + p2 := json.RawMessage(`{"b":2}`) |
3654 | + ns := []Notification{Notification{Payload: p1}, Notification{Payload: p2}} |
3655 | + c.Check(ExtractPayloads(ns), DeepEquals, []json.RawMessage{p1, p2}) |
3656 | +} |
3657 | + |
3658 | +func (s *messagesSuite) TestSplitNotificationsMsgNop(c *C) { |
3659 | + n := &NotificationsMsg{ |
3660 | + Type: "notifications", |
3661 | + Notifications: []Notification{ |
3662 | + Notification{"app1", "msg1", json.RawMessage(`{m:1}`)}, |
3663 | + Notification{"app1", "msg1", json.RawMessage(`{m:2}`)}, |
3664 | + }, |
3665 | + } |
3666 | + done := n.Split() |
3667 | + c.Check(done, Equals, true) |
3668 | + c.Check(cap(n.Notifications), Equals, 2) |
3669 | + c.Check(len(n.Notifications), Equals, 2) |
3670 | +} |
3671 | + |
3672 | +var payloadFmt2 = fmt.Sprintf(`{"b":%%d,"bloat":"%s"}`, strings.Repeat("x", 1024*2-notificationOverhead-4-6)) // 4 = app1 6 = msg%03d |
3673 | + |
3674 | +func manyNotifications(c int) []Notification { |
3675 | + notifs := make([]Notification, 0, 1) |
3676 | + for i := 0; i < c; i++ { |
3677 | + notifs = append(notifs, Notification{ |
3678 | + "app1", |
3679 | + fmt.Sprintf("msg%03d", i), |
3680 | + json.RawMessage(fmt.Sprintf(payloadFmt2, i)), |
3681 | + }) |
3682 | + } |
3683 | + return notifs |
3684 | +} |
3685 | + |
3686 | +func (s *messagesSuite) TestSplitNotificationsMsgMany(c *C) { |
3687 | + notifs := manyNotifications(33) |
3688 | + n := len(notifs) |
3689 | + // more interesting this way |
3690 | + c.Assert(cap(notifs), Not(Equals), n) |
3691 | + nm := &NotificationsMsg{ |
3692 | + Type: "notifications", |
3693 | + Notifications: notifs, |
3694 | + } |
3695 | + done := nm.Split() |
3696 | + c.Assert(done, Equals, false) |
3697 | + n1 := len(nm.Notifications) |
3698 | + buf, err := json.Marshal(nm) |
3699 | + c.Assert(err, IsNil) |
3700 | + c.Assert(len(buf) <= 65535, Equals, true) |
3701 | + c.Check(len(buf)+len(notifs[n1].Payload) > maxPayloadSize, Equals, true) |
3702 | + done = nm.Split() |
3703 | + c.Assert(done, Equals, true) |
3704 | + n2 := len(nm.Notifications) |
3705 | + c.Check(n1+n2, Equals, n) |
3706 | + |
3707 | + notifs = manyNotifications(61) |
3708 | + n = len(notifs) |
3709 | + nm = &NotificationsMsg{ |
3710 | + Type: "notifications", |
3711 | + Notifications: notifs, |
3712 | + } |
3713 | + done = nm.Split() |
3714 | + c.Assert(done, Equals, false) |
3715 | + n1 = len(nm.Notifications) |
3716 | + done = nm.Split() |
3717 | + c.Assert(done, Equals, false) |
3718 | + n2 = len(nm.Notifications) |
3719 | + done = nm.Split() |
3720 | + c.Assert(done, Equals, true) |
3721 | + n3 := len(nm.Notifications) |
3722 | + c.Check(n1+n2+n3, Equals, n) |
3723 | + // reset |
3724 | + nm.Type = "" |
3725 | + nm.Reset() |
3726 | + c.Check(nm.Type, Equals, "notifications") |
3727 | + c.Check(nm.splitting, Equals, 0) |
3728 | } |
3729 | |
3730 | === modified file 'protocol/state-diag-client.gv' |
3731 | --- protocol/state-diag-client.gv 2014-01-16 20:07:13 +0000 |
3732 | +++ protocol/state-diag-client.gv 2014-05-29 12:04:32 +0000 |
3733 | @@ -2,7 +2,7 @@ |
3734 | label = "State diagram for client"; |
3735 | size="12,6"; |
3736 | rankdir=LR; |
3737 | - node [shape = doublecircle]; pingTimeout; |
3738 | + node [shape = doublecircle]; pingTimeout; connBroken; |
3739 | node [shape = circle]; |
3740 | start1 -> start2 [ label = "Write wire version" ]; |
3741 | start2 -> start3 [ label = "Write CONNECT" ]; |
3742 | @@ -13,4 +13,7 @@ |
3743 | broadcast -> loop [label = "Write ACK"]; |
3744 | loop -> pingTimeout [ |
3745 | label = "Elapsed ping interval + exchange interval"]; |
3746 | + loop -> connBroken [label = "Read CONNBROKEN"]; |
3747 | + loop -> warn [label = "Read CONNWARN"]; |
3748 | + warn -> loop; |
3749 | } |
3750 | |
3751 | === modified file 'protocol/state-diag-client.svg' |
3752 | --- protocol/state-diag-client.svg 2014-01-16 19:37:57 +0000 |
3753 | +++ protocol/state-diag-client.svg 2014-05-29 12:04:32 +0000 |
3754 | @@ -4,95 +4,123 @@ |
3755 | <!-- Generated by graphviz version 2.26.3 (20100126.1600) |
3756 | --> |
3757 | <!-- Title: state_diagram_client Pages: 1 --> |
3758 | -<svg width="864pt" height="279pt" |
3759 | - viewBox="0.00 0.00 864.00 278.89" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> |
3760 | -<g id="graph1" class="graph" transform="scale(0.683544 0.683544) rotate(0) translate(4 404)"> |
3761 | +<svg width="822pt" height="432pt" |
3762 | + viewBox="0.00 0.00 822.36 432.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> |
3763 | +<g id="graph1" class="graph" transform="scale(0.650602 0.650602) rotate(0) translate(4 660)"> |
3764 | <title>state_diagram_client</title> |
3765 | -<polygon fill="white" stroke="white" points="-4,5 -4,-404 1261,-404 1261,5 -4,5"/> |
3766 | +<polygon fill="white" stroke="white" points="-4,5 -4,-660 1261,-660 1261,5 -4,5"/> |
3767 | <text text-anchor="middle" x="628" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for client</text> |
3768 | <!-- pingTimeout --> |
3769 | <g id="node1" class="node"><title>pingTimeout</title> |
3770 | -<ellipse fill="none" stroke="black" cx="1180" cy="-324" rx="72.1249" ry="72.1249"/> |
3771 | -<ellipse fill="none" stroke="black" cx="1180" cy="-324" rx="76.1249" ry="76.1249"/> |
3772 | -<text text-anchor="middle" x="1180" y="-320.4" font-family="Times Roman,serif" font-size="14.00">pingTimeout</text> |
3773 | +<ellipse fill="none" stroke="black" cx="1180" cy="-580" rx="72.1249" ry="72.1249"/> |
3774 | +<ellipse fill="none" stroke="black" cx="1180" cy="-580" rx="76.1249" ry="76.1249"/> |
3775 | +<text text-anchor="middle" x="1180" y="-576.4" font-family="Times Roman,serif" font-size="14.00">pingTimeout</text> |
3776 | +</g> |
3777 | +<!-- connBroken --> |
3778 | +<g id="node2" class="node"><title>connBroken</title> |
3779 | +<ellipse fill="none" stroke="black" cx="1180" cy="-413" rx="68.8251" ry="69.2965"/> |
3780 | +<ellipse fill="none" stroke="black" cx="1180" cy="-413" rx="72.7978" ry="73.2965"/> |
3781 | +<text text-anchor="middle" x="1180" y="-409.4" font-family="Times Roman,serif" font-size="14.00">connBroken</text> |
3782 | </g> |
3783 | <!-- start1 --> |
3784 | -<g id="node2" class="node"><title>start1</title> |
3785 | -<ellipse fill="none" stroke="black" cx="42" cy="-166" rx="41.2167" ry="41.7193"/> |
3786 | -<text text-anchor="middle" x="42" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start1</text> |
3787 | +<g id="node3" class="node"><title>start1</title> |
3788 | +<ellipse fill="none" stroke="black" cx="42" cy="-231" rx="41.2167" ry="41.7193"/> |
3789 | +<text text-anchor="middle" x="42" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start1</text> |
3790 | </g> |
3791 | <!-- start2 --> |
3792 | -<g id="node4" class="node"><title>start2</title> |
3793 | -<ellipse fill="none" stroke="black" cx="292" cy="-166" rx="41.2167" ry="41.7193"/> |
3794 | -<text text-anchor="middle" x="292" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start2</text> |
3795 | +<g id="node5" class="node"><title>start2</title> |
3796 | +<ellipse fill="none" stroke="black" cx="292" cy="-231" rx="41.2167" ry="41.7193"/> |
3797 | +<text text-anchor="middle" x="292" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start2</text> |
3798 | </g> |
3799 | <!-- start1->start2 --> |
3800 | <g id="edge2" class="edge"><title>start1->start2</title> |
3801 | -<path fill="none" stroke="black" d="M83.5631,-166C126.547,-166 193.757,-166 240.181,-166"/> |
3802 | -<polygon fill="black" stroke="black" points="240.338,-169.5 250.338,-166 240.338,-162.5 240.338,-169.5"/> |
3803 | -<text text-anchor="middle" x="167" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Write wire version</text> |
3804 | +<path fill="none" stroke="black" d="M83.5631,-231C126.547,-231 193.757,-231 240.181,-231"/> |
3805 | +<polygon fill="black" stroke="black" points="240.338,-234.5 250.338,-231 240.338,-227.5 240.338,-234.5"/> |
3806 | +<text text-anchor="middle" x="167" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Write wire version</text> |
3807 | </g> |
3808 | <!-- start3 --> |
3809 | -<g id="node6" class="node"><title>start3</title> |
3810 | -<ellipse fill="none" stroke="black" cx="526" cy="-166" rx="41.2167" ry="41.7193"/> |
3811 | -<text text-anchor="middle" x="526" y="-162.4" font-family="Times Roman,serif" font-size="14.00">start3</text> |
3812 | +<g id="node7" class="node"><title>start3</title> |
3813 | +<ellipse fill="none" stroke="black" cx="526" cy="-231" rx="41.2167" ry="41.7193"/> |
3814 | +<text text-anchor="middle" x="526" y="-227.4" font-family="Times Roman,serif" font-size="14.00">start3</text> |
3815 | </g> |
3816 | <!-- start2->start3 --> |
3817 | <g id="edge4" class="edge"><title>start2->start3</title> |
3818 | -<path fill="none" stroke="black" d="M333.565,-166C372.875,-166 431.992,-166 474.321,-166"/> |
3819 | -<polygon fill="black" stroke="black" points="474.429,-169.5 484.429,-166 474.429,-162.5 474.429,-169.5"/> |
3820 | -<text text-anchor="middle" x="409" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Write CONNECT</text> |
3821 | +<path fill="none" stroke="black" d="M333.565,-231C372.875,-231 431.992,-231 474.321,-231"/> |
3822 | +<polygon fill="black" stroke="black" points="474.429,-234.5 484.429,-231 474.429,-227.5 474.429,-234.5"/> |
3823 | +<text text-anchor="middle" x="409" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Write CONNECT</text> |
3824 | </g> |
3825 | <!-- loop --> |
3826 | -<g id="node8" class="node"><title>loop</title> |
3827 | -<ellipse fill="none" stroke="black" cx="746" cy="-166" rx="31.8198" ry="31.8198"/> |
3828 | -<text text-anchor="middle" x="746" y="-162.4" font-family="Times Roman,serif" font-size="14.00">loop</text> |
3829 | +<g id="node9" class="node"><title>loop</title> |
3830 | +<ellipse fill="none" stroke="black" cx="746" cy="-231" rx="31.8198" ry="31.8198"/> |
3831 | +<text text-anchor="middle" x="746" y="-227.4" font-family="Times Roman,serif" font-size="14.00">loop</text> |
3832 | </g> |
3833 | <!-- start3->loop --> |
3834 | <g id="edge6" class="edge"><title>start3->loop</title> |
3835 | -<path fill="none" stroke="black" d="M567.639,-166C606.633,-166 664.616,-166 703.793,-166"/> |
3836 | -<polygon fill="black" stroke="black" points="703.818,-169.5 713.818,-166 703.818,-162.5 703.818,-169.5"/> |
3837 | -<text text-anchor="middle" x="641" y="-171.4" font-family="Times Roman,serif" font-size="14.00">Read CONNACK</text> |
3838 | +<path fill="none" stroke="black" d="M567.639,-231C606.633,-231 664.616,-231 703.793,-231"/> |
3839 | +<polygon fill="black" stroke="black" points="703.818,-234.5 713.818,-231 703.818,-227.5 703.818,-234.5"/> |
3840 | +<text text-anchor="middle" x="641" y="-236.4" font-family="Times Roman,serif" font-size="14.00">Read CONNACK</text> |
3841 | </g> |
3842 | <!-- loop->pingTimeout --> |
3843 | <g id="edge16" class="edge"><title>loop->pingTimeout</title> |
3844 | -<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"/> |
3845 | -<polygon fill="black" stroke="black" points="1093.67,-319.928 1104.02,-317.68 1094.53,-312.981 1093.67,-319.928"/> |
3846 | -<text text-anchor="middle" x="941" y="-319.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval + exchange interval</text> |
3847 | +<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"/> |
3848 | +<polygon fill="black" stroke="black" points="1093.96,-573.992 1104.39,-572.09 1095.05,-567.078 1093.96,-573.992"/> |
3849 | +<text text-anchor="middle" x="941" y="-572.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval + exchange interval</text> |
3850 | +</g> |
3851 | +<!-- loop->connBroken --> |
3852 | +<g id="edge18" class="edge"><title>loop->connBroken</title> |
3853 | +<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"/> |
3854 | +<polygon fill="black" stroke="black" points="1096.19,-416.445 1106.33,-413.388 1096.5,-409.452 1096.19,-416.445"/> |
3855 | +<text text-anchor="middle" x="941" y="-417.4" font-family="Times Roman,serif" font-size="14.00">Read CONNBROKEN</text> |
3856 | </g> |
3857 | <!-- pong --> |
3858 | -<g id="node10" class="node"><title>pong</title> |
3859 | -<ellipse fill="none" stroke="black" cx="1180" cy="-195" rx="34.8574" ry="35.3553"/> |
3860 | -<text text-anchor="middle" x="1180" y="-191.4" font-family="Times Roman,serif" font-size="14.00">pong</text> |
3861 | +<g id="node11" class="node"><title>pong</title> |
3862 | +<ellipse fill="none" stroke="black" cx="1180" cy="-287" rx="34.8574" ry="35.3553"/> |
3863 | +<text text-anchor="middle" x="1180" y="-283.4" font-family="Times Roman,serif" font-size="14.00">pong</text> |
3864 | </g> |
3865 | <!-- loop->pong --> |
3866 | <g id="edge8" class="edge"><title>loop->pong</title> |
3867 | -<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"/> |
3868 | -<polygon fill="black" stroke="black" points="1134.89,-202.186 1144.62,-198.003 1134.4,-195.203 1134.89,-202.186"/> |
3869 | -<text text-anchor="middle" x="941" y="-207.4" font-family="Times Roman,serif" font-size="14.00">Read PING</text> |
3870 | +<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"/> |
3871 | +<polygon fill="black" stroke="black" points="1135.49,-297.371 1144.94,-292.588 1134.57,-290.432 1135.49,-297.371"/> |
3872 | +<text text-anchor="middle" x="941" y="-307.4" font-family="Times Roman,serif" font-size="14.00">Read PING</text> |
3873 | </g> |
3874 | <!-- broadcast --> |
3875 | -<g id="node12" class="node"><title>broadcast</title> |
3876 | -<ellipse fill="none" stroke="black" cx="1180" cy="-84" rx="58.1882" ry="58.6899"/> |
3877 | -<text text-anchor="middle" x="1180" y="-80.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text> |
3878 | +<g id="node13" class="node"><title>broadcast</title> |
3879 | +<ellipse fill="none" stroke="black" cx="1180" cy="-176" rx="58.1882" ry="58.6899"/> |
3880 | +<text text-anchor="middle" x="1180" y="-172.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text> |
3881 | </g> |
3882 | <!-- loop->broadcast --> |
3883 | <g id="edge10" class="edge"><title>loop->broadcast</title> |
3884 | -<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"/> |
3885 | -<polygon fill="black" stroke="black" points="1113.34,-101.917 1122.5,-96.5998 1112.02,-95.0419 1113.34,-101.917"/> |
3886 | -<text text-anchor="middle" x="941" y="-136.4" font-family="Times Roman,serif" font-size="14.00">Read BROADCAST</text> |
3887 | +<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"/> |
3888 | +<polygon fill="black" stroke="black" points="1112.44,-189.353 1121.9,-184.574 1111.53,-182.413 1112.44,-189.353"/> |
3889 | +<text text-anchor="middle" x="941" y="-217.4" font-family="Times Roman,serif" font-size="14.00">Read BROADCAST</text> |
3890 | +</g> |
3891 | +<!-- warn --> |
3892 | +<g id="node19" class="node"><title>warn</title> |
3893 | +<ellipse fill="none" stroke="black" cx="1180" cy="-63" rx="36.7696" ry="36.7696"/> |
3894 | +<text text-anchor="middle" x="1180" y="-59.4" font-family="Times Roman,serif" font-size="14.00">warn</text> |
3895 | +</g> |
3896 | +<!-- loop->warn --> |
3897 | +<g id="edge20" class="edge"><title>loop->warn</title> |
3898 | +<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"/> |
3899 | +<polygon fill="black" stroke="black" points="1135.26,-79.0068 1144.04,-73.0757 1133.48,-72.2376 1135.26,-79.0068"/> |
3900 | +<text text-anchor="middle" x="941" y="-136.4" font-family="Times Roman,serif" font-size="14.00">Read CONNWARN</text> |
3901 | </g> |
3902 | <!-- pong->loop --> |
3903 | <g id="edge12" class="edge"><title>pong->loop</title> |
3904 | -<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"/> |
3905 | -<polygon fill="black" stroke="black" points="787.736,-157.528 778.16,-162.06 788.472,-164.489 787.736,-157.528"/> |
3906 | -<text text-anchor="middle" x="941" y="-168.4" font-family="Times Roman,serif" font-size="14.00">Write PONG</text> |
3907 | +<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"/> |
3908 | +<polygon fill="black" stroke="black" points="788.085,-227.996 778.035,-231.348 787.982,-234.995 788.085,-227.996"/> |
3909 | +<text text-anchor="middle" x="941" y="-254.4" font-family="Times Roman,serif" font-size="14.00">Write PONG</text> |
3910 | </g> |
3911 | <!-- broadcast->loop --> |
3912 | <g id="edge14" class="edge"><title>broadcast->loop</title> |
3913 | -<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"/> |
3914 | -<polygon fill="black" stroke="black" points="756.044,-124.528 755.336,-135.099 762.482,-127.277 756.044,-124.528"/> |
3915 | -<text text-anchor="middle" x="941" y="-86.4" font-family="Times Roman,serif" font-size="14.00">Write ACK</text> |
3916 | +<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"/> |
3917 | +<polygon fill="black" stroke="black" points="763.182,-192.043 760.465,-202.284 768.975,-195.973 763.182,-192.043"/> |
3918 | +<text text-anchor="middle" x="941" y="-172.4" font-family="Times Roman,serif" font-size="14.00">Write ACK</text> |
3919 | +</g> |
3920 | +<!-- warn->loop --> |
3921 | +<g id="edge22" class="edge"><title>warn->loop</title> |
3922 | +<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"/> |
3923 | +<polygon fill="black" stroke="black" points="751.574,-188.904 752.686,-199.44 758.387,-190.51 751.574,-188.904"/> |
3924 | </g> |
3925 | </g> |
3926 | </svg> |
3927 | |
3928 | === modified file 'protocol/state-diag-session.gv' |
3929 | --- protocol/state-diag-session.gv 2014-01-16 20:07:13 +0000 |
3930 | +++ protocol/state-diag-session.gv 2014-05-29 12:04:32 +0000 |
3931 | @@ -2,6 +2,7 @@ |
3932 | label = "State diagram for session"; |
3933 | size="12,6"; |
3934 | rankdir=LR; |
3935 | + node [shape = doublecircle]; stop; |
3936 | node [shape = circle]; |
3937 | start1 -> start2 [ label = "Read wire version" ]; |
3938 | start2 -> start3 [ label = "Read CONNECT" ]; |
3939 | @@ -17,4 +18,13 @@ |
3940 | split_broadcast -> split_ack_wait [label = "Write split BROADCAST"]; |
3941 | split_ack_wait -> split_broadcast [label = "Read ACK"]; |
3942 | split_broadcast -> loop [label = "All split msgs written"]; |
3943 | + // other |
3944 | + loop -> conn_broken [label = "Receive connbroken request"]; |
3945 | + loop -> conn_warn [label = "Receive connwarn request"]; |
3946 | + conn_broken -> stop [label = "Write CONNBROKEN"]; |
3947 | + conn_warn -> loop [label = "Write CONNWARN"]; |
3948 | + // timeouts |
3949 | + ack_wait -> stop [label = "Elapsed exhange timeout"]; |
3950 | + split_ack_wait -> stop [label = "Elapsed exhange timeout"]; |
3951 | + pong_wait -> stop [label = "Elapsed exhange timeout"]; |
3952 | } |
3953 | |
3954 | === modified file 'protocol/state-diag-session.svg' |
3955 | --- protocol/state-diag-session.svg 2014-01-16 19:37:57 +0000 |
3956 | +++ protocol/state-diag-session.svg 2014-05-29 12:04:32 +0000 |
3957 | @@ -4,139 +4,197 @@ |
3958 | <!-- Generated by graphviz version 2.26.3 (20100126.1600) |
3959 | --> |
3960 | <!-- Title: state_diagram_session Pages: 1 --> |
3961 | -<svg width="864pt" height="208pt" |
3962 | - viewBox="0.00 0.00 864.00 207.94" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> |
3963 | -<g id="graph1" class="graph" transform="scale(0.435923 0.435923) rotate(0) translate(4 473)"> |
3964 | +<svg width="864pt" height="266pt" |
3965 | + viewBox="0.00 0.00 864.00 265.73" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> |
3966 | +<g id="graph1" class="graph" transform="scale(0.367035 0.367035) rotate(0) translate(4 720)"> |
3967 | <title>state_diagram_session</title> |
3968 | -<polygon fill="white" stroke="white" points="-4,5 -4,-473 1979,-473 1979,5 -4,5"/> |
3969 | -<text text-anchor="middle" x="987" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for session</text> |
3970 | +<polygon fill="white" stroke="white" points="-4,5 -4,-720 2351,-720 2351,5 -4,5"/> |
3971 | +<text text-anchor="middle" x="1173" y="-9.4" font-family="Times Roman,serif" font-size="14.00">State diagram for session</text> |
3972 | +<!-- stop --> |
3973 | +<g id="node1" class="node"><title>stop</title> |
3974 | +<ellipse fill="none" stroke="black" cx="2309" cy="-335" rx="32.0813" ry="32.5269"/> |
3975 | +<ellipse fill="none" stroke="black" cx="2309" cy="-335" rx="36.0265" ry="36.5269"/> |
3976 | +<text text-anchor="middle" x="2309" y="-331.4" font-family="Times Roman,serif" font-size="14.00">stop</text> |
3977 | +</g> |
3978 | <!-- start1 --> |
3979 | -<g id="node1" class="node"><title>start1</title> |
3980 | -<ellipse fill="none" stroke="black" cx="42" cy="-294" rx="41.2167" ry="41.7193"/> |
3981 | -<text text-anchor="middle" x="42" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start1</text> |
3982 | +<g id="node2" class="node"><title>start1</title> |
3983 | +<ellipse fill="none" stroke="black" cx="42" cy="-395" rx="41.2167" ry="41.7193"/> |
3984 | +<text text-anchor="middle" x="42" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start1</text> |
3985 | </g> |
3986 | <!-- start2 --> |
3987 | -<g id="node3" class="node"><title>start2</title> |
3988 | -<ellipse fill="none" stroke="black" cx="286" cy="-294" rx="41.2167" ry="41.7193"/> |
3989 | -<text text-anchor="middle" x="286" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start2</text> |
3990 | +<g id="node4" class="node"><title>start2</title> |
3991 | +<ellipse fill="none" stroke="black" cx="286" cy="-395" rx="41.2167" ry="41.7193"/> |
3992 | +<text text-anchor="middle" x="286" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start2</text> |
3993 | </g> |
3994 | <!-- start1->start2 --> |
3995 | <g id="edge2" class="edge"><title>start1->start2</title> |
3996 | -<path fill="none" stroke="black" d="M83.6679,-294C125.213,-294 189.13,-294 233.981,-294"/> |
3997 | -<polygon fill="black" stroke="black" points="234.096,-297.5 244.096,-294 234.096,-290.5 234.096,-297.5"/> |
3998 | -<text text-anchor="middle" x="164" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Read wire version</text> |
3999 | +<path fill="none" stroke="black" d="M83.6679,-395C125.213,-395 189.13,-395 233.981,-395"/> |
4000 | +<polygon fill="black" stroke="black" points="234.096,-398.5 244.096,-395 234.096,-391.5 234.096,-398.5"/> |
4001 | +<text text-anchor="middle" x="164" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Read wire version</text> |
4002 | </g> |
4003 | <!-- start3 --> |
4004 | -<g id="node5" class="node"><title>start3</title> |
4005 | -<ellipse fill="none" stroke="black" cx="516" cy="-294" rx="41.2167" ry="41.7193"/> |
4006 | -<text text-anchor="middle" x="516" y="-290.4" font-family="Times Roman,serif" font-size="14.00">start3</text> |
4007 | +<g id="node6" class="node"><title>start3</title> |
4008 | +<ellipse fill="none" stroke="black" cx="537" cy="-395" rx="41.2167" ry="41.7193"/> |
4009 | +<text text-anchor="middle" x="537" y="-391.4" font-family="Times Roman,serif" font-size="14.00">start3</text> |
4010 | </g> |
4011 | <!-- start2->start3 --> |
4012 | <g id="edge4" class="edge"><title>start2->start3</title> |
4013 | -<path fill="none" stroke="black" d="M327.651,-294C365.959,-294 422.903,-294 464.145,-294"/> |
4014 | -<polygon fill="black" stroke="black" points="464.271,-297.5 474.271,-294 464.271,-290.5 464.271,-297.5"/> |
4015 | -<text text-anchor="middle" x="401" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Read CONNECT</text> |
4016 | +<path fill="none" stroke="black" d="M327.729,-395C370.886,-395 438.364,-395 484.973,-395"/> |
4017 | +<polygon fill="black" stroke="black" points="485.171,-398.5 495.171,-395 485.171,-391.5 485.171,-398.5"/> |
4018 | +<text text-anchor="middle" x="401" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Read CONNECT</text> |
4019 | </g> |
4020 | <!-- loop --> |
4021 | -<g id="node7" class="node"><title>loop</title> |
4022 | -<ellipse fill="none" stroke="black" cx="740" cy="-294" rx="31.8198" ry="31.8198"/> |
4023 | -<text text-anchor="middle" x="740" y="-290.4" font-family="Times Roman,serif" font-size="14.00">loop</text> |
4024 | +<g id="node8" class="node"><title>loop</title> |
4025 | +<ellipse fill="none" stroke="black" cx="790" cy="-395" rx="31.8198" ry="31.8198"/> |
4026 | +<text text-anchor="middle" x="790" y="-391.4" font-family="Times Roman,serif" font-size="14.00">loop</text> |
4027 | </g> |
4028 | <!-- start3->loop --> |
4029 | <g id="edge6" class="edge"><title>start3->loop</title> |
4030 | -<path fill="none" stroke="black" d="M557.608,-294C597.53,-294 657.517,-294 697.677,-294"/> |
4031 | -<polygon fill="black" stroke="black" points="697.687,-297.5 707.687,-294 697.687,-290.5 697.687,-297.5"/> |
4032 | -<text text-anchor="middle" x="633" y="-299.4" font-family="Times Roman,serif" font-size="14.00">Write CONNACK</text> |
4033 | +<path fill="none" stroke="black" d="M578.778,-395C625.49,-395 700.728,-395 747.665,-395"/> |
4034 | +<polygon fill="black" stroke="black" points="747.805,-398.5 757.805,-395 747.805,-391.5 747.805,-398.5"/> |
4035 | +<text text-anchor="middle" x="675" y="-400.4" font-family="Times Roman,serif" font-size="14.00">Write CONNACK</text> |
4036 | </g> |
4037 | <!-- ping --> |
4038 | -<g id="node9" class="node"><title>ping</title> |
4039 | -<ellipse fill="none" stroke="black" cx="1063" cy="-416" rx="32.0265" ry="32.5269"/> |
4040 | -<text text-anchor="middle" x="1063" y="-412.4" font-family="Times Roman,serif" font-size="14.00">ping</text> |
4041 | +<g id="node10" class="node"><title>ping</title> |
4042 | +<ellipse fill="none" stroke="black" cx="1135" cy="-593" rx="32.0265" ry="32.5269"/> |
4043 | +<text text-anchor="middle" x="1135" y="-589.4" font-family="Times Roman,serif" font-size="14.00">ping</text> |
4044 | </g> |
4045 | <!-- loop->ping --> |
4046 | <g id="edge8" class="edge"><title>loop->ping</title> |
4047 | -<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"/> |
4048 | -<polygon fill="black" stroke="black" points="1020.35,-420.33 1030.38,-416.906 1020.4,-413.33 1020.35,-420.33"/> |
4049 | -<text text-anchor="middle" x="881" y="-418.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval</text> |
4050 | +<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"/> |
4051 | +<polygon fill="black" stroke="black" points="1092.15,-591.877 1102.53,-589.734 1093.08,-584.939 1092.15,-591.877"/> |
4052 | +<text text-anchor="middle" x="946" y="-583.4" font-family="Times Roman,serif" font-size="14.00">Elapsed ping interval</text> |
4053 | </g> |
4054 | <!-- broadcast --> |
4055 | -<g id="node11" class="node"><title>broadcast</title> |
4056 | -<ellipse fill="none" stroke="black" cx="1063" cy="-200" rx="58.1882" ry="58.6899"/> |
4057 | -<text text-anchor="middle" x="1063" y="-196.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text> |
4058 | +<g id="node12" class="node"><title>broadcast</title> |
4059 | +<ellipse fill="none" stroke="black" cx="1135" cy="-281" rx="58.1882" ry="58.6899"/> |
4060 | +<text text-anchor="middle" x="1135" y="-277.4" font-family="Times Roman,serif" font-size="14.00">broadcast</text> |
4061 | </g> |
4062 | <!-- loop->broadcast --> |
4063 | <g id="edge10" class="edge"><title>loop->broadcast</title> |
4064 | -<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"/> |
4065 | -<polygon fill="black" stroke="black" points="995.396,-212.238 1004.75,-207.269 994.34,-205.318 995.396,-212.238"/> |
4066 | -<text text-anchor="middle" x="881" y="-267.4" font-family="Times Roman,serif" font-size="14.00">Receive broadcast request</text> |
4067 | +<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"/> |
4068 | +<polygon fill="black" stroke="black" points="1066.94,-289.319 1076.53,-284.811 1066.22,-282.355 1066.94,-289.319"/> |
4069 | +<text text-anchor="middle" x="946" y="-348.4" font-family="Times Roman,serif" font-size="14.00">Receive broadcast request</text> |
4070 | +</g> |
4071 | +<!-- conn_broken --> |
4072 | +<g id="node26" class="node"><title>conn_broken</title> |
4073 | +<ellipse fill="none" stroke="black" cx="1361" cy="-99" rx="73.0388" ry="73.5391"/> |
4074 | +<text text-anchor="middle" x="1361" y="-95.4" font-family="Times Roman,serif" font-size="14.00">conn_broken</text> |
4075 | +</g> |
4076 | +<!-- loop->conn_broken --> |
4077 | +<g id="edge28" class="edge"><title>loop->conn_broken</title> |
4078 | +<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"/> |
4079 | +<polygon fill="black" stroke="black" points="1278.34,-85.2954 1288.79,-83.4998 1279.5,-78.392 1278.34,-85.2954"/> |
4080 | +<text text-anchor="middle" x="946" y="-160.4" font-family="Times Roman,serif" font-size="14.00">Receive connbroken request</text> |
4081 | +</g> |
4082 | +<!-- conn_warn --> |
4083 | +<g id="node28" class="node"><title>conn_warn</title> |
4084 | +<ellipse fill="none" stroke="black" cx="1135" cy="-477" rx="65.7609" ry="65.7609"/> |
4085 | +<text text-anchor="middle" x="1135" y="-473.4" font-family="Times Roman,serif" font-size="14.00">conn_warn</text> |
4086 | +</g> |
4087 | +<!-- loop->conn_warn --> |
4088 | +<g id="edge30" class="edge"><title>loop->conn_warn</title> |
4089 | +<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"/> |
4090 | +<polygon fill="black" stroke="black" points="1058.71,-479.855 1068.85,-476.786 1059.01,-472.861 1058.71,-479.855"/> |
4091 | +<text text-anchor="middle" x="946" y="-480.4" font-family="Times Roman,serif" font-size="14.00">Receive connwarn request</text> |
4092 | </g> |
4093 | <!-- pong_wait --> |
4094 | -<g id="node13" class="node"><title>pong_wait</title> |
4095 | -<ellipse fill="none" stroke="black" cx="1526" cy="-406" rx="62.9325" ry="62.9325"/> |
4096 | -<text text-anchor="middle" x="1526" y="-402.4" font-family="Times Roman,serif" font-size="14.00">pong_wait</text> |
4097 | +<g id="node14" class="node"><title>pong_wait</title> |
4098 | +<ellipse fill="none" stroke="black" cx="537" cy="-653" rx="62.9325" ry="62.9325"/> |
4099 | +<text text-anchor="middle" x="537" y="-649.4" font-family="Times Roman,serif" font-size="14.00">pong_wait</text> |
4100 | </g> |
4101 | <!-- ping->pong_wait --> |
4102 | <g id="edge12" class="edge"><title>ping->pong_wait</title> |
4103 | -<path fill="none" stroke="black" d="M1095.56,-415.297C1169.19,-413.707 1350.04,-409.8 1452.36,-407.591"/> |
4104 | -<polygon fill="black" stroke="black" points="1452.69,-411.084 1462.61,-407.369 1452.54,-404.086 1452.69,-411.084"/> |
4105 | -<text text-anchor="middle" x="1289" y="-418.4" font-family="Times Roman,serif" font-size="14.00">Write PING</text> |
4106 | +<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"/> |
4107 | +<polygon fill="black" stroke="black" points="609.573,-662.637 599.214,-664.858 608.697,-669.582 609.573,-662.637"/> |
4108 | +<text text-anchor="middle" x="790" y="-670.4" font-family="Times Roman,serif" font-size="14.00">Write PING</text> |
4109 | </g> |
4110 | <!-- ack_wait --> |
4111 | -<g id="node15" class="node"><title>ack_wait</title> |
4112 | -<ellipse fill="none" stroke="black" cx="1526" cy="-269" rx="55.1543" ry="55.1543"/> |
4113 | -<text text-anchor="middle" x="1526" y="-265.4" font-family="Times Roman,serif" font-size="14.00">ack_wait</text> |
4114 | +<g id="node16" class="node"><title>ack_wait</title> |
4115 | +<ellipse fill="none" stroke="black" cx="1598" cy="-373" rx="55.1543" ry="55.1543"/> |
4116 | +<text text-anchor="middle" x="1598" y="-369.4" font-family="Times Roman,serif" font-size="14.00">ack_wait</text> |
4117 | </g> |
4118 | <!-- broadcast->ack_wait --> |
4119 | <g id="edge14" class="edge"><title>broadcast->ack_wait</title> |
4120 | -<path fill="none" stroke="black" d="M1121.17,-208.669C1207.93,-221.599 1370.7,-245.856 1461.17,-259.339"/> |
4121 | -<polygon fill="black" stroke="black" points="1460.9,-262.837 1471.3,-260.849 1461.93,-255.913 1460.9,-262.837"/> |
4122 | -<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> |
4123 | +<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"/> |
4124 | +<polygon fill="black" stroke="black" points="1534.79,-356.813 1545.37,-356.254 1536.75,-350.093 1534.79,-356.813"/> |
4125 | +<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> |
4126 | </g> |
4127 | <!-- split_broadcast --> |
4128 | -<g id="node17" class="node"><title>split_broadcast</title> |
4129 | -<ellipse fill="none" stroke="black" cx="1526" cy="-110" rx="84.1457" ry="84.1457"/> |
4130 | -<text text-anchor="middle" x="1526" y="-106.4" font-family="Times Roman,serif" font-size="14.00">split_broadcast</text> |
4131 | +<g id="node18" class="node"><title>split_broadcast</title> |
4132 | +<ellipse fill="none" stroke="black" cx="1598" cy="-216" rx="84.1457" ry="84.1457"/> |
4133 | +<text text-anchor="middle" x="1598" y="-212.4" font-family="Times Roman,serif" font-size="14.00">split_broadcast</text> |
4134 | </g> |
4135 | <!-- broadcast->split_broadcast --> |
4136 | <g id="edge16" class="edge"><title>broadcast->split_broadcast</title> |
4137 | -<path fill="none" stroke="black" d="M1120.7,-188.783C1199.06,-173.553 1340.01,-146.154 1433.29,-128.021"/> |
4138 | -<polygon fill="black" stroke="black" points="1434.15,-131.421 1443.29,-126.077 1432.81,-124.549 1434.15,-131.421"/> |
4139 | -<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> |
4140 | +<path fill="none" stroke="black" d="M1193.17,-272.834C1271.44,-261.846 1411.56,-242.174 1504.66,-229.104"/> |
4141 | +<polygon fill="black" stroke="black" points="1505.23,-232.558 1514.65,-227.702 1504.26,-225.626 1505.23,-232.558"/> |
4142 | +<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> |
4143 | +</g> |
4144 | +<!-- pong_wait->stop --> |
4145 | +<g id="edge40" class="edge"><title>pong_wait->stop</title> |
4146 | +<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"/> |
4147 | +<polygon fill="black" stroke="black" points="2292.5,-378.343 2293.84,-367.834 2286.24,-375.212 2292.5,-378.343"/> |
4148 | +<text text-anchor="middle" x="1361" y="-658.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text> |
4149 | </g> |
4150 | <!-- pong_wait->loop --> |
4151 | <g id="edge18" class="edge"><title>pong_wait->loop</title> |
4152 | -<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"/> |
4153 | -<polygon fill="black" stroke="black" points="781.898,-299.011 771.42,-300.582 780.59,-305.888 781.898,-299.011"/> |
4154 | -<text text-anchor="middle" x="1063" y="-359.4" font-family="Times Roman,serif" font-size="14.00">Read PONG</text> |
4155 | +<path fill="none" stroke="black" d="M581.359,-607.765C632.774,-555.333 716.085,-470.376 760.273,-425.314"/> |
4156 | +<polygon fill="black" stroke="black" points="762.774,-427.763 767.277,-418.172 757.776,-422.862 762.774,-427.763"/> |
4157 | +<text text-anchor="middle" x="675" y="-574.4" font-family="Times Roman,serif" font-size="14.00">Read PONG</text> |
4158 | +</g> |
4159 | +<!-- ack_wait->stop --> |
4160 | +<g id="edge36" class="edge"><title>ack_wait->stop</title> |
4161 | +<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"/> |
4162 | +<polygon fill="black" stroke="black" points="2263.58,-346.389 2272.99,-341.517 2262.59,-339.459 2263.58,-346.389"/> |
4163 | +<text text-anchor="middle" x="1972" y="-373.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text> |
4164 | </g> |
4165 | <!-- ack_wait->loop --> |
4166 | <g id="edge20" class="edge"><title>ack_wait->loop</title> |
4167 | -<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"/> |
4168 | -<polygon fill="black" stroke="black" points="781.977,-289.56 772.059,-293.288 782.136,-296.558 781.977,-289.56"/> |
4169 | -<text text-anchor="middle" x="1063" y="-292.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text> |
4170 | +<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"/> |
4171 | +<polygon fill="black" stroke="black" points="831.485,-386.79 821.871,-391.242 832.163,-393.757 831.485,-386.79"/> |
4172 | +<text text-anchor="middle" x="1135" y="-389.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text> |
4173 | </g> |
4174 | <!-- split_broadcast->loop --> |
4175 | <g id="edge26" class="edge"><title>split_broadcast->loop</title> |
4176 | -<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"/> |
4177 | -<polygon fill="black" stroke="black" points="753.014,-253.718 751.785,-264.241 759.308,-256.781 753.014,-253.718"/> |
4178 | -<text text-anchor="middle" x="1063" y="-120.4" font-family="Times Roman,serif" font-size="14.00">All split msgs written</text> |
4179 | +<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"/> |
4180 | +<polygon fill="black" stroke="black" points="800.027,-353.699 799.658,-364.287 806.549,-356.24 800.027,-353.699"/> |
4181 | +<text text-anchor="middle" x="1135" y="-201.4" font-family="Times Roman,serif" font-size="14.00">All split msgs written</text> |
4182 | </g> |
4183 | <!-- split_ack_wait --> |
4184 | -<g id="node21" class="node"><title>split_ack_wait</title> |
4185 | -<ellipse fill="none" stroke="black" cx="1893" cy="-110" rx="80.1095" ry="80.6102"/> |
4186 | -<text text-anchor="middle" x="1893" y="-106.4" font-family="Times Roman,serif" font-size="14.00">split_ack_wait</text> |
4187 | +<g id="node22" class="node"><title>split_ack_wait</title> |
4188 | +<ellipse fill="none" stroke="black" cx="1972" cy="-257" rx="80.1095" ry="80.6102"/> |
4189 | +<text text-anchor="middle" x="1972" y="-253.4" font-family="Times Roman,serif" font-size="14.00">split_ack_wait</text> |
4190 | </g> |
4191 | <!-- split_broadcast->split_ack_wait --> |
4192 | <g id="edge22" class="edge"><title>split_broadcast->split_ack_wait</title> |
4193 | -<path fill="none" stroke="black" d="M1610.2,-110C1667.61,-110 1743.59,-110 1802.33,-110"/> |
4194 | -<polygon fill="black" stroke="black" points="1802.35,-113.5 1812.35,-110 1802.34,-106.5 1802.35,-113.5"/> |
4195 | -<text text-anchor="middle" x="1711" y="-115.4" font-family="Times Roman,serif" font-size="14.00">Write split BROADCAST</text> |
4196 | +<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"/> |
4197 | +<polygon fill="black" stroke="black" points="1881.23,-255.523 1891.43,-252.676 1881.68,-248.537 1881.23,-255.523"/> |
4198 | +<text text-anchor="middle" x="1783" y="-256.4" font-family="Times Roman,serif" font-size="14.00">Write split BROADCAST</text> |
4199 | +</g> |
4200 | +<!-- split_ack_wait->stop --> |
4201 | +<g id="edge38" class="edge"><title>split_ack_wait->stop</title> |
4202 | +<path fill="none" stroke="black" d="M2050.59,-275.189C2116.55,-290.456 2208.51,-311.74 2263.08,-324.372"/> |
4203 | +<polygon fill="black" stroke="black" points="2262.62,-327.857 2273.15,-326.702 2264.2,-321.037 2262.62,-327.857"/> |
4204 | +<text text-anchor="middle" x="2166" y="-327.4" font-family="Times Roman,serif" font-size="14.00">Elapsed exhange timeout</text> |
4205 | </g> |
4206 | <!-- split_ack_wait->split_broadcast --> |
4207 | <g id="edge24" class="edge"><title>split_ack_wait->split_broadcast</title> |
4208 | -<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"/> |
4209 | -<polygon fill="black" stroke="black" points="1617.23,-85.8043 1607.87,-90.7602 1618.28,-92.7256 1617.23,-85.8043"/> |
4210 | -<text text-anchor="middle" x="1711" y="-93.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text> |
4211 | +<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"/> |
4212 | +<polygon fill="black" stroke="black" points="1691.25,-202.053 1681.52,-206.256 1691.74,-209.035 1691.25,-202.053"/> |
4213 | +<text text-anchor="middle" x="1783" y="-218.4" font-family="Times Roman,serif" font-size="14.00">Read ACK</text> |
4214 | +</g> |
4215 | +<!-- conn_broken->stop --> |
4216 | +<g id="edge32" class="edge"><title>conn_broken->stop</title> |
4217 | +<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"/> |
4218 | +<polygon fill="black" stroke="black" points="2275.42,-302.808 2284.91,-307.517 2280.43,-297.917 2275.42,-302.808"/> |
4219 | +<text text-anchor="middle" x="1783" y="-127.4" font-family="Times Roman,serif" font-size="14.00">Write CONNBROKEN</text> |
4220 | +</g> |
4221 | +<!-- conn_warn->loop --> |
4222 | +<g id="edge34" class="edge"><title>conn_warn->loop</title> |
4223 | +<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"/> |
4224 | +<polygon fill="black" stroke="black" points="831.758,-388.346 821.959,-392.373 832.131,-395.337 831.758,-388.346"/> |
4225 | +<text text-anchor="middle" x="946" y="-420.4" font-family="Times Roman,serif" font-size="14.00">Write CONNWARN</text> |
4226 | </g> |
4227 | </g> |
4228 | </svg> |
4229 | |
4230 | === added file 'scripts/broadcast' |
4231 | --- scripts/broadcast 1970-01-01 00:00:00 +0000 |
4232 | +++ scripts/broadcast 2014-05-29 12:04:32 +0000 |
4233 | @@ -0,0 +1,59 @@ |
4234 | +#!/usr/bin/python |
4235 | +""" |
4236 | +send broadcast to channel with payload data |
4237 | +""" |
4238 | +import argparse |
4239 | +import json |
4240 | +import requests |
4241 | +import requests.auth |
4242 | +import datetime |
4243 | +import sys |
4244 | + |
4245 | + |
4246 | +def main(): |
4247 | + parser = argparse.ArgumentParser(description=__doc__) |
4248 | + parser.add_argument('channel', nargs=1) |
4249 | + parser.add_argument('data', nargs=1) |
4250 | + parser.add_argument('-H', '--host', |
4251 | + help="host:port (default: %(default)s)", |
4252 | + default="localhost:8080") |
4253 | + parser.add_argument('-e', '--expire', |
4254 | + help="expire after the given amount of time, " |
4255 | + "use 'd' suffix for days, 's' for seconds" |
4256 | + " (default: %(default)s)", default="1d") |
4257 | + parser.add_argument('--no-https', action='store_true', default=False) |
4258 | + parser.add_argument('--insecure', action='store_true', default=False, |
4259 | + help="don't check host/certs with https") |
4260 | + parser.add_argument('-u', '--user', default="") |
4261 | + parser.add_argument('-p', '--password', default="") |
4262 | + args = parser.parse_args() |
4263 | + expire_on = datetime.datetime.utcnow() |
4264 | + ex = args.expire |
4265 | + if ex.endswith('d'): |
4266 | + delta = datetime.timedelta(days=int(ex[:-1])) |
4267 | + elif ex.endswith('s'): |
4268 | + delta = datetime.timedelta(seconds=int(ex[:-1])) |
4269 | + else: |
4270 | + print >>sys.stderr, "unknown --expire suffix:", ex |
4271 | + sys.exit(1) |
4272 | + expire_on += delta |
4273 | + scheme = 'https' |
4274 | + if args.no_https: |
4275 | + scheme = 'http' |
4276 | + url = "%s://%s/broadcast" % (scheme, args.host) |
4277 | + body = { |
4278 | + 'channel': args.channel[0], |
4279 | + 'data': json.loads(args.data[0]), |
4280 | + 'expire_on': expire_on.replace(microsecond=0).isoformat()+"Z" |
4281 | + } |
4282 | + xauth = {} |
4283 | + if args.user and args.password: |
4284 | + xauth = {'auth': requests.auth.HTTPBasicAuth(args.user, args.password)} |
4285 | + headers = {'Content-Type': 'application/json'} |
4286 | + r = requests.post(url, data=json.dumps(body), headers=headers, |
4287 | + verify=not args.insecure, **xauth) |
4288 | + print r.status_code |
4289 | + print r.text |
4290 | + |
4291 | +if __name__ == '__main__': |
4292 | + main() |
4293 | |
4294 | === added file 'scripts/deps.sh' |
4295 | --- scripts/deps.sh 1970-01-01 00:00:00 +0000 |
4296 | +++ scripts/deps.sh 2014-05-29 12:04:32 +0000 |
4297 | @@ -0,0 +1,28 @@ |
4298 | +#!/bin/sh |
4299 | +set -eu |
4300 | + |
4301 | +PROJECT=launchpad.net/ubuntu-push |
4302 | + |
4303 | +mktpl () { |
4304 | + for f in GoFiles CgoFiles; do |
4305 | + echo '{{join .'$f' "\\n"}}' |
4306 | + done |
4307 | +} |
4308 | + |
4309 | +directs () { |
4310 | + go list -f "$(mktpl)" $1 | sed -e "s|^|$1|" |
4311 | +} |
4312 | + |
4313 | +indirects () { |
4314 | + for i in $(go list -f '{{join .Deps "\n"}}' $1 | grep ^$PROJECT ); do |
4315 | + directs $i/ |
4316 | + done |
4317 | + wait |
4318 | +} |
4319 | + |
4320 | +norm () { |
4321 | + tr "\n" " " | sed -r -e "s|$PROJECT/?||g" -e 's/ *$//' |
4322 | +} |
4323 | + |
4324 | +out=".$1.deps" |
4325 | +( echo -n "${1%.go} ${out}: "; indirects $(echo $1 | norm) | norm ) > "$out" |
4326 | |
4327 | === added file 'scripts/unicast' |
4328 | --- scripts/unicast 1970-01-01 00:00:00 +0000 |
4329 | +++ scripts/unicast 2014-05-29 12:04:32 +0000 |
4330 | @@ -0,0 +1,65 @@ |
4331 | +#!/usr/bin/python |
4332 | +""" |
4333 | +send broadcast to channel with payload data |
4334 | +""" |
4335 | +import argparse |
4336 | +import json |
4337 | +import requests |
4338 | +import requests.auth |
4339 | +import datetime |
4340 | +import sys |
4341 | + |
4342 | + |
4343 | +def main(): |
4344 | + parser = argparse.ArgumentParser(description=__doc__) |
4345 | + parser.add_argument('reg', nargs=1) # userid:deviceid or reg |
4346 | + parser.add_argument('appid', nargs=1) |
4347 | + parser.add_argument('data', nargs=1) |
4348 | + parser.add_argument('-H', '--host', |
4349 | + help="host:port (default: %(default)s)", |
4350 | + default="localhost:8080") |
4351 | + parser.add_argument('-e', '--expire', |
4352 | + help="expire after the given amount of time, " |
4353 | + "use 'd' suffix for days, 's' for seconds" |
4354 | + " (default: %(default)s)", default="1d") |
4355 | + parser.add_argument('--no-https', action='store_true', default=False) |
4356 | + parser.add_argument('--insecure', action='store_true', default=False, |
4357 | + help="don't check host/certs with https") |
4358 | + parser.add_argument('-u', '--user', default="") |
4359 | + parser.add_argument('-p', '--password', default="") |
4360 | + args = parser.parse_args() |
4361 | + expire_on = datetime.datetime.utcnow() |
4362 | + ex = args.expire |
4363 | + if ex.endswith('d'): |
4364 | + delta = datetime.timedelta(days=int(ex[:-1])) |
4365 | + elif ex.endswith('s'): |
4366 | + delta = datetime.timedelta(seconds=int(ex[:-1])) |
4367 | + else: |
4368 | + print >>sys.stderr, "unknown --expire suffix:", ex |
4369 | + sys.exit(1) |
4370 | + expire_on += delta |
4371 | + scheme = 'https' |
4372 | + if args.no_https: |
4373 | + scheme = 'http' |
4374 | + url = "%s://%s/notify" % (scheme, args.host) |
4375 | + body = { |
4376 | + 'appid': args.appid[0], |
4377 | + 'data': json.loads(args.data[0]), |
4378 | + 'expire_on': expire_on.replace(microsecond=0).isoformat()+"Z" |
4379 | + } |
4380 | + reg = args.reg[0] |
4381 | + if ':' in reg: |
4382 | + userid, devid = reg.split(':', 1) |
4383 | + body['userid'] = userid |
4384 | + body['deviceid'] = devid |
4385 | + xauth = {} |
4386 | + if args.user and args.password: |
4387 | + xauth = {'auth': requests.auth.HTTPBasicAuth(args.user, args.password)} |
4388 | + headers = {'Content-Type': 'application/json'} |
4389 | + r = requests.post(url, data=json.dumps(body), headers=headers, |
4390 | + verify=not args.insecure, **xauth) |
4391 | + print r.status_code |
4392 | + print r.text |
4393 | + |
4394 | +if __name__ == '__main__': |
4395 | + main() |
4396 | |
4397 | === modified file 'server/acceptance/acceptance_test.go' |
4398 | --- server/acceptance/acceptance_test.go 2014-04-07 19:39:19 +0000 |
4399 | +++ server/acceptance/acceptance_test.go 2014-05-29 12:04:32 +0000 |
4400 | @@ -59,3 +59,6 @@ |
4401 | |
4402 | // broadcast |
4403 | var _ = Suite(&suites.BroadcastAcceptanceSuite{suites.AcceptanceSuite{StartServer: StartServer}}) |
4404 | + |
4405 | +// unicast |
4406 | +var _ = Suite(&suites.UnicastAcceptanceSuite{suites.AcceptanceSuite{StartServer: StartServer}, nil}) |
4407 | |
4408 | === modified file 'server/acceptance/acceptanceclient.go' |
4409 | --- server/acceptance/acceptanceclient.go 2014-04-09 19:30:53 +0000 |
4410 | +++ server/acceptance/acceptanceclient.go 2014-05-29 12:04:32 +0000 |
4411 | @@ -24,6 +24,7 @@ |
4412 | "errors" |
4413 | "fmt" |
4414 | "net" |
4415 | + "strings" |
4416 | "time" |
4417 | |
4418 | "launchpad.net/ubuntu-push/protocol" |
4419 | @@ -44,6 +45,7 @@ |
4420 | Levels map[string]int64 |
4421 | Insecure bool // don't verify certs |
4422 | Prefix string // prefix for events |
4423 | + Auth string |
4424 | // connection |
4425 | Connection net.Conn |
4426 | } |
4427 | @@ -73,6 +75,7 @@ |
4428 | Type string `json:"T"` |
4429 | protocol.BroadcastMsg |
4430 | protocol.NotificationsMsg |
4431 | + protocol.ConnWarnMsg |
4432 | } |
4433 | |
4434 | // Run the session with the server, emits a stream of events. |
4435 | @@ -93,6 +96,7 @@ |
4436 | "device": sess.Model, |
4437 | "channel": sess.ImageChannel, |
4438 | }, |
4439 | + Authorization: sess.Auth, |
4440 | }) |
4441 | if err != nil { |
4442 | return err |
4443 | @@ -125,9 +129,24 @@ |
4444 | if sess.ReportPings { |
4445 | events <- sess.Prefix + "ping" |
4446 | } |
4447 | + case "notifications": |
4448 | + conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) |
4449 | + err := proto.WriteMessage(protocol.AckMsg{Type: "ack"}) |
4450 | + if err != nil { |
4451 | + return err |
4452 | + } |
4453 | + parts := make([]string, len(recv.Notifications)) |
4454 | + for i, notif := range recv.Notifications { |
4455 | + pack, err := json.Marshal(¬if.Payload) |
4456 | + if err != nil { |
4457 | + return err |
4458 | + } |
4459 | + parts[i] = fmt.Sprintf("app:%v payload:%s;", notif.AppId, pack) |
4460 | + } |
4461 | + events <- fmt.Sprintf("%sunicast %s", sess.Prefix, strings.Join(parts, " ")) |
4462 | case "broadcast": |
4463 | conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) |
4464 | - err := proto.WriteMessage(protocol.PingPongMsg{Type: "ack"}) |
4465 | + err := proto.WriteMessage(protocol.AckMsg{Type: "ack"}) |
4466 | if err != nil { |
4467 | return err |
4468 | } |
4469 | @@ -136,6 +155,8 @@ |
4470 | return err |
4471 | } |
4472 | events <- fmt.Sprintf("%sbroadcast chan:%v app:%v topLevel:%d payloads:%s", sess.Prefix, recv.ChanId, recv.AppId, recv.TopLevel, pack) |
4473 | + case "connwarn": |
4474 | + events <- fmt.Sprintf("%sconnwarn %s", sess.Prefix, recv.Reason) |
4475 | } |
4476 | } |
4477 | return nil |
4478 | |
4479 | === modified file 'server/acceptance/cmd/acceptanceclient.go' |
4480 | --- server/acceptance/cmd/acceptanceclient.go 2014-04-14 14:54:14 +0000 |
4481 | +++ server/acceptance/cmd/acceptanceclient.go 2014-05-29 12:04:32 +0000 |
4482 | @@ -22,7 +22,9 @@ |
4483 | "fmt" |
4484 | "log" |
4485 | "os" |
4486 | + "os/exec" |
4487 | "path/filepath" |
4488 | + "strings" |
4489 | |
4490 | "launchpad.net/ubuntu-push/config" |
4491 | "launchpad.net/ubuntu-push/server/acceptance" |
4492 | @@ -40,12 +42,13 @@ |
4493 | ExchangeTimeout config.ConfigTimeDuration `json:"exchange_timeout"` |
4494 | // server connection config |
4495 | Addr config.ConfigHostPort |
4496 | - CertPEMFile string `json:"cert_pem_file"` |
4497 | + CertPEMFile string `json:"cert_pem_file"` |
4498 | + AuthHelper []string `json:"auth_helper"` |
4499 | } |
4500 | |
4501 | func main() { |
4502 | flag.Usage = func() { |
4503 | - fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] <config.json> <device id>\n") |
4504 | + fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] <device id>\n") |
4505 | flag.PrintDefaults() |
4506 | } |
4507 | missingArg := func(what string) { |
4508 | @@ -53,28 +56,24 @@ |
4509 | flag.Usage() |
4510 | os.Exit(2) |
4511 | } |
4512 | - flag.Parse() |
4513 | + cfg := &configuration{} |
4514 | + err := config.ReadFilesDefaults(cfg, map[string]interface{}{ |
4515 | + "exchange_timeout": "5s", |
4516 | + "cert_pem_file": "", |
4517 | + "auth_helper": []string{}, |
4518 | + }, "<flags>") |
4519 | + if err != nil { |
4520 | + log.Fatalf("reading config: %v", err) |
4521 | + } |
4522 | narg := flag.NArg() |
4523 | switch { |
4524 | case narg < 1: |
4525 | - missingArg("config file") |
4526 | - case narg < 2: |
4527 | missingArg("device-id") |
4528 | } |
4529 | - configFName := flag.Arg(0) |
4530 | - f, err := os.Open(configFName) |
4531 | - if err != nil { |
4532 | - log.Fatalf("reading config: %v", err) |
4533 | - } |
4534 | - cfg := &configuration{} |
4535 | - err = config.ReadConfig(f, cfg) |
4536 | - if err != nil { |
4537 | - log.Fatalf("reading config: %v", err) |
4538 | - } |
4539 | session := &acceptance.ClientSession{ |
4540 | ExchangeTimeout: cfg.ExchangeTimeout.TimeDuration(), |
4541 | ServerAddr: cfg.Addr.HostPort(), |
4542 | - DeviceId: flag.Arg(1), |
4543 | + DeviceId: flag.Arg(0), |
4544 | // flags |
4545 | Model: *deviceModel, |
4546 | ImageChannel: *imageChannel, |
4547 | @@ -82,12 +81,21 @@ |
4548 | Insecure: *insecureFlag, |
4549 | } |
4550 | log.Printf("with: %#v", session) |
4551 | - if !*insecureFlag { |
4552 | - session.CertPEMBlock, err = config.LoadFile(cfg.CertPEMFile, filepath.Dir(configFName)) |
4553 | + if !*insecureFlag && cfg.CertPEMFile != "" { |
4554 | + cfgDir := filepath.Dir(flag.Lookup("cfg@").Value.String()) |
4555 | + log.Printf("cert: %v relToDir: %v", cfg.CertPEMFile, cfgDir) |
4556 | + session.CertPEMBlock, err = config.LoadFile(cfg.CertPEMFile, cfgDir) |
4557 | if err != nil { |
4558 | log.Fatalf("reading CertPEMFile: %v", err) |
4559 | } |
4560 | } |
4561 | + if len(cfg.AuthHelper) != 0 { |
4562 | + auth, err := exec.Command(cfg.AuthHelper[0], cfg.AuthHelper[1:]...).Output() |
4563 | + if err != nil { |
4564 | + log.Fatalf("auth helper: %v", err) |
4565 | + } |
4566 | + session.Auth = strings.TrimSpace(string(auth)) |
4567 | + } |
4568 | err = session.Dial() |
4569 | if err != nil { |
4570 | log.Fatalln(err) |
4571 | |
4572 | === modified file 'server/acceptance/suites/broadcast.go' |
4573 | --- server/acceptance/suites/broadcast.go 2014-04-07 19:39:19 +0000 |
4574 | +++ server/acceptance/suites/broadcast.go 2014-05-29 12:04:32 +0000 |
4575 | @@ -29,14 +29,11 @@ |
4576 | "launchpad.net/ubuntu-push/server/api" |
4577 | ) |
4578 | |
4579 | -// BroadCastAcceptanceSuite has tests about broadcast. |
4580 | +// BroadcastAcceptanceSuite has tests about broadcast. |
4581 | type BroadcastAcceptanceSuite struct { |
4582 | AcceptanceSuite |
4583 | } |
4584 | |
4585 | -// Long after the end of the tests. |
4586 | -var future = time.Now().Add(9 * time.Hour).Format(time.RFC3339) |
4587 | - |
4588 | func (s *BroadcastAcceptanceSuite) TestBroadcastToConnected(c *C) { |
4589 | events, errCh, stop := s.StartClient(c, "DEVB", nil) |
4590 | got, err := s.PostRequest("/broadcast", &api.Broadcast{ |
4591 | @@ -91,7 +88,7 @@ |
4592 | c.Check(len(errCh), Equals, 0) |
4593 | } |
4594 | |
4595 | -func (s *BroadcastAcceptanceSuite) TestBroadcasLargeNeedsSplitting(c *C) { |
4596 | +func (s *BroadcastAcceptanceSuite) TestBroadcastLargeNeedsSplitting(c *C) { |
4597 | // send bunch of broadcasts that will be pending |
4598 | payloadFmt := fmt.Sprintf(`{"img1/m1":%%d,"bloat":"%s"}`, strings.Repeat("x", 1024*2)) |
4599 | for i := 0; i < 32; i++ { |
4600 | @@ -106,8 +103,17 @@ |
4601 | |
4602 | events, errCh, stop := s.StartClient(c, "DEVC", nil) |
4603 | // gettting pending on connect |
4604 | - c.Check(NextEvent(events, errCh), Matches, `broadcast chan:0 app: topLevel:30 payloads:\[{"img1/m1":0,.*`) |
4605 | - c.Check(NextEvent(events, errCh), Matches, `broadcast chan:0 app: topLevel:32 payloads:\[.*`) |
4606 | + n := 0 |
4607 | + for { |
4608 | + evt := NextEvent(events, errCh) |
4609 | + c.Check(evt, Matches, "broadcast chan:0 .*") |
4610 | + n += 1 |
4611 | + if strings.Contains(evt, "topLevel:32") { |
4612 | + break |
4613 | + } |
4614 | + } |
4615 | + // was split |
4616 | + c.Check(n > 1, Equals, true) |
4617 | stop() |
4618 | c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) |
4619 | c.Check(len(errCh), Equals, 0) |
4620 | @@ -265,7 +271,11 @@ |
4621 | |
4622 | func (s *BroadcastAcceptanceSuite) TestGetHosts(c *C) { |
4623 | gh := gethosts.New("", s.ServerAPIURL+"/delivery-hosts", 2*time.Second) |
4624 | - hosts, err := gh.Get() |
4625 | + host, err := gh.Get() |
4626 | c.Assert(err, IsNil) |
4627 | - c.Check(hosts, DeepEquals, []string{s.ServerAddr}) |
4628 | + expected := &gethosts.Host{ |
4629 | + Domain: "localhost", |
4630 | + Hosts: []string{s.ServerAddr}, |
4631 | + } |
4632 | + c.Check(host, DeepEquals, expected) |
4633 | } |
4634 | |
4635 | === modified file 'server/acceptance/suites/suite.go' |
4636 | --- server/acceptance/suites/suite.go 2014-04-03 16:47:47 +0000 |
4637 | +++ server/acceptance/suites/suite.go 2014-05-29 12:04:32 +0000 |
4638 | @@ -44,10 +44,19 @@ |
4639 | |
4640 | // Start a client. |
4641 | func (h *ServerHandle) StartClient(c *C, devId string, levels map[string]int64) (events <-chan string, errorCh <-chan error, stop func()) { |
4642 | + return h.StartClientAuth(c, devId, levels, "") |
4643 | +} |
4644 | + |
4645 | +// Start a client with auth. |
4646 | +func (h *ServerHandle) StartClientAuth(c *C, devId string, levels map[string]int64, auth string) (events <-chan string, errorCh <-chan error, stop func()) { |
4647 | errCh := make(chan error, 1) |
4648 | cliEvents := make(chan string, 10) |
4649 | sess := testClientSession(h.ServerAddr, devId, "m1", "img1", false) |
4650 | sess.Levels = levels |
4651 | + sess.Auth = auth |
4652 | + if auth != "" { |
4653 | + sess.ExchangeTimeout = 5 * time.Second |
4654 | + } |
4655 | err := sess.Dial() |
4656 | c.Assert(err, IsNil) |
4657 | clientShutdown := make(chan bool, 1) // abused as an atomic flag |
4658 | @@ -186,3 +195,6 @@ |
4659 | } |
4660 | return |
4661 | } |
4662 | + |
4663 | +// Long after the end of the tests. |
4664 | +var future = time.Now().Add(9 * time.Hour).Format(time.RFC3339) |
4665 | |
4666 | === added file 'server/acceptance/suites/unicast.go' |
4667 | --- server/acceptance/suites/unicast.go 1970-01-01 00:00:00 +0000 |
4668 | +++ server/acceptance/suites/unicast.go 2014-05-29 12:04:32 +0000 |
4669 | @@ -0,0 +1,149 @@ |
4670 | +/* |
4671 | + Copyright 2013-2014 Canonical Ltd. |
4672 | + |
4673 | + This program is free software: you can redistribute it and/or modify it |
4674 | + under the terms of the GNU General Public License version 3, as published |
4675 | + by the Free Software Foundation. |
4676 | + |
4677 | + This program is distributed in the hope that it will be useful, but |
4678 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
4679 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4680 | + PURPOSE. See the GNU General Public License for more details. |
4681 | + |
4682 | + You should have received a copy of the GNU General Public License along |
4683 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
4684 | +*/ |
4685 | + |
4686 | +package suites |
4687 | + |
4688 | +import ( |
4689 | + "encoding/json" |
4690 | + "fmt" |
4691 | + "strings" |
4692 | + |
4693 | + . "launchpad.net/gocheck" |
4694 | + |
4695 | + "launchpad.net/ubuntu-push/server/api" |
4696 | +) |
4697 | + |
4698 | +// UnicastAcceptanceSuite has tests about unicast. |
4699 | +type UnicastAcceptanceSuite struct { |
4700 | + AcceptanceSuite |
4701 | + AssociatedAuth func(string) (string, string) |
4702 | +} |
4703 | + |
4704 | +func (s *UnicastAcceptanceSuite) associatedAuth(deviceId string) (userId string, auth string) { |
4705 | + if s.AssociatedAuth != nil { |
4706 | + return s.AssociatedAuth(deviceId) |
4707 | + } |
4708 | + return deviceId, "" |
4709 | +} |
4710 | + |
4711 | +func (s *UnicastAcceptanceSuite) TestUnicastToConnected(c *C) { |
4712 | + userId, auth := s.associatedAuth("DEV1") |
4713 | + events, errCh, stop := s.StartClientAuth(c, "DEV1", nil, auth) |
4714 | + got, err := s.PostRequest("/notify", &api.Unicast{ |
4715 | + UserId: userId, |
4716 | + DeviceId: "DEV1", |
4717 | + AppId: "app1", |
4718 | + ExpireOn: future, |
4719 | + Data: json.RawMessage(`{"a": 42}`), |
4720 | + }) |
4721 | + c.Assert(err, IsNil) |
4722 | + c.Assert(got, Matches, ".*ok.*") |
4723 | + c.Check(NextEvent(events, errCh), Equals, `unicast app:app1 payload:{"a":42};`) |
4724 | + stop() |
4725 | + c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) |
4726 | + c.Check(len(errCh), Equals, 0) |
4727 | +} |
4728 | + |
4729 | +func (s *UnicastAcceptanceSuite) TestUnicastCorrectDistribution(c *C) { |
4730 | + userId1, auth1 := s.associatedAuth("DEV1") |
4731 | + userId2, auth2 := s.associatedAuth("DEV2") |
4732 | + // start 1st client |
4733 | + events1, errCh1, stop1 := s.StartClientAuth(c, "DEV1", nil, auth1) |
4734 | + // start 2nd client |
4735 | + events2, errCh2, stop2 := s.StartClientAuth(c, "DEV2", nil, auth2) |
4736 | + // unicast to one and the other |
4737 | + got, err := s.PostRequest("/notify", &api.Unicast{ |
4738 | + UserId: userId1, |
4739 | + DeviceId: "DEV1", |
4740 | + AppId: "app1", |
4741 | + ExpireOn: future, |
4742 | + Data: json.RawMessage(`{"to": 1}`), |
4743 | + }) |
4744 | + c.Assert(err, IsNil) |
4745 | + c.Assert(got, Matches, ".*ok.*") |
4746 | + got, err = s.PostRequest("/notify", &api.Unicast{ |
4747 | + UserId: userId2, |
4748 | + DeviceId: "DEV2", |
4749 | + AppId: "app1", |
4750 | + ExpireOn: future, |
4751 | + Data: json.RawMessage(`{"to": 2}`), |
4752 | + }) |
4753 | + c.Assert(err, IsNil) |
4754 | + c.Assert(got, Matches, ".*ok.*") |
4755 | + c.Check(NextEvent(events1, errCh1), Equals, `unicast app:app1 payload:{"to":1};`) |
4756 | + c.Check(NextEvent(events2, errCh2), Equals, `unicast app:app1 payload:{"to":2};`) |
4757 | + stop1() |
4758 | + stop2() |
4759 | + c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) |
4760 | + c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) |
4761 | + c.Check(len(errCh1), Equals, 0) |
4762 | + c.Check(len(errCh2), Equals, 0) |
4763 | +} |
4764 | + |
4765 | +func (s *UnicastAcceptanceSuite) TestUnicastPending(c *C) { |
4766 | + // send unicast that will be pending |
4767 | + userId, auth := s.associatedAuth("DEV1") |
4768 | + got, err := s.PostRequest("/notify", &api.Unicast{ |
4769 | + UserId: userId, |
4770 | + DeviceId: "DEV1", |
4771 | + AppId: "app1", |
4772 | + ExpireOn: future, |
4773 | + Data: json.RawMessage(`{"a": 42}`), |
4774 | + }) |
4775 | + c.Assert(err, IsNil) |
4776 | + c.Assert(got, Matches, ".*ok.*") |
4777 | + |
4778 | + // get pending on connect |
4779 | + events, errCh, stop := s.StartClientAuth(c, "DEV1", nil, auth) |
4780 | + c.Check(NextEvent(events, errCh), Equals, `unicast app:app1 payload:{"a":42};`) |
4781 | + stop() |
4782 | + c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) |
4783 | + c.Check(len(errCh), Equals, 0) |
4784 | +} |
4785 | + |
4786 | +func (s *UnicastAcceptanceSuite) TestUnicastLargeNeedsSplitting(c *C) { |
4787 | + userId, auth := s.associatedAuth("DEV2") |
4788 | + // send bunch of unicasts that will be pending |
4789 | + payloadFmt := fmt.Sprintf(`{"serial":%%d,"bloat":"%s"}`, strings.Repeat("x", 1024*2)) |
4790 | + for i := 0; i < 32; i++ { |
4791 | + got, err := s.PostRequest("/notify", &api.Unicast{ |
4792 | + UserId: userId, |
4793 | + DeviceId: "DEV2", |
4794 | + AppId: "app1", |
4795 | + ExpireOn: future, |
4796 | + Data: json.RawMessage(fmt.Sprintf(payloadFmt, i)), |
4797 | + }) |
4798 | + c.Assert(err, IsNil) |
4799 | + c.Assert(got, Matches, ".*ok.*") |
4800 | + } |
4801 | + |
4802 | + events, errCh, stop := s.StartClientAuth(c, "DEV2", nil, auth) |
4803 | + // gettting pending on connect |
4804 | + n := 0 |
4805 | + for { |
4806 | + evt := NextEvent(events, errCh) |
4807 | + c.Check(evt, Matches, "unicast app:app1 .*") |
4808 | + n += 1 |
4809 | + if strings.Contains(evt, `"serial":31`) { |
4810 | + break |
4811 | + } |
4812 | + } |
4813 | + // was split |
4814 | + c.Check(n > 1, Equals, true) |
4815 | + stop() |
4816 | + c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) |
4817 | + c.Check(len(errCh), Equals, 0) |
4818 | +} |
4819 | |
4820 | === modified file 'server/api/handlers.go' |
4821 | --- server/api/handlers.go 2014-02-20 17:09:03 +0000 |
4822 | +++ server/api/handlers.go 2014-05-29 12:04:32 +0000 |
4823 | @@ -19,12 +19,15 @@ |
4824 | package api |
4825 | |
4826 | import ( |
4827 | + "encoding/base64" |
4828 | "encoding/json" |
4829 | "fmt" |
4830 | "io" |
4831 | "net/http" |
4832 | "time" |
4833 | |
4834 | + "launchpad.net/~ubuntu-push-hackers/ubuntu-push/go-uuid/uuid" |
4835 | + |
4836 | "launchpad.net/ubuntu-push/logger" |
4837 | "launchpad.net/ubuntu-push/server/broker" |
4838 | "launchpad.net/ubuntu-push/server/store" |
4839 | @@ -93,6 +96,11 @@ |
4840 | ioError, |
4841 | "Could not read request body", |
4842 | } |
4843 | + ErrMissingIdField = &APIError{ |
4844 | + http.StatusBadRequest, |
4845 | + invalidRequest, |
4846 | + "Missing id field", |
4847 | + } |
4848 | ErrMissingData = &APIError{ |
4849 | http.StatusBadRequest, |
4850 | invalidRequest, |
4851 | @@ -130,10 +138,17 @@ |
4852 | } |
4853 | ) |
4854 | |
4855 | -type Message struct { |
4856 | - Registration string `json:"registration"` |
4857 | - CoalesceTag string `json:"coalesce_tag"` |
4858 | - Data json.RawMessage `json:"data"` |
4859 | +type castCommon struct { |
4860 | +} |
4861 | + |
4862 | +type Unicast struct { |
4863 | + UserId string `json:"userid"` |
4864 | + DeviceId string `json:"deviceid"` |
4865 | + AppId string `json:"appid"` |
4866 | + //Registration string `json:"registration"` |
4867 | + //CoalesceTag string `json:"coalesce_tag"` |
4868 | + ExpireOn string `json:"expire_on"` |
4869 | + Data json.RawMessage `json:"data"` |
4870 | } |
4871 | |
4872 | // Broadcast request JSON object. |
4873 | @@ -198,11 +213,11 @@ |
4874 | |
4875 | var zeroTime = time.Time{} |
4876 | |
4877 | -func checkBroadcast(bcast *Broadcast) (time.Time, *APIError) { |
4878 | - if len(bcast.Data) == 0 { |
4879 | +func checkCastCommon(data json.RawMessage, expireOn string) (time.Time, *APIError) { |
4880 | + if len(data) == 0 { |
4881 | return zeroTime, ErrMissingData |
4882 | } |
4883 | - expire, err := time.Parse(time.RFC3339, bcast.ExpireOn) |
4884 | + expire, err := time.Parse(time.RFC3339, expireOn) |
4885 | if err != nil { |
4886 | return zeroTime, ErrInvalidExpiration |
4887 | } |
4888 | @@ -212,6 +227,10 @@ |
4889 | return expire, nil |
4890 | } |
4891 | |
4892 | +func checkBroadcast(bcast *Broadcast) (time.Time, *APIError) { |
4893 | + return checkCastCommon(bcast.Data, bcast.ExpireOn) |
4894 | +} |
4895 | + |
4896 | type StoreForRequest func(w http.ResponseWriter, request *http.Request) (store.PendingStore, error) |
4897 | |
4898 | // context holds the interfaces to delegate to serving requests |
4899 | @@ -234,6 +253,20 @@ |
4900 | return sto, nil |
4901 | } |
4902 | |
4903 | +func (ctx *context) prepare(w http.ResponseWriter, request *http.Request, reqObj interface{}) (store.PendingStore, *APIError) { |
4904 | + body, apiErr := ReadBody(request, MaxRequestBodyBytes) |
4905 | + if apiErr != nil { |
4906 | + return nil, apiErr |
4907 | + } |
4908 | + |
4909 | + err := json.Unmarshal(body, reqObj) |
4910 | + if err != nil { |
4911 | + return nil, ErrMalformedJSONObject |
4912 | + } |
4913 | + |
4914 | + return ctx.getStore(w, request) |
4915 | +} |
4916 | + |
4917 | type BroadcastHandler struct { |
4918 | *context |
4919 | } |
4920 | @@ -270,23 +303,13 @@ |
4921 | } |
4922 | }() |
4923 | |
4924 | - body, apiErr := ReadBody(request, MaxRequestBodyBytes) |
4925 | - if apiErr != nil { |
4926 | - return |
4927 | - } |
4928 | - |
4929 | - sto, apiErr := h.getStore(writer, request) |
4930 | - if apiErr != nil { |
4931 | - return |
4932 | - } |
4933 | - defer sto.Close() |
4934 | - |
4935 | broadcast := &Broadcast{} |
4936 | - err := json.Unmarshal(body, broadcast) |
4937 | - if err != nil { |
4938 | - apiErr = ErrMalformedJSONObject |
4939 | + |
4940 | + sto, apiErr := h.prepare(writer, request, broadcast) |
4941 | + if apiErr != nil { |
4942 | return |
4943 | } |
4944 | + defer sto.Close() |
4945 | |
4946 | apiErr = h.doBroadcast(sto, broadcast) |
4947 | if apiErr != nil { |
4948 | @@ -297,6 +320,64 @@ |
4949 | fmt.Fprintf(writer, `{"ok":true}`) |
4950 | } |
4951 | |
4952 | +type UnicastHandler struct { |
4953 | + *context |
4954 | +} |
4955 | + |
4956 | +func checkUnicast(ucast *Unicast) (time.Time, *APIError) { |
4957 | + if ucast.UserId == "" || ucast.DeviceId == "" || ucast.AppId == "" { |
4958 | + return zeroTime, ErrMissingIdField |
4959 | + } |
4960 | + return checkCastCommon(ucast.Data, ucast.ExpireOn) |
4961 | +} |
4962 | + |
4963 | +// use a base64 encoded TimeUUID |
4964 | +var generateMsgId = func() string { |
4965 | + return base64.StdEncoding.EncodeToString(uuid.NewUUID()) |
4966 | +} |
4967 | + |
4968 | +func (h *UnicastHandler) doUnicast(sto store.PendingStore, ucast *Unicast) *APIError { |
4969 | + expire, apiErr := checkUnicast(ucast) |
4970 | + if apiErr != nil { |
4971 | + return apiErr |
4972 | + } |
4973 | + chanId := store.UnicastInternalChannelId(ucast.UserId, ucast.DeviceId) |
4974 | + msgId := generateMsgId() |
4975 | + err := sto.AppendToUnicastChannel(chanId, ucast.AppId, ucast.Data, msgId, expire) |
4976 | + if err != nil { |
4977 | + h.logger.Errorf("could not store notification: %v", err) |
4978 | + return ErrCouldNotStoreNotification |
4979 | + } |
4980 | + |
4981 | + h.broker.Unicast(chanId) |
4982 | + return nil |
4983 | +} |
4984 | + |
4985 | +func (h *UnicastHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { |
4986 | + var apiErr *APIError |
4987 | + defer func() { |
4988 | + if apiErr != nil { |
4989 | + RespondError(writer, apiErr) |
4990 | + } |
4991 | + }() |
4992 | + |
4993 | + unicast := &Unicast{} |
4994 | + |
4995 | + sto, apiErr := h.prepare(writer, request, unicast) |
4996 | + if apiErr != nil { |
4997 | + return |
4998 | + } |
4999 | + defer sto.Close() |
5000 | + |
The diff has been truncated for viewing.