plugin XEP-0166: refactoring, and various improvments:
- add models for transport and applications handlers and linked data - split models into separate file - some type hints - some documentation comments - add actions to prepare confirmation, useful to do initial parsing of all contents - application arg/kwargs and some transport data can be initialised during Jingle `initiate` call, this is notably useful when a call is made with transport data (this is the call for A/V calls where codecs and ICE candidate can be specified when starting a call) - session data can be specified during Jingle `initiate` call - new `store_in_session` argument in `_parse_elements`, which can be used to avoid race-condition when a context element (<decription> or <transport>) is being parsed for an action while an other action happens (like `transport-info`) - don't sed `sid` in `transport_elt` during a `transport-info` action anymore in `build_action`: this is specific to Jingle File Transfer and has been moved there rel 419
This commit is contained in:
parent
45e15eb189
commit
9141922d57
6 changed files with 493 additions and 152 deletions
|
|
@ -27,6 +27,7 @@ from twisted.words.xish import domish
|
|||
|
||||
class SatXMPPEntity:
|
||||
|
||||
profile: str
|
||||
jid: t_jid.JID
|
||||
is_component: bool
|
||||
server_jid: t_jid.JID
|
||||
|
|
|
|||
1390
sat/plugins/plugin_xep_0166/__init__.py
Normal file
1390
sat/plugins/plugin_xep_0166/__init__.py
Normal file
File diff suppressed because it is too large
Load diff
183
sat/plugins/plugin_xep_0166/models.py
Normal file
183
sat/plugins/plugin_xep_0166/models.py
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Libervia plugin for Jingle (XEP-0166)
|
||||
# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import abc
|
||||
from dataclasses import dataclass
|
||||
from typing import Awaitable, Callable, Union
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.words.xish import domish
|
||||
|
||||
from sat.core.core_types import SatXMPPEntity
|
||||
from sat.core.i18n import _
|
||||
|
||||
|
||||
class BaseApplicationHandler(abc.ABC):
|
||||
|
||||
@abc.abstractmethod
|
||||
def jingle_request_confirmation(
|
||||
self,
|
||||
client: SatXMPPEntity,
|
||||
action: str,
|
||||
session: dict,
|
||||
content_name: str,
|
||||
desc_elt: domish.Element,
|
||||
) -> Union[
|
||||
Callable[..., Union[bool, defer.Deferred]],
|
||||
Callable[..., Awaitable[bool]]
|
||||
]:
|
||||
"""
|
||||
If present, it is called on when session must be accepted.
|
||||
If not present, a generic accept dialog will be used.
|
||||
|
||||
@param session: Jingle Session
|
||||
@param desc_elt: <description> element
|
||||
@return: True if the session is accepted.
|
||||
A Deferred can be returned.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def jingle_session_init(
|
||||
self,
|
||||
client: SatXMPPEntity,
|
||||
session: dict,
|
||||
content_name: str,
|
||||
*args, **kwargs
|
||||
) -> Union[
|
||||
Callable[..., domish.Element],
|
||||
Callable[..., Awaitable[domish.Element]]
|
||||
]:
|
||||
"""
|
||||
Must return the domish.Element used for initial content.
|
||||
|
||||
@param client: SatXMPPEntity instance
|
||||
@param session: Jingle Session
|
||||
@param content_name: Name of the content
|
||||
@return: The domish.Element used for initial content
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def jingle_handler(
|
||||
self,
|
||||
client: SatXMPPEntity,
|
||||
action: str,
|
||||
session: dict,
|
||||
content_name: str,
|
||||
transport_elt: domish.Element
|
||||
) -> Union[
|
||||
Callable[..., None],
|
||||
Callable[..., Awaitable[None]]
|
||||
]:
|
||||
"""
|
||||
Called on several actions to negotiate the application or transport.
|
||||
|
||||
@param client: SatXMPPEntity instance
|
||||
@param action: Jingle action
|
||||
@param session: Jingle Session
|
||||
@param content_name: Name of the content
|
||||
@param transport_elt: Transport element
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def jingle_terminate(
|
||||
self,
|
||||
reason_elt: domish.Element
|
||||
) -> Union[
|
||||
Callable[..., None],
|
||||
Callable[..., Awaitable[None]]
|
||||
]:
|
||||
"""
|
||||
Called on session terminate, with reason_elt.
|
||||
May be used to clean session.
|
||||
|
||||
@param reason_elt: Reason element
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BaseTransportHandler(abc.ABC):
|
||||
|
||||
@abc.abstractmethod
|
||||
def jingle_session_init(
|
||||
self,
|
||||
client: SatXMPPEntity,
|
||||
session: dict,
|
||||
content_name: str,
|
||||
*args, **kwargs
|
||||
) -> Union[
|
||||
Callable[..., domish.Element],
|
||||
Callable[..., Awaitable[domish.Element]]
|
||||
]:
|
||||
"""
|
||||
Must return the domish.Element used for initial content.
|
||||
|
||||
@param client: SatXMPPEntity instance
|
||||
@param session: Jingle Session
|
||||
@param content_name: Name of the content
|
||||
@return: The domish.Element used for initial content
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def jingle_handler(
|
||||
self,
|
||||
client: SatXMPPEntity,
|
||||
action: str,
|
||||
session: dict,
|
||||
content_name: str,
|
||||
transport_elt: domish.Element
|
||||
) -> Union[
|
||||
Callable[..., None],
|
||||
Callable[..., Awaitable[None]]
|
||||
]:
|
||||
"""
|
||||
Called on several actions to negotiate the application or transport.
|
||||
|
||||
@param client: SatXMPPEntity instance
|
||||
@param action: Jingle action
|
||||
@param session: Jingle Session
|
||||
@param content_name: Name of the content
|
||||
@param transport_elt: Transport element
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ApplicationData:
|
||||
namespace: str
|
||||
handler: BaseApplicationHandler
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TransportData:
|
||||
namespace: str
|
||||
handler: BaseTransportHandler
|
||||
priority: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ContentData:
|
||||
application: ApplicationData
|
||||
app_args: list
|
||||
app_kwargs: dict
|
||||
transport_data: dict
|
||||
content_name: str
|
||||
|
|
@ -167,7 +167,7 @@ class XEP_0215:
|
|||
|
||||
Response is cached after first query
|
||||
|
||||
@param entity: XMPP entity to query. Defaut to our own server
|
||||
@param entity: XMPP entity to query. Default to our own server
|
||||
@return: found services
|
||||
"""
|
||||
if entity is None:
|
||||
|
|
|
|||
|
|
@ -161,9 +161,11 @@ class XEP_0260(object):
|
|||
cf XEP-0260 § 2.4
|
||||
"""
|
||||
# now that the proxy is activated, we have to inform other peer
|
||||
content_data = session["contents"][content_name]
|
||||
iq_elt, transport_elt = self._j.build_action(
|
||||
client, self._j.A_TRANSPORT_INFO, session, content_name
|
||||
)
|
||||
transport_elt["sid"] = content_data["transport_data"]["sid"]
|
||||
activated_elt = transport_elt.addElement("activated")
|
||||
activated_elt["cid"] = candidate.id
|
||||
iq_elt.send()
|
||||
|
|
@ -175,9 +177,11 @@ class XEP_0260(object):
|
|||
"""
|
||||
# TODO: fallback to IBB
|
||||
# now that the proxy is activated, we have to inform other peer
|
||||
content_data = session["contents"][content_name]
|
||||
iq_elt, transport_elt = self._j.build_action(
|
||||
client, self._j.A_TRANSPORT_INFO, session, content_name
|
||||
)
|
||||
transport_elt["sid"] = content_data["transport_data"]["sid"]
|
||||
transport_elt.addElement("proxy-error")
|
||||
iq_elt.send()
|
||||
log.warning(
|
||||
|
|
@ -200,6 +204,7 @@ class XEP_0260(object):
|
|||
@param client(unicode): %(doc_client)s
|
||||
"""
|
||||
|
||||
content_data = session["contents"][content_name]
|
||||
transport_data["best_candidate"] = candidate
|
||||
# we need to disconnect all non selected candidates before removing them
|
||||
for c in transport_data["peer_candidates"]:
|
||||
|
|
@ -210,6 +215,7 @@ class XEP_0260(object):
|
|||
iq_elt, transport_elt = self._j.build_action(
|
||||
client, self._j.A_TRANSPORT_INFO, session, content_name
|
||||
)
|
||||
transport_elt["sid"] = content_data["transport_data"]["sid"]
|
||||
if candidate is None:
|
||||
log.warning("Can't connect to any peer candidate")
|
||||
candidate_elt = transport_elt.addElement("candidate-error")
|
||||
|
|
|
|||
|
|
@ -105,22 +105,30 @@ class XEP_0353:
|
|||
|
||||
mess_data = self.build_message_data(client, peer_jid, "propose", session['id'])
|
||||
for content in contents:
|
||||
application, app_args, app_kwargs, content_name = self._j.get_content_data(
|
||||
content_data = self._j.get_content_data(
|
||||
content)
|
||||
try:
|
||||
jingle_description_elt = application.handler.jingle_description_elt
|
||||
jingle_description_elt = (
|
||||
content_data.application.handler.jingle_description_elt
|
||||
)
|
||||
except AttributeError:
|
||||
log.debug(f"no jingle_description_elt set for {application.handler}")
|
||||
log.debug(
|
||||
"no jingle_description_elt set for "
|
||||
f"{content_data.application.handler}"
|
||||
)
|
||||
description_elt = domish.Element((content["app_ns"], "description"))
|
||||
else:
|
||||
description_elt = await utils.as_deferred(
|
||||
jingle_description_elt,
|
||||
client, session, content_name, *app_args, **app_kwargs
|
||||
client, session, content_data.content_name, *content_data.app_args,
|
||||
**content_data.app_kwargs
|
||||
)
|
||||
mess_data["xml"].propose.addChild(description_elt)
|
||||
response_d = defer.Deferred()
|
||||
# we wait for 2 min before cancelling the session init
|
||||
response_d.addTimeout(2*60, reactor)
|
||||
# response_d.addTimeout(2*60, reactor)
|
||||
# FIXME: let's application decide timeout?
|
||||
response_d.addTimeout(2, reactor)
|
||||
client._xep_0353_pending_sessions[session['id']] = response_d
|
||||
await client.send_message_data(mess_data)
|
||||
try:
|
||||
|
|
@ -158,7 +166,7 @@ class XEP_0353:
|
|||
if peer_jid.userhostJID() not in client.roster:
|
||||
app_ns = elt.description.uri
|
||||
try:
|
||||
application = self._j.getApplication(app_ns)
|
||||
application = self._j.get_application(app_ns)
|
||||
human_name = getattr(application.handler, "human_name", application.name)
|
||||
except (exceptions.NotFound, AttributeError):
|
||||
if app_ns.startswith("urn:xmpp:jingle:apps:"):
|
||||
|
|
@ -193,6 +201,7 @@ class XEP_0353:
|
|||
else:
|
||||
await client.presence.available(peer_jid)
|
||||
session_id = elt["id"]
|
||||
# FIXME: accept is not used anymore in new specification, check it and remove it
|
||||
mess_data = self.build_message_data(
|
||||
client, client.jid.userhostJID(), "accept", session_id)
|
||||
await client.send_message_data(mess_data)
|
||||
|
|
|
|||
Loading…
Reference in a new issue