Mercurial > libervia-backend
comparison src/plugins/plugin_xep_0260.py @ 1570:37d4be4a9fed
plugins XEP-0260, XEP-0065: proxy handling:
- XEP-0065: Candidate.activate launch proxy activation
- XEP-0065: a candidate is individually connected with connectCandidate
- transport-info action handling can now manage candidate and proxy infos
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 08 Nov 2015 14:44:33 +0100 |
parents | 268fda4236ca |
children | c668081eba1c |
comparison
equal
deleted
inserted
replaced
1569:44854fb5d3b2 | 1570:37d4be4a9fed |
---|---|
44 "dependencies": ["XEP-0166", "XEP-0065"], | 44 "dependencies": ["XEP-0166", "XEP-0065"], |
45 "main": "XEP_0260", | 45 "main": "XEP_0260", |
46 "handler": "yes", | 46 "handler": "yes", |
47 "description": _("""Implementation of Jingle SOCKS5 Bytestreams""") | 47 "description": _("""Implementation of Jingle SOCKS5 Bytestreams""") |
48 } | 48 } |
49 | |
50 | |
51 class ProxyError(Exception): | |
52 pass | |
49 | 53 |
50 | 54 |
51 class XEP_0260(object): | 55 class XEP_0260(object): |
52 # TODO: udp handling | 56 # TODO: udp handling |
53 | 57 |
125 mode = 'tcp' # XXX: we only manage tcp for now | 129 mode = 'tcp' # XXX: we only manage tcp for now |
126 transport_elt = self._buildCandidates(session, candidates, sid, session_hash, client, mode) | 130 transport_elt = self._buildCandidates(session, candidates, sid, session_hash, client, mode) |
127 | 131 |
128 defer.returnValue(transport_elt) | 132 defer.returnValue(transport_elt) |
129 | 133 |
134 def _proxyActivatedCb(self, iq_result_elt, candidate, session, content_name, profile): | |
135 """Called when activation confirmation has been received from proxy | |
136 | |
137 cf XEP-0260 § 2.4 | |
138 """ | |
139 # now that the proxy is activated, we have to inform other peer | |
140 iq_elt, transport_elt = self._j.buildAction(self._j.A_TRANSPORT_INFO, session, content_name, profile) | |
141 activated_elt = transport_elt.addElement('activated') | |
142 activated_elt['cid'] = candidate.id | |
143 iq_elt.send | |
144 | |
145 def _proxyActivatedEb(self, stanza_error, candidate, session, content_name, profile): | |
146 """Called when activation error has been received from proxy | |
147 | |
148 cf XEP-0260 § 2.4 | |
149 """ | |
150 # TODO: fallback to IBB | |
151 # now that the proxy is activated, we have to inform other peer | |
152 iq_elt, transport_elt = self._j.buildAction(self._j.A_TRANSPORT_INFO, session, content_name, profile) | |
153 transport_elt.addElement('proxy-error') | |
154 iq_elt.send | |
155 return stanza_error | |
156 | |
130 def _foundPeerCandidate(self, candidate, session, transport_data, content_name, client): | 157 def _foundPeerCandidate(self, candidate, session, transport_data, content_name, client): |
131 """Called when the best candidate from other peer is found | 158 """Called when the best candidate from other peer is found |
132 | 159 |
133 @param candidate(XEP_0065.Candidate, None): selected candidate, | 160 @param candidate(XEP_0065.Candidate, None): selected candidate, |
134 or None if no candidate is accessible | 161 or None if no candidate is accessible |
135 @param session(dict): session data | 162 @param session(dict): session data |
136 @param transport_data(dict): transport data | 163 @param transport_data(dict): transport data |
137 @param content_name(dict): name of the current content | 164 @param content_name(unicode): name of the current content |
138 @param client(unicode): %(doc_client)s | 165 @param client(unicode): %(doc_client)s |
139 """ | 166 """ |
140 | 167 |
141 transport_data['best_candidate'] = candidate | 168 transport_data['best_candidate'] = candidate |
142 # we need to disconnect all non selected candidates before removing them | 169 # we need to disconnect all non selected candidates before removing them |
152 else: | 179 else: |
153 log.info(u"Found best peer candidate: {}".format(unicode(candidate))) | 180 log.info(u"Found best peer candidate: {}".format(unicode(candidate))) |
154 candidate_elt = transport_elt.addElement('candidate-used') | 181 candidate_elt = transport_elt.addElement('candidate-used') |
155 candidate_elt['cid'] = candidate.id | 182 candidate_elt['cid'] = candidate.id |
156 iq_elt.send() # TODO: check result stanza | 183 iq_elt.send() # TODO: check result stanza |
157 content_data = session['contents'][content_name] | 184 self._checkCandidates(session, content_name, transport_data, client) |
158 self._checkCandidates(session, content_data, transport_data, client) | 185 |
159 | 186 def _checkCandidates(self, session, content_name, transport_data, client): |
160 def _checkCandidates(self, session, content_data, transport_data, client): | |
161 """Called when a candidate has been choosed | 187 """Called when a candidate has been choosed |
162 | 188 |
163 if we have both candidates, we select one, or fallback to an other transport | 189 if we have both candidates, we select one, or fallback to an other transport |
164 @param session(dict): session data | 190 @param session(dict): session data |
165 @param content_data(dict): content data | 191 @param content_name(unicode): name of the current content |
166 @param transport_data(dict): transport data | 192 @param transport_data(dict): transport data |
167 @param client(unicode): %(doc_client)s | 193 @param client(unicode): %(doc_client)s |
168 """ | 194 """ |
195 content_data = session['contents'][content_name] | |
169 try: | 196 try: |
170 best_candidate = transport_data['best_candidate'] | 197 best_candidate = transport_data['best_candidate'] |
171 except KeyError: | 198 except KeyError: |
172 # we have not our best candidate yet | 199 # we have not our best candidate yet |
173 return | 200 return |
192 choosed_candidate = max(best_candidate, peer_best_candidate, key=lambda c:c.priority) | 219 choosed_candidate = max(best_candidate, peer_best_candidate, key=lambda c:c.priority) |
193 | 220 |
194 if choosed_candidate is None: | 221 if choosed_candidate is None: |
195 log.warning(u"Socks5 negociation failed, we need to fallback to IBB") | 222 log.warning(u"Socks5 negociation failed, we need to fallback to IBB") |
196 else: | 223 else: |
197 if choosed_candidate==best_candidate: | 224 # best_peer_candidate was choosed from the candidates we have sent |
198 who = u'our' | 225 # so our_candidate is true if choosed_candidate is peer_best_candidate |
199 else: | 226 our_candidate = choosed_candidate==peer_best_candidate |
200 who = u'other peer' | |
201 best_candidate.discard() | |
202 | 227 |
203 log.info(u"Socks5 negociation successful, {who} candidate will be used: {candidate}".format( | 228 log.info(u"Socks5 negociation successful, {who} candidate will be used: {candidate}".format( |
204 who = who, | 229 who = u'our' if our_candidate else u'other peer', |
205 candidate = choosed_candidate)) | 230 candidate = choosed_candidate)) |
206 del transport_data['best_candidate'] | 231 del transport_data['best_candidate'] |
207 del transport_data['peer_best_candidate'] | 232 del transport_data['peer_best_candidate'] |
233 | |
234 if choosed_candidate.type == self._s5b.TYPE_PROXY: | |
235 # the file transfer need to wait for proxy activation | |
236 # (see XEP-0260 § 2.4) | |
237 if our_candidate: | |
238 d = self._s5b.connectCandidate(choosed_candidate, transport_data['session_hash'], profile=client.profile) | |
239 d.addCallback(lambda dummy: choosed_candidate.activate(transport_data['sid'], session['peer_jid'], client)) | |
240 args = [choosed_candidate, session, content_name, client.profile] | |
241 d.addCallbacks(self._proxyActivatedCb, self._proxyActivatedEb, args, None, args) | |
242 else: | |
243 # this Deferred will be called when we'll receive activation confirmation from other peer | |
244 d = transport_data['activation_d'] = defer.Deferred() | |
245 else: | |
246 d = defer.succeed(None) | |
247 | |
208 if content_data['senders'] == session['role']: | 248 if content_data['senders'] == session['role']: |
209 # we can now start the file transfer | 249 # we can now start the file transfer (or start it after proxy activation) |
210 choosed_candidate.startTransfer(transport_data['session_hash']) | 250 d.addCallback(lambda dummy: choosed_candidate.startTransfer(transport_data['session_hash'])) |
251 | |
252 def _candidateInfo(self, candidate_elt, session, content_name, transport_data, client): | |
253 """Called when best candidate has been received from peer (or if none is working) | |
254 | |
255 @param candidate_elt(domish.Element): candidate-used or candidate-error element | |
256 (see XEP-0260 §2.3) | |
257 @param session(dict): session data | |
258 @param content_name(unicode): name of the current content | |
259 @param transport_data(dict): transport data | |
260 @param client(unicode): %(doc_client)s | |
261 """ | |
262 if candidate_elt.name == 'candidate-error': | |
263 # candidate-error, no candidate worked | |
264 transport_data['peer_best_candidate'] = None | |
265 else: | |
266 # candidate-used, one candidate was choosed | |
267 try: | |
268 cid = candidate_elt.attributes['cid'] | |
269 except KeyError: | |
270 log.warning(u"No cid found in <candidate-used>") | |
271 raise exceptions.DataError | |
272 try: | |
273 candidate = (c for c in transport_data['candidates'] if c.id == cid).next() | |
274 except StopIteration: | |
275 log.warning(u"Given cid doesn't correspond to any known candidate !") | |
276 raise exceptions.DataError # TODO: send an error to other peer, and use better exception | |
277 except KeyError: | |
278 # a transport-info can also be intentionaly sent too early by other peer | |
279 # but there is little probability | |
280 log.error(u'"candidates" key doesn\'t exists in transport_data, it should at this point') | |
281 raise exceptions.InternalError | |
282 # at this point we have the candidate choosed by other peer | |
283 transport_data['peer_best_candidate'] = candidate | |
284 log.info(u"Other peer best candidate: {}".format(candidate)) | |
285 | |
286 del transport_data['candidates'] | |
287 self._checkCandidates(session, content_name, transport_data, client) | |
288 | |
289 def _proxyActivationInfo(self, proxy_elt, session, content_name, transport_data, client): | |
290 """Called when proxy has been activated (or has sent an error) | |
291 | |
292 @param proxy_elt(domish.Element): <activated/> or <proxy-error/> element | |
293 (see XEP-0260 §2.4) | |
294 @param session(dict): session data | |
295 @param content_name(unicode): name of the current content | |
296 @param transport_data(dict): transport data | |
297 @param client(unicode): %(doc_client)s | |
298 """ | |
299 try: | |
300 activation_d = transport_data.pop('activation_d') | |
301 except KeyError: | |
302 log.warning(u"Received unexpected transport-info for proxy activation") | |
303 | |
304 if proxy_elt.name == 'activated': | |
305 activation_d.callback(None) | |
306 else: | |
307 activation_d.errback(ProxyError) | |
211 | 308 |
212 @defer.inlineCallbacks | 309 @defer.inlineCallbacks |
213 def jingleHandler(self, action, session, content_name, transport_elt, profile): | 310 def jingleHandler(self, action, session, content_name, transport_elt, profile): |
214 client = self.host.getClient(profile) | 311 client = self.host.getClient(profile) |
215 content_data = session['contents'][content_name] | 312 content_data = session['contents'][content_name] |
254 transport_data['candidates'] = candidates | 351 transport_data['candidates'] = candidates |
255 # we can now build a new <transport> element with our candidates | 352 # we can now build a new <transport> element with our candidates |
256 transport_elt = self._buildCandidates(session, candidates, sid, session_hash, client) | 353 transport_elt = self._buildCandidates(session, candidates, sid, session_hash, client) |
257 | 354 |
258 elif action == self._j.A_TRANSPORT_INFO: | 355 elif action == self._j.A_TRANSPORT_INFO: |
259 # other peer gave us its choosed candidate | 356 # transport-info can be about candidate or proxy activation |
260 try: | 357 candidate_elt = None |
261 candidate_elt = transport_elt.elements(NS_JINGLE_S5B, 'candidate-used').next() | 358 |
262 except StopIteration: | 359 for method, names in ((self._candidateInfo, ('candidate-used', 'candidate-error')), |
263 try: | 360 (self._proxyActivationInfo, ('activated', 'proxy-error'))): |
264 candidate_elt = transport_elt.elements(NS_JINGLE_S5B, 'candidate-error').next() | 361 for name in names: |
265 except StopIteration: | 362 try: |
266 log.warning(u"Unexpected transport element: {}".format(transport_elt.toXml())) | 363 candidate_elt = transport_elt.elements(NS_JINGLE_S5B, name).next() |
267 raise exceptions.DataError | 364 except StopIteration: |
268 else: | 365 continue |
269 # candidate-error, no candidate worked | 366 else: |
270 transport_data['peer_best_candidate'] = None | 367 method(candidate_elt, session, content_name, transport_data, client) |
271 else: | 368 break |
272 # candidate-used, one candidate was choosed | 369 |
273 try: | 370 if candidate_elt is None: |
274 cid = candidate_elt.attributes['cid'] | 371 log.warning(u"Unexpected transport element: {}".format(transport_elt.toXml())) |
275 except KeyError: | |
276 log.warning(u"No cid found in <candidate-used>") | |
277 raise exceptions.DataError | |
278 try: | |
279 candidate = (c for c in transport_data['candidates'] if c.id == cid).next() | |
280 except StopIteration: | |
281 log.warning(u"Given cid doesn't correspond to any known candidate !") | |
282 raise exceptions.DataError # TODO: send an error to other peer, and use better exception | |
283 except KeyError: | |
284 # a transport-info can also be intentionaly sent too early by other peer | |
285 # but there is little probability | |
286 log.error(u'"candidates" key doesn\'t exists in transport_data, it should at this point') | |
287 raise exceptions.InternalError | |
288 # at this point we have the candidate choosed by other peer | |
289 transport_data['peer_best_candidate'] = candidate | |
290 log.info(u"Other peer best candidate: {}".format(candidate)) | |
291 | |
292 del transport_data['candidates'] | |
293 self._checkCandidates(session, content_data, transport_data, client) | |
294 | 372 |
295 else: | 373 else: |
296 log.warning(u"FIXME: unmanaged action {}".format(action)) | 374 log.warning(u"FIXME: unmanaged action {}".format(action)) |
297 | 375 |
298 defer.returnValue(transport_elt) | 376 defer.returnValue(transport_elt) |