Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_xep_0166/__init__.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 79c8a70e1813 |
children | f46891f2c9cb |
comparison
equal
deleted
inserted
replaced
4269:64a85ce8be70 | 4270:0d7bb4df2343 |
---|---|
49 | 49 |
50 | 50 |
51 log = getLogger(__name__) | 51 log = getLogger(__name__) |
52 | 52 |
53 | 53 |
54 IQ_SET : Final = '/iq[@type="set"]' | 54 IQ_SET: Final = '/iq[@type="set"]' |
55 NS_JINGLE : Final = "urn:xmpp:jingle:1" | 55 NS_JINGLE: Final = "urn:xmpp:jingle:1" |
56 NS_JINGLE_ERROR : Final = "urn:xmpp:jingle:errors:1" | 56 NS_JINGLE_ERROR: Final = "urn:xmpp:jingle:errors:1" |
57 JINGLE_REQUEST : Final = f'{IQ_SET}/jingle[@xmlns="{NS_JINGLE}"]' | 57 JINGLE_REQUEST: Final = f'{IQ_SET}/jingle[@xmlns="{NS_JINGLE}"]' |
58 CONFIRM_TXT : Final = D_( | 58 CONFIRM_TXT: Final = D_( |
59 "{entity} want to start a jingle session with you, do you accept ?" | 59 "{entity} want to start a jingle session with you, do you accept ?" |
60 ) | 60 ) |
61 | 61 |
62 PLUGIN_INFO : Final = { | 62 PLUGIN_INFO: Final = { |
63 C.PI_NAME: "Jingle", | 63 C.PI_NAME: "Jingle", |
64 C.PI_IMPORT_NAME: "XEP-0166", | 64 C.PI_IMPORT_NAME: "XEP-0166", |
65 C.PI_TYPE: "XEP", | 65 C.PI_TYPE: "XEP", |
66 C.PI_MODES: C.PLUG_MODE_BOTH, | 66 C.PI_MODES: C.PLUG_MODE_BOTH, |
67 C.PI_PROTOCOLS: ["XEP-0166"], | 67 C.PI_PROTOCOLS: ["XEP-0166"], |
70 C.PI_DESCRIPTION: _("""Implementation of Jingle"""), | 70 C.PI_DESCRIPTION: _("""Implementation of Jingle"""), |
71 } | 71 } |
72 | 72 |
73 | 73 |
74 class XEP_0166: | 74 class XEP_0166: |
75 namespace : Final = NS_JINGLE | 75 namespace: Final = NS_JINGLE |
76 | 76 |
77 ROLE_INITIATOR : Final = "initiator" | 77 ROLE_INITIATOR: Final = "initiator" |
78 ROLE_RESPONDER : Final = "responder" | 78 ROLE_RESPONDER: Final = "responder" |
79 | 79 |
80 TRANSPORT_DATAGRAM : Final = "UDP" | 80 TRANSPORT_DATAGRAM: Final = "UDP" |
81 TRANSPORT_STREAMING : Final = "TCP" | 81 TRANSPORT_STREAMING: Final = "TCP" |
82 | 82 |
83 REASON_SUCCESS : Final = "success" | 83 REASON_SUCCESS: Final = "success" |
84 REASON_DECLINE : Final = "decline" | 84 REASON_DECLINE: Final = "decline" |
85 REASON_FAILED_APPLICATION : Final = "failed-application" | 85 REASON_FAILED_APPLICATION: Final = "failed-application" |
86 REASON_FAILED_TRANSPORT : Final = "failed-transport" | 86 REASON_FAILED_TRANSPORT: Final = "failed-transport" |
87 REASON_CONNECTIVITY_ERROR : Final = "connectivity-error" | 87 REASON_CONNECTIVITY_ERROR: Final = "connectivity-error" |
88 | 88 |
89 STATE_PENDING : Final = "PENDING" | 89 STATE_PENDING: Final = "PENDING" |
90 STATE_ACTIVE : Final = "ACTIVE" | 90 STATE_ACTIVE: Final = "ACTIVE" |
91 STATE_ENDED : Final = "ENDED" | 91 STATE_ENDED: Final = "ENDED" |
92 | 92 |
93 # standard actions | 93 # standard actions |
94 | 94 |
95 A_SESSION_INITIATE : Final = "session-initiate" | 95 A_SESSION_INITIATE: Final = "session-initiate" |
96 A_SESSION_ACCEPT : Final = "session-accept" | 96 A_SESSION_ACCEPT: Final = "session-accept" |
97 A_SESSION_TERMINATE : Final = "session-terminate" | 97 A_SESSION_TERMINATE: Final = "session-terminate" |
98 A_SESSION_INFO : Final = "session-info" | 98 A_SESSION_INFO: Final = "session-info" |
99 A_TRANSPORT_REPLACE : Final = "transport-replace" | 99 A_TRANSPORT_REPLACE: Final = "transport-replace" |
100 A_TRANSPORT_ACCEPT : Final = "transport-accept" | 100 A_TRANSPORT_ACCEPT: Final = "transport-accept" |
101 A_TRANSPORT_REJECT : Final = "transport-reject" | 101 A_TRANSPORT_REJECT: Final = "transport-reject" |
102 A_TRANSPORT_INFO : Final = "transport-info" | 102 A_TRANSPORT_INFO: Final = "transport-info" |
103 | 103 |
104 # non standard actions | 104 # non standard actions |
105 | 105 |
106 #: called before the confirmation request, first event for responder, useful for | 106 #: called before the confirmation request, first event for responder, useful for |
107 #: parsing | 107 #: parsing |
108 A_PREPARE_CONFIRMATION : Final = "prepare-confirmation" | 108 A_PREPARE_CONFIRMATION: Final = "prepare-confirmation" |
109 #: initiator must prepare tranfer | 109 #: initiator must prepare tranfer |
110 A_PREPARE_INITIATOR : Final = "prepare-initiator" | 110 A_PREPARE_INITIATOR: Final = "prepare-initiator" |
111 #: responder must prepare tranfer | 111 #: responder must prepare tranfer |
112 A_PREPARE_RESPONDER : Final = "prepare-responder" | 112 A_PREPARE_RESPONDER: Final = "prepare-responder" |
113 #; session accepted ack has been received from initiator | 113 # ; session accepted ack has been received from initiator |
114 A_ACCEPTED_ACK : Final = ( | 114 A_ACCEPTED_ACK: Final = "accepted-ack" |
115 "accepted-ack" | 115 A_START: Final = "start" # application can start |
116 ) | |
117 A_START : Final = "start" # application can start | |
118 #: called when a transport is destroyed (e.g. because it is remplaced). Used to do | 116 #: called when a transport is destroyed (e.g. because it is remplaced). Used to do |
119 #: cleaning operations | 117 #: cleaning operations |
120 A_DESTROY : Final = ( | 118 A_DESTROY: Final = "destroy" |
121 "destroy" | |
122 ) | |
123 | 119 |
124 def __init__(self, host): | 120 def __init__(self, host): |
125 log.info(_("plugin Jingle initialization")) | 121 log.info(_("plugin Jingle initialization")) |
126 self.host = host | 122 self.host = host |
127 self._applications = {} # key: namespace, value: application data | 123 self._applications = {} # key: namespace, value: application data |
155 @raise exceptions.NotFound: no session with this SID has been found | 151 @raise exceptions.NotFound: no session with this SID has been found |
156 """ | 152 """ |
157 try: | 153 try: |
158 return client.jingle_sessions[session_id] | 154 return client.jingle_sessions[session_id] |
159 except KeyError: | 155 except KeyError: |
160 raise exceptions.NotFound( | 156 raise exceptions.NotFound(f"No session with SID {session_id} found") |
161 f"No session with SID {session_id} found" | |
162 ) | |
163 | 157 |
164 def create_session( | 158 def create_session( |
165 self, | 159 self, |
166 client: SatXMPPEntity, | 160 client: SatXMPPEntity, |
167 sid: str, | 161 sid: str, |
168 role: str, | 162 role: str, |
169 peer_jid: jid.JID, | 163 peer_jid: jid.JID, |
170 local_jid: jid.JID|None = None, | 164 local_jid: jid.JID | None = None, |
171 **kwargs | 165 **kwargs, |
172 ) -> dict: | 166 ) -> dict: |
173 """Create a new jingle session. | 167 """Create a new jingle session. |
174 | 168 |
175 @param client: The client entity. | 169 @param client: The client entity. |
176 @param sid: Session ID. | 170 @param sid: Session ID. |
189 if not sid: | 183 if not sid: |
190 raise exceptions.DataError("Empty session ID is not allowed") | 184 raise exceptions.DataError("Empty session ID is not allowed") |
191 | 185 |
192 if role not in [XEP_0166.ROLE_INITIATOR, XEP_0166.ROLE_RESPONDER]: | 186 if role not in [XEP_0166.ROLE_INITIATOR, XEP_0166.ROLE_RESPONDER]: |
193 raise ValueError(f"Invalid role {role}. Expected initiator or responder.") | 187 raise ValueError(f"Invalid role {role}. Expected initiator or responder.") |
194 | |
195 | 188 |
196 session_data = { | 189 session_data = { |
197 "id": sid, | 190 "id": sid, |
198 "state": XEP_0166.STATE_PENDING, | 191 "state": XEP_0166.STATE_PENDING, |
199 "initiator": client.jid if role == XEP_0166.ROLE_INITIATOR else peer_jid, | 192 "initiator": client.jid if role == XEP_0166.ROLE_INITIATOR else peer_jid, |
200 "role": role, | 193 "role": role, |
201 "local_jid": local_jid or client.jid, | 194 "local_jid": local_jid or client.jid, |
202 "peer_jid": peer_jid, | 195 "peer_jid": peer_jid, |
203 "started": time.time(), | 196 "started": time.time(), |
204 "contents": {} | 197 "contents": {}, |
205 } | 198 } |
206 | 199 |
207 # If extra kw args are provided, merge them into the session_data | 200 # If extra kw args are provided, merge them into the session_data |
208 if kwargs: | 201 if kwargs: |
209 session_data.update(kwargs) | 202 session_data.update(kwargs) |
210 | 203 |
211 # Add the session to the client's jingle sessions | 204 # Add the session to the client's jingle sessions |
212 client.jingle_sessions[sid] = session_data | 205 client.jingle_sessions[sid] = session_data |
213 | 206 |
214 return session_data | 207 return session_data |
215 | |
216 | 208 |
217 def delete_session(self, client, sid): | 209 def delete_session(self, client, sid): |
218 try: | 210 try: |
219 del client.jingle_sessions[sid] | 211 del client.jingle_sessions[sid] |
220 except KeyError: | 212 except KeyError: |
221 log.debug( | 213 log.debug( |
222 f"Jingle session id {sid!r} is unknown, nothing to delete " | 214 f"Jingle session id {sid!r} is unknown, nothing to delete " |
223 f"[{client.profile}]") | 215 f"[{client.profile}]" |
216 ) | |
224 else: | 217 else: |
225 log.debug(f"Jingle session id {sid!r} deleted [{client.profile}]") | 218 log.debug(f"Jingle session id {sid!r} deleted [{client.profile}]") |
226 | 219 |
227 ## helpers methods to build stanzas ## | 220 ## helpers methods to build stanzas ## |
228 | 221 |
229 def _build_jingle_elt( | 222 def _build_jingle_elt( |
230 self, | 223 self, client: SatXMPPEntity, session: dict, action: str |
231 client: SatXMPPEntity, | |
232 session: dict, | |
233 action: str | |
234 ) -> Tuple[xmlstream.IQ, domish.Element]: | 224 ) -> Tuple[xmlstream.IQ, domish.Element]: |
235 iq_elt = client.IQ("set") | 225 iq_elt = client.IQ("set") |
236 iq_elt["from"] = session['local_jid'].full() | 226 iq_elt["from"] = session["local_jid"].full() |
237 iq_elt["to"] = session["peer_jid"].full() | 227 iq_elt["to"] = session["peer_jid"].full() |
238 jingle_elt = iq_elt.addElement("jingle", NS_JINGLE) | 228 jingle_elt = iq_elt.addElement("jingle", NS_JINGLE) |
239 jingle_elt["sid"] = session["id"] | 229 jingle_elt["sid"] = session["id"] |
240 jingle_elt["action"] = action | 230 jingle_elt["action"] = action |
241 return iq_elt, jingle_elt | 231 return iq_elt, jingle_elt |
262 | 252 |
263 def _terminate_eb(self, failure_): | 253 def _terminate_eb(self, failure_): |
264 log.warning(_("Error while terminating session: {msg}").format(msg=failure_)) | 254 log.warning(_("Error while terminating session: {msg}").format(msg=failure_)) |
265 | 255 |
266 def _terminate( | 256 def _terminate( |
267 self, | 257 self, session_id: str, reason: str, reason_txt: str, profile: str |
268 session_id: str, | |
269 reason: str, | |
270 reason_txt: str, | |
271 profile: str | |
272 ) -> defer.Deferred: | 258 ) -> defer.Deferred: |
273 client = self.host.get_client(profile) | 259 client = self.host.get_client(profile) |
274 session = self.get_session(client, session_id) | 260 session = self.get_session(client, session_id) |
275 if reason not in ("", "cancel", "decline", "busy"): | 261 if reason not in ("", "cancel", "decline", "busy"): |
276 raise ValueError( | 262 raise ValueError( |
277 'only "cancel", "decline" and "busy" and empty value are allowed' | 263 'only "cancel", "decline" and "busy" and empty value are allowed' |
278 ) | 264 ) |
279 return self.terminate( | 265 return self.terminate(client, reason or None, session, text=reason_txt or None) |
280 client, | |
281 reason or None, | |
282 session, | |
283 text=reason_txt or None | |
284 ) | |
285 | 266 |
286 def terminate( | 267 def terminate( |
287 self, | 268 self, |
288 client: SatXMPPEntity, | 269 client: SatXMPPEntity, |
289 reason: str|list[domish.Element]|None, | 270 reason: str | list[domish.Element] | None, |
290 session: dict, | 271 session: dict, |
291 text: str|None = None | 272 text: str | None = None, |
292 ) -> defer.Deferred: | 273 ) -> defer.Deferred: |
293 """Terminate the session | 274 """Terminate the session |
294 | 275 |
295 send the session-terminate action, and delete the session data | 276 send the session-terminate action, and delete the session data |
296 @param reason: if unicode, will be transformed to an element | 277 @param reason: if unicode, will be transformed to an element |
309 reason_elt.addChild(elt) | 290 reason_elt.addChild(elt) |
310 else: | 291 else: |
311 reason_elt = None | 292 reason_elt = None |
312 if text is not None: | 293 if text is not None: |
313 if reason_elt is None: | 294 if reason_elt is None: |
314 raise ValueError( | 295 raise ValueError("You have to specify a reason if text is specified") |
315 "You have to specify a reason if text is specified" | |
316 ) | |
317 reason_elt.addElement("text", content=text) | 296 reason_elt.addElement("text", content=text) |
318 if not self.host.trigger.point( | 297 if not self.host.trigger.point("XEP-0166_terminate", client, session, reason_elt): |
319 "XEP-0166_terminate", | |
320 client, session, reason_elt | |
321 ): | |
322 return defer.succeed(None) | 298 return defer.succeed(None) |
323 self.delete_session(client, session["id"]) | 299 self.delete_session(client, session["id"]) |
324 d = iq_elt.send() | 300 d = iq_elt.send() |
325 d.addErrback(self._terminate_eb) | 301 d.addErrback(self._terminate_eb) |
326 return d | 302 return d |
327 | 303 |
328 ## errors which doesn't imply a stanza sending ## | 304 ## errors which doesn't imply a stanza sending ## |
329 | 305 |
330 def _jingle_error_cb( | 306 def _jingle_error_cb( |
331 self, | 307 self, |
332 failure_: failure.Failure|BaseException, | 308 failure_: failure.Failure | BaseException, |
333 session: dict, | 309 session: dict, |
334 request: domish.Element, | 310 request: domish.Element, |
335 client: SatXMPPEntity | 311 client: SatXMPPEntity, |
336 ) -> defer.Deferred: | 312 ) -> defer.Deferred: |
337 """Called when something is going wrong while parsing jingle request | 313 """Called when something is going wrong while parsing jingle request |
338 | 314 |
339 The error condition depend of the exceptions raised: | 315 The error condition depend of the exceptions raised: |
340 exceptions.DataError raise a bad-request condition | 316 exceptions.DataError raise a bad-request condition |
350 if isinstance(failure_.value, defer.FirstError): | 326 if isinstance(failure_.value, defer.FirstError): |
351 failure_ = failure_.value.subFailure.value | 327 failure_ = failure_.value.subFailure.value |
352 if isinstance(failure_, exceptions.DataError): | 328 if isinstance(failure_, exceptions.DataError): |
353 return self.sendError(client, "bad-request", session["id"], request) | 329 return self.sendError(client, "bad-request", session["id"], request) |
354 elif isinstance(failure_, error.StanzaError): | 330 elif isinstance(failure_, error.StanzaError): |
355 return self.terminate(client, self.REASON_FAILED_APPLICATION, session, | 331 return self.terminate( |
356 text=str(failure_)) | 332 client, self.REASON_FAILED_APPLICATION, session, text=str(failure_) |
333 ) | |
357 else: | 334 else: |
358 log.error(f"Unmanaged jingle exception: {failure_}") | 335 log.error(f"Unmanaged jingle exception: {failure_}") |
359 return self.terminate(client, self.REASON_FAILED_APPLICATION, session, | 336 return self.terminate( |
360 text=str(failure_)) | 337 client, self.REASON_FAILED_APPLICATION, session, text=str(failure_) |
338 ) | |
361 | 339 |
362 ## methods used by other plugins ## | 340 ## methods used by other plugins ## |
363 | 341 |
364 def register_application( | 342 def register_application( |
365 self, | 343 self, namespace: str, handler: BaseApplicationHandler, priority: int = 0 |
366 namespace: str, | |
367 handler: BaseApplicationHandler, | |
368 priority: int = 0 | |
369 ) -> None: | 344 ) -> None: |
370 """Register an application plugin | 345 """Register an application plugin |
371 | 346 |
372 @param namespace(unicode): application namespace managed by the plugin | 347 @param namespace(unicode): application namespace managed by the plugin |
373 @param handler(object): instance of a class which manage the application. | 348 @param handler(object): instance of a class which manage the application. |
403 namespace=namespace, handler=handler, priority=priority | 378 namespace=namespace, handler=handler, priority=priority |
404 ) | 379 ) |
405 log.debug("new jingle application registered") | 380 log.debug("new jingle application registered") |
406 | 381 |
407 def register_transport( | 382 def register_transport( |
408 self, | 383 self, |
409 namespace: str, | 384 namespace: str, |
410 transport_type: str, | 385 transport_type: str, |
411 handler: BaseTransportHandler, | 386 handler: BaseTransportHandler, |
412 priority: int = 0 | 387 priority: int = 0, |
413 ) -> None: | 388 ) -> None: |
414 """Register a transport plugin | 389 """Register a transport plugin |
415 | 390 |
416 @param namespace: the XML namespace used for this transport | 391 @param namespace: the XML namespace used for this transport |
417 @param transport_type: type of transport to use (see XEP-0166 §8) | 392 @param transport_type: type of transport to use (see XEP-0166 §8) |
464 ) | 439 ) |
465 content_elt = jingle_elt.addElement("content") | 440 content_elt = jingle_elt.addElement("content") |
466 content_elt["name"] = content_name | 441 content_elt["name"] = content_name |
467 content_elt["creator"] = content_data["creator"] | 442 content_elt["creator"] = content_data["creator"] |
468 | 443 |
469 transport_elt = transport.handler.jingle_session_init(client, session, content_name) | 444 transport_elt = transport.handler.jingle_session_init( |
445 client, session, content_name | |
446 ) | |
470 content_elt.addChild(transport_elt) | 447 content_elt.addChild(transport_elt) |
471 iq_elt.send() | 448 iq_elt.send() |
472 | 449 |
473 def build_action( | 450 def build_action( |
474 self, | 451 self, |
475 client: SatXMPPEntity, | 452 client: SatXMPPEntity, |
476 action: str, | 453 action: str, |
477 session: dict, | 454 session: dict, |
478 content_name: str, | 455 content_name: str, |
479 iq_elt: Optional[xmlstream.IQ] = None, | 456 iq_elt: Optional[xmlstream.IQ] = None, |
480 context_elt: Optional[domish.Element] = None | 457 context_elt: Optional[domish.Element] = None, |
481 ) -> Tuple[xmlstream.IQ, domish.Element]: | 458 ) -> Tuple[xmlstream.IQ, domish.Element]: |
482 """Build an element according to requested action | 459 """Build an element according to requested action |
483 | 460 |
484 @param action: a jingle action (see XEP-0166 §7.2), | 461 @param action: a jingle action (see XEP-0166 §7.2), |
485 session-* actions are not managed here | 462 session-* actions are not managed here |
532 @raise exceptions.NotFound if application can't be found | 509 @raise exceptions.NotFound if application can't be found |
533 """ | 510 """ |
534 try: | 511 try: |
535 return self._applications[namespace] | 512 return self._applications[namespace] |
536 except KeyError: | 513 except KeyError: |
537 raise exceptions.NotFound( | 514 raise exceptions.NotFound(f"No application registered for {namespace}") |
538 f"No application registered for {namespace}" | |
539 ) | |
540 | 515 |
541 def get_content_data(self, content: dict, content_idx: int) -> ContentData: | 516 def get_content_data(self, content: dict, content_idx: int) -> ContentData: |
542 """"Retrieve application and its argument from content""" | 517 """ "Retrieve application and its argument from content""" |
543 app_ns = content["app_ns"] | 518 app_ns = content["app_ns"] |
544 try: | 519 try: |
545 application = self.get_application(app_ns) | 520 application = self.get_application(app_ns) |
546 except exceptions.NotFound as e: | 521 except exceptions.NotFound as e: |
547 raise exceptions.InternalError(str(e)) | 522 raise exceptions.InternalError(str(e)) |
551 try: | 526 try: |
552 content_name = content["name"] | 527 content_name = content["name"] |
553 except KeyError: | 528 except KeyError: |
554 content_name = content["name"] = str(content_idx) | 529 content_name = content["name"] = str(content_idx) |
555 return ContentData( | 530 return ContentData( |
556 application, | 531 application, app_args, app_kwargs, transport_data, content_name |
557 app_args, | |
558 app_kwargs, | |
559 transport_data, | |
560 content_name | |
561 ) | 532 ) |
562 | 533 |
563 async def initiate( | 534 async def initiate( |
564 self, | 535 self, |
565 client: SatXMPPEntity, | 536 client: SatXMPPEntity, |
566 peer_jid: jid.JID, | 537 peer_jid: jid.JID, |
567 contents: List[dict], | 538 contents: List[dict], |
568 encrypted: bool = False, | 539 encrypted: bool = False, |
569 sid: str|None = None, | 540 sid: str | None = None, |
570 **extra_data: Any | 541 **extra_data: Any, |
571 ) -> str: | 542 ) -> str: |
572 """Send a session initiation request | 543 """Send a session initiation request |
573 | 544 |
574 @param peer_jid: jid to establith session with | 545 @param peer_jid: jid to establith session with |
575 @param contents: list of contents to use: | 546 @param contents: list of contents to use: |
589 @param sid: Session ID. | 560 @param sid: Session ID. |
590 If None, one will be generated (and used as return value) | 561 If None, one will be generated (and used as return value) |
591 @return: Sesson ID | 562 @return: Sesson ID |
592 """ | 563 """ |
593 assert contents # there must be at least one content | 564 assert contents # there must be at least one content |
594 if (peer_jid == client.jid | 565 if ( |
595 or client.is_component and peer_jid.host == client.jid.host): | 566 peer_jid == client.jid |
567 or client.is_component | |
568 and peer_jid.host == client.jid.host | |
569 ): | |
596 raise ValueError(_("You can't do a jingle session with yourself")) | 570 raise ValueError(_("You can't do a jingle session with yourself")) |
597 if sid is None: | 571 if sid is None: |
598 sid = str(uuid.uuid4()) | 572 sid = str(uuid.uuid4()) |
599 session = self.create_session( | 573 session = self.create_session( |
600 client, sid, XEP_0166.ROLE_INITIATOR, peer_jid, **extra_data | 574 client, sid, XEP_0166.ROLE_INITIATOR, peer_jid, **extra_data |
601 ) | 575 ) |
602 initiator = session["initiator"] | 576 initiator = session["initiator"] |
603 | 577 |
604 if not await self.host.trigger.async_point( | 578 if not await self.host.trigger.async_point( |
605 "XEP-0166_initiate", | 579 "XEP-0166_initiate", client, session, contents |
606 client, session, contents | |
607 ): | 580 ): |
608 return sid | 581 return sid |
609 | 582 |
610 iq_elt, jingle_elt = self._build_jingle_elt( | 583 iq_elt, jingle_elt = self._build_jingle_elt( |
611 client, session, XEP_0166.A_SESSION_INITIATE | 584 client, session, XEP_0166.A_SESSION_INITIATE |
656 pass | 629 pass |
657 | 630 |
658 # then the description element | 631 # then the description element |
659 application_data["desc_elt"] = desc_elt = await utils.as_deferred( | 632 application_data["desc_elt"] = desc_elt = await utils.as_deferred( |
660 content_data.application.handler.jingle_session_init, | 633 content_data.application.handler.jingle_session_init, |
661 client, session, content_data.content_name, | 634 client, |
662 *content_data.app_args, **content_data.app_kwargs | 635 session, |
636 content_data.content_name, | |
637 *content_data.app_args, | |
638 **content_data.app_kwargs, | |
663 ) | 639 ) |
664 content_elt.addChild(desc_elt) | 640 content_elt.addChild(desc_elt) |
665 | 641 |
666 # and the transport one | 642 # and the transport one |
667 transport_data["transport_elt"] = transport_elt = await utils.as_deferred( | 643 transport_data["transport_elt"] = transport_elt = await utils.as_deferred( |
668 transport.handler.jingle_session_init, | 644 transport.handler.jingle_session_init, |
669 client, session, content_data.content_name, | 645 client, |
646 session, | |
647 content_data.content_name, | |
670 ) | 648 ) |
671 content_elt.addChild(transport_elt) | 649 content_elt.addChild(transport_elt) |
672 | 650 |
673 if not await self.host.trigger.async_point( | 651 if not await self.host.trigger.async_point( |
674 "XEP-0166_initiate_elt_built", | 652 "XEP-0166_initiate_elt_built", client, session, iq_elt, jingle_elt |
675 client, session, iq_elt, jingle_elt | |
676 ): | 653 ): |
677 return sid | 654 return sid |
678 | 655 |
679 # processing is done, we can remove elements | 656 # processing is done, we can remove elements |
680 for content_data in session_contents.values(): | 657 for content_data in session_contents.values(): |
735 | 712 |
736 def _on_jingle_request(self, request: domish.Element, client: SatXMPPEntity) -> None: | 713 def _on_jingle_request(self, request: domish.Element, client: SatXMPPEntity) -> None: |
737 defer.ensureDeferred(self.on_jingle_request(client, request)) | 714 defer.ensureDeferred(self.on_jingle_request(client, request)) |
738 | 715 |
739 async def on_jingle_request( | 716 async def on_jingle_request( |
740 self, | 717 self, client: SatXMPPEntity, request: domish.Element |
741 client: SatXMPPEntity, | |
742 request: domish.Element | |
743 ) -> None: | 718 ) -> None: |
744 """Called when any jingle request is received | 719 """Called when any jingle request is received |
745 | 720 |
746 The request will then be dispatched to appropriate method | 721 The request will then be dispatched to appropriate method |
747 according to current state | 722 according to current state |
801 session = self.get_session(client, sid) | 776 session = self.get_session(client, sid) |
802 except exceptions.NotFound: | 777 except exceptions.NotFound: |
803 # XXX: we store local_jid using request['to'] because for a component the | 778 # XXX: we store local_jid using request['to'] because for a component the |
804 # jid used may not be client.jid (if a local part is used). | 779 # jid used may not be client.jid (if a local part is used). |
805 session = self.create_session( | 780 session = self.create_session( |
806 client, sid, XEP_0166.ROLE_RESPONDER, peer_jid, jid.JID(request['to']) | 781 client, sid, XEP_0166.ROLE_RESPONDER, peer_jid, jid.JID(request["to"]) |
807 ) | 782 ) |
808 else: | 783 else: |
809 if session["peer_jid"] != peer_jid: | 784 if session["peer_jid"] != peer_jid: |
810 log.warning( | 785 log.warning( |
811 "sid conflict ({}), the jid doesn't match. Can be a collision, a " | 786 "sid conflict ({}), the jid doesn't match. Can be a collision, a " |
812 "hack attempt, or a bad sid generation".format( | 787 "hack attempt, or a bad sid generation".format(sid) |
813 sid | |
814 ) | |
815 ) | 788 ) |
816 self.sendError(client, "service-unavailable", sid, request) | 789 self.sendError(client, "service-unavailable", sid, request) |
817 return | 790 return |
818 if session["id"] != sid: | 791 if session["id"] != sid: |
819 log.error("session id doesn't match") | 792 log.error("session id doesn't match") |
847 session: dict, | 820 session: dict, |
848 request: domish.Element, | 821 request: domish.Element, |
849 client: SatXMPPEntity, | 822 client: SatXMPPEntity, |
850 new: bool = False, | 823 new: bool = False, |
851 creator: str = ROLE_INITIATOR, | 824 creator: str = ROLE_INITIATOR, |
852 with_application: bool =True, | 825 with_application: bool = True, |
853 with_transport: bool = True, | 826 with_transport: bool = True, |
854 store_in_session: bool = True, | 827 store_in_session: bool = True, |
855 ) -> Dict[str, dict]: | 828 ) -> Dict[str, dict]: |
856 """Parse contents elements and fill contents_dict accordingly | 829 """Parse contents elements and fill contents_dict accordingly |
857 | 830 |
924 raise exceptions.CancelError | 897 raise exceptions.CancelError |
925 | 898 |
926 try: | 899 try: |
927 application = self._applications[app_ns] | 900 application = self._applications[app_ns] |
928 except KeyError: | 901 except KeyError: |
929 log.warning( | 902 log.warning("Unmanaged application namespace [{}]".format(app_ns)) |
930 "Unmanaged application namespace [{}]".format(app_ns) | |
931 ) | |
932 self.sendError( | 903 self.sendError( |
933 client, "service-unavailable", session["id"], request | 904 client, "service-unavailable", session["id"], request |
934 ) | 905 ) |
935 raise exceptions.CancelError | 906 raise exceptions.CancelError |
936 | 907 |
996 transp_method_name: Optional[str] = "jingle_handler", | 967 transp_method_name: Optional[str] = "jingle_handler", |
997 app_default_cb: Optional[Callable] = None, | 968 app_default_cb: Optional[Callable] = None, |
998 transp_default_cb: Optional[Callable] = None, | 969 transp_default_cb: Optional[Callable] = None, |
999 delete: bool = True, | 970 delete: bool = True, |
1000 elements: bool = True, | 971 elements: bool = True, |
1001 force_element: Optional[domish.Element] = None | 972 force_element: Optional[domish.Element] = None, |
1002 ) -> list[Any]: | 973 ) -> list[Any]: |
1003 """Call application and transport plugin methods for all contents | 974 """Call application and transport plugin methods for all contents |
1004 | 975 |
1005 @param action: jingle action name | 976 @param action: jingle action name |
1006 @param session: jingle session data | 977 @param session: jingle session data |
1057 async def on_session_initiate( | 1028 async def on_session_initiate( |
1058 self, | 1029 self, |
1059 client: SatXMPPEntity, | 1030 client: SatXMPPEntity, |
1060 request: domish.Element, | 1031 request: domish.Element, |
1061 jingle_elt: domish.Element, | 1032 jingle_elt: domish.Element, |
1062 session: Dict[str, Any] | 1033 session: Dict[str, Any], |
1063 ) -> None: | 1034 ) -> None: |
1064 """Called on session-initiate action | 1035 """Called on session-initiate action |
1065 | 1036 |
1066 The "jingle_request_confirmation" method of each application will be called | 1037 The "jingle_request_confirmation" method of each application will be called |
1067 (or self.jingle_request_confirmation_default if the former doesn't exist). | 1038 (or self.jingle_request_confirmation_default if the former doesn't exist). |
1091 return | 1062 return |
1092 | 1063 |
1093 # at this point we can send the <iq/> result to confirm reception of the request | 1064 # at this point we can send the <iq/> result to confirm reception of the request |
1094 client.send(xmlstream.toResponse(request, "result")) | 1065 client.send(xmlstream.toResponse(request, "result")) |
1095 | 1066 |
1096 | |
1097 assert "jingle_elt" not in session | 1067 assert "jingle_elt" not in session |
1098 session["jingle_elt"] = jingle_elt | 1068 session["jingle_elt"] = jingle_elt |
1099 if not await self.host.trigger.async_point( | 1069 if not await self.host.trigger.async_point( |
1100 "XEP-0166_on_session_initiate", | 1070 "XEP-0166_on_session_initiate", client, session, request, jingle_elt |
1101 client, session, request, jingle_elt | |
1102 ): | 1071 ): |
1103 return | 1072 return |
1104 | 1073 |
1105 await self._call_plugins( | 1074 await self._call_plugins( |
1106 client, | 1075 client, XEP_0166.A_PREPARE_CONFIRMATION, session, delete=False |
1107 XEP_0166.A_PREPARE_CONFIRMATION, | |
1108 session, | |
1109 delete=False | |
1110 ) | 1076 ) |
1111 | 1077 |
1112 # we now request each application plugin confirmation | 1078 # we now request each application plugin confirmation |
1113 # and if all are accepted, we can accept the session | 1079 # and if all are accepted, we can accept the session |
1114 try: | 1080 try: |
1129 async def _confirmation_cb( | 1095 async def _confirmation_cb( |
1130 self, | 1096 self, |
1131 confirmations: list[bool], | 1097 confirmations: list[bool], |
1132 session: dict, | 1098 session: dict, |
1133 jingle_elt: domish.Element, | 1099 jingle_elt: domish.Element, |
1134 client: SatXMPPEntity | 1100 client: SatXMPPEntity, |
1135 ) -> None: | 1101 ) -> None: |
1136 """Method called when confirmation from user has been received | 1102 """Method called when confirmation from user has been received |
1137 | 1103 |
1138 This method is only called for the responder | 1104 This method is only called for the responder |
1139 @param confirm_results: all True if session is accepted | 1105 @param confirm_results: all True if session is accepted |
1148 return | 1114 return |
1149 | 1115 |
1150 iq_elt, jingle_elt = self._build_jingle_elt( | 1116 iq_elt, jingle_elt = self._build_jingle_elt( |
1151 client, session, XEP_0166.A_SESSION_ACCEPT | 1117 client, session, XEP_0166.A_SESSION_ACCEPT |
1152 ) | 1118 ) |
1153 jingle_elt["responder"] = session['local_jid'].full() | 1119 jingle_elt["responder"] = session["local_jid"].full() |
1154 session["jingle_elt"] = jingle_elt | 1120 session["jingle_elt"] = jingle_elt |
1155 | 1121 |
1156 # contents | 1122 # contents |
1157 | 1123 |
1158 try: | 1124 try: |
1217 except StopIteration: | 1183 except StopIteration: |
1218 log.warning("No reason given for session termination") | 1184 log.warning("No reason given for session termination") |
1219 reason_elt = parent_elt.addElement("reason") | 1185 reason_elt = parent_elt.addElement("reason") |
1220 return reason_elt | 1186 return reason_elt |
1221 | 1187 |
1222 def parse_reason_elt(self, reason_elt: domish.Element) -> tuple[str|None, str|None]: | 1188 def parse_reason_elt( |
1189 self, reason_elt: domish.Element | |
1190 ) -> tuple[str | None, str | None]: | |
1223 """Parse a <reason> element | 1191 """Parse a <reason> element |
1224 | 1192 |
1225 @return: reason found, and text if any | 1193 @return: reason found, and text if any |
1226 """ | 1194 """ |
1227 reason, text = None, None | 1195 reason, text = None, None |
1240 async def on_session_terminate( | 1208 async def on_session_terminate( |
1241 self, | 1209 self, |
1242 client: SatXMPPEntity, | 1210 client: SatXMPPEntity, |
1243 request: domish.Element, | 1211 request: domish.Element, |
1244 jingle_elt: domish.Element, | 1212 jingle_elt: domish.Element, |
1245 session: dict | 1213 session: dict, |
1246 ) -> None: | 1214 ) -> None: |
1247 # TODO: check reason, display a message to user if needed | 1215 # TODO: check reason, display a message to user if needed |
1248 log.debug(f"Jingle Session {session['id']} terminated") | 1216 log.debug(f"Jingle Session {session['id']} terminated") |
1249 reason_elt = self.get_reason_elt(jingle_elt) | 1217 reason_elt = self.get_reason_elt(jingle_elt) |
1250 | 1218 |
1284 # and change the state | 1252 # and change the state |
1285 session["state"] = XEP_0166.STATE_ACTIVE | 1253 session["state"] = XEP_0166.STATE_ACTIVE |
1286 session["jingle_elt"] = jingle_elt | 1254 session["jingle_elt"] = jingle_elt |
1287 | 1255 |
1288 await self._call_plugins( | 1256 await self._call_plugins( |
1289 client, | 1257 client, XEP_0166.A_PREPARE_INITIATOR, session, delete=False |
1290 XEP_0166.A_PREPARE_INITIATOR, | |
1291 session, | |
1292 delete=False | |
1293 ) | 1258 ) |
1294 | 1259 |
1295 await self._call_plugins(client, XEP_0166.A_SESSION_ACCEPT, session) | 1260 await self._call_plugins(client, XEP_0166.A_SESSION_ACCEPT, session) |
1296 | 1261 |
1297 # after negociations we start the transfer | 1262 # after negociations we start the transfer |
1396 ) | 1361 ) |
1397 for content_name, content_data, transport, transport_elt in to_replace: | 1362 for content_name, content_data, transport, transport_elt in to_replace: |
1398 # we can now actually replace the transport | 1363 # we can now actually replace the transport |
1399 await utils.as_deferred( | 1364 await utils.as_deferred( |
1400 content_data["transport"].handler.jingle_handler, | 1365 content_data["transport"].handler.jingle_handler, |
1401 client, XEP_0166.A_DESTROY, session, content_name, None | 1366 client, |
1367 XEP_0166.A_DESTROY, | |
1368 session, | |
1369 content_name, | |
1370 None, | |
1402 ) | 1371 ) |
1403 content_data["transport"] = transport | 1372 content_data["transport"] = transport |
1404 content_data["transport_data"].clear() | 1373 content_data["transport_data"].clear() |
1405 # and build the element | 1374 # and build the element |
1406 content_elt = accept_jingle_elt.addElement("content") | 1375 content_elt = accept_jingle_elt.addElement("content") |
1407 content_elt["name"] = content_name | 1376 content_elt["name"] = content_name |
1408 content_elt["creator"] = content_data["creator"] | 1377 content_elt["creator"] = content_data["creator"] |
1409 # we notify the transport and insert its <transport/> in the answer | 1378 # we notify the transport and insert its <transport/> in the answer |
1410 accept_transport_elt = await utils.as_deferred( | 1379 accept_transport_elt = await utils.as_deferred( |
1411 transport.handler.jingle_handler, | 1380 transport.handler.jingle_handler, |
1412 client, XEP_0166.A_TRANSPORT_REPLACE, session, content_name, transport_elt | 1381 client, |
1382 XEP_0166.A_TRANSPORT_REPLACE, | |
1383 session, | |
1384 content_name, | |
1385 transport_elt, | |
1413 ) | 1386 ) |
1414 content_elt.addChild(accept_transport_elt) | 1387 content_elt.addChild(accept_transport_elt) |
1415 # there is no confirmation needed here, so we can directly prepare it | 1388 # there is no confirmation needed here, so we can directly prepare it |
1416 await utils.as_deferred( | 1389 await utils.as_deferred( |
1417 transport.handler.jingle_handler, | 1390 transport.handler.jingle_handler, |
1418 client, XEP_0166.A_PREPARE_RESPONDER, session, content_name, None | 1391 client, |
1392 XEP_0166.A_PREPARE_RESPONDER, | |
1393 session, | |
1394 content_name, | |
1395 None, | |
1419 ) | 1396 ) |
1420 | 1397 |
1421 iq_elt.send() | 1398 iq_elt.send() |
1422 | 1399 |
1423 async def on_transport_accept( | 1400 async def on_transport_accept( |
1424 self, | 1401 self, |
1425 client: SatXMPPEntity, | 1402 client: SatXMPPEntity, |
1426 request: domish.Element, | 1403 request: domish.Element, |
1427 jingle_elt: domish.Element, | 1404 jingle_elt: domish.Element, |
1428 session: dict | 1405 session: dict, |
1429 ) -> None: | 1406 ) -> None: |
1430 """Method called once transport replacement is accepted | 1407 """Method called once transport replacement is accepted |
1431 | 1408 |
1432 @param client: SatXMPPEntity instance | 1409 @param client: SatXMPPEntity instance |
1433 @param request: full <iq> request | 1410 @param request: full <iq> request |
1470 def on_transport_info( | 1447 def on_transport_info( |
1471 self, | 1448 self, |
1472 client: SatXMPPEntity, | 1449 client: SatXMPPEntity, |
1473 request: domish.Element, | 1450 request: domish.Element, |
1474 jingle_elt: domish.Element, | 1451 jingle_elt: domish.Element, |
1475 session: dict | 1452 session: dict, |
1476 ) -> None: | 1453 ) -> None: |
1477 """Method called when a transport-info action is received from other peer | 1454 """Method called when a transport-info action is received from other peer |
1478 | 1455 |
1479 The request is parsed, and jingle_handler is called on concerned transport | 1456 The request is parsed, and jingle_handler is called on concerned transport |
1480 plugin(s) | 1457 plugin(s) |
1485 """ | 1462 """ |
1486 log.debug(f"Jingle session {session['id']} has been accepted") | 1463 log.debug(f"Jingle session {session['id']} has been accepted") |
1487 | 1464 |
1488 try: | 1465 try: |
1489 parsed_contents = self._parse_elements( | 1466 parsed_contents = self._parse_elements( |
1490 jingle_elt, session, request, client, with_application=False, | 1467 jingle_elt, |
1491 store_in_session=False | 1468 session, |
1469 request, | |
1470 client, | |
1471 with_application=False, | |
1472 store_in_session=False, | |
1492 ) | 1473 ) |
1493 except exceptions.CancelError: | 1474 except exceptions.CancelError: |
1494 return | 1475 return |
1495 | 1476 |
1496 # The parsing was OK, we send the <iq> result | 1477 # The parsing was OK, we send the <iq> result |