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