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