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
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