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