diff --git a/sipsimple/account/registration.py b/sipsimple/account/registration.py index 0a4b88b9..95b9beb0 100644 --- a/sipsimple/account/registration.py +++ b/sipsimple/account/registration.py @@ -1,302 +1,309 @@ """Implements the registration handler""" __all__ = ['Registrar'] import random from time import time from application.notification import IObserver, NotificationCenter, NotificationData from application.python import Null, limit from eventlib import coros, proc from twisted.internet import reactor from zope.interface import implementer from sipsimple.core import ContactHeader, FromHeader, Header, Registration, RouteHeader, SIPURI, SIPCoreError, NoGRUU from sipsimple.configuration.settings import SIPSimpleSettings from sipsimple.lookup import DNSLookup, DNSLookupError from sipsimple.threading import run_in_twisted_thread from sipsimple.threading.green import Command, run_in_green_thread Command.register_defaults('register', refresh_interval=None) class SIPRegistrationDidFail(Exception): def __init__(self, data): self.data = data class SIPRegistrationDidNotEnd(Exception): def __init__(self, data): self.data = data class RegistrationError(Exception): def __init__(self, error, retry_after, refresh_interval=None): self.error = error self.retry_after = retry_after self.refresh_interval = refresh_interval @implementer(IObserver) class Registrar(object): def __init__(self, account): self.account = account self.started = False self.active = False self.registered = False self._command_proc = None self._command_channel = coros.queue() self._data_channel = coros.queue() self._registration = None self._dns_wait = 1 self._register_wait = 1 self._registration_timer = None def start(self): if self.started: return self.started = True notification_center = NotificationCenter() notification_center.add_observer(self, name='CFGSettingsObjectDidChange', sender=self.account) notification_center.add_observer(self, name='CFGSettingsObjectDidChange', sender=SIPSimpleSettings()) notification_center.add_observer(self, name='NetworkConditionsDidChange') self._command_proc = proc.spawn(self._run) if self.account.sip.register: self.activate() def stop(self): if not self.started: return self.started = False self.active = False notification_center = NotificationCenter() notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=self.account) notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=SIPSimpleSettings()) notification_center.remove_observer(self, name='NetworkConditionsDidChange') command = Command('terminate') self._command_channel.send(command) command.wait() self._command_proc = None def activate(self): if not self.started: raise RuntimeError("not started") self.active = True self._command_channel.send(Command('register')) def deactivate(self): if not self.started: raise RuntimeError("not started") self.active = False self._command_channel.send(Command('unregister')) def reregister(self): if self.active: self._command_channel.send(Command('unregister')) self._command_channel.send(Command('register')) def _run(self): while True: command = self._command_channel.wait() handler = getattr(self, '_CH_%s' % command.name) handler(command) def _CH_register(self, command): notification_center = NotificationCenter() settings = SIPSimpleSettings() if self._registration_timer is not None and self._registration_timer.active(): self._registration_timer.cancel() self._registration_timer = None # Initialize the registration if self._registration is None: duration = command.refresh_interval or self.account.sip.register_interval - self._registration = Registration(FromHeader(self.account.uri, self.account.display_name), credentials=self.account.credentials, duration=duration, extra_headers=[Header('Supported', 'gruu')]) + self._registration = Registration(FromHeader(self.account.uri, self.account.display_name), + credentials=self.account.credentials, + duration=duration, + extra_headers=[Header('Supported', 'gruu')]) + notification_center.add_observer(self, sender=self._registration) notification_center.post_notification('SIPAccountWillRegister', sender=self.account) else: notification_center.post_notification('SIPAccountRegistrationWillRefresh', sender=self.account) try: # Lookup routes if self.account.sip.outbound_proxy is not None and self.account.sip.outbound_proxy.transport in settings.sip.transport_list: uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, parameters={'transport': self.account.sip.outbound_proxy.transport}) else: uri = SIPURI(host=self.account.id.domain) lookup = DNSLookup() try: routes = lookup.lookup_sip_proxy(uri, settings.sip.transport_list).wait() except DNSLookupError as e: retry_after = random.uniform(self._dns_wait, 2*self._dns_wait) self._dns_wait = limit(2*self._dns_wait, max=30) raise RegistrationError('DNS lookup failed: %s' % e, retry_after=retry_after) else: self._dns_wait = 1 # Register by trying each route in turn register_timeout = time() + 30 for route in routes: remaining_time = register_timeout-time() if remaining_time > 0: try: contact_uri = self.account.contact[NoGRUU, route] except KeyError: continue contact_header = ContactHeader(contact_uri) - contact_header.parameters['+sip.instance'] = '"<%s>"' % settings.instance_id + instance_id = '"<%s>"' % settings.instance_id + + contact_header.parameters[b"+sip.instance"] = instance_id.encode() if self.account.nat_traversal.use_ice: - contact_header.parameters['+sip.ice'] = None + contact_header.parameters[b"+sip.ice"] = None route_header = RouteHeader(route.uri) try: self._registration.register(contact_header, route_header, timeout=limit(remaining_time, min=1, max=10)) except SIPCoreError: raise RegistrationError('Internal error', retry_after=5) try: while True: notification = self._data_channel.wait() if notification.name == 'SIPRegistrationDidSucceed': break if notification.name == 'SIPRegistrationDidEnd': raise RegistrationError('Registration expired', retry_after=0) # registration expired while we were trying to re-register except SIPRegistrationDidFail as e: notification_data = NotificationData(code=e.data.code, reason=e.data.reason, registration=self._registration, registrar=route) notification_center.post_notification('SIPAccountRegistrationGotAnswer', sender=self.account, data=notification_data) if e.data.code == 401: # Authentication failed, so retry the registration in some time raise RegistrationError('Authentication failed', retry_after=random.uniform(60, 120)) elif e.data.code == 423: # Get the value of the Min-Expires header if e.data.min_expires is not None and e.data.min_expires > self.account.sip.register_interval: refresh_interval = e.data.min_expires else: refresh_interval = None raise RegistrationError('Interval too short', retry_after=random.uniform(60, 120), refresh_interval=refresh_interval) else: # Otherwise just try the next route - continue + #continue + break else: notification_data = NotificationData(code=notification.data.code, reason=notification.data.reason, registration=self._registration, registrar=route) notification_center.post_notification('SIPAccountRegistrationGotAnswer', sender=self.account, data=notification_data) self.registered = True # Save GRUU try: header = next(header for header in notification.data.contact_header_list if header.parameters.get('+sip.instance', '').strip('"<>') == settings.instance_id) except StopIteration: self.account.contact.public_gruu = None self.account.contact.temporary_gruu = None else: public_gruu = header.parameters.get('pub-gruu', None) temporary_gruu = header.parameters.get('temp-gruu', None) try: self.account.contact.public_gruu = SIPURI.parse(public_gruu.strip('"')) except (AttributeError, SIPCoreError): self.account.contact.public_gruu = None try: self.account.contact.temporary_gruu = SIPURI.parse(temporary_gruu.strip('"')) except (AttributeError, SIPCoreError): self.account.contact.temporary_gruu = None notification_data = NotificationData(contact_header=notification.data.contact_header, contact_header_list=notification.data.contact_header_list, expires=notification.data.expires_in, registrar=route) notification_center.post_notification('SIPAccountRegistrationDidSucceed', sender=self.account, data=notification_data) self._register_wait = 1 command.signal() break else: # There are no more routes to try, reschedule the registration retry_after = random.uniform(self._register_wait, 2*self._register_wait) self._register_wait = limit(self._register_wait*2, max=30) raise RegistrationError('No more routes to try', retry_after=retry_after) except RegistrationError as e: self.registered = False notification_center.remove_observer(self, sender=self._registration) notification_center.post_notification('SIPAccountRegistrationDidFail', sender=self.account, data=NotificationData(error=e.error, retry_after=e.retry_after)) def register(): if self.active: self._command_channel.send(Command('register', command.event, refresh_interval=e.refresh_interval)) self._registration_timer = None self._registration_timer = reactor.callLater(e.retry_after, register) self._registration = None self.account.contact.public_gruu = None self.account.contact.temporary_gruu = None def _CH_unregister(self, command): # Cancel any timer which would restart the registration process if self._registration_timer is not None and self._registration_timer.active(): self._registration_timer.cancel() self._registration_timer = None registered = self.registered self.registered = False if self._registration is not None: notification_center = NotificationCenter() if registered: self._registration.end(timeout=2) try: while True: notification = self._data_channel.wait() if notification.name == 'SIPRegistrationDidEnd': break except (SIPRegistrationDidFail, SIPRegistrationDidNotEnd) as e: notification_center.post_notification('SIPAccountRegistrationDidNotEnd', sender=self.account, data=NotificationData(code=e.data.code, reason=e.data.reason, registration=self._registration)) else: notification_center.post_notification('SIPAccountRegistrationDidEnd', sender=self.account, data=NotificationData(registration=self._registration)) notification_center.remove_observer(self, sender=self._registration) self._registration = None self.account.contact.public_gruu = None self.account.contact.temporary_gruu = None command.signal() def _CH_terminate(self, command): self._CH_unregister(command) raise proc.ProcExit @run_in_twisted_thread def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_SIPRegistrationDidSucceed(self, notification): if notification.sender is self._registration: self._data_channel.send(notification) def _NH_SIPRegistrationDidFail(self, notification): if notification.sender is self._registration: self._data_channel.send_exception(SIPRegistrationDidFail(notification.data)) def _NH_SIPRegistrationDidEnd(self, notification): if notification.sender is self._registration: self._data_channel.send(notification) def _NH_SIPRegistrationDidNotEnd(self, notification): if notification.sender is self._registration: self._data_channel.send_exception(SIPRegistrationDidNotEnd(notification.data)) def _NH_SIPRegistrationWillExpire(self, notification): if self.active: self._command_channel.send(Command('register')) @run_in_green_thread def _NH_CFGSettingsObjectDidChange(self, notification): if not self.started: return if 'enabled' in notification.data.modified: return # global account activation is handled separately by the account itself elif 'sip.register' in notification.data.modified: if self.account.sip.register: self.activate() else: self.deactivate() elif self.active and {'__id__', 'auth.password', 'auth.username', 'nat_traversal.use_ice', 'sip.outbound_proxy', 'sip.transport_list', 'sip.register_interval'}.intersection(notification.data.modified): self._command_channel.send(Command('unregister')) self._command_channel.send(Command('register')) def _NH_NetworkConditionsDidChange(self, notification): if self.active: self._command_channel.send(Command('unregister')) self._command_channel.send(Command('register')) diff --git a/sipsimple/core/_core.headers.pxi b/sipsimple/core/_core.headers.pxi index 1ae29ab5..672a3ad6 100644 --- a/sipsimple/core/_core.headers.pxi +++ b/sipsimple/core/_core.headers.pxi @@ -1,1939 +1,1939 @@ # Classes # cdef object BaseHeader_richcmp(object self, object other, int op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseHeader): return NotImplemented if op == 2: return self.name == other.name and self.body == other.body else: return self.name != other.name or self.body != other.body cdef class BaseHeader: normal_type = Header frozen_type = FrozenHeader def __init__(self, *args, **kwargs): raise TypeError("BaseHeader cannot be instantiated directly") def __repr__(self): return "%s(%r, %r)" % (self.__class__.__name__, self.name, self.body) def __str__(self): return "%s: %s" % (self.name, self.body) def __richcmp__(self, other, op): return BaseHeader_richcmp(self, other, op) def Header_new(cls, BaseHeader header): return cls(header.name, header.body) cdef class Header(BaseHeader): def __init__(self, str name not None, str body not None): self.name = name self.body = body property name: def __get__(self): return self._name def __set__(self, str name not None): self._name = name property body: def __get__(self): return self._body def __set__(self, str body not None): self._body = body new = classmethod(Header_new) del Header_new def FrozenHeader_new(cls, BaseHeader header): if isinstance(header, cls): return header return cls(header.name, header.body) cdef class FrozenHeader(BaseHeader): def __init__(self, str name not None, str body not None): self.name = name self.body = body def __hash__(self): return hash((self.name, self.body)) def __richcmp__(self, other, op): return BaseHeader_richcmp(self, other, op) new = classmethod(FrozenHeader_new) del FrozenHeader_new class ContentType(str): def __init__(self, value): if '' in value.partition('/'): raise ValueError('invalid content type') @property def type(self): return self.partition('/')[0] @property def subtype(self): return self.partition('/')[2] cdef object BaseContentTypeHeader_richcmp(object self, object other, object op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseContentTypeHeader): return NotImplemented if op == 2: return self.content_type == other.content_type and self.parameters == other.parameters else: return self.content_type != other.content_type and self.parameters != other.parameters cdef class BaseContentTypeHeader: normal_type = ContentTypeHeader frozen_type = FrozenContentTypeHeader def __init__(self, *args, **kwargs): raise TypeError("%s cannot be instantiated directly" % self.__class__.__name__) def __repr__(self): return "%s(%r, %r)" % (self.__class__.__name__, self.content_type, self.parameters) def __str__(self): return "%s: %s" % (self.name, self.body) def __unicode__(self): - return unicode(self.__str__(), encoding='utf-8') + return self.__str__().encode() def __richcmp__(self, other, op): return BaseContentTypeHeader_richcmp(self, other, op) property name: def __get__(self): return "Content-Type" property body: def __get__(self): if self.parameters: parameters = ";" + ";".join(["%s%s" % (name, "" if value is None else "=%s" % value) - for name, value in self.parameters.iteritems()]) + for name, value in list(self.parameters.items())]) else: parameters = "" return self.content_type + parameters def ContentTypeHeader_new(cls, BaseContentTypeHeader header): return cls(header.content_type, dict(header.parameters)) cdef class ContentTypeHeader(BaseContentTypeHeader): def __init__(self, str content_type, dict parameters=None): self.content_type = content_type self.parameters = parameters if parameters is not None else {} property content_type: def __get__(self): try: return ContentType(self._content_type) except ValueError: return None def __set__(self, str content_type): self._content_type = content_type property parameters: def __get__(self): return self._parameters def __set__(self, dict parameters not None): self._parameters = parameters new = classmethod(ContentTypeHeader_new) del ContentTypeHeader_new def FrozenContentTypeHeader_new(cls, BaseContentTypeHeader header): if isinstance(header, cls): return header return cls(header.content_type, frozendict(header.parameters)) cdef class FrozenContentTypeHeader(BaseContentTypeHeader): def __init__(self, str content_type, frozendict parameters not None=frozendict()): if not self.initialized: self._content_type = content_type self.parameters = parameters self.initialized = 1 property content_type: def __get__(self): try: return ContentType(self._content_type) except ValueError: return None def __hash__(self): return hash((self.content_type, self.parameters)) def __richcmp__(self, other, op): return BaseContentTypeHeader_richcmp(self, other, op) new = classmethod(FrozenContentTypeHeader_new) del FrozenContentTypeHeader_new cdef object BaseContactHeader_richcmp(object self, object other, object op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseContactHeader): return NotImplemented if op == 2: return self.name == other.name and self.uri == other.uri and self.display_name == other.display_name and self.parameters == other.parameters else: return self.name != other.name or self.uri != other.uri or self.display_name != other.display_name or self.parameters != other.parameters cdef class BaseContactHeader: normal_type = ContactHeader frozen_type = FrozenContactHeader def __init__(self, *args, **kwargs): raise TypeError("%s cannot be instantiated directly" % self.__class__.__name__) def __repr__(self): return "%s(%r, %r, %r)" % (self.__class__.__name__, self.uri, self.display_name, self.parameters) def __str__(self): return "%s: %s" % (self.name, self.body) def __unicode__(self): - return unicode(self.__str__(), encoding='utf-8') + return self.__str__().encode() def __richcmp__(self, other, op): return BaseContactHeader_richcmp(self, other, op) property name: def __get__(self): return "Contact" property body: def __get__(self): if self.uri is None: return "*" if self.parameters: parameters = ";" + ";".join(["%s%s" % (name, "" if value is None else "=%s" % value) - for name, value in self.parameters.iteritems()]) + for name, value in list(self.parameters.items())]) else: parameters = "" if self.display_name: - return '"%s" <%s>%s' % (self.display_name.encode('utf-8'), self.uri, parameters) + return '"%s" <%s>%s' % (self.display_name, self.uri, parameters) else: return '<%s>%s' % (self.uri, parameters) def ContactHeader_new(cls, BaseContactHeader header): return cls(SIPURI.new(header.uri), header.display_name, dict(header.parameters)) cdef class ContactHeader(BaseContactHeader): def __init__(self, SIPURI uri, unicode display_name=None, dict parameters=None): if uri is None and (display_name is not None or parameters not in (None, {})): raise ValueError("uri cannot be None if display_name or parameters are specified") self.uri = uri self.display_name = display_name self.parameters = parameters if parameters is not None else {} property uri: def __get__(self): return self._uri def __set__(self, SIPURI uri): if uri is None and (self.display_name is not None or self.parameters != {}): raise ValueError("uri cannot be None if display_name or parameters are specified") self._uri = uri property display_name: def __get__(self): return self._display_name def __set__(self, unicode display_name): if self.uri is None and display_name is not None: raise ValueError("display_name cannot be specified if uri is None") self._display_name = display_name property parameters: def __get__(self): return self._parameters def __set__(self, dict parameters not None): if self.uri is None and parameters != {}: raise ValueError("parameters cannot be specified if uri is None") self._parameters = parameters property q: def __get__(self): value = self.parameters.get("q", None) if value is not None: value = float(value) return value def __set__(self, object value): if value is None: self.parameters.pop("q", None) else: if self.uri is None: raise ValueError("parameters cannot be specified if uri is None") self.parameters["q"] = str(float(value)) property expires: def __get__(self): value = self.parameters.get("expires", None) if value is not None: value = int(value) return value def __set__(self, object value): if value is None: self.parameters.pop("expires", None) else: if self.uri is None: raise ValueError("parameters cannot be specified if uri is None") self.parameters["expires"] = str(int(value)) new = classmethod(ContactHeader_new) del ContactHeader_new def FrozenContactHeader_new(cls, BaseContactHeader header): if isinstance(header, cls): return header return cls(FrozenSIPURI.new(header.uri), header.display_name, frozendict(header.parameters)) cdef class FrozenContactHeader(BaseContactHeader): def __init__(self, FrozenSIPURI uri, unicode display_name=None, frozendict parameters not None=frozendict()): if not self.initialized: if uri is None and (display_name is not None or parameters not in (None, {})): raise ValueError("uri cannot be None if display_name or parameters are specified") self.uri = uri self.display_name = display_name self.parameters = parameters self.initialized = 1 def __hash__(self): return hash((self.uri, self.display_name, self.parameters)) def __richcmp__(self, other, op): return BaseContactHeader_richcmp(self, other, op) property q: def __get__(self): value = self.parameters.get("q", None) if value is not None: value = float(value) return value property expires: def __get__(self): value = self.parameters.get("expires", None) if value is not None: value = int(value) return value new = classmethod(FrozenContactHeader_new) del FrozenContactHeader_new cdef object BaseIdentityHeader_richcmp(object self, object other, int op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseIdentityHeader): return NotImplemented if op == 2: return self.name == other.name and self.uri == other.uri and self.display_name == other.display_name and self.parameters == other.parameters else: return self.name != other.name or self.uri != other.uri or self.display_name != other.display_name or self.parameters != other.parameters cdef class BaseIdentityHeader: def __init__(self, *args, **kwargs): raise TypeError("%s cannot be instantiated directly" % self.__class__.__name__) def __repr__(self): return "%s(%r, %r, %r)" % (self.__class__.__name__, self.uri, self.display_name, self.parameters) def __str__(self): return "%s: %s" % (self.name, self.body) def __unicode__(self): - return unicode(self.__str__(), encoding='utf-8') + return self.__str__().encode() def __richcmp__(self, other, op): return BaseIdentityHeader_richcmp(self, other, op) property body: def __get__(self): if self.parameters: parameters = ";" + ";".join(["%s%s" % (name, "" if value is None else "=%s" % value) - for name, value in self.parameters.iteritems()]) + for name, value in list(self.parameters.items())]) else: parameters = "" if self.display_name: - return '"%s" <%s>%s' % (self.display_name.encode('utf-8'), self.uri, parameters) + return '"%s" <%s>%s' % (self.display_name, self.uri, parameters) else: return '<%s>%s' % (self.uri, parameters) def IdentityHeader_new(cls, BaseIdentityHeader contact_header): return cls(SIPURI.new(contact_header.uri), contact_header.display_name, dict(contact_header.parameters)) cdef class IdentityHeader(BaseIdentityHeader): property uri: def __get__(self): return self._uri def __set__(self, SIPURI uri not None): self._uri = uri property parameters: def __get__(self): return self._parameters def __set__(self, dict parameters not None): self._parameters = parameters new = classmethod(IdentityHeader_new) del IdentityHeader_new def FrozenIdentityHeader_new(cls, BaseIdentityHeader contact_header): if isinstance(contact_header, cls): return contact_header return cls(FrozenSIPURI.new(contact_header.uri), contact_header.display_name, frozendict(contact_header.parameters)) cdef class FrozenIdentityHeader(BaseIdentityHeader): def __hash__(self): return hash((self.uri, self.display_name, self.parameters)) def __richcmp__(self, other, op): return BaseIdentityHeader_richcmp(self, other, op) new = classmethod(FrozenIdentityHeader_new) del FrozenIdentityHeader_new cdef class FromHeader(IdentityHeader): normal_type = FromHeader frozen_type = FrozenFromHeader def __init__(self, SIPURI uri not None, unicode display_name=None, dict parameters=None): self.uri = uri self.display_name = display_name self.parameters = parameters if parameters is not None else {} property tag: def __get__(self): return self.parameters.get("tag", None) def __set__(self, str value): if value is None: self.parameters.pop("tag", None) else: self.parameters["tag"] = value property name: def __get__(self): return "From" cdef class FrozenFromHeader(FrozenIdentityHeader): normal_type = FromHeader frozen_type = FrozenFromHeader def __init__(self, FrozenSIPURI uri not None, unicode display_name=None, frozendict parameters not None=frozendict()): if not self.initialized: self.uri = uri self.display_name = display_name self.parameters = parameters self.initialized = 1 property tag: def __get__(self): return self.parameters.get("tag", None) property name: def __get__(self): return "From" cdef class ToHeader(IdentityHeader): normal_type = ToHeader frozen_type = FrozenToHeader def __init__(self, SIPURI uri not None, unicode display_name=None, dict parameters=None): self.uri = uri self.display_name = display_name self.parameters = parameters if parameters is not None else {} property tag: def __get__(self): return self.parameters.get("tag", None) def __set__(self, str value): if value is None: self.parameters.pop("tag", None) else: self.parameters["tag"] = value property name: def __get__(self): return "To" cdef class FrozenToHeader(FrozenIdentityHeader): normal_type = ToHeader frozen_type = FrozenToHeader def __init__(self, FrozenSIPURI uri not None, unicode display_name=None, frozendict parameters not None=frozendict()): if not self.initialized: self.uri = uri self.display_name = display_name self.parameters = parameters self.initialized = 1 property tag: def __get__(self): return self.parameters.get("tag", None) property name: def __get__(self): return "To" cdef class RouteHeader(IdentityHeader): normal_type = RouteHeader frozen_type = FrozenRouteHeader def __init__(self, SIPURI uri not None, unicode display_name=None, dict parameters=None): self.uri = uri self.display_name = display_name self.parameters = parameters if parameters is not None else {} property name: def __get__(self): return "Route" cdef class FrozenRouteHeader(FrozenIdentityHeader): normal_type = RouteHeader frozen_type = FrozenRouteHeader def __init__(self, FrozenSIPURI uri not None, unicode display_name=None, frozendict parameters not None=frozendict()): if not self.initialized: self.uri = uri self.display_name = display_name self.parameters = parameters self.initialized = 1 property name: def __get__(self): return "Route" cdef class RecordRouteHeader(IdentityHeader): normal_type = RecordRouteHeader frozen_type = FrozenRecordRouteHeader def __init__(self, SIPURI uri not None, unicode display_name=None, dict parameters=None): self.uri = uri self.display_name = display_name self.parameters = parameters if parameters is not None else {} property name: def __get__(self): return "Record-Route" cdef class FrozenRecordRouteHeader(FrozenIdentityHeader): normal_type = RecordRouteHeader frozen_type = FrozenRecordRouteHeader def __init__(self, FrozenSIPURI uri not None, unicode display_name=None, frozendict parameters not None=frozendict()): if not self.initialized: self.uri = uri self.display_name = display_name self.parameters = parameters self.initialized = 1 property name: def __get__(self): return "Record-Route" cdef object BaseRetryAfterHeader_richcmp(object self, object other, int op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseRetryAfterHeader): return NotImplemented if op == 2: return self.seconds == other.seconds and self.comment == other.comment and self.parameters == other.parameters else: return self.seconds != other.seconds or self.comment != other.comment or self.parameters != other.parameters cdef class BaseRetryAfterHeader: normal_type = RetryAfterHeader frozen_type = FrozenRetryAfterHeader def __init__(self, *args, **kwargs): raise TypeError("BaseRetryAfterHeader cannot be instantiated directly") def __repr__(self): return "%s(%r, %r, %r)" % (self.__class__.__name__, self.seconds, self.comment, self.parameters) def __str__(self): return "%s: %s" % (self.name, self.body) def __richcmp__(self, other, op): return BaseRetryAfterHeader_richcmp(self, other, op) property name: def __get__(self): return "Retry-After" property body: def __get__(self): string = str(self.seconds) if self.comment is not None: string += " (%s)" % self.comment if self.parameters: string += ";" + ";".join(["%s%s" % (name, "" if value is None else "=%s" % value) - for name, value in self.parameters.iteritems()]) + for name, value in list(self.parameters.items())]) return string def RetryAfterHeader_new(cls, BaseRetryAfterHeader header): return cls(header.seconds, header.comment, dict(header.parameters)) cdef class RetryAfterHeader(BaseRetryAfterHeader): def __init__(self, int seconds, str comment=None, dict parameters=None): self.seconds = seconds self.comment = comment self.parameters = parameters if parameters is not None else {} property parameters: def __get__(self): return self._parameters def __set__(self, dict parameters not None): self._parameters = parameters property duration: def __get__(self): value = self.parameters.get("duration", None) if value is not None: value = int(value) return value def __set__(self, object value): if value is None: self.parameters.pop("duration", None) else: self.parameters["duration"] = str(int(value)) new = classmethod(RetryAfterHeader_new) del RetryAfterHeader_new def FrozenRetryAfterHeader_new(cls, BaseRetryAfterHeader header): if isinstance(header, cls): return header return cls(header.seconds, header.comment, frozendict(header.parameters)) cdef class FrozenRetryAfterHeader(BaseRetryAfterHeader): def __init__(self, int seconds, str comment=None, frozendict parameters not None=frozendict()): if not self.initialized: self.seconds = seconds self.comment = comment self.parameters = parameters self.initialized = 1 def __hash__(self): return hash((self.seconds, self.comment, self.parameters)) def __richcmp__(self, other, op): return BaseRetryAfterHeader_richcmp(self, other, op) property duration: def __get__(self): value = self.parameters.get("duration", None) if value is not None: value = int(value) return value new = classmethod(FrozenRetryAfterHeader_new) del FrozenRetryAfterHeader_new cdef object BaseViaHeader_richcmp(object self, object other, int op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseViaHeader): return NotImplemented if op == 2: return self.transport == other.transport and self.host == other.host and self.port == other.port and self.parameters == other.parameters else: return self.transport != other.transport or self.host != other.host or self.port != other.port or self.parameters != other.parameters cdef class BaseViaHeader: normal_type = ViaHeader frozen_type = FrozenViaHeader def __init__(self, *args, **kwargs): raise TypeError("BaseViaHeader cannot be instantiated directly") def __repr__(self): return "%s(%r, %r, %r, %r)" % (self.__class__.__name__, self.transport, self.host, self.port, self.parameters) def __str__(self): return "%s: %s" % (self.name, self.body) def __richcmp__(self, other, op): return BaseViaHeader_richcmp(self, other, op) property name: def __get__(self): return "Via" property body: def __get__(self): string = "SIP/2.0/%s %s:%d" % (self.transport, self.host, self.port) if self.parameters: string += ";" + ";".join(["%s%s" % (name, "" if value is None else "=%s" % value) - for name, value in self.parameters.iteritems()]) + for name, value in list(self.parameters.items())]) return string def ViaHeader_new(cls, BaseViaHeader header): return cls(header.transport, header.host, header.port, dict(header.parameters)) cdef class ViaHeader(BaseViaHeader): def __init__(self, str transport not None, str host not None, int port=5060, dict parameters=None): self.transport = transport self.host = host self.port = port self.parameters = parameters if parameters is not None else {} property transport: def __get__(self): return self._transport def __set__(self, str transport not None): self._transport = transport property host: def __get__(self): return self._host def __set__(self, str host not None): self._host = host property port: def __get__(self): return self._port def __set__(self, int port): if not (0 < port <= 65535): raise ValueError("Invalid port: %d" % port) self._port = port property parameters: def __get__(self): return self._parameters def __set__(self, dict parameters not None): self._parameters = parameters property ttl: def __get__(self): value = self.parameters.get("ttl", None) if value is not None: value = int(value) return value def __set__(self, object value): if value is None: self.parameters.pop("ttl", None) else: self.parameters["ttl"] = str(int(value)) property maddr: def __get__(self): return self.parameters.get("maddr", None) def __set__(self, str value): if value is None: self.parameters.pop("maddr", None) else: self.parameters["maddr"] = value property received: def __get__(self): return self.parameters.get("received", None) def __set__(self, str value): if value is None: self.parameters.pop("received", None) else: self.parameters["received"] = value property branch: def __get__(self): return self.parameters.get("branch", None) def __set__(self, str value): if value is None: self.parameters.pop("branch", None) else: self.parameters["branch"] = value property compression: def __get__(self): return self.parameters.get("compression", None) def __set__(self, str value): if value is None: self.parameters.pop("compression", None) else: self.parameters["compression"] = value property rport: def __get__(self): value = self.parameters.get("rport", None) if value is not None: value = int(value) return value def __set__(self, object value): if value is None: self.parameters.pop("rport", None) else: self.parameters["rport"] = str(int(value)) new = classmethod(ViaHeader_new) del ViaHeader_new def FrozenViaHeader_new(cls, BaseViaHeader header): if isinstance(header, cls): return header return cls(header.transport, header.host, header.port, frozendict(header.parameters)) cdef class FrozenViaHeader(BaseViaHeader): def __init__(self, str transport not None, str host not None, int port=5060, frozendict parameters not None=frozendict()): if not self.initialized: if not (0 < port <= 65535): raise ValueError("Invalid port: %d" % port) self.transport = transport self.host = host self.port = port self.parameters = parameters self.initialized = 1 def __hash__(self): return hash((self.transport, self.host, self.port, self.parameters)) def __richcmp__(self, other, op): return BaseViaHeader_richcmp(self, other, op) property ttl: def __get__(self): value = self.parameters.get("ttl", None) if value is not None: value = int(value) return value property maddr: def __get__(self): return self.parameters.get("maddr", None) property received: def __get__(self): return self.parameters.get("received", None) property branch: def __get__(self): return self.parameters.get("branch", None) property compression: def __get__(self): return self.parameters.get("compression", None) property rport: def __get__(self): value = self.parameters.get("rport", None) if value is not None: value = int(value) return value new = classmethod(FrozenViaHeader_new) del FrozenViaHeader_new cdef object BaseWarningHeader_richcmp(object self, object other, int op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseWarningHeader): return NotImplemented if op == 2: return self.code == other.code and self.agent == other.agent and self.text == other.text else: return self.code != other.code or self.agent != other.agent or self.text != other.text cdef class BaseWarningHeader: normal_type = WarningHeader frozen_type = FrozenWarningHeader def __init__(self, *args, **kwargs): raise TypeError("BaseWarningHeader cannot be instantiated directly") def __repr__(self): return "%s(%r, %r, %r)" % (self.__class__.__name__, self.code, self.agent, self.text) def __str__(self): return "%s: %s" % (self.name, self.body) def __richcmp__(self, other, op): return BaseWarningHeader_richcmp(self, other, op) property name: def __get__(self): return "Warning" property body: def __get__(self): return '%d %s "%s"' % (self.code, self.agent, self.text) def WarningHeader_new(cls, BaseWarningHeader header): return cls(header.code, header.agent, header.text) cdef class WarningHeader(BaseWarningHeader): def __init__(self, int code, str agent not None, str text not None): self.code = code self.agent = agent self.text = text property code: def __get__(self): return self._code def __set__(self, int code): if code < 100 or code > 999: raise ValueError("code needs to be a 3 digit number") self._code = code property agent: def __get__(self): return self._agent def __set__(self, str agent not None): self._agent = agent property text: def __get__(self): return self._text def __set__(self, str text not None): self._text = text new = classmethod(WarningHeader_new) del WarningHeader_new def FrozenWarningHeader_new(cls, BaseWarningHeader header): if isinstance(header, cls): return header return cls(header.code, header.agent, header.text) cdef class FrozenWarningHeader(BaseWarningHeader): def __init__(self, int code, str agent not None, str text not None): if not self.initialized: if code < 100 or code > 999: raise ValueError("code needs to be a 3 digit number") self.code = code self.agent = agent self.text = text self.initialized = 1 def __hash__(self): return hash((self.code, self.agent, self.text)) def __richcmp__(self, other, op): return BaseWarningHeader_richcmp(self, other, op) new = classmethod(FrozenWarningHeader_new) del FrozenWarningHeader_new cdef object BaseEventHeader_richcmp(object self, object other, object op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseEventHeader): return NotImplemented if op == 2: return self.event == other.event and self.parameters == other.parameters else: return self.event != other.event or self.parameters != other.parameters cdef class BaseEventHeader: normal_type = EventHeader frozen_type = FrozenEventHeader def __init__(self, *args, **kwargs): raise TypeError("%s cannot be instantiated directly" % self.__class__.__name__) def __repr__(self): return "%s(%r, %r)" % (self.__class__.__name__, self.event, self.parameters) def __str__(self): return "%s: %s" % (self.name, self.body) def __richcmp__(self, other, op): return BaseEventHeader_richcmp(self, other, op) property name: def __get__(self): return "Event" property body: def __get__(self): if self.parameters: parameters = ";" + ";".join(["%s%s" % (name, "" if value is None else "=%s" % value) - for name, value in self.parameters.iteritems()]) + for name, value in list(self.parameters.items())]) else: parameters = "" return self.event + parameters def EventHeader_new(cls, BaseEventHeader header): return cls(header.event, dict(header.parameters)) cdef class EventHeader(BaseEventHeader): def __init__(self, str event not None, dict parameters=None): self.event = event self.parameters = parameters if parameters is not None else {} property id: def __get__(self): return self._parameters.get("id", None) def __set__(self, str id): if id is None: self._parameters.pop("id", None) else: self._parameters["id"] = id property parameters: def __get__(self): return self._parameters.copy() def __set__(self, dict parameters not None): self._parameters = parameters new = classmethod(EventHeader_new) del EventHeader_new def FrozenEventHeader_new(cls, BaseEventHeader header): if isinstance(header, cls): return header return cls(header.event, frozendict(header.parameters)) cdef class FrozenEventHeader(BaseEventHeader): def __init__(self, str event not None, frozendict parameters not None=frozendict()): if not self.initialized: self.event = event self.parameters = parameters self.initialized = 1 def __hash__(self): return hash((self.event, self.parameters)) def __richcmp__(self, other, op): return BaseEventHeader_richcmp(self, other, op) property id: def __get__(self): return self.parameters.get("id", None) new = classmethod(FrozenEventHeader_new) del FrozenEventHeader_new cdef object BaseSubscriptionStateHeader_richcmp(object self, object other, object op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseSubscriptionStateHeader): return NotImplemented if op == 2: return self.state == other.state and self.parameters == other.parameters else: return self.state != other.state or self.parameters != other.parameters cdef class BaseSubscriptionStateHeader: normal_type = SubscriptionStateHeader frozen_type = FrozenSubscriptionStateHeader def __init__(self, *args, **kwargs): raise TypeError("%s cannot be instantiated directly" % self.__class__.__name__) def __repr__(self): return "%s(%r, %r)" % (self.__class__.__name__, self.state, self.parameters) def __str__(self): return "%s: %s" % (self.name, self.body) def __richcmp__(self, other, op): return BaseSubscriptionStateHeader_richcmp(self, other, op) property name: def __get__(self): return "SubscriptionState" property body: def __get__(self): if self.parameters: parameters = ";" + ";".join(["%s%s" % (name, "" if value is None else "=%s" % value) - for name, value in self.parameters.iteritems()]) + for name, value in list(self.parameters.items())]) else: parameters = "" return self.state + parameters def SubscriptionStateHeader_new(cls, BaseSubscriptionStateHeader header): return cls(header.state, dict(header.parameters)) cdef class SubscriptionStateHeader(BaseSubscriptionStateHeader): def __init__(self, str state not None, dict parameters=None): self.state = state self.parameters = parameters if parameters is not None else {} property reason: def __get__(self): return self._parameters.get("reason", None) def __set__(self, str reason): if reason is None: self._parameters.pop("reason", None) else: self._parameters["reason"] = reason property expires: def __get__(self): return int(self._parameters.get("expires", None)) def __set__(self, object expires): cdef int expires_i if expires is None: self._parameters.pop("expires", None) else: expires_i = expires self._parameters["expires"] = str(expires_i) property retry_after: def __get__(self): return int(self._parameters.get("retry-after", None)) def __set__(self, object retry_after): cdef int retry_after_i if retry_after is None: self._parameters.pop("retry-after", None) else: retry_after_i = retry_after self._parameters["retry-after"] = str(retry_after_i) property parameters: def __get__(self): return self._parameters.copy() def __set__(self, dict parameters not None): self._parameters = parameters new = classmethod(SubscriptionStateHeader_new) del SubscriptionStateHeader_new def FrozenSubscriptionStateHeader_new(cls, BaseSubscriptionStateHeader header): if isinstance(header, cls): return header return cls(header.state, frozendict(header.parameters)) cdef class FrozenSubscriptionStateHeader(BaseSubscriptionStateHeader): def __init__(self, str state not None, frozendict parameters not None=frozendict()): if not self.initialized: self.state = state self.parameters = parameters self.initialized = 1 def __hash__(self): return hash((self.state, self.parameters)) def __richcmp__(self, other, op): return BaseSubscriptionStateHeader_richcmp(self, other, op) property reason: def __get__(self): return self.parameters.get("reason", None) property expires: def __get__(self): expires = self.parameters.get("expires", None) if expires is not None: expires = int(expires) return expires property retry_after: def __get__(self): retry_after = self.parameters.get("retry-after", None) if retry_after is not None: retry_after = int(retry_after) return retry_after new = classmethod(FrozenSubscriptionStateHeader_new) del FrozenSubscriptionStateHeader_new cdef object BaseReasonHeader_richcmp(object self, object other, object op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseSubscriptionStateHeader): return NotImplemented if op == 2: return self.protocol == other.protocol and self.parameters == other.parameters else: return self.protocol != other.protocol or self.parameters != other.parameters cdef class BaseReasonHeader: normal_type = ReasonHeader frozen_type = FrozenReasonHeader def __init__(self, *args, **kwargs): raise TypeError("%s cannot be instantiated directly" % self.__class__.__name__) def __repr__(self): return "%s(%r, %r)" % (self.__class__.__name__, self.protocol, self.parameters) def __str__(self): return "%s: %s" % (self.name, self.body) def __richcmp__(self, other, op): return BaseReasonHeader_richcmp(self, other, op) property name: def __get__(self): return "Reason" property body: def __get__(self): if self.parameters: parameters = " ;" + " ;".join(["%s%s" % (name, "" if value is None else "=%s" % value) - for name, value in self.parameters.iteritems()]) + for name, value in list(self.parameters.items())]) else: parameters = "" return self.protocol + parameters def ReasonHeader_new(cls, BaseReasonHeader header): return cls(header.state, dict(header.parameters)) cdef class ReasonHeader(BaseReasonHeader): def __init__(self, str protocol not None, dict parameters=None): self.protocol = protocol self.parameters = parameters if parameters is not None else {} property cause: def __get__(self): cause = self.parameters.get("cause", None) if cause is not None: cause = int(cause) return cause def __set__(self, object cause): cdef int cause_i if cause is None: self.parameters.pop("cause", None) else: cause_i = int(cause) self.parameters["cause"] = str(cause_i) property text: def __get__(self): text = self.parameters.get("text", None) if text is not None: text = text.strip().strip('"') return text def __set__(self, str text): if text is None: self.parameters.pop("text", None) else: if not text.startswith('"'): text = '"'+text if not text[1:].endswith('"'): text = text+'"' self.parameters["text"] = text new = classmethod(ReasonHeader_new) del ReasonHeader_new def FrozenReasonHeader_new(cls, BaseReasonHeader header): if isinstance(header, cls): return header return cls(header.protocol, frozendict(header.parameters)) cdef class FrozenReasonHeader(BaseReasonHeader): def __init__(self, str protocol not None, frozendict parameters not None=frozendict()): if not self.initialized: self.protocol = protocol self.parameters = parameters self.initialized = 1 def __hash__(self): return hash((self.protocol, self.parameters)) def __richcmp__(self, other, op): return BaseReasonHeader_richcmp(self, other, op) property cause: def __get__(self): cause = self.parameters.get("cause", None) if cause is not None: cause = int(cause) return cause property text: def __get__(self): text = self.parameters.get("text", None) if text is not None: text = text.strip().strip('"') return text new = classmethod(FrozenReasonHeader_new) del FrozenReasonHeader_new cdef object BaseReferToHeader_richcmp(object self, object other, object op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseReferToHeader): return NotImplemented if op == 2: return self.uri == other.uri and self.parameters == other.parameters else: return self.uri != other.uri or self.parameters != other.parameters cdef class BaseReferToHeader: normal_type = ReferToHeader frozen_type = FrozenReferToHeader def __init__(self, *args, **kwargs): raise TypeError("%s cannot be instantiated directly" % self.__class__.__name__) def __repr__(self): return "%s(%r, %r)" % (self.__class__.__name__, self.uri, self.parameters) def __str__(self): return "%s: %s" % (self.name, self.body) def __richcmp__(self, other, op): return BaseReferToHeader_richcmp(self, other, op) property name: def __get__(self): return "Refer-To" property body: def __get__(self): if self.parameters: parameters = ";" + ";".join(["%s%s" % (name, "" if value is None else "=%s" % value) - for name, value in self.parameters.iteritems()]) + for name, value in list(self.parameters.items())]) else: parameters = "" return "<%s>%s" % (self.uri, parameters) def ReferToHeader_new(cls, BaseReferToHeader header): return cls(header.uri, dict(header.parameters)) cdef class ReferToHeader(BaseReferToHeader): def __init__(self, str uri not None, dict parameters=None): self.uri = uri self.parameters = parameters if parameters is not None else {} property parameters: def __get__(self): return self._parameters def __set__(self, dict parameters not None): self._parameters = parameters new = classmethod(ReferToHeader_new) del ReferToHeader_new def FrozenReferToHeader_new(cls, BaseReferToHeader header): if isinstance(header, cls): return header return cls(header.uri, frozendict(header.parameters)) cdef class FrozenReferToHeader(BaseReferToHeader): def __init__(self, str uri not None, frozendict parameters not None=frozendict()): if not self.initialized: self.uri = uri self.parameters = parameters self.initialized = 1 def __hash__(self): return hash((self.uri, self.parameters)) def __richcmp__(self, other, op): return BaseReferToHeader_richcmp(self, other, op) new = classmethod(FrozenReferToHeader_new) del FrozenReferToHeader_new cdef object BaseSubjectHeader_richcmp(object self, object other, int op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseSubjectHeader): return NotImplemented if op == 2: return self.subject == other.subject else: return self.subject != other.subject cdef class BaseSubjectHeader: def __init__(self, *args, **kwargs): raise TypeError("%s cannot be instantiated directly" % self.__class__.__name__) def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self.subject) def __str__(self): return "%s: %s" % (self.name, self.body) def __unicode__(self): - return unicode(self.__str__(), encoding='utf-8') + return self.__str__().encode() def __richcmp__(self, other, op): return BaseSubjectHeader_richcmp(self, other, op) property name: def __get__(self): return "Subject" property body: def __get__(self): return self.subject.encode('utf-8') def SubjectHeader_new(cls, BaseSubjectHeader subject_header): if isinstance(subject_header, cls): return subject_header return cls(subject_header.subject) cdef class SubjectHeader(BaseSubjectHeader): def __init__(self, unicode subject=None): if subject is None: raise ValueError('subject must be specified') self.subject = subject new = classmethod(SubjectHeader_new) del SubjectHeader_new def FrozenSubjectHeader_new(cls, BaseSubjectHeader subject_header): if isinstance(subject_header, cls): return subject_header return cls(subject_header.subject) cdef class FrozenSubjectHeader(BaseSubjectHeader): def __init__(self, unicode subject=None): if not self.initialized: if subject is None: raise ValueError('subject must be specified') self.subject = subject self.initialized = 1 def __hash__(self): return hash((self.subject)) def __richcmp__(self, other, op): return BaseSubjectHeader_richcmp(self, other, op) new = classmethod(FrozenSubjectHeader_new) del FrozenSubjectHeader_new cdef object BaseReplacesHeader_richcmp(object self, object other, int op) with gil: if op not in (2, 3): return NotImplemented if not isinstance(other, BaseReplacesHeader): return NotImplemented if op == 2: return self.call_id == other.call_id and self.from_tag == other.from_tag and self.to_tag == other.to_tag and self.early_only == other.early_only and self.parameters == other.parameters else: return self.call_id != other.call_id or self.from_tag != other.from_tag or self.to_tag != other.to_tag or self.early_only != other.early_only or self.parameters != other.parameters cdef class BaseReplacesHeader: normal_type = ReplacesHeader frozen_type = FrozenReplacesHeader def __init__(self, *args, **kwargs): raise TypeError("BaseReplacesHeader cannot be instantiated directly") def __repr__(self): return "%s(%r, %r, %r, %r, %r)" % (self.__class__.__name__, self.call_id, self.from_tag, self.to_tag, self.early_only, self.parameters) def __str__(self): return "%s: %s" % (self.name, self.body) def __richcmp__(self, other, op): return BaseReplacesHeader_richcmp(self, other, op) property name: def __get__(self): return "Replaces" property body: def __get__(self): string = "%s;from-tag=%s;to-tag=%s" % (self.call_id, self.from_tag, self.to_tag) if self.early_only: string += ";early-only" if self.parameters: string += ";" + ";".join(["%s%s" % (name, "" if value is None else "=%s" % value) - for name, value in self.parameters.iteritems()]) + for name, value in list(self.parameters.items())]) return string def ReplacesHeader_new(cls, BaseReplacesHeader header): return cls(header.call_id, header.from_tag, header.to_tag, header.early_only, dict(header.parameters)) cdef class ReplacesHeader(BaseReplacesHeader): def __init__(self, str call_id not None, str from_tag not None, str to_tag not None, int early_only=0, dict parameters=None): self.call_id = call_id self.from_tag = from_tag self.to_tag = to_tag self.early_only = early_only self.parameters = parameters if parameters is not None else {} property parameters: def __get__(self): return self._parameters def __set__(self, dict parameters not None): self._parameters = parameters new = classmethod(ReplacesHeader_new) del ReplacesHeader_new def FrozenReplacesHeader_new(cls, BaseReplacesHeader header): if isinstance(header, cls): return header return cls(header.call_id, header.header.from_tag, header.to_tag, header.early_only, frozendict(header.parameters)) cdef class FrozenReplacesHeader(BaseReplacesHeader): def __init__(self, str call_id not None, str from_tag not None, str to_tag not None, int early_only=0, frozendict parameters not None=frozendict()): if not self.initialized: self.call_id = call_id self.from_tag = from_tag self.to_tag = to_tag self.early_only = early_only self.parameters = parameters self.initialized = 1 def __hash__(self): return hash((self.call_id, self.from_tag, self.to_tag, self.early_only, self.parameters)) def __richcmp__(self, other, op): return BaseReplacesHeader_richcmp(self, other, op) new = classmethod(FrozenReplacesHeader_new) del FrozenReplacesHeader_new # Factory functions # cdef Header Header_create(pjsip_generic_string_hdr *header): return Header(_pj_str_to_str(header.name), _pj_str_to_str(header.hvalue)) cdef FrozenHeader FrozenHeader_create(pjsip_generic_string_hdr *header): return FrozenHeader(_pj_str_to_str(header.name), _pj_str_to_str(header.hvalue)) cdef ContactHeader ContactHeader_create(pjsip_contact_hdr *header): cdef pjsip_name_addr* name_addr if header.star: return ContactHeader(None) else: uri = SIPURI_create(pjsip_uri_get_uri(header.uri)) name_addr = header.uri if name_addr.display.slen > 0: - display_name = unicode(_pj_str_to_str(name_addr.display), encoding='utf-8') + display_name = _pj_str_to_str(name_addr.display) else: display_name = None parameters = _pjsip_param_to_dict(&header.other_param) if header.q1000 != -1: parameters["q"] = str(float(header.q1000)/1000) if header.expires != -1: parameters["expires"] = str(header.expires) return ContactHeader(uri, display_name, parameters) cdef FrozenContactHeader FrozenContactHeader_create(pjsip_contact_hdr *header): cdef pjsip_name_addr* name_addr if header.star: return FrozenContactHeader(None) else: uri = FrozenSIPURI_create(pjsip_uri_get_uri(header.uri)) name_addr = header.uri if name_addr.display.slen > 0: - display_name = unicode(_pj_str_to_str(name_addr.display), encoding='utf-8') + display_name = _pj_str_to_str(name_addr.display) else: display_name = None parameters = _pjsip_param_to_dict(&header.other_param) if header.q1000 != -1: parameters["q"] = str(float(header.q1000)/1000) if header.expires != -1: parameters["expires"] = str(header.expires) return FrozenContactHeader(uri, display_name, frozendict(parameters)) cdef ContentTypeHeader ContentTypeHeader_create(pjsip_ctype_hdr *header): type_str = _pj_str_to_str(header.media.type) subtype_str = _pj_str_to_str(header.media.subtype) parameters = _pjsip_param_to_dict(&header.media.param) if subtype_str: content_type = "%s/%s" % (type_str, subtype_str) else: content_type = type_str return ContentTypeHeader(content_type, parameters) cdef FrozenContentTypeHeader FrozenContentTypeHeader_create(pjsip_ctype_hdr *header): type_str = _pj_str_to_str(header.media.type) subtype_str = _pj_str_to_str(header.media.subtype) parameters = _pjsip_param_to_dict(&header.media.param) if subtype_str: content_type = "%s/%s" % (type_str, subtype_str) else: content_type = type_str return FrozenContentTypeHeader(content_type, frozendict(parameters)) cdef FromHeader FromHeader_create(pjsip_fromto_hdr *header): cdef pjsip_name_addr* name_addr uri = SIPURI_create(pjsip_uri_get_uri(header.uri)) name_addr = header.uri if name_addr.display.slen > 0: - display_name = unicode(_pj_str_to_str(name_addr.display), encoding='utf-8') + display_name = _pj_str_to_str(name_addr.display) else: display_name = None parameters = _pjsip_param_to_dict(&header.other_param) if header.tag.slen > 0: parameters["tag"] = _pj_str_to_str(header.tag) return FromHeader(uri, display_name, parameters) cdef FrozenFromHeader FrozenFromHeader_create(pjsip_fromto_hdr *header): cdef pjsip_name_addr* name_addr uri = FrozenSIPURI_create(pjsip_uri_get_uri(header.uri)) name_addr = header.uri if name_addr.display.slen > 0: - display_name = unicode(_pj_str_to_str(name_addr.display), encoding='utf-8') + display_name = _pj_str_to_str(name_addr.display) else: display_name = None parameters = _pjsip_param_to_dict(&header.other_param) if header.tag.slen > 0: parameters["tag"] = _pj_str_to_str(header.tag) return FrozenFromHeader(uri, display_name, frozendict(parameters)) cdef ToHeader ToHeader_create(pjsip_fromto_hdr *header): cdef pjsip_name_addr* name_addr uri = SIPURI_create(pjsip_uri_get_uri(header.uri)) name_addr = header.uri if name_addr.display.slen > 0: - display_name = unicode(_pj_str_to_str(name_addr.display), encoding='utf-8') + display_name = _pj_str_to_str(name_addr.display) else: display_name = None parameters = _pjsip_param_to_dict(&header.other_param) if header.tag.slen > 0: parameters["tag"] = _pj_str_to_str(header.tag) return ToHeader(uri, display_name, parameters) cdef FrozenToHeader FrozenToHeader_create(pjsip_fromto_hdr *header): cdef pjsip_name_addr* name_addr uri = FrozenSIPURI_create(pjsip_uri_get_uri(header.uri)) name_addr = header.uri if name_addr.display.slen > 0: - display_name = unicode(_pj_str_to_str(name_addr.display), encoding='utf-8') + display_name = _pj_str_to_str(name_addr.display) else: display_name = None parameters = _pjsip_param_to_dict(&header.other_param) if header.tag.slen > 0: parameters["tag"] = _pj_str_to_str(header.tag) return FrozenToHeader(uri, display_name, frozendict(parameters)) cdef RouteHeader RouteHeader_create(pjsip_routing_hdr *header): uri = SIPURI_create(pjsip_uri_get_uri(&header.name_addr)) if header.name_addr.display.slen > 0: - display_name = unicode(_pj_str_to_str(header.name_addr.display), encoding='utf-8') + display_name = _pj_str_to_str(header.name_addr.display) else: display_name = None parameters = _pjsip_param_to_dict(&header.other_param) return RouteHeader(uri, display_name, parameters) cdef FrozenRouteHeader FrozenRouteHeader_create(pjsip_routing_hdr *header): uri = FrozenSIPURI_create(pjsip_uri_get_uri(&header.name_addr)) if header.name_addr.display.slen > 0: - display_name = unicode(_pj_str_to_str(header.name_addr.display), encoding='utf-8') + display_name = _pj_str_to_str(header.name_addr.display) else: display_name = None parameters = frozendict(_pjsip_param_to_dict(&header.other_param)) return FrozenRouteHeader(uri, display_name, parameters) cdef RecordRouteHeader RecordRouteHeader_create(pjsip_routing_hdr *header): uri = SIPURI_create(pjsip_uri_get_uri(&header.name_addr)) if header.name_addr.display.slen > 0: - display_name = unicode(_pj_str_to_str(header.name_addr.display), encoding='utf-8') + display_name = _pj_str_to_str(header.name_addr.display) else: display_name = None parameters = _pjsip_param_to_dict(&header.other_param) return RecordRouteHeader(uri, display_name, parameters) cdef FrozenRecordRouteHeader FrozenRecordRouteHeader_create(pjsip_routing_hdr *header): uri = FrozenSIPURI_create(pjsip_uri_get_uri(&header.name_addr)) if header.name_addr.display.slen > 0: - display_name = unicode(_pj_str_to_str(header.name_addr.display), encoding='utf-8') + display_name = _pj_str_to_str(header.name_addr.display) else: display_name = None parameters = frozendict(_pjsip_param_to_dict(&header.other_param)) return FrozenRecordRouteHeader(uri, display_name, parameters) cdef RetryAfterHeader RetryAfterHeader_create(pjsip_retry_after_hdr *header): seconds = header.ivalue if header.comment.slen > 0: comment = _pj_str_to_str(header.comment) else: comment = None parameters = _pjsip_param_to_dict(&header.param) return RetryAfterHeader(seconds, comment, parameters) cdef FrozenRetryAfterHeader FrozenRetryAfterHeader_create(pjsip_retry_after_hdr *header): seconds = header.ivalue if header.comment.slen > 0: comment = _pj_str_to_str(header.comment) else: comment = None parameters = frozendict(_pjsip_param_to_dict(&header.param)) return FrozenRetryAfterHeader(seconds, comment, parameters) cdef ViaHeader ViaHeader_create(pjsip_via_hdr *header): transport = _pj_str_to_str(header.transport) host = _pj_str_to_str(header.sent_by.host) port = header.sent_by.port or 5060 parameters = _pjsip_param_to_dict(&header.other_param) if header.ttl_param != -1: parameters["ttl"] = header.ttl_param if header.rport_param != -1: parameters["rport"] = header.rport_param if header.maddr_param.slen > 0: parameters["maddr"] = _pj_str_to_str(header.maddr_param) if header.recvd_param.slen > 0: parameters["received"] = _pj_str_to_str(header.recvd_param) if header.branch_param.slen > 0: parameters["branch"] = _pj_str_to_str(header.branch_param) return ViaHeader(transport, host, port, parameters) cdef FrozenViaHeader FrozenViaHeader_create(pjsip_via_hdr *header): transport = _pj_str_to_str(header.transport) host = _pj_str_to_str(header.sent_by.host) port = header.sent_by.port or 5060 parameters = _pjsip_param_to_dict(&header.other_param) if header.ttl_param != -1: parameters["ttl"] = header.ttl_param if header.rport_param != -1: parameters["rport"] = header.rport_param if header.maddr_param.slen > 0: parameters["maddr"] = _pj_str_to_str(header.maddr_param) if header.recvd_param.slen > 0: parameters["received"] = _pj_str_to_str(header.recvd_param) if header.branch_param.slen > 0: parameters["branch"] = _pj_str_to_str(header.branch_param) return FrozenViaHeader(transport, host, port, frozendict(parameters)) cdef EventHeader EventHeader_create(pjsip_event_hdr *header): cdef dict parameters parameters = _pjsip_param_to_dict(&header.other_param) if header.id_param.slen != 0: parameters["id"] = _pj_str_to_str(header.id_param) return EventHeader(_pj_str_to_str(header.event_type), parameters) cdef FrozenEventHeader FrozenEventHeader_create(pjsip_event_hdr *header): cdef dict parameters parameters = _pjsip_param_to_dict(&header.other_param) if header.id_param.slen != 0: parameters["id"] = _pj_str_to_str(header.id_param) return FrozenEventHeader(_pj_str_to_str(header.event_type), frozendict(parameters)) cdef SubscriptionStateHeader SubscriptionStateHeader_create(pjsip_sub_state_hdr *header): cdef dict parameters parameters = _pjsip_param_to_dict(&header.other_param) if header.reason_param.slen != 0: parameters["reason"] = _pj_str_to_str(header.reason_param) if header.expires_param != -1: parameters["expires"] = str(header.expires_param) if header.retry_after != -1: parameters["retry-after"] = str(header.retry_after) return SubscriptionStateHeader(_pj_str_to_str(header.sub_state), parameters) cdef FrozenSubscriptionStateHeader FrozenSubscriptionStateHeader_create(pjsip_sub_state_hdr *header): cdef dict parameters parameters = _pjsip_param_to_dict(&header.other_param) if header.reason_param.slen != 0: parameters["reason"] = _pj_str_to_str(header.reason_param) if header.expires_param != -1: parameters["expires"] = str(header.expires_param) if header.retry_after != -1: parameters["retry-after"] = str(header.retry_after) return FrozenSubscriptionStateHeader(_pj_str_to_str(header.sub_state), frozendict(parameters)) cdef ReferToHeader ReferToHeader_create(pjsip_generic_string_hdr *header): cdef dict parameters cdef str value value = _pj_str_to_str((header).hvalue) uri, sep, params_str = value.partition('>') if sep: uri += '>' parameters = dict([(name, value or None) for name, sep, value in [param.partition('=') for param in params_str.split(';') if param]]) return ReferToHeader(uri, parameters) cdef FrozenReferToHeader FrozenReferToHeader_create(pjsip_generic_string_hdr *header): cdef dict parameters cdef str value value = _pj_str_to_str((header).hvalue) uri, sep, params_str = value.partition('>') if sep: uri += '>' parameters = dict([(name, value or None) for name, sep, value in [param.partition('=') for param in params_str.split(';') if param]]) return FrozenReferToHeader(uri, frozendict(parameters)) cdef SubjectHeader SubjectHeader_create(pjsip_generic_string_hdr *header): - subject = unicode(_pj_str_to_str((header).hvalue), encoding='utf-8') + subject = _pj_str_to_str((header).hvalue) return SubjectHeader(subject) cdef FrozenSubjectHeader FrozenSubjectHeader_create(pjsip_generic_string_hdr *header): - subject = unicode(_pj_str_to_str((header).hvalue), encoding='utf-8') + subject = _pj_str_to_str((header).hvalue) return FrozenSubjectHeader(subject) cdef ReplacesHeader ReplacesHeader_create(pjsip_replaces_hdr *header): cdef dict parameters = _pjsip_param_to_dict(&header.other_param) call_id = _pj_str_to_str(header.call_id) from_tag = _pj_str_to_str(header.from_tag) to_tag = _pj_str_to_str(header.to_tag) early_only = int(header.early_only) return ReplacesHeader(call_id, from_tag, to_tag, early_only, parameters) cdef FrozenReplacesHeader FrozenReplacesHeader_create(pjsip_replaces_hdr *header): cdef dict parameters = _pjsip_param_to_dict(&header.other_param) call_id = _pj_str_to_str(header.call_id) from_tag = _pj_str_to_str(header.from_tag) to_tag = _pj_str_to_str(header.to_tag) early_only = int(header.early_only) return FrozenReplacesHeader(call_id, from_tag, to_tag, early_only, frozendict(parameters)) diff --git a/sipsimple/core/_core.helper.pxi b/sipsimple/core/_core.helper.pxi index 1f70d489..2b13be93 100644 --- a/sipsimple/core/_core.helper.pxi +++ b/sipsimple/core/_core.helper.pxi @@ -1,347 +1,357 @@ import re import urllib # Classes # cdef class BaseCredentials: def __cinit__(self, *args, **kwargs): global _Credentials_scheme_digest, _Credentials_realm_wildcard self._credentials.scheme = _Credentials_scheme_digest.pj_str def __init__(self, str username not None, str password not None, str realm='*', bint digest=False): if self.__class__ is BaseCredentials: raise TypeError("BaseCredentials cannot be instantiated directly") self.username = username self.realm = realm self.password = password self.digest = digest def __repr__(self): return "%s(username=%r, password=%r, realm=%r, digest=%r)" % (self.__class__.__name__, self.username, self.password, self.realm, self.digest) def __str__(self): return '<%s for "%s@%s">' % (self.__class__.__name__, self.username, self.realm) cdef pjsip_cred_info* get_cred_info(self): return &self._credentials cdef class Credentials(BaseCredentials): property username: def __get__(self): return self._username def __set__(self, str username not None): - _str_to_pj_str(username, &self._credentials.username) + _str_to_pj_str(username.encode(), &self._credentials.username) self._username = username property realm: def __get__(self): return self._realm def __set__(self, str realm not None): - _str_to_pj_str(realm, &self._credentials.realm) + _str_to_pj_str(realm.encode(), &self._credentials.realm) self._realm = realm property password: def __get__(self): return self._password def __set__(self, str password not None): - _str_to_pj_str(password, &self._credentials.data) + _str_to_pj_str(password.encode(), &self._credentials.data) self._password = password property digest: def __get__(self): return self._digest def __set__(self, bint digest): self._credentials.data_type = PJSIP_CRED_DATA_DIGEST if digest else PJSIP_CRED_DATA_PLAIN_PASSWD self._digest = digest @classmethod def new(cls, BaseCredentials credentials): return cls(credentials.username, credentials.password, credentials.realm, credentials.digest) cdef class FrozenCredentials(BaseCredentials): def __init__(self, str username not None, str password not None, str realm='*', bint digest=False): if not self.initialized: self.username = username self.realm = realm self.password = password self.digest = digest - _str_to_pj_str(self.username, &self._credentials.username) - _str_to_pj_str(self.realm, &self._credentials.realm) - _str_to_pj_str(self.password, &self._credentials.data) + _str_to_pj_str(self.username.encode(), &self._credentials.username) + _str_to_pj_str(self.realm.encode(), &self._credentials.realm) + _str_to_pj_str(self.password.encode(), &self._credentials.data) self._credentials.data_type = PJSIP_CRED_DATA_DIGEST if digest else PJSIP_CRED_DATA_PLAIN_PASSWD self.initialized = 1 else: raise TypeError("{0.__class__.__name__} is read-only".format(self)) def __hash__(self): return hash((self.username, self.realm, self.password, self.digest)) @classmethod def new(cls, BaseCredentials credentials): if isinstance(credentials, FrozenCredentials): return credentials return cls(credentials.username, credentials.password, credentials.realm, credentials.digest) cdef class BaseSIPURI: def __init__(self, object host not None, object user=None, object password=None, object port=None, bint secure=False, dict parameters=None, dict headers=None): if self.__class__ is BaseSIPURI: raise TypeError("BaseSIPURI cannot be instantiated directly") self.host = host self.user = user self.password = password self.port = port self.secure = secure self.parameters = parameters if parameters is not None else {} self.headers = headers if headers is not None else {} property transport: def __get__(self): return self.parameters.get('transport', 'udp') def __set__(self, str transport not None): if transport.lower() == 'udp': self.parameters.pop('transport', None) else: self.parameters['transport'] = transport def __reduce__(self): return self.__class__, (self.host, self.user, self.password, self.port, self.secure, self.parameters, self.headers), None def __repr__(self): return "%s(%r, %r, %r, %r, %r, %r, %r)" % (self.__class__.__name__, self.host, self.user, self.password, self.port, self.secure, self.parameters, self.headers) def __str__(self): cdef object string = self.host if self.port: string = "%s:%d" % (string, self.port) if self.user is not None: if self.password is not None: string = "%s:%s@%s" % (self.user, self.password, string) else: string = "%s@%s" % (self.user, string) if self.parameters: - string += ";" + ";".join(["%s%s" % (name, ("" if val is None else "="+urllib.quote(val, safe="()[]-_.!~*'/:&+$"))) - for name, val in self.parameters.iteritems()]) + string += ";" + ";".join(["%s%s" % (name, ("" if val is None else "="+urllib.parse.quote(val, safe="()[]-_.!~*'/:&+$"))) + for name, val in list(self.parameters.items())]) if self.headers: - string += "?" + "&".join(["%s%s" % (name, ("" if val is None else "="+urllib.quote(val, safe="()[]-_.!~*'/:?+$"))) - for name, val in self.headers.iteritems()]) + string += "?" + "&".join(["%s%s" % (name, ("" if val is None else "="+urllib.parse.quote(val, safe="()[]-_.!~*'/:?+$"))) + for name, val in self.headers.items()]) if self.secure: string = "sips:" + string else: string = "sip:" + string return string def __richcmp__(self, other, op): if not isinstance(other, BaseSIPURI): return NotImplemented if op == 2: # 2 is == return all(getattr(self, name) == getattr(other, name) for name in ("user", "password", "host", "port", "secure", "parameters", "headers")) elif op == 3: # 3 is != return any(getattr(self, name) != getattr(other, name) for name in ("user", "password", "host", "port", "secure", "parameters", "headers")) else: operator_map = {0: '<', 1: '<=', 2: '==', 3: '!=', 4: '>', 5: '>='} raise TypeError("unorderable types: {0.__class__.__name__}() {2} {1.__class__.__name__}()".format(self, other, operator_map[op])) def matches(self, address): match = re.match(r'^((?Psip|sips):)?(?P.+?)(@(?P.+?)(:(?P\d+?))?)?(;(?P.+?))?(\?(?P.+?))?$', address) if match is None: return False components = match.groupdict() if components['scheme'] is not None: expected_scheme = 'sips' if self.secure else 'sip' if components['scheme'] != expected_scheme: return False if components['username'] != self.user: return False if components['domain'] is not None and components['domain'] != self.host: return False if components['port'] is not None and int(components['port']) != self.port: return False if components['parameters']: parameters = dict([(name, value) for name, sep, value in [param.partition('=') for param in components['parameters'].split(';')]]) - expected_parameters = dict([(name, str(value) if value is not None else None) for name, value in self.parameters.iteritems() if name in parameters]) + expected_parameters = dict([(name, str(value) if value is not None else None) for name, value in list(self.parameters.items()) if name in parameters]) if parameters != expected_parameters: return False if components['headers']: headers = dict([(name, value) for name, sep, value in [header.partition('=') for header in components['headers'].split('&')]]) - expected_headers = dict([(name, str(value) if value is not None else None) for name, value in self.headers.iteritems() if name in headers]) + expected_headers = dict([(name, str(value) if value is not None else None) for name, value in self.headers.items() if name in headers]) if headers != expected_headers: return False return True cdef class SIPURI(BaseSIPURI): property port: def __get__(self): return self._port def __set__(self, object port): if port is not None: port = int(port) if not (0 < port <= 65535): raise ValueError("Invalid port: %d" % port) self._port = port @classmethod def new(cls, BaseSIPURI sipuri): return cls(user=sipuri.user, password=sipuri.password, host=sipuri.host, port=sipuri.port, secure=sipuri.secure, parameters=dict(sipuri.parameters), headers=dict(sipuri.headers)) @classmethod def parse(cls, object uri_str): if not isinstance(uri_str, basestring): raise TypeError('a string or unicode is required') - cdef bytes uri_bytes = str(uri_str) + cdef bytes uri_bytes = uri_str.encode() cdef pjsip_uri *uri = NULL cdef pj_pool_t *pool = NULL cdef pj_str_t tmp cdef char buffer[4096] pool = pj_pool_create_on_buf("SIPURI_parse", buffer, sizeof(buffer)) if pool == NULL: raise SIPCoreError("Could not allocate memory pool") pj_strdup2_with_null(pool, &tmp, uri_bytes) uri = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, 0) if uri == NULL: raise SIPCoreError("Not a valid SIP URI: %s" % uri_str) return SIPURI_create(pjsip_uri_get_uri(uri)) cdef class FrozenSIPURI(BaseSIPURI): def __init__(self, object host not None, object user=None, object password=None, object port=None, bint secure=False, frozendict parameters not None=frozendict(), frozendict headers not None=frozendict()): if not self.initialized: if port is not None: port = int(port) if not (0 < port <= 65535): raise ValueError("Invalid port: %d" % port) self.host = host self.user = user self.password = password self.port = port self.secure = secure self.parameters = parameters self.headers = headers self.initialized = 1 else: raise TypeError("{0.__class__.__name__} is read-only".format(self)) property transport: def __get__(self): return self.parameters.get('transport', 'udp') def __hash__(self): return hash((self.user, self.password, self.host, self.port, self.secure, self.parameters, self.headers)) def __richcmp__(self, other, op): # since we define __hash__, __richcmp__ is not inherited (see https://docs.python.org/2/c-api/typeobj.html#c.PyTypeObject.tp_richcompare) if not isinstance(other, BaseSIPURI): return NotImplemented if op == 2: # 2 is == return all(getattr(self, name) == getattr(other, name) for name in ("user", "password", "host", "port", "secure", "parameters", "headers")) elif op == 3: # 3 is != return any(getattr(self, name) != getattr(other, name) for name in ("user", "password", "host", "port", "secure", "parameters", "headers")) else: operator_map = {0: '<', 1: '<=', 2: '==', 3: '!=', 4: '>', 5: '>='} raise TypeError("unorderable types: {0.__class__.__name__}() {2} {1.__class__.__name__}()".format(self, other, operator_map[op])) @classmethod def new(cls, BaseSIPURI sipuri): if isinstance(sipuri, FrozenSIPURI): return sipuri return cls(user=sipuri.user, password=sipuri.password, host=sipuri.host, port=sipuri.port, secure=sipuri.secure, parameters=frozendict(sipuri.parameters), headers=frozendict(sipuri.headers)) @classmethod def parse(cls, object uri_str): if not isinstance(uri_str, basestring): raise TypeError('a string or unicode is required') - cdef bytes uri_bytes = str(uri_str) + cdef bytes uri_bytes = uri_str.encode() cdef pjsip_uri *uri = NULL cdef pj_pool_t *pool = NULL cdef pj_str_t tmp cdef char buffer[4096] pool = pj_pool_create_on_buf("FrozenSIPURI_parse", buffer, sizeof(buffer)) if pool == NULL: raise SIPCoreError("Could not allocate memory pool") pj_strdup2_with_null(pool, &tmp, uri_bytes) uri = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, 0) if uri == NULL: raise SIPCoreError("Not a valid SIP URI: %s" % uri_str) return FrozenSIPURI_create(pjsip_uri_get_uri(uri)) # Factory functions # cdef dict _pj_sipuri_to_dict(pjsip_sip_uri *uri): cdef object scheme cdef pj_str_t *scheme_str cdef pjsip_param *param cdef object parameters = {} cdef object headers = {} cdef object kwargs = dict(parameters=parameters, headers=headers) - kwargs["host"] = _pj_str_to_str(uri.host) + scheme = _pj_str_to_str(pjsip_uri_get_scheme(uri)[0]) if scheme == "sip": kwargs["secure"] = False elif scheme == "sips": kwargs["secure"] = True else: - raise SIPCoreError("Not a sip(s) URI") + raise SIPCoreError("Not a valid SIP URI") + + try: + kwargs["host"] = _pj_str_to_str(uri.host) + except UnicodeDecodeError: + raise SIPCoreError("Not a valid hostname %s" % uri.host.ptr) + if uri.user.slen > 0: kwargs["user"] = _pj_str_to_str(uri.user) if uri.passwd.slen > 0: kwargs["password"] = _pj_str_to_str(uri.passwd) if uri.port > 0: kwargs["port"] = uri.port if uri.user_param.slen > 0: parameters["user"] = _pj_str_to_str(uri.user_param) if uri.method_param.slen > 0: parameters["method"] = _pj_str_to_str(uri.method_param) if uri.transport_param.slen > 0: parameters["transport"] = _pj_str_to_str(uri.transport_param) if uri.ttl_param != -1: parameters["ttl"] = uri.ttl_param if uri.lr_param != 0: parameters["lr"] = None if uri.maddr_param.slen > 0: parameters["maddr"] = _pj_str_to_str(uri.maddr_param) param = ( &uri.other_param).next + while param != &uri.other_param: if param.value.slen == 0: parameters[_pj_str_to_str(param.name)] = None else: parameters[_pj_str_to_str(param.name)] = _pj_str_to_str(param.value) param = ( param).next param = ( &uri.header_param).next + while param != &uri.header_param: if param.value.slen == 0: headers[_pj_str_to_str(param.name)] = None else: headers[_pj_str_to_str(param.name)] = _pj_str_to_str(param.value) param = ( param).next return kwargs + cdef SIPURI SIPURI_create(pjsip_sip_uri *uri): cdef dict kwargs = _pj_sipuri_to_dict(uri) return SIPURI(**kwargs) + cdef FrozenSIPURI FrozenSIPURI_create(pjsip_sip_uri *uri): cdef dict kwargs = _pj_sipuri_to_dict(uri) kwargs["parameters"] = frozendict(kwargs["parameters"]) kwargs["headers"] = frozendict(kwargs["headers"]) return FrozenSIPURI(**kwargs) # Globals # -cdef PJSTR _Credentials_scheme_digest = PJSTR("digest") +cdef PJSTR _Credentials_scheme_digest = PJSTR(b"digest") diff --git a/sipsimple/core/_core.invitation.pxi b/sipsimple/core/_core.invitation.pxi index dde2736a..52f07074 100644 --- a/sipsimple/core/_core.invitation.pxi +++ b/sipsimple/core/_core.invitation.pxi @@ -1,1854 +1,1855 @@ import weakref from errno import EADDRNOTAVAIL, ENETUNREACH from operator import itemgetter # classes cdef class SDPPayloads: def __init__(self): self.proposed_local = None self.proposed_remote = None self.active_local = None self.active_remote = None cdef class StateCallbackTimer(Timer): def __init__(self, state, sub_state, rdata, tdata, originator): self.state = state self.sub_state = sub_state self.rdata = rdata self.tdata = tdata self.originator = originator cdef class SDPCallbackTimer(Timer): def __init__(self, int status, active_local, active_remote): self.status = status self.active_local = active_local self.active_remote = active_remote cdef class TransferStateCallbackTimer(Timer): def __init__(self, state, code, reason): self.state = state self.code = code self.reason = reason cdef class TransferResponseCallbackTimer(Timer): def __init__(self, method, rdata): self.method = method self.rdata = rdata cdef class TransferRequestCallbackTimer(Timer): def __init__(self, rdata): self.rdata = rdata class DialogID(tuple): call_id = property(itemgetter(0)) local_tag = property(itemgetter(1)) remote_tag = property(itemgetter(2)) def __new__(cls, call_id, local_tag, remote_tag): return tuple.__new__(cls, (call_id, local_tag, remote_tag)) def __repr__(self): return 'DialogID(call_id=%r, local_tag=%r, remote_tag=%r)' % self cdef class Invitation: expire_warning_time = 30 def __cinit__(self, *args, **kwargs): cdef int status self.weakref = weakref.ref(self) Py_INCREF(self.weakref) status = pj_mutex_create_recursive(_get_ua()._pjsip_endpoint._pool, "invitation_lock", &self._lock) if status != 0: raise PJSIPError("failed to create lock", status) pj_list_init( &self._route_set) self._invite_session = NULL self._dialog = NULL self._reinvite_transaction = NULL self._transfer_usage = NULL self._sdp_neg_status = -1 self._failed_response = 0 self._timer = None self._transfer_timeout_timer = None self._transfer_refresh_timer = None self.from_header = None self.to_header = None self.request_uri = None self.route_header = None self.local_contact_header = None self.remote_contact_header = None self.credentials = None self.sdp = SDPPayloads() self.remote_user_agent = None self.state = None self.sub_state = None self.transport = None self.transfer_state = None self.direction = None self.call_id = None self.peer_address = None cdef int init_incoming(self, PJSIPUA ua, pjsip_rx_data *rdata, unsigned int inv_options) except -1: cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_sdp_session_ptr_const sdp cdef pjsip_dialog *replaced_dialog = NULL cdef pjsip_tpselector tp_sel cdef pjsip_tx_data *tdata = NULL cdef PJSTR contact_str cdef char *error_message with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: # Validate replaces header with nogil: status = pjsip_replaces_verify_request(rdata, &replaced_dialog, 0, &tdata) if status != 0: if tdata != NULL: pjsip_endpt_send_response2(ua._pjsip_endpoint._obj, rdata, tdata, NULL, NULL) else: pjsip_endpt_respond_stateless(ua._pjsip_endpoint._obj, rdata, 500, NULL, NULL, NULL) if status != 0: return 0 self.direction = "incoming" - self.transport = rdata.tp_info.transport.type_name.lower() + self.transport = rdata.tp_info.transport.type_name.decode().lower() self.request_uri = FrozenSIPURI_create( pjsip_uri_get_uri(rdata.msg_info.msg.line.req.uri)) - if _is_valid_ip(pj_AF_INET(), self.request_uri.host): + if _is_valid_ip(pj_AF_INET(), self.request_uri.host.encode()): self.local_contact_header = FrozenContactHeader(self.request_uri) else: self.local_contact_header = FrozenContactHeader(FrozenSIPURI(host=_pj_str_to_str(rdata.tp_info.transport.local_name.host), user=self.request_uri.user, port=rdata.tp_info.transport.local_name.port, parameters=(frozendict(transport=self.transport) if self.transport != "udp" else frozendict()))) - contact_str = PJSTR(str(self.local_contact_header.body)) + contact_str = PJSTR(str(self.local_contact_header.body).encode()) tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT tp_sel.u.transport = rdata.tp_info.transport with nogil: status = pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(), rdata, &contact_str.pj_str, &self._dialog) if status != 0: error_message = "Could not create dialog for new INVITE session" else: pjsip_dlg_set_transport(self._dialog, &tp_sel) status = pjsip_inv_create_uas(self._dialog, rdata, NULL, inv_options, &self._invite_session) pjsip_dlg_dec_lock(self._dialog) if status != 0: error_message = "Could not create new INVITE session" else: status = pjsip_inv_initial_answer(self._invite_session, rdata, 100, NULL, NULL, &tdata) if status != 0: error_message = "Could not create initial (unused) response to INVITE" else: pjsip_tx_data_dec_ref(tdata) if status != 0: raise PJSIPError(error_message, status) if self._invite_session.neg != NULL: if pjmedia_sdp_neg_get_state(self._invite_session.neg) == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER: pjmedia_sdp_neg_get_neg_remote(self._invite_session.neg, &sdp) self.sdp.proposed_remote = FrozenSDPSession_create(sdp) self._invite_session.sdp_neg_flags = PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE self._invite_session.mod_data[ua._module.id] = self.weakref self.call_id = _pj_str_to_str(self._dialog.call_id.id) self.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) event_dict = dict(obj=self, prev_state=self.state, state="incoming", originator="remote") _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) + print(event_dict) self.state = "incoming" self.remote_user_agent = event_dict['headers']['User-Agent'].body if 'User-Agent' in event_dict['headers'] else None try: self.remote_contact_header = event_dict['headers']['Contact'][0] except LookupError: pass _add_event("SIPInvitationChangedState", event_dict) self.from_header = FrozenFromHeader_create(rdata.msg_info.from_hdr) self.to_header = FrozenToHeader_create(rdata.msg_info.to_hdr) except: if self._invite_session != NULL: with nogil: pjsip_inv_terminate(self._invite_session, 500, 0) self._invite_session = NULL elif self._dialog != NULL: with nogil: pjsip_dlg_terminate(self._dialog) self._dialog = NULL else: with nogil: status = pjsip_endpt_create_response(ua._pjsip_endpoint._obj, rdata, 500, NULL, &tdata) if status != 0: error_message = "Could not create response" else: status = pjsip_endpt_send_response2(ua._pjsip_endpoint._obj, rdata, tdata, NULL, NULL) if status != 0: pjsip_tx_data_dec_ref(tdata) error_message = "Could not send response" if status != 0: raise PJSIPError(error_message, status) raise finally: with nogil: pj_mutex_unlock(lock) return 0 cdef int process_incoming_transfer(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: global _incoming_transfer_cb global _event_hdr_name cdef int status, status2 cdef dict rdata_dict = dict(obj=self) cdef pjsip_tx_data *tdata cdef pjsip_transaction *initial_tsx cdef Timer timer cdef char *error_message if self._transfer_usage != NULL: with nogil: status = pjsip_endpt_create_response(ua._pjsip_endpoint._obj, rdata, 480, NULL, &tdata) if status != 0: error_message = "Could not create response" else: status = pjsip_endpt_send_response2(ua._pjsip_endpoint._obj, rdata, tdata, NULL, NULL) if status != 0: pjsip_tx_data_dec_ref(tdata) error_message = "Could not send response" if status != 0: raise PJSIPError(error_message, status) return 0 _pjsip_msg_to_dict(rdata.msg_info.msg, rdata_dict) try: refer_to_hdr = rdata_dict["headers"]["Refer-To"] SIPURI.parse(refer_to_hdr.uri) except (KeyError, SIPCoreError): with nogil: status = pjsip_endpt_create_response(ua._pjsip_endpoint._obj, rdata, 400, NULL, &tdata) if status != 0: error_message = "Could not create response" else: status = pjsip_endpt_send_response2(ua._pjsip_endpoint._obj, rdata, tdata, NULL, NULL) if status != 0: pjsip_tx_data_dec_ref(tdata) error_message = "Could not send response" if status != 0: raise PJSIPError(error_message, status) return 0 try: self._set_transfer_state("INCOMING") _add_event("SIPInvitationTransferNewIncoming", rdata_dict) # PJSIP event framework needs an Event header, even if it's not needed for REFER, so we insert a fake one event_header = pjsip_msg_find_hdr_by_name(rdata.msg_info.msg, &_event_hdr_name.pj_str, NULL) if event_header == NULL: event_header = pjsip_event_hdr_create(rdata.tp_info.pool) event_header.event_type = _refer_event.pj_str pjsip_msg_add_hdr(rdata.msg_info.msg, event_header) initial_tsx = pjsip_rdata_get_tsx(rdata) with nogil: status = pjsip_evsub_create_uas(self._dialog, &_incoming_transfer_cb, rdata, 0, &self._transfer_usage) if status != 0: pjsip_tsx_terminate(initial_tsx, 500) error_message = "Could not create incoming REFER session" else: self._transfer_usage_role = PJSIP_ROLE_UAS pjsip_evsub_set_mod_data(self._transfer_usage, ua._event_module.id, self.weakref) status = pjsip_dlg_create_response(self._dialog, rdata, 202, NULL, &tdata) if status != 0: pjsip_tsx_terminate(initial_tsx, 500) error_message = "Could not create response for incoming REFER" else: pjsip_evsub_update_expires(self._transfer_usage, 90) status = pjsip_dlg_send_response(self._dialog, initial_tsx, tdata) if status != 0: status2 = pjsip_dlg_modify_response(self._dialog, tdata, 500, NULL) if status2 != 0: error_message = "Could not modify response" status = status2 else: pjsip_tx_data_dec_ref(tdata) # pjsip_dlg_modify_response() increases ref count unnecessarily error_message = "Could not send response" if status != 0: raise PJSIPError(error_message, status) except PJSIPError, e: code = 0 reason = e.args[0] if self._transfer_usage != NULL: with nogil: pjsip_evsub_terminate(self._transfer_usage, 0) # Manually trigger the state callback since we handle the timeout ourselves state_timer = TransferStateCallbackTimer("TERMINATED", code, reason) state_timer.schedule(0, self._transfer_cb_state, self) raise else: self._set_transfer_state("ACTIVE") _add_event("SIPInvitationTransferDidStart", dict(obj=self)) timer = Timer() timer.schedule(0, self._start_incoming_transfer, self) return 0 cdef int process_incoming_options(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: cdef pjsip_tx_data *tdata cdef pjsip_transaction *initial_tsx cdef int status cdef char *error_message initial_tsx = pjsip_rdata_get_tsx(rdata) with nogil: status = pjsip_dlg_create_response(self._dialog, rdata, 200, NULL, &tdata) if status != 0: pjsip_tsx_terminate(initial_tsx, 500) error_message = "Could not create response for incoming OPTIONS" else: status = pjsip_dlg_send_response(self._dialog, initial_tsx, tdata) if status != 0: error_message = "Could not send response" if status != 0: raise PJSIPError(error_message, status) def send_invite(self, SIPURI request_uri not None, FromHeader from_header not None, ToHeader to_header not None, RouteHeader route_header not None, ContactHeader contact_header not None, SDPSession sdp not None, Credentials credentials=None, list extra_headers not None=list(), timeout=None): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_sdp_session *local_sdp cdef pjsip_cred_info *cred_info cdef pjsip_replaces_hdr *pj_replaces_hdr cdef pjsip_route_hdr *route_set cdef pjsip_tx_data *tdata cdef PJSIPUA ua cdef PJSTR contact_str cdef PJSTR from_header_str cdef PJSTR to_header_str cdef PJSTR request_uri_str ua = _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: route_set = &self._route_set if self.state is not None: raise SIPCoreInvalidStateError('Can only transition to the "outgoing" state from the "None" state, currently in the "%s" state' % self.state) if timeout is not None and timeout <= 0: raise ValueError("Timeout value must be positive") self.transport = route_header.uri.transport self.direction = "outgoing" self.credentials = FrozenCredentials.new(credentials) if credentials is not None else None self.request_uri = FrozenSIPURI.new(request_uri) self.route_header = FrozenRouteHeader.new(route_header) - self.route_header.uri.parameters.dict["lr"] = None # always send lr parameter in Route header - self.route_header.uri.parameters.dict["hide"] = None # always hide Route header + self.route_header.uri.parameters.dict[b"lr"] = None # always send lr parameter in Route header + self.route_header.uri.parameters.dict[b"hide"] = None # always hide Route header self.local_contact_header = FrozenContactHeader.new(contact_header) self.sdp.proposed_local = FrozenSDPSession.new(sdp) if sdp is not None else None from_header_parameters = from_header.parameters.copy() from_header_parameters.pop("tag", None) from_header.parameters = {} from_header_str = PJSTR(from_header.body) to_header_parameters = to_header.parameters.copy() to_header_parameters.pop("tag", None) to_header.parameters = {} to_header_str = PJSTR(to_header.body) contact_str = PJSTR(str(self.local_contact_header.body)) request_uri_str = PJSTR(str(request_uri)) with nogil: status = pjsip_dlg_create_uac(pjsip_ua_instance(), &from_header_str.pj_str, &contact_str.pj_str, &to_header_str.pj_str, &request_uri_str.pj_str, &self._dialog) if status != 0: raise PJSIPError("Could not create dialog for outgoing INVITE session", status) with nogil: pjsip_dlg_inc_lock(self._dialog) if contact_header.expires is not None: self._dialog.local.contact.expires = contact_header.expires if contact_header.q is not None: self._dialog.local.contact.q1000 = int(contact_header.q*1000) contact_parameters = contact_header.parameters.copy() contact_parameters.pop("q", None) contact_parameters.pop("expires", None) _dict_to_pjsip_param(contact_parameters, &self._dialog.local.contact.other_param, self._dialog.pool) _dict_to_pjsip_param(from_header_parameters, &self._dialog.local.info.other_param, self._dialog.pool) _dict_to_pjsip_param(to_header_parameters, &self._dialog.remote.info.other_param, self._dialog.pool) self.from_header = FrozenFromHeader_create(self._dialog.local.info) self.to_header = FrozenToHeader.new(to_header) self.call_id = _pj_str_to_str(self._dialog.call_id.id) local_sdp = self.sdp.proposed_local.get_sdp_session() if sdp is not None else NULL with nogil: status = pjsip_inv_create_uac(self._dialog, local_sdp, 0, &self._invite_session) if status != 0: raise PJSIPError("Could not create outgoing INVITE session", status) self._invite_session.sdp_neg_flags = PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE self._invite_session.mod_data[ua._module.id] = self.weakref if self.credentials is not None: cred_info = self.credentials.get_cred_info() with nogil: status = pjsip_auth_clt_set_credentials(&self._dialog.auth_sess, 1, cred_info) if status != 0: raise PJSIPError("Could not set credentials for INVITE session", status) _BaseRouteHeader_to_pjsip_route_hdr(self.route_header, &self._route_header, self._dialog.pool) pj_list_insert_after( &self._route_set, &self._route_header) with nogil: status = pjsip_dlg_set_route_set(self._dialog, route_set) if status != 0: raise PJSIPError("Could not set route for INVITE session", status) with nogil: status = pjsip_inv_invite(self._invite_session, &tdata) if status != 0: raise PJSIPError("Could not create INVITE message", status) replaces_headers = [header for header in extra_headers if isinstance(header, BaseReplacesHeader)] if len(replaces_headers) > 1: raise SIPCoreError("Only one Replaces header is allowed") try: replaces_header = replaces_headers[0] except IndexError: pass else: extra_headers.remove(replaces_header) pj_replaces_hdr = pjsip_replaces_hdr_create(self._dialog.pool) _str_to_pj_str(replaces_header.call_id, &pj_replaces_hdr.call_id) _str_to_pj_str(replaces_header.to_tag, &pj_replaces_hdr.to_tag) _str_to_pj_str(replaces_header.from_tag, &pj_replaces_hdr.from_tag) _dict_to_pjsip_param(replaces_header.parameters, &pj_replaces_hdr.other_param, self._dialog.pool) pjsip_msg_add_hdr(tdata.msg, pj_replaces_hdr) _add_headers_to_tdata(tdata, extra_headers) with nogil: status = pjsip_inv_send_msg(self._invite_session, tdata) if status != 0: raise PJSIPError("Could not send initial INVITE", status) if timeout is not None: self._timer = Timer() self._timer.schedule(timeout, self._cb_timer_disconnect, self) with nogil: pjsip_dlg_dec_lock(self._dialog) except Exception, e: if isinstance(e, PJSIPError) and e.errno == EADDRNOTAVAIL: self._invite_session = NULL pjsip_dlg_dec_lock(self._dialog) self._dialog = NULL raise if self._invite_session != NULL: pjsip_inv_terminate(self._invite_session, 500, 0) self._invite_session = NULL elif self._dialog != NULL: pjsip_dlg_dec_lock(self._dialog) self._dialog = NULL raise finally: with nogil: pj_mutex_unlock(lock) def send_response(self, int code, str reason=None, BaseContactHeader contact_header=None, BaseSDPSession sdp=None, list extra_headers not None=list()): cdef int status cdef int clean_tdata = 0 cdef pj_mutex_t *lock = self._lock cdef pj_str_t reason_str cdef pjmedia_sdp_session_ptr_const lsdp = NULL cdef pjmedia_sdp_session *local_sdp cdef pjsip_inv_session *invite_session cdef pjsip_msg_body *body cdef pjsip_tx_data *tdata cdef PJSIPUA ua ua = _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: invite_session = self._invite_session if reason is not None: _str_to_pj_str(reason, &reason_str) if self.state not in ("incoming", "early", "connected"): raise SIPCoreInvalidStateError('Can only send response from the "incoming", "early" and "connected" states current in the "%s" state.' % self.state) if self.state == "early" and self.direction != "incoming": raise SIPCoreInvalidStateError('Cannot send response in the "early" state for an outgoing INVITE') if self.state == "connected" and self.sub_state not in ("received_proposal", "received_proposal_request"): raise SIPCoreInvalidStateError('Cannot send response in the "connected" state if a proposal has not been received') if contact_header is not None: self._update_contact_header(contact_header) if 200 <= code < 300 and sdp is None: raise SIPCoreError("Local SDP needs to be set for a positive response") if code >= 300 and sdp is not None: raise SIPCoreError("Local SDP cannot be specified for a negative response") self.sdp.proposed_local = FrozenSDPSession.new(sdp) if sdp is not None else None local_sdp = self.sdp.proposed_local.get_sdp_session() if sdp is not None else NULL if sdp is not None and self.sdp.proposed_remote is None: # There was no remote proposal, this is a reply with an offer with nogil: status = pjmedia_sdp_neg_modify_local_offer(self._dialog.pool, invite_session.neg, local_sdp) if status != 0: raise PJSIPError("Could not modify local SDP offer", status) # Retrieve the "fixed" offer from negotiator pjmedia_sdp_neg_get_neg_local(invite_session.neg, &lsdp) local_sdp = lsdp with nogil: status = pjsip_inv_answer(invite_session, code, &reason_str if reason is not None else NULL, local_sdp, &tdata) if status != 0: raise PJSIPError("Could not create %d reply to INVITE" % code, status) _add_headers_to_tdata(tdata, extra_headers) with nogil: status = pjsip_inv_send_msg(invite_session, tdata) if status != 0: exc = PJSIPError("Could not send %d response" % code, status) if sdp is not None and self.sdp.proposed_remote is not None and exc.errno in (EADDRNOTAVAIL, ENETUNREACH): self._failed_response = 1 raise exc self._failed_response = 0 finally: with nogil: pj_mutex_unlock(lock) def send_reinvite(self, BaseContactHeader contact_header=None, BaseSDPSession sdp=None, list extra_headers not None=list()): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_sdp_session *local_sdp cdef pjsip_inv_session *invite_session cdef pjsip_tx_data *tdata cdef PJSIPUA ua ua = _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: invite_session = self._invite_session if self.state != "connected": raise SIPCoreError('Can only send re-INVITE in "connected" state, not "%s" state' % self.state) if self.sub_state != "normal": raise SIPCoreError('Can only send re-INVITE if no another re-INVITE transaction is active') if contact_header is not None: self._update_contact_header(contact_header) self.sdp.proposed_local = FrozenSDPSession.new(sdp) if sdp is not None else self.sdp.active_local local_sdp = self.sdp.proposed_local.get_sdp_session() with nogil: status = pjsip_inv_reinvite(invite_session, NULL, local_sdp, &tdata) if status != 0: raise PJSIPError("Could not create re-INVITE message", status) _add_headers_to_tdata(tdata, extra_headers) with nogil: status = pjsip_inv_send_msg(invite_session, tdata) if status != 0: raise PJSIPError("Could not send re-INVITE", status) self._failed_response = 0 # TODO: use a callback tiner here instead? self._reinvite_transaction = self._invite_session.invite_tsx self.sub_state = "sent_proposal" event_dict = dict(obj=self, prev_state="connected", state="connected", prev_sub_state="normal", sub_state="sent_proposal", originator="local") _pjsip_msg_to_dict(tdata.msg, event_dict) _add_event("SIPInvitationChangedState", event_dict) finally: with nogil: pj_mutex_unlock(lock) def cancel_reinvite(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjsip_inv_session *invite_session cdef pjsip_tx_data *tdata cdef PJSIPUA ua ua = _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: invite_session = self._invite_session if not self.sub_state == "sent_proposal": raise SIPCoreError("re-INVITE can only be cancelled if INVITE session is in 'sent_proposal' sub state") if self._invite_session == NULL: raise SIPCoreError("INVITE session is not active") if self._reinvite_transaction == NULL: raise SIPCoreError("there is no active re-INVITE transaction") with nogil: status = pjsip_inv_cancel_reinvite(invite_session, &tdata) if status != 0: raise PJSIPError("Could not create message to CANCEL re-INVITE transaction", status) if tdata != NULL: with nogil: status = pjsip_inv_send_msg(invite_session, tdata) if status != 0: raise PJSIPError("Could not send %s" % _pj_str_to_str(tdata.msg.line.req.method.name), status) finally: with nogil: pj_mutex_unlock(lock) def transfer(self, SIPURI target_uri, object replaced_dialog_id=None, list extra_headers not None=list()): global _refer_event global _refer_method cdef int status cdef PJSIPUA ua cdef pj_mutex_t *lock = self._lock cdef pjsip_method refer_method cdef pjsip_tx_data *tdata cdef dict tdata_dict = dict(obj=self) ua = _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state != "connected": raise SIPCoreError('Can only start transfer in "connected" state, not "%s" state' % self.state) if self._transfer_usage != NULL: raise SIPCoreError('Another transfer is in progress') with nogil: status = pjsip_evsub_create_uac(self._dialog, &_transfer_cb, &_refer_event.pj_str, PJSIP_EVSUB_NO_EVENT_ID, &self._transfer_usage) if status != 0: raise PJSIPError("Could not create REFER", status) self._transfer_usage_role = PJSIP_ROLE_UAC pjsip_evsub_set_mod_data(self._transfer_usage, ua._event_module.id, self.weakref) pjsip_method_init_np(&refer_method, &_refer_method.pj_str) with nogil: status = pjsip_evsub_initiate(self._transfer_usage, &refer_method, -1, &tdata) if status != 0: raise PJSIPError("Could not create REFER message", status) if replaced_dialog_id is not None and None not in replaced_dialog_id: target_uri.headers["Replaces"] = "%s;from-tag=%s;to-tag=%s" % replaced_dialog_id refer_to_header = ReferToHeader(str(target_uri)) _add_headers_to_tdata(tdata, [refer_to_header, Header('Referred-By', str(self.local_identity.uri))]) _add_headers_to_tdata(tdata, extra_headers) # We can't remove the Event header or PJSIP will fail to match responses to this request _remove_headers_from_tdata(tdata, ["Expires"]) with nogil: status = pjsip_evsub_send_request(self._transfer_usage, tdata) if status != 0: raise PJSIPError("Could not send REFER message", status) _pjsip_msg_to_dict(tdata.msg, tdata_dict) _add_event("SIPInvitationTransferNewOutgoing", tdata_dict) self._transfer_timeout_timer = Timer() self._transfer_timeout_timer.schedule(90, self._transfer_cb_timeout_timer, self) finally: with nogil: pj_mutex_unlock(lock) def notify_transfer_progress(self, int code, str reason=None): cdef int status cdef PJSIPUA ua cdef pj_mutex_t *lock = self._lock ua = _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self._transfer_usage == NULL: raise SIPCoreError("No transfer is in progress") if self._transfer_usage_role != PJSIP_ROLE_UAS: raise SIPCoreError("Transfer progress can only be notified by the transfer UAS") self._set_sipfrag_payload(code, reason) if 200 <= code < 700: self._terminate_transfer_uas() else: self._send_notify() finally: with nogil: pj_mutex_unlock(lock) def end(self, list extra_headers not None=list(), timeout=None): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjsip_inv_session *invite_session cdef pjsip_tx_data *tdata cdef PJSIPUA ua ua = _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: invite_session = self._invite_session if self.state == "disconnected": return if self.state == "disconnecting": raise SIPCoreError('INVITE session is already in the "disconnecting" state') if self._invite_session == NULL: raise SIPCoreError("INVITE session is not active") if self.state not in ("outgoing", "early", "connecting", "connected"): raise SIPCoreError('Can only end the INVITE dialog from the "outgoing", "early", "connecting" and "connected" states' + 'current in the "%s" state.' % self.state) if self.state == "early" and self.direction != "outgoing": raise SIPCoreError('Cannot end incoming INVITE dialog while in the "early" state') if timeout is not None and timeout <= 0: raise ValueError("Timeout value cannot be negative") # End ongoing transfer self._terminate_transfer() with nogil: status = pjsip_inv_end_session(invite_session, 0, NULL, &tdata) if status != 0: raise PJSIPError("Could not create message to end INVITE session", status) if tdata != NULL: _add_headers_to_tdata(tdata, extra_headers) with nogil: status = pjsip_inv_send_msg(invite_session, tdata) if status != 0: raise PJSIPError("Could not send %s" % _pj_str_to_str(tdata.msg.line.req.method.name), status) if self._timer is not None: self._timer.cancel() self._timer = None if timeout is not None and timeout > 0: self._timer = Timer() self._timer.schedule(timeout, self._cb_timer_disconnect, self) event_dict = dict(obj=self, prev_state=self.state, state="disconnecting", originator="local") if self.state == "connected": event_dict["prev_sub_state"] = self.sub_state self.state = "disconnecting" self.sub_state = None if tdata != NULL: _pjsip_msg_to_dict(tdata.msg, event_dict) _add_event("SIPInvitationChangedState", event_dict) finally: with nogil: pj_mutex_unlock(lock) property local_identity: def __get__(self): if self.direction == 'outgoing': return self.from_header elif self.direction == 'incoming': return self.to_header else: return None property remote_identity: def __get__(self): if self.direction == 'incoming': return self.from_header elif self.direction == 'outgoing': return self.to_header else: return None property dialog_id: def __get__(self): local_tag = remote_tag = None if self.local_identity is not None: local_tag = self.local_identity.tag if self.remote_identity is not None: remote_tag = self.remote_identity.tag return DialogID(self.call_id, local_tag, remote_tag) cdef PJSIPUA _check_ua(self): try: return _get_ua() except: self.state = "disconnected" self.sub_state = None self._dialog = NULL self._invite_session = NULL self._reinvite_transaction = NULL cdef int _do_dealloc(self) except -1: cdef int status cdef pj_mutex_t *lock = self._lock cdef pjsip_inv_session *invite_session cdef PJSIPUA ua try: ua = _get_ua() except SIPCoreError: return 0 with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: invite_session = self._invite_session if self._invite_session != NULL: self._invite_session.mod_data[ua._module.id] = NULL if self.state != "disconnecting": with nogil: pjsip_inv_terminate(invite_session, 481, 0) self._dialog = NULL self._invite_session = NULL self._reinvite_transaction = NULL if self._timer is not None: self._timer.cancel() self._timer = None finally: with nogil: pj_mutex_unlock(lock) return 0 def __dealloc__(self): cdef Timer timer self._do_dealloc() if self._lock != NULL: pj_mutex_destroy(self._lock) timer = Timer() try: timer.schedule(60, deallocate_weakref, self.weakref) except SIPCoreError: pass cdef int _update_contact_header(self, BaseContactHeader contact_header) except -1: # The PJSIP functions called here don't do much, so there is no need to call them # without the gil. cdef pj_str_t contact_str_pj cdef pjsip_uri *contact contact_str = str(contact_header.uri) if contact_header.display_name: contact_str = "%s <%s>" % (contact_header.display_name.encode('utf-8'), contact_str) pj_strdup2_with_null(self._dialog.pool, &contact_str_pj, contact_str) contact = pjsip_parse_uri(self._dialog.pool, contact_str_pj.ptr, contact_str_pj.slen, PJSIP_PARSE_URI_AS_NAMEADDR) if contact == NULL: raise SIPCoreError("Not a valid Contact header: %s" % contact_str) self._dialog.local.contact = pjsip_contact_hdr_create(self._dialog.pool) self._dialog.local.contact.uri = contact if contact_header.expires is not None: self._dialog.local.contact.expires = contact_header.expires if contact_header.q is not None: self._dialog.local.contact.q1000 = int(contact_header.q*1000) parameters = contact_header.parameters.copy() parameters.pop("q", None) parameters.pop("expires", None) _dict_to_pjsip_param(parameters, &self._dialog.local.contact.other_param, self._dialog.pool) self.local_contact_header = FrozenContactHeader.new(contact_header) return 0 cdef int _fail(self, PJSIPUA ua) except -1: cdef Timer timer ua._handle_exception(0) if self._transfer_usage != NULL: with nogil: pjsip_evsub_terminate(self._transfer_usage, 0) pjsip_evsub_set_mod_data(self._transfer_usage, ua._event_module.id, NULL) if self._transfer_timeout_timer is not None: self._transfer_timeout_timer.cancel() self._transfer_timeout_timer = None if self._transfer_refresh_timer is not None: self._transfer_refresh_timer.cancel() self._transfer_refresh_timer = None self._transfer_usage = NULL _add_event("SIPInvitationTransferDidFail", dict(obj=self, code=0, reason="internal error")) self._invite_session.mod_data[ua._module.id] = NULL if self.state != "disconnected": event_dict = dict(obj=self, prev_state=self.state, state="disconnected", originator="local", code=0, reason="internal error", disconnect_reason="internal error") if self.state == "connected": event_dict["prev_sub_state"] = self.sub_state self.state = "disconnected" self.sub_state = None _add_event("SIPInvitationChangedState", event_dict) # calling do_dealloc from within a callback makes PJSIP crash # the handler will be executed after pjsip_endpt_handle_events returns timer = Timer() timer.schedule(0, self._cb_postpoll_fail, self) return 0 cdef int _cb_state(self, StateCallbackTimer timer) except -1: cdef int status cdef bint pjsip_error = False cdef pj_mutex_t *lock = self._lock cdef pjmedia_sdp_session_ptr_const sdp cdef pjsip_inv_session *invite_session cdef object state cdef object sub_state cdef object rdata cdef object tdata cdef object originator cdef PJSIPUA ua ua = self._check_ua() if ua is None: return 0 with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: invite_session = self._invite_session state = timer.state sub_state = timer.sub_state rdata = timer.rdata tdata = timer.tdata originator = timer.originator if state != "early" and state == self.state and sub_state == self.sub_state: return 0 if state == "connected": if self.state == "connecting" and self._sdp_neg_status != 0: self.end() return 0 if state == "disconnected" and self.state != "disconnecting": # the invite session may have been destroyed if it failed if not self._invite_session: return 0 # we either sent a cancel or a negative reply to an incoming INVITE if self._invite_session.cancelling or (self.state in ("incoming", "early") and self.direction == "incoming" and rdata is None): # we caused the disconnect so send the transition to the disconnecting state pjsip_error = True event_dict = dict(obj=self, prev_state=self.state, state="disconnecting", originator="local") self.state = "disconnecting" _add_event("SIPInvitationChangedState", event_dict) if self.direction == "outgoing" and state in ('connecting', 'connected') and self.state in ('outgoing', 'early') and rdata is not None: self.to_header = rdata['headers']['To'] if self.direction == "incoming" and state in ('connecting', 'connected') and self.state in ('incoming', 'early') and tdata is not None: self.to_header = tdata['headers']['To'] event_dict = dict(obj=self, prev_state=self.state, state=state) if self.state == "connected": event_dict["prev_sub_state"] = self.sub_state if state == "connected": event_dict["sub_state"] = sub_state event_dict["originator"] = originator if rdata is not None: event_dict.update(rdata) if tdata is not None: event_dict.update(tdata) if rdata is None and tdata is None: event_dict['headers'] = dict() event_dict['body'] = None if self.remote_user_agent is None and state in ('connecting', 'connected') and rdata is not None: if 'User-Agent' in event_dict['headers']: self.remote_user_agent = event_dict['headers']['User-Agent'].body elif 'Server' in event_dict['headers']: self.remote_user_agent = event_dict['headers']['Server'].body if state not in ('disconnecting', 'disconnected') and rdata is not None: try: self.remote_contact_header = event_dict['headers']['Contact'][0] except LookupError: pass if state == "connected": if sub_state == "received_proposal": self._reinvite_transaction = self._invite_session.invite_tsx if pjmedia_sdp_neg_get_state(self._invite_session.neg) == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER: pjmedia_sdp_neg_get_neg_remote(self._invite_session.neg, &sdp) self.sdp.proposed_remote = FrozenSDPSession_create(sdp) elif sub_state == "sent_proposal": if pjmedia_sdp_neg_get_state(self._invite_session.neg) == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER: pjmedia_sdp_neg_get_neg_local(self._invite_session.neg, &sdp) self.sdp.proposed_local = FrozenSDPSession_create(sdp) elif sub_state == "received_proposal_request": self._reinvite_transaction = self._invite_session.invite_tsx if pjmedia_sdp_neg_get_state(self._invite_session.neg) == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER: pjmedia_sdp_neg_get_neg_local(self._invite_session.neg, &sdp) self.sdp.proposed_local = FrozenSDPSession_create(sdp) elif self.sub_state in ("received_proposal", "sent_proposal", "received_proposal_request"): if (rdata, tdata) == (None, None): event_dict['code'] = 408 event_dict['reason'] = 'Request Timeout' if pjmedia_sdp_neg_get_state(self._invite_session.neg) in (PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER): pjmedia_sdp_neg_cancel_offer(self._invite_session.neg) self._reinvite_transaction = NULL if state == "disconnected": event_dict["disconnect_reason"] = "user request" if not pjsip_error else "internal error" event_dict["code"] = self._invite_session.cause if self._invite_session.cause > 0: event_dict["reason"] = _pj_str_to_str(self._invite_session.cause_text) else: event_dict["reason"] = "" if not self._invite_session.cancelling and rdata is None and self._invite_session.cause > 0: # pjsip internally generates 408 and 503 if self._invite_session.cause == 408: if self.direction == "incoming" and self.state == "connecting": event_dict["disconnect_reason"] = "missing ACK" else: event_dict["disconnect_reason"] = "timeout" else: event_dict["disconnect_reason"] = _pj_str_to_str(self._invite_session.cause_text) elif self._invite_session.cancelling and rdata is None and self._invite_session.cause == 408 and self.state == "disconnecting": # silly pjsip sets cancelling field when we call pjsip_inv_end_session in end even if we send a BYE event_dict["disconnect_reason"] = "timeout" elif rdata is not None and 'Reason' in event_dict['headers']: try: reason = event_dict['headers']['Reason'].text if reason: event_dict["disconnect_reason"] = reason except (ValueError, IndexError): pass if self._transfer_usage != NULL: with nogil: pjsip_evsub_terminate(self._transfer_usage, 0) pjsip_evsub_set_mod_data(self._transfer_usage, ua._event_module.id, NULL) if self._transfer_timeout_timer is not None: self._transfer_timeout_timer.cancel() self._transfer_timeout_timer = None if self._transfer_refresh_timer is not None: self._transfer_refresh_timer.cancel() self._transfer_refresh_timer = None self._transfer_usage = NULL _add_event("SIPInvitationTransferDidFail", dict(obj=self, code=0, reason="invite dialog ended")) self._invite_session.mod_data[ua._module.id] = NULL self._invite_session = NULL self._dialog = NULL if self._timer is not None: self._timer.cancel() self._timer = None elif state in ("early", "connecting") and self._timer is not None: self._timer.cancel() self._timer = None self.state = state self.sub_state = sub_state _add_event("SIPInvitationChangedState", event_dict) finally: with nogil: pj_mutex_unlock(lock) return 0 cdef int _cb_sdp_done(self, SDPCallbackTimer timer) except -1: cdef int status cdef pj_mutex_t *lock = self._lock with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self._failed_response == 1: return 0 self._sdp_neg_status = timer.status self.sdp.proposed_local = None self.sdp.proposed_remote = None if timer.status == 0: self.sdp.active_local = timer.active_local self.sdp.active_remote = timer.active_remote if self.state in ["disconnecting", "disconnected"]: return 0 event_dict = dict(obj=self, succeeded=timer.status == 0) if timer.status == 0: event_dict["local_sdp"] = self.sdp.active_local event_dict["remote_sdp"] = self.sdp.active_remote else: event_dict["error"] = _pj_status_to_str(timer.status) _add_event("SIPInvitationGotSDPUpdate", event_dict) finally: with nogil: pj_mutex_unlock(lock) return 0 cdef int _cb_timer_disconnect(self, timer) except -1: cdef pjsip_inv_session *invite_session = self._invite_session with nogil: pjsip_inv_terminate(invite_session, 408, 1) cdef int _cb_postpoll_fail(self, timer) except -1: self._do_dealloc() cdef int _start_incoming_transfer(self, timer) except -1: cdef int status cdef pj_mutex_t *lock = self._lock cdef PJSIPUA ua ua = self._check_ua() if ua is None: return 0 with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: self._set_sipfrag_payload(100, "Trying") self._send_notify() finally: with nogil: pj_mutex_unlock(lock) return 0 cdef int _terminate_transfer(self) except -1: if self._transfer_usage == NULL: return 0 if self._transfer_usage_role == PJSIP_ROLE_UAC: self._terminate_transfer_uac() else: self._terminate_transfer_uas() cdef int _terminate_transfer_uac(self) except -1: cdef pjsip_tx_data *tdata cdef int status cdef TransferStateCallbackTimer state_timer try: with nogil: status = pjsip_evsub_initiate(self._transfer_usage, NULL, 0, &tdata) if status != 0: raise PJSIPError("Could not create SUBSCRIBE message", status) with nogil: status = pjsip_evsub_send_request(self._transfer_usage, tdata) if status != 0: raise PJSIPError("Could not send SUBSCRIBE message", status) if self._transfer_timeout_timer is not None: self._transfer_timeout_timer.cancel() self._transfer_timeout_timer = None if self._transfer_refresh_timer is not None: self._transfer_refresh_timer.cancel() self._transfer_refresh_timer = None self._transfer_timeout_timer = Timer() self._transfer_timeout_timer.schedule(1, self._transfer_cb_timeout_timer, self) except PJSIPError, e: if self._transfer_usage != NULL: code = 0 reason = e.args[0] with nogil: pjsip_evsub_terminate(self._transfer_usage, 0) # Manually trigger the state callback since we handle the timeout ourselves state_timer = TransferStateCallbackTimer("TERMINATED", code, reason) state_timer.schedule(0, self._transfer_cb_state, self) cdef int _terminate_transfer_uas(self) except -1: global sipfrag_re cdef int code cdef TransferStateCallbackTimer state_timer if self.transfer_state == "TERMINATED": return 0 self._set_transfer_state("TERMINATED") self._send_notify() with nogil: pjsip_evsub_terminate(self._transfer_usage, 0) match = sipfrag_re.match(self._sipfrag_payload.str) code = int(match.group('code')) reason = match.group('reason') state_timer = TransferStateCallbackTimer("TERMINATED", code, reason) state_timer.schedule(0, self._transfer_cb_state, self) cdef int _set_transfer_state(self, str state) except -1: cdef str prev_state prev_state = self.transfer_state self.transfer_state = state if prev_state != state: _add_event("SIPInvitationTransferChangedState", dict(obj=self, prev_state=prev_state, state=state)) cdef int _set_sipfrag_payload(self, int code, str status) except -1: cdef str content if status is None: try: status = sip_status_messages[code] except IndexError: status = "Unknown" content = "SIP/2.0 %d %s\r\n" % (code, status) self._sipfrag_payload = PJSTR(content) cdef int _send_notify(self) except -1: cdef pjsip_evsub_state state cdef pj_str_t *reason_p = NULL cdef pjsip_tx_data *tdata cdef int status cdef dict _sipfrag_version = dict(version="2.0") - cdef PJSTR _content_type = PJSTR("message") - cdef PJSTR _content_subtype = PJSTR("sipfrag") - cdef PJSTR noresource = PJSTR("noresource") + cdef PJSTR _content_type = PJSTR(b"message") + cdef PJSTR _content_subtype = PJSTR(b"sipfrag") + cdef PJSTR noresource = PJSTR(b"noresource") cdef PJSTR content if self.transfer_state == "ACTIVE": state = PJSIP_EVSUB_STATE_ACTIVE else: state = PJSIP_EVSUB_STATE_TERMINATED reason_p = &noresource.pj_str with nogil: status = pjsip_evsub_notify(self._transfer_usage, state, NULL, reason_p, &tdata) if status != 0: raise PJSIPError("Could not create NOTIFY request", status) if self.transfer_state in ("ACTIVE", "TERMINATED"): tdata.msg.body = pjsip_msg_body_create(tdata.pool, &_content_type.pj_str, &_content_subtype.pj_str, &self._sipfrag_payload.pj_str) _dict_to_pjsip_param(_sipfrag_version, &tdata.msg.body.content_type.param, tdata.pool) with nogil: status = pjsip_evsub_send_request(self._transfer_usage, tdata) if status != 0: raise PJSIPError("Could not send NOTIFY request", status) return 0 cdef int _transfer_cb_timeout_timer(self, timer) except -1: global sip_status_messages cdef int code cdef str reason cdef int status cdef TransferStateCallbackTimer state_timer cdef pj_mutex_t *lock = self._lock cdef PJSIPUA ua ua = self._check_ua() if ua is None: return 0 with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self._transfer_usage != NULL: code = PJSIP_SC_TSX_TIMEOUT reason = sip_status_messages[PJSIP_SC_TSX_TIMEOUT] with nogil: pjsip_evsub_terminate(self._transfer_usage, 0) # Manually trigger the state callback since we handle the timeout ourselves state_timer = TransferStateCallbackTimer("TERMINATED", code, reason) state_timer.schedule(0, self._transfer_cb_state, self) finally: with nogil: pj_mutex_unlock(lock) return 0 cdef int _transfer_cb_refresh_timer(self, timer) except -1: cdef int status cdef pj_mutex_t *lock = self._lock cdef PJSIPUA ua ua = self._check_ua() if ua is None: return 0 with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: self._terminate_transfer() finally: with nogil: pj_mutex_unlock(lock) return 0 cdef int _transfer_cb_state(self, TransferStateCallbackTimer timer) except -1: cdef int status cdef str prev_state cdef pj_mutex_t *lock = self._lock cdef PJSIPUA ua ua = self._check_ua() if ua is None: return 0 with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: prev_state = self.transfer_state self._set_transfer_state(timer.state) if timer.state == "ACCEPTED" and prev_state == "SENT": _add_event("SIPInvitationTransferDidStart", dict(obj=self)) elif timer.state == "TERMINATED": # If a NOTIFY is rejected with 408 or 481 PJSIP will erase the subscription if self._transfer_usage != NULL: pjsip_evsub_set_mod_data(self._transfer_usage, ua._event_module.id, NULL) if self._transfer_timeout_timer is not None: self._transfer_timeout_timer.cancel() self._transfer_timeout_timer = None if self._transfer_refresh_timer is not None: self._transfer_refresh_timer.cancel() self._transfer_refresh_timer = None self._transfer_usage = NULL if timer.code/100 == 2: _add_event("SIPInvitationTransferDidEnd", dict(obj=self)) else: _add_event("SIPInvitationTransferDidFail", dict(obj=self, code=timer.code, reason=timer.reason)) finally: with nogil: pj_mutex_unlock(lock) return 0 cdef int _transfer_cb_response(self, TransferResponseCallbackTimer timer) except -1: cdef int expires cdef int status cdef pj_mutex_t *lock = self._lock cdef PJSIPUA ua ua = self._check_ua() if ua is None: return 0 with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self._transfer_timeout_timer is not None: self._transfer_timeout_timer.cancel() self._transfer_timeout_timer = None finally: with nogil: pj_mutex_unlock(lock) return 0 cdef int _transfer_cb_notify(self, TransferRequestCallbackTimer timer) except -1: cdef pj_time_val refresh cdef int expires cdef dict notify_dict = dict(obj=self) cdef pj_mutex_t *lock = self._lock cdef PJSIPUA ua ua = self._check_ua() if ua is None: return 0 with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: sub_state_hdr = timer.rdata["headers"].get("Subscription-State", None) if self.transfer_state != "TERMINATED" and sub_state_hdr is not None and sub_state_hdr.expires > 0: if self._transfer_refresh_timer is not None: self._transfer_refresh_timer.cancel() self._transfer_refresh_timer = None expires = max(1, sub_state_hdr.expires - self.expire_warning_time, sub_state_hdr.expires/2) self._transfer_refresh_timer = Timer() self._transfer_refresh_timer.schedule(expires, self._transfer_cb_refresh_timer, self) notify_dict["request_uri"] = timer.rdata["request_uri"] notify_dict["from_header"] = timer.rdata["headers"].get("From", None) notify_dict["to_header"] = timer.rdata["headers"].get("To", None) notify_dict["headers"] = timer.rdata["headers"] notify_dict["body"] = timer.rdata["body"] content_type = notify_dict["headers"].get("Content-Type", None) notify_dict["content_type"] = content_type.content_type if content_type else None event = notify_dict["headers"].get("Event", None) notify_dict["event"] = event.event if event else None _add_event("SIPInvitationTransferGotNotify", notify_dict) finally: with nogil: pj_mutex_unlock(lock) return 0 cdef int _transfer_cb_server_timeout(self, timer) except -1: cdef int status cdef pj_mutex_t *lock = self._lock cdef PJSIPUA ua ua = self._check_ua() if ua is None: return 0 with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: self._terminate_transfer() finally: with nogil: pj_mutex_unlock(lock) return 0 # Callback functions # cdef void _Invitation_cb_state(pjsip_inv_session *inv, pjsip_event *e) with gil: cdef pjsip_rx_data *rdata = NULL cdef pjsip_tx_data *tdata = NULL cdef object state cdef object rdata_dict = None cdef object tdata_dict = None cdef object originator = None cdef Invitation invitation cdef PJSIPUA ua cdef StateCallbackTimer timer try: ua = _get_ua() except: return try: if inv.state == PJSIP_INV_STATE_INCOMING: return if inv.mod_data[ua._module.id] != NULL: invitation = ( inv.mod_data[ua._module.id])() if invitation is None: return - state = pjsip_inv_state_name(inv.state).lower() + state = pjsip_inv_state_name(inv.state).decode().lower() sub_state = None if state == "calling": state = "outgoing" elif state == "confirmed": state = "connected" sub_state = "normal" elif state == "disconnctd": state = "disconnected" if e != NULL: if e.type == PJSIP_EVENT_TSX_STATE and e.body.tsx_state.type == PJSIP_EVENT_TX_MSG: tdata = e.body.tsx_state.src.tdata if (tdata.msg.type == PJSIP_RESPONSE_MSG and tdata.msg.line.status.code == 487 and state == "disconnected" and invitation.state in ["incoming", "early"]): return elif e.type == PJSIP_EVENT_RX_MSG: rdata = e.body.rx_msg.rdata elif e.type == PJSIP_EVENT_TSX_STATE and e.body.tsx_state.type == PJSIP_EVENT_RX_MSG: if (inv.state != PJSIP_INV_STATE_CONFIRMED or e.body.tsx_state.src.rdata.msg_info.msg.type == PJSIP_REQUEST_MSG): rdata = e.body.tsx_state.src.rdata elif e.type == PJSIP_EVENT_TSX_STATE and e.body.tsx_state.type == PJSIP_EVENT_TRANSPORT_ERROR and e.body.tsx_state.tsx.role == PJSIP_ROLE_UAC: # A transport error occurred, fake a local reply rdata_dict = dict() rdata_dict["code"] = 408 rdata_dict["reason"] = "Transport Error" rdata_dict["headers"] = dict() rdata_dict["body"] = None originator = "local" if rdata != NULL: if invitation.peer_address is None: invitation.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: invitation.peer_address.ip = rdata.pkt_info.src_name invitation.peer_address.port = rdata.pkt_info.src_port rdata_dict = dict() _pjsip_msg_to_dict(rdata.msg_info.msg, rdata_dict) originator = "remote" if tdata != NULL: tdata_dict = dict() _pjsip_msg_to_dict(tdata.msg, tdata_dict) originator = "local" try: timer = StateCallbackTimer(state, sub_state, rdata_dict, tdata_dict, originator) timer.schedule(0, invitation._cb_state, invitation) except: invitation._fail(ua) except: ua._handle_exception(1) cdef void _Invitation_cb_sdp_done(pjsip_inv_session *inv, int status) with gil: cdef Invitation invitation cdef PJSIPUA ua cdef SDPCallbackTimer timer cdef pjmedia_sdp_session_ptr_const sdp try: ua = _get_ua() except: return try: if inv.mod_data[ua._module.id] != NULL: invitation = ( inv.mod_data[ua._module.id])() if invitation is None: return if status == 0: if pjmedia_sdp_neg_get_active_local(invitation._invite_session.neg, &sdp) == 0: local_sdp = SDPSession_create(sdp) else: local_sdp = None if pjmedia_sdp_neg_get_active_remote(invitation._invite_session.neg, &sdp) == 0: remote_sdp = SDPSession_create(sdp) else: remote_sdp = None if local_sdp is None or remote_sdp is None: active_local = None active_remote = None else: if len(local_sdp.media) > len(remote_sdp.media): local_sdp.media = local_sdp.media[:len(remote_sdp.media)] if len(remote_sdp.media) > len(local_sdp.media): remote_sdp.media = remote_sdp.media[:len(local_sdp.media)] for index, local_media in enumerate(local_sdp.media): remote_media = remote_sdp.media[index] if not local_media.port and remote_media.port: remote_media.port = 0 if not remote_media.port and local_media.port: local_media.port = 0 active_local = FrozenSDPSession.new(local_sdp) active_remote = FrozenSDPSession.new(remote_sdp) else: active_local = None active_remote = None try: timer = SDPCallbackTimer(status, active_local, active_remote) timer.schedule(0, invitation._cb_sdp_done, invitation) except: invitation._fail(ua) except: ua._handle_exception(1) cdef int _Invitation_cb_rx_reinvite(pjsip_inv_session *inv, pjmedia_sdp_session_ptr_const offer, pjsip_rx_data *rdata) with gil: cdef int status cdef pjsip_tx_data *answer_tdata cdef object rdata_dict = None cdef Invitation invitation cdef PJSIPUA ua cdef StateCallbackTimer timer try: ua = _get_ua() except: return 1 try: if inv.mod_data[ua._module.id] != NULL: invitation = ( inv.mod_data[ua._module.id])() if invitation is None: return 1 if invitation.peer_address is None: invitation.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: invitation.peer_address.ip = rdata.pkt_info.src_name invitation.peer_address.port = rdata.pkt_info.src_port rdata_dict = dict() _pjsip_msg_to_dict(rdata.msg_info.msg, rdata_dict) with nogil: status = pjsip_inv_initial_answer(inv, rdata, 100, NULL, NULL, &answer_tdata) if status != 0: raise PJSIPError("Could not create initial (unused) response to re-INVITE", status) with nogil: pjsip_tx_data_dec_ref(answer_tdata) if offer != NULL: sub_state = "received_proposal" else: sub_state = "received_proposal_request" try: timer = StateCallbackTimer("connected", sub_state, rdata_dict, None, "remote") timer.schedule(0, invitation._cb_state, invitation) except: invitation._fail(ua) return 1 return 0 except: ua._handle_exception(1) return 1 cdef void _Invitation_cb_tsx_state_changed(pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e) with gil: cdef pjsip_rx_data *rdata = NULL cdef pjsip_tx_data *tdata = NULL cdef object rdata_dict = None cdef object tdata_dict = None cdef object originator = None cdef Invitation invitation cdef PJSIPUA ua cdef StateCallbackTimer timer cdef TransferRequestCallbackTimer transfer_timer try: ua = _get_ua() except: return try: if tsx == NULL or e == NULL: return if e.type == PJSIP_EVENT_TSX_STATE and e.body.tsx_state.type == PJSIP_EVENT_RX_MSG: rdata = e.body.tsx_state.src.rdata if e.type == PJSIP_EVENT_TSX_STATE and e.body.tsx_state.type == PJSIP_EVENT_TX_MSG: tdata = e.body.tsx_state.src.tdata if inv.mod_data[ua._module.id] != NULL: invitation = ( inv.mod_data[ua._module.id])() if invitation is None: return if rdata != NULL: if invitation.peer_address is None: invitation.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: invitation.peer_address.ip = rdata.pkt_info.src_name invitation.peer_address.port = rdata.pkt_info.src_port if ((tsx.state == PJSIP_TSX_STATE_TERMINATED or tsx.state == PJSIP_TSX_STATE_COMPLETED) and (inv.neg != NULL and pjmedia_sdp_neg_get_state(inv.neg) in (PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, PJMEDIA_SDP_NEG_STATE_DONE)) and invitation._reinvite_transaction != NULL and invitation._reinvite_transaction == tsx): if rdata != NULL: rdata_dict = dict() _pjsip_msg_to_dict(rdata.msg_info.msg, rdata_dict) originator = "remote" if tdata != NULL: tdata_dict = dict() _pjsip_msg_to_dict(tdata.msg, tdata_dict) originator = "local" try: timer = StateCallbackTimer("connected", "normal", rdata_dict, tdata_dict, originator) timer.schedule(0, invitation._cb_state, invitation) except: invitation._fail(ua) elif (invitation.state in ("incoming", "early") and invitation.direction == "incoming" and rdata != NULL and rdata.msg_info.msg.type == PJSIP_REQUEST_MSG and rdata.msg_info.msg.line.req.method.id == PJSIP_CANCEL_METHOD): rdata_dict = dict() _pjsip_msg_to_dict(rdata.msg_info.msg, rdata_dict) originator = "remote" try: timer = StateCallbackTimer("disconnected", None, rdata_dict, None, originator) timer.schedule(0, invitation._cb_state, invitation) except: invitation._fail(ua) elif (tsx.role == PJSIP_ROLE_UAS and tsx.state == PJSIP_TSX_STATE_TRYING and rdata != NULL and rdata.msg_info.msg.type == PJSIP_REQUEST_MSG and _pj_str_to_str(tsx.method.name) == "REFER"): invitation.process_incoming_transfer(ua, rdata) elif (tsx.role == PJSIP_ROLE_UAS and tsx.state == PJSIP_TSX_STATE_TRYING and rdata != NULL and rdata.msg_info.msg.type == PJSIP_REQUEST_MSG and tsx.method.id == PJSIP_OPTIONS_METHOD): invitation.process_incoming_options(ua, rdata) except: ua._handle_exception(1) cdef void _Invitation_cb_new(pjsip_inv_session *inv, pjsip_event *e) with gil: # As far as I can tell this is never actually called! pass cdef void _Invitation_transfer_cb_state(pjsip_evsub *sub, pjsip_event *event) with gil: cdef void *invitation_void cdef Invitation invitation cdef object state cdef int code = 0 cdef dict event_dict = dict() cdef str reason = None cdef pjsip_rx_data *rdata = NULL cdef PJSIPUA ua try: ua = _get_ua() except: return try: invitation_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if invitation_void == NULL: return invitation = ( invitation_void)() if invitation is None: return state = pjsip_evsub_get_state_name(sub) if (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and (event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_COMPLETED or event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_TERMINATED)): if state == "TERMINATED": if event.body.tsx_state.tsx.role == PJSIP_ROLE_UAC: code = event.body.tsx_state.tsx.status_code reason = _pj_str_to_str(event.body.tsx_state.tsx.status_text) else: reason = "Referral has expired" if event.body.tsx_state.type == PJSIP_EVENT_RX_MSG and _pj_str_to_str(event.body.tsx_state.tsx.method.name) == "NOTIFY": # Extract code and reason from the sipfrag payload rdata = event.body.tsx_state.src.rdata if rdata != NULL: _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) if event_dict.get('body', None) is not None: match = sipfrag_re.match(event_dict['body']) if match: code = int(match.group('code')) reason = match.group('reason') try: timer = TransferStateCallbackTimer(state, code, reason) timer.schedule(0, invitation._transfer_cb_state, invitation) except: invitation._fail(ua) except: ua._handle_exception(1) cdef void _Invitation_transfer_cb_tsx(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) with gil: cdef void *invitation_void cdef Invitation invitation cdef pjsip_rx_data *rdata cdef PJSIPUA ua try: ua = _get_ua() except: return try: invitation_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if invitation_void == NULL: return invitation = ( invitation_void)() if invitation is None: return if (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.type == PJSIP_EVENT_RX_MSG and event.body.tsx_state.tsx.role == PJSIP_ROLE_UAC and event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_COMPLETED and _pj_str_to_str(event.body.tsx_state.tsx.method.name) in ("REFER", "SUBSCRIBE") and event.body.tsx_state.tsx.status_code/100 == 2): rdata = event.body.tsx_state.src.rdata if rdata != NULL: rdata_dict = dict() _pjsip_msg_to_dict(rdata.msg_info.msg, rdata_dict) try: - timer = TransferResponseCallbackTimer(_pj_str_to_str(event.body.tsx_state.tsx.method.name), rdata_dict) + timer = TransferResponseCallbackTimer(_pj_str_to_bytes(event.body.tsx_state.tsx.method.name), rdata_dict) timer.schedule(0, invitation._transfer_cb_response, invitation) except: invitation._fail(ua) except: ua._handle_exception(1) cdef void _Invitation_transfer_cb_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil: cdef void *invitation_void cdef Invitation invitation cdef TransferRequestCallbackTimer timer cdef PJSIPUA ua try: ua = _get_ua() except: return try: invitation_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if invitation_void == NULL: return invitation = ( invitation_void)() if invitation is None: return if rdata != NULL: rdata_dict = dict() _pjsip_msg_to_dict(rdata.msg_info.msg, rdata_dict) try: timer = TransferRequestCallbackTimer(rdata_dict) timer.schedule(0, invitation._transfer_cb_notify, invitation) except: invitation._fail(ua) except: ua._handle_exception(1) cdef void _Invitation_transfer_cb_refresh(pjsip_evsub *sub) with gil: # We want to handle the refresh timer oursevles, ignore the PJSIP provided timer pass cdef void _Invitation_transfer_in_cb_rx_refresh(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil: cdef void *invitation_void cdef dict rdata_dict cdef pjsip_expires_hdr *expires_header cdef Invitation invitation cdef Timer timer cdef PJSIPUA ua try: ua = _get_ua() except: return try: invitation_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if invitation_void == NULL: p_st_code[0] = 481 return invitation = ( invitation_void)() if invitation is None: p_st_code[0] = 481 return expires_header = pjsip_msg_find_hdr(rdata.msg_info.msg, PJSIP_H_EXPIRES, NULL) if expires_header != NULL and expires_header.ivalue == 0: try: timer = Timer() timer.schedule(0, invitation._terminate_transfer, invitation) except: invitation._fail(ua) p_st_code[0] = 200 return p_st_code[0] = 501 except: ua._handle_exception(1) cdef void _Invitation_transfer_in_cb_server_timeout(pjsip_evsub *sub) with gil: cdef void *invitation_void cdef Invitation invitation cdef Timer timer cdef PJSIPUA ua try: ua = _get_ua() except: return try: invitation_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if invitation_void == NULL: return invitation = ( invitation_void)() if invitation is None: return try: timer = Timer() timer.schedule(0, invitation._transfer_cb_server_timeout, invitation) except: invitation._fail(ua) except: ua._handle_exception(1) cdef void _Invitation_transfer_in_cb_tsx(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) with gil: cdef void *invitation_void cdef Invitation invitation cdef PJSIPUA ua cdef pjsip_rx_data *rdata cdef dict event_dict cdef int code cdef str reason cdef TransferStateCallbackTimer timer try: ua = _get_ua() except: return try: invitation_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if invitation_void == NULL: return invitation = ( invitation_void)() if invitation is None: return if (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.tsx.role == PJSIP_ROLE_UAC and _pj_str_to_str(event.body.tsx_state.tsx.method.name) == "NOTIFY" and event.body.tsx_state.tsx.state in (PJSIP_TSX_STATE_COMPLETED, PJSIP_TSX_STATE_TERMINATED)): code = event.body.tsx_state.tsx.status_code reason = _pj_str_to_str(event.body.tsx_state.tsx.status_text) if code in (408, 481) or code/100==7: # Be careful! PJSIP will erase the subscription timer = TransferStateCallbackTimer("TERMINATED", code, reason) timer.schedule(0, invitation._transfer_cb_state, invitation) except: ua._handle_exception(1) # Globals # cdef pjsip_inv_callback _inv_cb _inv_cb.on_state_changed = _Invitation_cb_state _inv_cb.on_media_update = _Invitation_cb_sdp_done _inv_cb.on_rx_reinvite = _Invitation_cb_rx_reinvite _inv_cb.on_tsx_state_changed = _Invitation_cb_tsx_state_changed _inv_cb.on_new_session = _Invitation_cb_new cdef pjsip_evsub_user _transfer_cb _transfer_cb.on_evsub_state = _Invitation_transfer_cb_state _transfer_cb.on_tsx_state = _Invitation_transfer_cb_tsx _transfer_cb.on_rx_notify = _Invitation_transfer_cb_notify _transfer_cb.on_client_refresh = _Invitation_transfer_cb_refresh cdef pjsip_evsub_user _incoming_transfer_cb _incoming_transfer_cb.on_rx_refresh = _Invitation_transfer_in_cb_rx_refresh _incoming_transfer_cb.on_server_timeout = _Invitation_transfer_in_cb_server_timeout _incoming_transfer_cb.on_tsx_state = _Invitation_transfer_in_cb_tsx diff --git a/sipsimple/core/_core.lib.pxi b/sipsimple/core/_core.lib.pxi index 736cc31c..7416b13c 100644 --- a/sipsimple/core/_core.lib.pxi +++ b/sipsimple/core/_core.lib.pxi @@ -1,547 +1,548 @@ import sys # classes cdef class PJLIB: def __cinit__(self): cdef int status status = pj_init() if status != 0: raise PJSIPError("Could not initialize PJLIB", status) self._init_done = 1 status = pjlib_util_init() if status != 0: raise PJSIPError("Could not initialize PJLIB-UTIL", status) status = pjnath_init() if status != 0: raise PJSIPError("Could not initialize PJNATH", status) def __dealloc__(self): if self._init_done: with nogil: pj_shutdown() cdef class PJCachingPool: def __cinit__(self): pj_caching_pool_init(&self._obj, &pj_pool_factory_default_policy, 0) self._init_done = 1 def __dealloc__(self): if self._init_done: pj_caching_pool_destroy(&self._obj) cdef class PJSIPEndpoint: def __cinit__(self, PJCachingPool caching_pool, ip_address, udp_port, tcp_port, tls_port, tls_verify_server, tls_ca_file, tls_cert_file, tls_privkey_file, int tls_timeout): cdef pj_dns_resolver *resolver cdef pjsip_tpmgr *tpmgr cdef int status - if ip_address is not None and not _is_valid_ip(pj_AF_INET(), ip_address): + if ip_address is not None and not _is_valid_ip(pj_AF_INET(), ip_address.encode()): raise ValueError("Not a valid IPv4 address: %s" % ip_address) self._local_ip_used = ip_address status = pjsip_endpt_create(&caching_pool._obj.factory, "core", &self._obj) if status != 0: raise PJSIPError("Could not initialize PJSIP endpoint", status) self._pool = pjsip_endpt_create_pool(self._obj, "PJSIPEndpoint", 4096, 4096) if self._pool == NULL: raise SIPCoreError("Could not allocate memory pool") status = pjsip_tsx_layer_init_module(self._obj) if status != 0: raise PJSIPError("Could not initialize transaction layer module", status) status = pjsip_ua_init_module(self._obj, NULL) # TODO: handle forking if status != 0: raise PJSIPError("Could not initialize common dialog layer module", status) status = pjsip_evsub_init_module(self._obj) if status != 0: raise PJSIPError("Could not initialize event subscription module", status) status = pjsip_100rel_init_module(self._obj) if status != 0: raise PJSIPError("Could not initialize 100rel module", status) status = pjsip_replaces_init_module(self._obj) if status != 0: raise PJSIPError("Could not initialize replaces module", status) status = pjsip_inv_usage_init(self._obj, &_inv_cb) if status != 0: raise PJSIPError("Could not initialize invitation module", status) status = pjsip_endpt_create_resolver(self._obj, &resolver) if status != 0: raise PJSIPError("Could not create fake DNS resolver for endpoint", status) status = pjsip_endpt_set_resolver(self._obj, resolver) if status != 0: raise PJSIPError("Could not set fake DNS resolver on endpoint", status) tpmgr = pjsip_endpt_get_tpmgr(self._obj) if tpmgr == NULL: raise SIPCoreError("Could not get the transport manager") status = pjsip_tpmgr_set_state_cb(tpmgr, _transport_state_cb) if status != 0: raise PJSIPError("Could not set transport state callback", status) if udp_port is not None: self._start_udp_transport(udp_port) if tcp_port is not None: self._start_tcp_transport(tcp_port) self._tls_verify_server = int(tls_verify_server) if tls_ca_file is not None: self._tls_ca_file = PJSTR(tls_ca_file.encode(sys.getfilesystemencoding())) if tls_cert_file is not None: self._tls_cert_file = PJSTR(tls_cert_file.encode(sys.getfilesystemencoding())) if tls_privkey_file is not None: self._tls_privkey_file = PJSTR(tls_privkey_file.encode(sys.getfilesystemencoding())) if tls_timeout < 0: raise ValueError("Invalid TLS timeout value: %d" % tls_timeout) self._tls_timeout = tls_timeout if tls_port is not None: self._start_tls_transport(tls_port) cdef int _make_local_addr(self, pj_sockaddr_in *local_addr, object ip_address, int port) except -1: cdef pj_str_t local_ip_pj cdef pj_str_t *local_ip_p = NULL cdef int status if not (0 <= port <= 65535): raise SIPCoreError("Invalid port: %d" % port) if ip_address is not None and ip_address is not "0.0.0.0": local_ip_p = &local_ip_pj - _str_to_pj_str(ip_address, local_ip_p) + _str_to_pj_str(ip_address.encode(), local_ip_p) status = pj_sockaddr_in_init(local_addr, local_ip_p, port) if status != 0: raise PJSIPError("Could not create local address", status) return 0 cdef int _start_udp_transport(self, int port) except -1: cdef pj_sockaddr_in local_addr self._make_local_addr(&local_addr, self._local_ip_used, port) status = pjsip_udp_transport_start(self._obj, &local_addr, NULL, 1, &self._udp_transport) if status != 0: raise PJSIPError("Could not create UDP transport", status) return 0 cdef int _stop_udp_transport(self) except -1: pjsip_transport_shutdown(self._udp_transport) self._udp_transport = NULL return 0 cdef int _start_tcp_transport(self, int port) except -1: cdef pj_sockaddr_in local_addr self._make_local_addr(&local_addr, self._local_ip_used, port) status = pjsip_tcp_transport_start2(self._obj, &local_addr, NULL, 1, &self._tcp_transport) if status != 0: raise PJSIPError("Could not create TCP transport", status) return 0 cdef int _stop_tcp_transport(self) except -1: self._tcp_transport.destroy(self._tcp_transport) self._tcp_transport = NULL return 0 cdef int _start_tls_transport(self, port) except -1: cdef pj_sockaddr_in local_addr cdef pjsip_tls_setting tls_setting self._make_local_addr(&local_addr, self._local_ip_used, port) pjsip_tls_setting_default(&tls_setting) # The following value needs to be reasonably low, as TLS negotiation hogs the PJSIP polling loop tls_setting.timeout.sec = self._tls_timeout / 1000 tls_setting.timeout.msec = self._tls_timeout % 1000 if self._tls_ca_file is not None: tls_setting.ca_list_file = self._tls_ca_file.pj_str if self._tls_cert_file is not None: tls_setting.cert_file = self._tls_cert_file.pj_str if self._tls_privkey_file is not None: tls_setting.privkey_file = self._tls_privkey_file.pj_str tls_setting.method = PJSIP_SSLV23_METHOD tls_setting.verify_server = self._tls_verify_server status = pjsip_tls_transport_start(self._obj, &tls_setting, &local_addr, NULL, 1, &self._tls_transport) if status in (PJSIP_TLS_EUNKNOWN, PJSIP_TLS_EINVMETHOD, PJSIP_TLS_ECACERT, PJSIP_TLS_ECERTFILE, PJSIP_TLS_EKEYFILE, PJSIP_TLS_ECIPHER, PJSIP_TLS_ECTX): raise PJSIPTLSError("Could not create TLS transport", status) elif status != 0: raise PJSIPError("Could not create TLS transport", status) return 0 cdef int _stop_tls_transport(self) except -1: self._tls_transport.destroy(self._tls_transport) self._tls_transport = NULL return 0 cdef int _set_dns_nameservers(self, list servers) except -1: cdef int num_servers = len(servers) cdef pj_str_t *pj_servers cdef int status cdef pj_dns_resolver *resolver if num_servers == 0: return 0 resolver = pjsip_endpt_get_resolver(self._obj) if resolver == NULL: raise SIPCoreError("Could not get DNS resolver on endpoint") pj_servers = malloc(sizeof(pj_str_t)*num_servers) if pj_servers == NULL: raise MemoryError() for i, ns in enumerate(servers): - _str_to_pj_str(ns, &pj_servers[i]) + _str_to_pj_str(ns.encode(), &pj_servers[i]) status = pj_dns_resolver_set_ns(resolver, num_servers, pj_servers, NULL) free(pj_servers) if status != 0: raise PJSIPError("Could not set nameservers on DNS resolver", status) return 0 def __dealloc__(self): cdef pjsip_tpmgr *tpmgr tpmgr = pjsip_endpt_get_tpmgr(self._obj) if tpmgr != NULL: pjsip_tpmgr_set_state_cb(tpmgr, NULL) if self._udp_transport != NULL: self._stop_udp_transport() if self._tcp_transport != NULL: self._stop_tcp_transport() if self._tls_transport != NULL: self._stop_tls_transport() if self._pool != NULL: pjsip_endpt_release_pool(self._obj, self._pool) if self._obj != NULL: with nogil: pjsip_endpt_destroy(self._obj) cdef class PJMEDIAEndpoint: def __cinit__(self, PJCachingPool caching_pool): cdef int status status = pjmedia_endpt_create(&caching_pool._obj.factory, NULL, 1, &self._obj) if status != 0: raise PJSIPError("Could not create PJMEDIA endpoint", status) self._pool = pjmedia_endpt_create_pool(self._obj, "PJMEDIAEndpoint", 4096, 4096) if self._pool == NULL: raise SIPCoreError("Could not allocate memory pool") self._audio_subsystem_init(caching_pool) self._video_subsystem_init(caching_pool) def __dealloc__(self): self._audio_subsystem_shutdown() self._video_subsystem_shutdown() if self._pool != NULL: pj_pool_release(self._pool) if self._obj != NULL: with nogil: pjmedia_endpt_destroy(self._obj) cdef void _audio_subsystem_init(self, PJCachingPool caching_pool): cdef int status cdef pjmedia_audio_codec_config audio_codec_cfg pjmedia_audio_codec_config_default(&audio_codec_cfg) audio_codec_cfg.speex.option = PJMEDIA_SPEEX_NO_NB audio_codec_cfg.ilbc.mode = 30 status = pjmedia_codec_register_audio_codecs(self._obj, &audio_codec_cfg) if status != 0: raise PJSIPError("Could not initialize audio codecs", status) self._has_audio_codecs = 1 cdef void _audio_subsystem_shutdown(self): pass cdef void _video_subsystem_init(self, PJCachingPool caching_pool): cdef int status status = pjmedia_video_format_mgr_create(self._pool, 64, 0, NULL) if status != 0: raise PJSIPError("Could not initialize video format manager", status) status = pjmedia_converter_mgr_create(self._pool, NULL) if status != 0: raise PJSIPError("Could not initialize converter manager", status) status = pjmedia_event_mgr_create(self._pool, 0, NULL) if status != 0: raise PJSIPError("Could not initialize event manager", status) status = pjmedia_vid_codec_mgr_create(self._pool, NULL) if status != 0: raise PJSIPError("Could not initialize video codec manager", status) status = pjmedia_codec_ffmpeg_vid_init(NULL, &caching_pool._obj.factory) if status != 0: raise PJSIPError("Could not initialize ffmpeg video codecs", status) self._has_ffmpeg_video = 1 status = pjmedia_codec_vpx_init(NULL, &caching_pool._obj.factory) if status != 0: raise PJSIPError("Could not initialize vpx video codecs", status) self._has_vpx = 1 status = pjmedia_vid_dev_subsys_init(&caching_pool._obj.factory) if status != 0: raise PJSIPError("Could not initialize video subsystem", status) self._has_video = 1 cdef void _video_subsystem_shutdown(self): if self._has_video: pjmedia_vid_dev_subsys_shutdown() if self._has_ffmpeg_video: pjmedia_codec_ffmpeg_vid_deinit() if self._has_vpx: pjmedia_codec_vpx_deinit() if pjmedia_vid_codec_mgr_instance() != NULL: pjmedia_vid_codec_mgr_destroy(NULL) if pjmedia_event_mgr_instance() != NULL: pjmedia_event_mgr_destroy(NULL) if pjmedia_converter_mgr_instance() != NULL: pjmedia_converter_mgr_destroy(NULL) if pjmedia_video_format_mgr_instance() != NULL: pjmedia_video_format_mgr_destroy(NULL) cdef list _get_codecs(self): cdef unsigned int count = PJMEDIA_CODEC_MGR_MAX_CODECS cdef pjmedia_codec_info info[PJMEDIA_CODEC_MGR_MAX_CODECS] cdef unsigned int prio[PJMEDIA_CODEC_MGR_MAX_CODECS] cdef int i cdef list retval cdef int status status = pjmedia_codec_mgr_enum_codecs(pjmedia_endpt_get_codec_mgr(self._obj), &count, info, prio) if status != 0: raise PJSIPError("Could not get available codecs", status) retval = list() for i from 0 <= i < count: - retval.append((prio[i], _pj_str_to_str(info[i].encoding_name), info[i].channel_cnt, info[i].clock_rate)) + retval.append((prio[i], _pj_str_to_bytes(info[i].encoding_name), info[i].channel_cnt, info[i].clock_rate)) return retval cdef list _get_all_codecs(self): cdef list codecs cdef tuple codec_data codecs = self._get_codecs() return list(set([codec_data[1] for codec_data in codecs])) cdef list _get_current_codecs(self): cdef list codecs cdef tuple codec_data cdef list retval codecs = [codec_data for codec_data in self._get_codecs() if codec_data[0] > 0] codecs.sort(reverse=True) retval = list(set([codec_data[1] for codec_data in codecs])) return retval cdef int _set_codecs(self, list req_codecs) except -1: cdef object new_codecs cdef object all_codecs cdef object codec_set cdef list codecs cdef tuple codec_data - cdef str codec + cdef object codec cdef int sample_rate cdef int channel_count - cdef str codec_name + cdef object codec_name cdef int prio cdef list codec_prio cdef pj_str_t codec_pj new_codecs = set(req_codecs) if len(new_codecs) != len(req_codecs): raise ValueError("Requested codec list contains doubles") all_codecs = set(self._get_all_codecs()) codec_set = new_codecs.difference(all_codecs) if len(codec_set) > 0: raise SIPCoreError("Unknown codec(s): %s" % ", ".join(codec_set)) # reverse the codec data tuples so that we can easily sort on sample rate # to make sure that bigger sample rates get higher priority codecs = [list(reversed(codec_data)) for codec_data in self._get_codecs()] codecs.sort(reverse=True) codec_prio = list() + for codec in req_codecs: for sample_rate, channel_count, codec_name, prio in codecs: if codec == codec_name and channel_count == 1: - codec_prio.append("%s/%d/%d" % (codec_name, sample_rate, channel_count)) + codec_prio.append("%s/%d/%d" % (codec_name.decode(), sample_rate, channel_count)) for prio, codec in enumerate(reversed(codec_prio)): - _str_to_pj_str(codec, &codec_pj) + _str_to_pj_str(codec.encode(), &codec_pj) status = pjmedia_codec_mgr_set_codec_priority(pjmedia_endpt_get_codec_mgr(self._obj), &codec_pj, prio + 1) if status != 0: raise PJSIPError("Could not set codec priority", status) for sample_rate, channel_count, codec_name, prio in codecs: if codec_name not in req_codecs or channel_count > 1: - codec = "%s/%d/%d" % (codec_name, sample_rate, channel_count) - _str_to_pj_str(codec, &codec_pj) + codec = "%s/%d/%d" % (codec_name.decode(), sample_rate, channel_count) + _str_to_pj_str(codec.encode(), &codec_pj) status = pjmedia_codec_mgr_set_codec_priority(pjmedia_endpt_get_codec_mgr(self._obj), &codec_pj, 0) if status != 0: raise PJSIPError("Could not set codec priority", status) return 0 cdef list _get_video_codecs(self): cdef unsigned int count = PJMEDIA_VID_CODEC_MGR_MAX_CODECS cdef pjmedia_vid_codec_info info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef unsigned int prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef int i cdef list retval cdef int status status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio) if status != 0: raise PJSIPError("Could not get available video codecs", status) retval = list() for i from 0 <= i < count: if info[i].packings & PJMEDIA_VID_PACKING_PACKETS: - retval.append((prio[i], _pj_str_to_str(info[i].encoding_name), info[i].pt)) + retval.append((prio[i], _pj_str_to_bytes(info[i].encoding_name), info[i].pt)) return retval cdef list _get_all_video_codecs(self): cdef list codecs cdef tuple codec_data codecs = self._get_video_codecs() return list(set([codec_data[1] for codec_data in codecs])) cdef list _get_current_video_codecs(self): cdef list codecs cdef tuple codec_data cdef list retval codecs = [codec_data for codec_data in self._get_video_codecs() if codec_data[0] > 0] codecs.sort(reverse=True) retval = list(set([codec_data[1] for codec_data in codecs])) return retval cdef int _set_video_codecs(self, list req_codecs) except -1: cdef object new_codecs cdef object codec_set cdef list codecs cdef tuple codec_data - cdef str codec + cdef object codec cdef int payload_type - cdef str codec_name + cdef object codec_name cdef int prio cdef list codec_prio cdef pj_str_t codec_pj new_codecs = set(req_codecs) if len(new_codecs) != len(req_codecs): raise ValueError("Requested video codec list contains doubles") codec_set = new_codecs.difference(set(self._get_all_video_codecs())) if len(codec_set) > 0: raise SIPCoreError("Unknown video codec(s): %s" % ", ".join(codec_set)) codecs = self._get_video_codecs() codec_prio = list() for codec in req_codecs: for prio, codec_name, payload_type in codecs: if codec == codec_name: - codec_prio.append("%s/%d" % (codec_name, payload_type)) + codec_prio.append("%s/%d" % (codec_name.decode(), payload_type)) for prio, codec in enumerate(reversed(codec_prio)): - _str_to_pj_str(codec, &codec_pj) + _str_to_pj_str(codec.encode(), &codec_pj) status = pjmedia_vid_codec_mgr_set_codec_priority(NULL, &codec_pj, prio + 1) if status != 0: raise PJSIPError("Could not set video codec priority", status) for prio, codec_name, payload_type in codecs: if codec_name not in req_codecs: - codec = "%s/%d" % (codec_name, payload_type) - _str_to_pj_str(codec, &codec_pj) + codec = "%s/%d" % (codec_name.decode(), payload_type) + _str_to_pj_str(codec.encode(), &codec_pj) status = pjmedia_vid_codec_mgr_set_codec_priority(NULL, &codec_pj, 0) if status != 0: raise PJSIPError("Could not set video codec priority", status) return 0 cdef void _set_h264_options(self, str profile, int level): global h264_profiles_map, h264_profile_level_id, h264_packetization_mode cdef unsigned int count = PJMEDIA_VID_CODEC_MGR_MAX_CODECS cdef pjmedia_vid_codec_info info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef pjmedia_vid_codec_param vparam cdef unsigned int prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef int i cdef int status cdef PJSTR h264_profile_level_id_value - cdef PJSTR h264_packetization_mode_value = PJSTR("1") # TODO; make it configurable? + cdef PJSTR h264_packetization_mode_value = PJSTR(b"1") # TODO; make it configurable? try: profile_n = h264_profiles_map[profile] except KeyError: raise ValueError("invalid profile specified: %s" % profile) - h264_profile_level_id_value = PJSTR("%xe0%x" % (profile_n, level)) # use common subset (e0) + h264_profile_level_id_value = PJSTR(b"%xe0%x" % (profile_n, level)) # use common subset (e0) status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio) if status != 0: raise PJSIPError("Could not get available video codecs", status) for i from 0 <= i < count: if not (info[i].packings & PJMEDIA_VID_PACKING_PACKETS): continue - if _pj_str_to_str(info[i].encoding_name) != 'H264': + if _pj_str_to_bytes(info[i].encoding_name) != 'H264': continue status = pjmedia_vid_codec_mgr_get_default_param(NULL, &info[i], &vparam) if status != 0: continue # 2 format parameters are currently defined for H264: profile-level-id and packetization-mode vparam.dec_fmtp.param[0].name = h264_profile_level_id.pj_str vparam.dec_fmtp.param[0].val = h264_profile_level_id_value.pj_str vparam.dec_fmtp.param[1].name = h264_packetization_mode.pj_str vparam.dec_fmtp.param[1].val = h264_packetization_mode_value.pj_str vparam.dec_fmtp.cnt = 2 status = pjmedia_vid_codec_mgr_set_default_param(NULL, &info[i], &vparam) if status != 0: raise PJSIPError("Could not set H264 options", status) cdef void _set_video_options(self, tuple max_resolution, int max_framerate, float max_bitrate): cdef unsigned int count = PJMEDIA_VID_CODEC_MGR_MAX_CODECS cdef pjmedia_vid_codec_info info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef pjmedia_vid_codec_param vparam cdef unsigned int prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef int i cdef int status max_width, max_height = max_resolution status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio) if status != 0: raise PJSIPError("Could not get available video codecs", status) for i from 0 <= i < count: if not (info[i].packings & PJMEDIA_VID_PACKING_PACKETS): continue status = pjmedia_vid_codec_mgr_get_default_param(NULL, &info[i], &vparam) if status != 0: continue # Max resolution vparam.enc_fmt.det.vid.size.w = max_width vparam.enc_fmt.det.vid.size.h = max_height vparam.dec_fmt.det.vid.size.w = max_width vparam.dec_fmt.det.vid.size.h = max_height # Max framerate vparam.enc_fmt.det.vid.fps.num = max_framerate vparam.enc_fmt.det.vid.fps.denum = 1 vparam.dec_fmt.det.vid.fps.num = 10 vparam.dec_fmt.det.vid.fps.denum = 1 # Average and max bitrate (set to 0 for 'unlimited') vparam.enc_fmt.det.vid.avg_bps = int(max_bitrate * 1e6) vparam.enc_fmt.det.vid.max_bps = int(max_bitrate * 1e6) vparam.dec_fmt.det.vid.avg_bps = 0 vparam.dec_fmt.det.vid.max_bps = 0 status = pjmedia_vid_codec_mgr_set_default_param(NULL, &info[i], &vparam) if status != 0: raise PJSIPError("Could not set video options", status) cdef void _transport_state_cb(pjsip_transport *tp, pjsip_transport_state state, pjsip_transport_state_info_ptr_const info) with gil: cdef PJSIPUA ua cdef str local_address cdef str remote_address cdef char buf[PJ_INET6_ADDRSTRLEN] cdef dict event_dict try: ua = _get_ua() except: return if pj_sockaddr_has_addr(&tp.local_addr): pj_sockaddr_print(&tp.local_addr, buf, 512, 0) local_address = '%s:%d' % (_buf_to_str(buf), pj_sockaddr_get_port(&tp.local_addr)) else: local_address = None - remote_address = '%s:%d' % (_pj_str_to_str(tp.remote_name.host), tp.remote_name.port) + remote_address = '%s:%d' % (_pj_str_to_bytes(tp.remote_name.host), tp.remote_name.port) event_dict = dict(transport=tp.type_name.lower(), local_address=local_address, remote_address=remote_address) if state == PJSIP_TP_STATE_CONNECTED: _add_event("SIPEngineTransportDidConnect", event_dict) else: event_dict['reason'] = _pj_status_to_str(info.status) _add_event("SIPEngineTransportDidDisconnect", event_dict) # globals -cdef PJSTR h264_profile_level_id = PJSTR("profile-level-id") -cdef PJSTR h264_packetization_mode = PJSTR("packetization-mode") +cdef PJSTR h264_profile_level_id = PJSTR(b"profile-level-id") +cdef PJSTR h264_packetization_mode = PJSTR(b"packetization-mode") cdef dict h264_profiles_map = dict(baseline=66, main=77, high=100) diff --git a/sipsimple/core/_core.mediatransport.pxi b/sipsimple/core/_core.mediatransport.pxi index 884494ff..3ef89dec 100644 --- a/sipsimple/core/_core.mediatransport.pxi +++ b/sipsimple/core/_core.mediatransport.pxi @@ -1,2557 +1,2557 @@ import sys from errno import EADDRINUSE # classes cdef class RTPTransport: def __cinit__(self, *args, **kwargs): cdef int status cdef pj_pool_t *pool cdef bytes pool_name cdef char* c_pool_name cdef PJSIPUA ua ua = _get_ua() pool_name = b"RTPTransport_%d" % id(self) self.weakref = weakref.ref(self) Py_INCREF(self.weakref) self._af = pj_AF_INET() status = pj_mutex_create_recursive(ua._pjsip_endpoint._pool, "rtp_transport_lock", &self._lock) if status != 0: raise PJSIPError("failed to create lock", status) pool = ua.create_memory_pool(pool_name, 4096, 4096) self._pool = pool self.state = "NULL" def __init__(self, encryption=None, use_ice=False, ice_stun_address=None, ice_stun_port=PJ_STUN_PORT): cdef PJSIPUA ua = _get_ua() if self.state != "NULL": raise SIPCoreError("RTPTransport.__init__() was already called") self._rtp_valid_pair = None self._encryption = encryption self.use_ice = use_ice self.ice_stun_address = ice_stun_address self.ice_stun_port = ice_stun_port def __dealloc__(self): cdef PJSIPUA ua cdef pjmedia_transport *transport cdef Timer timer try: ua = _get_ua() except: return transport = self._obj if transport != NULL: transport.user_data = NULL if self._wrapped_transport != NULL: self._wrapped_transport.user_data = NULL with nogil: pjmedia_transport_media_stop(transport) pjmedia_transport_close(transport) self._obj = NULL self._wrapped_transport = NULL ua.release_memory_pool(self._pool) self._pool = NULL if self._lock != NULL: pj_mutex_destroy(self._lock) timer = Timer() try: timer.schedule(60, deallocate_weakref, self.weakref) except SIPCoreError: pass cdef PJSIPUA _check_ua(self): cdef PJSIPUA ua try: ua = _get_ua() return ua except: self.state = "INVALID" self._obj = NULL self._wrapped_transport = NULL self._pool = NULL return None cdef void _get_info(self, pjmedia_transport_info *info): cdef int status cdef pjmedia_transport *transport transport = self._obj with nogil: pjmedia_transport_info_init(info) status = pjmedia_transport_get_info(transport, info) if status != 0: raise PJSIPError("Could not get transport info", status) property local_rtp_port: def __get__(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return None self._get_info(&info) if pj_sockaddr_has_addr(&info.sock_info.rtp_addr_name): return pj_sockaddr_get_port(&info.sock_info.rtp_addr_name) else: return None finally: with nogil: pj_mutex_unlock(lock) property local_rtp_address: def __get__(self): cdef char buf[PJ_INET6_ADDRSTRLEN] cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return None self._get_info(&info) if pj_sockaddr_has_addr(&info.sock_info.rtp_addr_name): return pj_sockaddr_print(&info.sock_info.rtp_addr_name, buf, PJ_INET6_ADDRSTRLEN, 0) else: return None finally: with nogil: pj_mutex_unlock(lock) property local_rtp_candidate: def __get__(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef PJSIPUA ua ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self._rtp_valid_pair: return self._rtp_valid_pair.local_candidate return None finally: with nogil: pj_mutex_unlock(lock) property remote_rtp_port: def __get__(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return None if self._ice_active() and self._rtp_valid_pair: return self._rtp_valid_pair.remote_candidate.port self._get_info(&info) if pj_sockaddr_has_addr(&info.src_rtp_name): return pj_sockaddr_get_port(&info.src_rtp_name) else: return None finally: with nogil: pj_mutex_unlock(lock) property remote_rtp_address: def __get__(self): cdef char buf[PJ_INET6_ADDRSTRLEN] cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return None if self._ice_active() and self._rtp_valid_pair: return self._rtp_valid_pair.remote_candidate.address self._get_info(&info) if pj_sockaddr_has_addr(&info.src_rtp_name): return pj_sockaddr_print(&info.src_rtp_name, buf, PJ_INET6_ADDRSTRLEN, 0) else: return None finally: with nogil: pj_mutex_unlock(lock) property remote_rtp_candidate: def __get__(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef PJSIPUA ua ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self._rtp_valid_pair: return self._rtp_valid_pair.remote_candidate return None finally: with nogil: pj_mutex_unlock(lock) property srtp_active: def __get__(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_srtp_info *srtp_info cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return False with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return False self._get_info(&info) srtp_info = pjmedia_transport_info_get_spc_info(&info, PJMEDIA_TRANSPORT_TYPE_SRTP) if srtp_info != NULL: return bool(srtp_info.active) return False finally: with nogil: pj_mutex_unlock(lock) property srtp_cipher: def __get__(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_srtp_info *srtp_info cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return None self._get_info(&info) srtp_info = pjmedia_transport_info_get_spc_info(&info, PJMEDIA_TRANSPORT_TYPE_SRTP) if srtp_info == NULL or not bool(srtp_info.active): return None - return _pj_str_to_str(srtp_info.tx_policy.name) + return _pj_str_to_bytes(srtp_info.tx_policy.name) finally: with nogil: pj_mutex_unlock(lock) property zrtp_active: def __get__(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_zrtp_info *zrtp_info cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return False with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return False self._get_info(&info) zrtp_info = pjmedia_transport_info_get_spc_info(&info, PJMEDIA_TRANSPORT_TYPE_ZRTP) if zrtp_info != NULL: return bool(zrtp_info.active) return False finally: with nogil: pj_mutex_unlock(lock) cdef int _ice_active(self): # this function needs to be called with the lock held cdef pjmedia_transport_info info cdef pjmedia_ice_transport_info *ice_info if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return 0 self._get_info(&info) ice_info = pjmedia_transport_info_get_spc_info(&info, PJMEDIA_TRANSPORT_TYPE_ICE) if ice_info != NULL and ice_info.sess_state == PJ_ICE_STRANS_STATE_RUNNING: return 1 return 0 property ice_active: def __get__(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef PJSIPUA ua ua = self._check_ua() if ua is None: return False with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: return bool(self._ice_active()) finally: with nogil: pj_mutex_unlock(lock) cdef int _init_local_sdp(self, BaseSDPSession local_sdp, BaseSDPSession remote_sdp, int sdp_index): cdef int status cdef pj_pool_t *pool cdef pjmedia_sdp_session *pj_local_sdp cdef pjmedia_sdp_session *pj_remote_sdp cdef pjmedia_transport *transport pool = self._pool transport = self._obj pj_local_sdp = local_sdp.get_sdp_session() if remote_sdp is not None: pj_remote_sdp = remote_sdp.get_sdp_session() else: pj_remote_sdp = NULL if sdp_index < 0: raise ValueError("sdp_index argument cannot be negative") if sdp_index >= pj_local_sdp.media_count: raise ValueError("sdp_index argument out of range") with nogil: status = pjmedia_transport_media_create(transport, pool, 0, pj_remote_sdp, sdp_index) if status != 0: raise PJSIPError("Could not create media transport", status) return 0 def set_LOCAL(self, SDPSession local_sdp, int sdp_index): cdef int status cdef pj_mutex_t *lock = self._lock _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if local_sdp is None: raise SIPCoreError("local_sdp argument cannot be None") if self.state == "LOCAL": return if self.state != "INIT": raise SIPCoreError('set_LOCAL can only be called in the "INIT" state, current state is "%s"' % self.state) self._init_local_sdp(local_sdp, None, sdp_index) self.state = "LOCAL" finally: with nogil: pj_mutex_unlock(lock) def set_REMOTE(self, BaseSDPSession local_sdp, BaseSDPSession remote_sdp, int sdp_index): cdef int status cdef pj_mutex_t *lock = self._lock _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if None in [local_sdp, remote_sdp]: raise SIPCoreError("SDP arguments cannot be None") if self.state == "REMOTE": return if self.state != "INIT": raise SIPCoreError('set_REMOTE can only be called in the "INIT" state, current state is "%s"' % self.state) self._init_local_sdp(local_sdp, remote_sdp, sdp_index) self.state = "REMOTE" finally: with nogil: pj_mutex_unlock(lock) def set_ESTABLISHED(self, BaseSDPSession local_sdp, BaseSDPSession remote_sdp, int sdp_index): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_sdp_session *pj_local_sdp cdef pjmedia_sdp_session *pj_remote_sdp cdef pjmedia_transport *transport = self._obj _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: transport = self._obj if None in [local_sdp, remote_sdp]: raise SIPCoreError("SDP arguments cannot be None") pj_local_sdp = local_sdp.get_sdp_session() pj_remote_sdp = remote_sdp.get_sdp_session() if self.state == "ESTABLISHED": return if self.state not in ["LOCAL", "REMOTE"]: raise SIPCoreError('set_ESTABLISHED can only be called in the "INIT" and "LOCAL" states, ' + 'current state is "%s"' % self.state) with nogil: status = pjmedia_transport_media_start(transport, self._pool, pj_local_sdp, pj_remote_sdp, sdp_index) if status != 0: raise PJSIPError("Could not start media transport", status) self.state = "ESTABLISHED" finally: with nogil: pj_mutex_unlock(lock) def set_INIT(self): global _ice_cb cdef int af cdef int i cdef int status cdef int port cdef pj_caching_pool *caching_pool cdef pj_ice_strans_cfg ice_cfg cdef pj_ice_strans *ice_st cdef pj_ice_strans_state ice_state cdef pj_mutex_t *lock = self._lock cdef pj_str_t local_ip cdef pj_str_t *local_ip_address cdef pjmedia_endpt *media_endpoint cdef pjmedia_srtp_setting srtp_setting cdef pjmedia_transport **transport_address cdef pjmedia_transport *wrapped_transport cdef pjsip_endpoint *sip_endpoint cdef bytes zid_file cdef char *c_zid_file cdef PJSIPUA ua ua = _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: af = self._af caching_pool = &ua._caching_pool._obj media_endpoint = ua._pjmedia_endpoint._obj sip_endpoint = ua._pjsip_endpoint._obj transport_address = &self._obj if self.state == "INIT": return if self.state in ["LOCAL", "ESTABLISHED"]: with nogil: status = pjmedia_transport_media_stop(transport_address[0]) if status != 0: raise PJSIPError("Could not stop media transport", status) self.state = "INIT" elif self.state == "NULL": if ua.ip_address is None: local_ip_address = NULL else: _str_to_pj_str(ua.ip_address, &local_ip) local_ip_address = &local_ip if self.use_ice: with nogil: pj_ice_strans_cfg_default(&ice_cfg) ice_cfg.af = self._af with nogil: pj_stun_config_init(&ice_cfg.stun_cfg, &caching_pool.factory, 0, pjmedia_endpt_get_ioqueue(media_endpoint), pjsip_endpt_get_timer_heap(sip_endpoint)) if self.ice_stun_address is not None: _str_to_pj_str(self.ice_stun_address, &ice_cfg.stun.server) ice_cfg.stun.port = self.ice_stun_port # IIRC we can't choose the port for ICE with nogil: status = pj_sockaddr_init(ice_cfg.af, &ice_cfg.stun.cfg.bound_addr, local_ip_address, 0) if status != 0: raise PJSIPError("Could not init ICE bound address", status) with nogil: status = pjmedia_ice_create2(media_endpoint, NULL, 2, &ice_cfg, &_ice_cb, 0, transport_address) if status != 0: raise PJSIPError("Could not create ICE media transport", status) else: status = PJ_EBUG for i in xrange(ua._rtp_port_index, ua._rtp_port_index + ua._rtp_port_usable_count, 2): port = ua._rtp_port_start + i % ua._rtp_port_usable_count with nogil: status = pjmedia_transport_udp_create3(media_endpoint, af, NULL, local_ip_address, port, 0, transport_address) if status != PJ_ERRNO_START_SYS + EADDRINUSE: ua._rtp_port_index = (i + 2) % ua._rtp_port_usable_count break if status != 0: raise PJSIPError("Could not create UDP/RTP media transport", status) self._obj.user_data = self.weakref if self._encryption is not None: wrapped_transport = self._wrapped_transport = self._obj self._obj = NULL if self._encryption.startswith('sdes'): with nogil: pjmedia_srtp_setting_default(&srtp_setting) if self._encryption == 'sdes_mandatory': srtp_setting.use = PJMEDIA_SRTP_MANDATORY with nogil: status = pjmedia_transport_srtp_create(media_endpoint, wrapped_transport, &srtp_setting, transport_address) if status != 0: with nogil: pjmedia_transport_close(wrapped_transport) self._wrapped_transport = NULL raise PJSIPError("Could not create SRTP media transport", status) elif self._encryption == 'zrtp': with nogil: status = pjmedia_transport_zrtp_create(media_endpoint, pjsip_endpt_get_timer_heap(sip_endpoint), wrapped_transport, transport_address, 1) if status == 0: zid_file = ua.zrtp_cache.encode(sys.getfilesystemencoding()) c_zid_file = zid_file with nogil: # Auto-enable is deactivated status = pjmedia_transport_zrtp_initialize(self._obj, c_zid_file, 0, &_zrtp_cb) if status != 0: with nogil: pjmedia_transport_close(wrapped_transport) self._wrapped_transport = NULL raise PJSIPError("Could not create ZRTP media transport", status) else: raise RuntimeError('invalid SRTP key negotiation specified: %s' % self._encryption) self._obj.user_data = self.weakref if not self.use_ice or self.ice_stun_address is None: self.state = "INIT" _add_event("RTPTransportDidInitialize", dict(obj=self)) else: self.state = "WAIT_STUN" if self.use_ice: _add_event("RTPTransportICENegotiationStateDidChange", dict(obj=self, prev_state="NULL", state="GATHERING")) ice_st = pjmedia_ice_get_strans(transport_address[0]) if ice_st != NULL: ice_state = pj_ice_strans_get_state(ice_st) if ice_state == PJ_ICE_STRANS_STATE_READY: _add_event("RTPTransportICENegotiationStateDidChange", dict(obj=self, prev_state="GATHERING", state="GATHERING_COMPLETE")) else: raise SIPCoreError('set_INIT can only be called in the "NULL", "LOCAL" and "ESTABLISHED" states, ' + 'current state is "%s"' % self.state) finally: with nogil: pj_mutex_unlock(lock) def set_zrtp_sas_verified(self, verified): cdef int status cdef int c_verified cdef pj_mutex_t *lock = self._lock cdef pjmedia_zrtp_info *zrtp_info cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return False with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return False self._get_info(&info) zrtp_info = pjmedia_transport_info_get_spc_info(&info, PJMEDIA_TRANSPORT_TYPE_ZRTP) if zrtp_info == NULL or not bool(zrtp_info.active): return False c_verified = int(verified) with nogil: pjmedia_transport_zrtp_setSASVerified(self._obj, c_verified) return True finally: with nogil: pj_mutex_unlock(lock) def set_zrtp_enabled(self, enabled, object master_stream): cdef int status cdef int c_enabled cdef pj_mutex_t *lock = self._lock cdef pjmedia_zrtp_info *zrtp_info cdef pjmedia_transport_info info cdef PJSIPUA ua cdef bytes multistream_params cdef char *c_multistream_params cdef int length cdef RTPTransport master_transport ua = self._check_ua() if ua is None: return with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return self._get_info(&info) zrtp_info = pjmedia_transport_info_get_spc_info(&info, PJMEDIA_TRANSPORT_TYPE_ZRTP) if zrtp_info == NULL: return if master_stream is not None: master_transport = master_stream._rtp_transport assert master_transport is not None # extract the multistream parameters multistream_params = master_transport.zrtp_multistream_parameters if multistream_params: # set multistream mode in ourselves c_multistream_params = multistream_params length = len(multistream_params) with nogil: pjmedia_transport_zrtp_setMultiStreamParameters(self._obj, c_multistream_params, length, master_transport._obj) c_enabled = int(enabled) with nogil: pjmedia_transport_zrtp_setEnableZrtp(self._obj, c_enabled) finally: with nogil: pj_mutex_unlock(lock) property zrtp_multistream_parameters: def __get__(self): cdef int status cdef char* c_name cdef pj_mutex_t *lock = self._lock cdef pjmedia_zrtp_info *zrtp_info cdef pjmedia_transport_info info cdef PJSIPUA ua cdef char *multistr_params cdef int length ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return None self._get_info(&info) zrtp_info = pjmedia_transport_info_get_spc_info(&info, PJMEDIA_TRANSPORT_TYPE_ZRTP) if zrtp_info == NULL or not bool(zrtp_info.active): return None with nogil: multistr_params = pjmedia_transport_zrtp_getMultiStreamParameters(self._obj, &length) if length > 0: ret = _pj_buf_len_to_str(multistr_params, length) free(multistr_params) return ret else: return None finally: with nogil: pj_mutex_unlock(lock) property zrtp_cipher: def __get__(self): cdef int status cdef char* c_name cdef pj_mutex_t *lock = self._lock cdef pjmedia_zrtp_info *zrtp_info cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return None self._get_info(&info) zrtp_info = pjmedia_transport_info_get_spc_info(&info, PJMEDIA_TRANSPORT_TYPE_ZRTP) if zrtp_info == NULL or not bool(zrtp_info.active): return None return _buf_to_str(zrtp_info.cipher) finally: with nogil: pj_mutex_unlock(lock) property zrtp_peer_name: def __get__(self): cdef int status cdef char* c_name cdef pj_mutex_t *lock = self._lock cdef pjmedia_zrtp_info *zrtp_info cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return '' with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return '' self._get_info(&info) zrtp_info = pjmedia_transport_info_get_spc_info(&info, PJMEDIA_TRANSPORT_TYPE_ZRTP) if zrtp_info == NULL or not bool(zrtp_info.active): return '' with nogil: c_name = pjmedia_transport_zrtp_getPeerName(self._obj) if c_name == NULL: return '' else: name = PyUnicode_FromString(c_name) or u'' free(c_name) return name finally: with nogil: pj_mutex_unlock(lock) def __set__(self, basestring name): cdef int status cdef char* c_name cdef pj_mutex_t *lock = self._lock cdef pjmedia_zrtp_info *zrtp_info cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return self._get_info(&info) zrtp_info = pjmedia_transport_info_get_spc_info(&info, PJMEDIA_TRANSPORT_TYPE_ZRTP) if zrtp_info == NULL or not bool(zrtp_info.active): return name = name.encode('utf-8') c_name = name with nogil: pjmedia_transport_zrtp_putPeerName(self._obj, c_name) finally: with nogil: pj_mutex_unlock(lock) property zrtp_peer_id: def __get__(self): cdef int status cdef unsigned char name[12] # IDENTIFIER_LEN, 96bits cdef pj_mutex_t *lock = self._lock cdef pjmedia_zrtp_info *zrtp_info cdef pjmedia_transport_info info cdef PJSIPUA ua ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self.state in ["NULL", "WAIT_STUN", "INVALID"]: return None self._get_info(&info) zrtp_info = pjmedia_transport_info_get_spc_info(&info, PJMEDIA_TRANSPORT_TYPE_ZRTP) if zrtp_info == NULL or not bool(zrtp_info.active): return None with nogil: status = pjmedia_transport_zrtp_getPeerZid(self._obj, name) if status <= 0: return None else: return _pj_buf_len_to_str(name, 12) finally: with nogil: pj_mutex_unlock(lock) def update_local_sdp(self, SDPSession local_sdp, BaseSDPSession remote_sdp=None, int sdp_index=0): cdef int status cdef pj_pool_t *pool cdef pjmedia_sdp_session *pj_local_sdp cdef pjmedia_sdp_session *pj_remote_sdp cdef pjmedia_transport *transport cdef SDPMediaStream local_media pool = self._pool transport = self._obj pj_local_sdp = local_sdp.get_sdp_session() if remote_sdp is not None: pj_remote_sdp = remote_sdp.get_sdp_session() else: pj_remote_sdp = NULL if sdp_index < 0: raise ValueError("sdp_index argument cannot be negative") if sdp_index >= pj_local_sdp.media_count: raise ValueError("sdp_index argument out of range") # Remove ICE and SRTP/ZRTP related attributes from SDP, they will be added by pjmedia_transport_encode_sdp local_media = local_sdp.media[sdp_index] local_media.attributes = [ attr for attr in local_media.attributes if attr.name not in ('crypto', 'zrtp-hash', 'ice-ufrag', 'ice-pwd', 'ice-mismatch', 'candidate', 'remote-candidates')] pj_local_sdp = local_sdp.get_sdp_session() with nogil: status = pjmedia_transport_encode_sdp(transport, pool, pj_local_sdp, pj_remote_sdp, sdp_index) if status != 0: raise PJSIPError("Could not update SDP for media transport", status) local_sdp._update() return 0 cdef class MediaCheckTimer(Timer): def __init__(self, media_check_interval): self.media_check_interval = media_check_interval cdef class SDPInfo: def __init__(self, BaseSDPMediaStream local_media=None, BaseSDPSession local_sdp=None, BaseSDPSession remote_sdp=None, int index=0): self.local_media = local_media self.local_sdp = local_sdp self.remote_sdp = remote_sdp self.index = index property local_media: def __get__(self): return self._local_media def __set__(self, local_media): if local_media is not None: local_media = SDPMediaStream.new(local_media) self._local_media = local_media property local_sdp: def __get__(self): return self._local_sdp def __set__(self, local_sdp): if local_sdp is not None: local_sdp = SDPSession.new(local_sdp) self._local_sdp = local_sdp property remote_sdp: def __get__(self): return self._remote_sdp def __set__(self, remote_sdp): if remote_sdp is not None: remote_sdp = SDPSession.new(remote_sdp) self._remote_sdp = remote_sdp cdef class AudioTransport: def __cinit__(self, *args, **kwargs): cdef int status cdef pj_pool_t *pool cdef bytes pool_name cdef char* c_pool_name cdef PJSIPUA ua ua = _get_ua() pool_name = b"AudioTransport_%d" % id(self) self.weakref = weakref.ref(self) Py_INCREF(self.weakref) status = pj_mutex_create_recursive(ua._pjsip_endpoint._pool, "audio_transport_lock", &self._lock) if status != 0: raise PJSIPError("failed to create lock", status) pool = ua.create_memory_pool(pool_name, 4096, 4096) self._pool = pool self._slot = -1 self._timer = None self._volume = 100 def __init__(self, AudioMixer mixer, RTPTransport transport, BaseSDPSession remote_sdp=None, int sdp_index=0, enable_silence_detection=False, list codecs=None): cdef int status cdef pj_pool_t *pool cdef pjmedia_endpt *media_endpoint cdef pjmedia_sdp_media *local_media_c cdef pjmedia_sdp_session *local_sdp_c cdef pj_sockaddr *addr cdef pjmedia_transport_info info cdef list global_codecs cdef SDPMediaStream local_media cdef SDPSession local_sdp cdef PJSIPUA ua ua = _get_ua() media_endpoint = ua._pjmedia_endpoint._obj pool = self._pool if self.transport is not None: raise SIPCoreError("AudioTransport.__init__() was already called") if mixer is None: raise ValueError("mixer argument may not be None") if transport is None: raise ValueError("transport argument cannot be None") if sdp_index < 0: raise ValueError("sdp_index argument cannot be negative") if transport.state != "INIT": raise SIPCoreError('RTPTransport object provided is not in the "INIT" state, but in the "%s" state' % transport.state) self._vad = int(bool(enable_silence_detection)) self.mixer = mixer self.transport = transport transport._get_info(&info) global_codecs = ua._pjmedia_endpoint._get_current_codecs() if codecs is None: codecs = global_codecs try: ua._pjmedia_endpoint._set_codecs(codecs) addr = &info.sock_info.rtp_addr_name with nogil: status = pjmedia_endpt_create_base_sdp(media_endpoint, pool, NULL, addr, &local_sdp_c) if status != 0: raise PJSIPError("Could not generate base SDP", status) with nogil: status = pjmedia_endpt_create_audio_sdp(media_endpoint, pool, &info.sock_info, 0, &local_media_c) if status != 0: raise PJSIPError("Could not generate SDP audio stream", status) # Create a 'fake' SDP, which only contains the audio stream, then the m line is extracted because the full # SDP is built by the Session local_sdp_c.media_count = 1 local_sdp_c.media[0] = local_media_c finally: ua._pjmedia_endpoint._set_codecs(global_codecs) local_sdp = SDPSession_create(local_sdp_c) local_media = local_sdp.media[0] if remote_sdp is None: self._is_offer = 1 self.transport.set_LOCAL(local_sdp, 0) else: self._is_offer = 0 if sdp_index != 0: local_sdp.media = [None] * (sdp_index+1) local_sdp.media[sdp_index] = local_media self.transport.set_REMOTE(local_sdp, remote_sdp, sdp_index) self._sdp_info = SDPInfo(local_media, local_sdp, remote_sdp, sdp_index) def __dealloc__(self): cdef PJSIPUA ua cdef Timer timer try: ua = _get_ua() except: return if self._obj != NULL: self.stop() ua.release_memory_pool(self._pool) self._pool = NULL if self._lock != NULL: pj_mutex_destroy(self._lock) timer = Timer() try: timer.schedule(60, deallocate_weakref, self.weakref) except SIPCoreError: pass cdef PJSIPUA _check_ua(self): cdef PJSIPUA ua try: ua = _get_ua() return ua except: self._obj = NULL self._pool = NULL return None property is_active: def __get__(self): self._check_ua() return bool(self._obj != NULL) property is_started: def __get__(self): return bool(self._is_started) property codec: def __get__(self): self._check_ua() if self._obj == NULL: return None else: - return _pj_str_to_str(self._stream_info.fmt.encoding_name) + return _pj_str_to_bytes(self._stream_info.fmt.encoding_name) property sample_rate: def __get__(self): self._check_ua() if self._obj == NULL: return None else: return self._stream_info.fmt.clock_rate property enable_silence_detection: def __get__(self): return bool(self._vad) property statistics: def __get__(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_rtcp_stat stat cdef pjmedia_stream *stream cdef dict statistics = dict() cdef PJSIPUA ua ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if stream == NULL: return None with nogil: status = pjmedia_stream_get_stat(stream, &stat) if status != 0: raise PJSIPError("Could not get RTP statistics", status) statistics["rtt"] = _pj_math_stat_to_dict(&stat.rtt) statistics["rx"] = _pjmedia_rtcp_stream_stat_to_dict(&stat.rx) statistics["tx"] = _pjmedia_rtcp_stream_stat_to_dict(&stat.tx) return statistics finally: with nogil: pj_mutex_unlock(lock) property volume: def __get__(self): return self._volume def __set__(self, value): cdef int slot cdef int status cdef int volume cdef pj_mutex_t *lock = self._lock cdef pjmedia_conf *conf_bridge cdef PJSIPUA ua ua = self._check_ua() if ua is not None: with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: conf_bridge = self.mixer._obj slot = self._slot if value < 0: raise ValueError("volume attribute cannot be negative") if ua is not None and self._obj != NULL: volume = int(value * 1.28 - 128) with nogil: status = pjmedia_conf_adjust_rx_level(conf_bridge, slot, volume) if status != 0: raise PJSIPError("Could not set volume of audio transport", status) self._volume = value finally: if ua is not None: with nogil: pj_mutex_unlock(lock) property slot: def __get__(self): self._check_ua() if self._slot == -1: return None else: return self._slot def get_local_media(self, BaseSDPSession remote_sdp=None, int index=0, direction="sendrecv"): global valid_sdp_directions cdef int status cdef pj_mutex_t *lock = self._lock cdef object direction_attr cdef SDPAttribute attr cdef SDPSession local_sdp cdef SDPMediaStream local_media cdef pjmedia_sdp_media *c_local_media _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: is_offer = remote_sdp is None if is_offer and direction not in valid_sdp_directions: raise SIPCoreError("Unknown direction: %s" % direction) self._sdp_info.index = index local_sdp = self._sdp_info.local_sdp local_media = self._sdp_info.local_media local_sdp.media = [None] * (index+1) local_sdp.media[index] = local_media self.transport.update_local_sdp(local_sdp, remote_sdp, index) # updating the local SDP might have modified the connection line if local_sdp.connection is not None and local_media.connection is None: local_media.connection = SDPConnection.new(local_sdp.connection) local_media.attributes = [ attr for attr in local_media.attributes if attr.name not in valid_sdp_directions] if is_offer: direction_attr = direction else: if self.direction is None or "recv" in self.direction: direction_attr = "sendrecv" else: direction_attr = "sendonly" local_media.attributes.append(SDPAttribute(direction_attr, "")) for attribute in local_media.attributes: if attribute.name == 'rtcp': attribute.value = attribute.value.split(' ', 1)[0] self._sdp_info.local_media = local_media return local_media finally: with nogil: pj_mutex_unlock(lock) def start(self, BaseSDPSession local_sdp, BaseSDPSession remote_sdp, int sdp_index, int timeout=30): cdef int status cdef object desired_state cdef pj_mutex_t *lock = self._lock cdef pj_pool_t *pool cdef pjmedia_endpt *media_endpoint cdef pjmedia_port *media_port cdef pjmedia_sdp_media *local_media cdef pjmedia_sdp_session *pj_local_sdp cdef pjmedia_sdp_session *pj_remote_sdp cdef pjmedia_stream **stream_address cdef pjmedia_stream_info *stream_info_address cdef pjmedia_transport *transport cdef PJSIPUA ua ua = _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: pool = self._pool media_endpoint = ua._pjmedia_endpoint._obj stream_address = &self._obj stream_info_address = &self._stream_info transport = self.transport._obj if self._is_started: raise SIPCoreError("This AudioTransport was already started once") desired_state = ("LOCAL" if self._is_offer else "REMOTE") if self.transport.state != desired_state: raise SIPCoreError('RTPTransport object provided is not in the "%s" state, but in the "%s" state' % (desired_state, self.transport.state)) if None in [local_sdp, remote_sdp]: raise ValueError("SDP arguments cannot be None") pj_local_sdp = local_sdp.get_sdp_session() pj_remote_sdp = remote_sdp.get_sdp_session() if sdp_index < 0: raise ValueError("sdp_index argument cannot be negative") if local_sdp.media[sdp_index].port == 0 or remote_sdp.media[sdp_index].port == 0: raise SIPCoreError("Cannot start a rejected audio stream") if timeout < 0: raise ValueError("timeout value cannot be negative") self.transport.set_ESTABLISHED(local_sdp, remote_sdp, sdp_index) with nogil: status = pjmedia_stream_info_from_sdp(stream_info_address, pool, media_endpoint, pj_local_sdp, pj_remote_sdp, sdp_index) if status != 0: raise PJSIPError("Could not parse SDP for audio session", status) if self._stream_info.param == NULL: raise SIPCoreError("Could not parse SDP for audio session") self._stream_info.param.setting.vad = self._vad self._stream_info.use_ka = 1 with nogil: status = pjmedia_stream_create(media_endpoint, pool, stream_info_address, transport, NULL, stream_address) if status != 0: raise PJSIPError("Could not initialize RTP for audio session", status) with nogil: status = pjmedia_stream_set_dtmf_callback(stream_address[0], _AudioTransport_cb_dtmf, self.weakref) if status != 0: with nogil: pjmedia_stream_destroy(stream_address[0]) self._obj = NULL raise PJSIPError("Could not set DTMF callback for audio session", status) with nogil: status = pjmedia_stream_start(stream_address[0]) if status != 0: with nogil: pjmedia_stream_destroy(stream_address[0]) self._obj = NULL raise PJSIPError("Could not start RTP for audio session", status) with nogil: status = pjmedia_stream_get_port(stream_address[0], &media_port) if status != 0: with nogil: pjmedia_stream_destroy(stream_address[0]) self._obj = NULL raise PJSIPError("Could not get audio port for audio session", status) try: self._slot = self.mixer._add_port(ua, pool, media_port) if self._volume != 100: self.volume = self._volume except: with nogil: pjmedia_stream_destroy(stream_address[0]) self._obj = NULL raise self.update_direction(local_sdp.media[sdp_index].direction) self._sdp_info.local_media = local_sdp.media[sdp_index] self._sdp_info.local_sdp = local_sdp self._sdp_info.remote_sdp = remote_sdp self._sdp_info.index = sdp_index self._is_started = 1 if timeout > 0: self._timer = MediaCheckTimer(timeout) self._timer.schedule(timeout, self._cb_check_rtp, self) self.mixer.reset_ec() finally: with nogil: pj_mutex_unlock(lock) def stop(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_stream *stream cdef PJSIPUA ua ua = self._check_ua() if ua is not None: with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if self._timer is not None: self._timer.cancel() self._timer = None if self._obj == NULL: return self._obj = NULL self.mixer._remove_port(ua, self._slot) with nogil: pjmedia_stream_destroy(stream) self.transport.set_INIT() finally: if ua is not None: with nogil: pj_mutex_unlock(lock) def update_direction(self, direction): cdef int status cdef pj_mutex_t *lock = self._lock _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self._obj == NULL: raise SIPCoreError("Stream is not active") if direction not in valid_sdp_directions: raise SIPCoreError("Unknown direction: %s" % direction) if direction != self.direction: self.mixer.reset_ec() self.direction = direction finally: with nogil: pj_mutex_unlock(lock) def update_sdp(self, local_sdp, remote_sdp, index): cdef int status cdef pj_mutex_t *lock = self._lock _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self._obj == NULL: raise SIPCoreError("Stream is not active") self._sdp_info.local_media = local_sdp.media[index] self._sdp_info.local_sdp = local_sdp self._sdp_info.remote_sdp = remote_sdp self._sdp_info.index = index finally: with nogil: pj_mutex_unlock(lock) def send_dtmf(self, digit): cdef int status cdef pj_mutex_t *lock = self._lock cdef pj_str_t digit_pj cdef pjmedia_stream *stream cdef PJSIPUA ua ua = _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if self._obj == NULL: raise SIPCoreError("Stream is not active") if len(digit) != 1 or digit not in "0123456789*#ABCD": raise SIPCoreError("Not a valid DTMF digit: %s" % digit) _str_to_pj_str(digit, &digit_pj) if not self._stream_info.tx_event_pt < 0: # If the remote doesn't support telephone-event just don't send DTMF with nogil: status = pjmedia_stream_dial_dtmf(stream, &digit_pj) if status != 0: raise PJSIPError("Could not send DTMF digit on audio stream", status) finally: with nogil: pj_mutex_unlock(lock) cdef int _cb_check_rtp(self, MediaCheckTimer timer) except -1: cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_rtcp_stat stat cdef pjmedia_stream *stream with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if stream == NULL: return 0 if self._timer is None: return 0 self._timer = None with nogil: status = pjmedia_stream_get_stat(stream, &stat) if status == 0: if self._packets_received == stat.rx.pkt and self.direction == "sendrecv": _add_event("RTPAudioTransportDidTimeout", dict(obj=self)) self._packets_received = stat.rx.pkt if timer.media_check_interval > 0: self._timer = MediaCheckTimer(timer.media_check_interval) self._timer.schedule(timer.media_check_interval, self._cb_check_rtp, self) finally: with nogil: pj_mutex_unlock(lock) cdef class VideoTransport: def __cinit__(self, *args, **kwargs): cdef int status cdef pj_pool_t *pool cdef bytes pool_name cdef PJSIPUA ua ua = _get_ua() endpoint = ua._pjsip_endpoint._obj pool_name = b"VideoTransport_%d" % id(self) self.weakref = weakref.ref(self) Py_INCREF(self.weakref) pool = ua.create_memory_pool(pool_name, 4096, 4096) self._pool = pool status = pj_mutex_create_recursive(pool, "video_transport_lock", &self._lock) if status != 0: raise PJSIPError("failed to create lock", status) self._timer = None def __init__(self, RTPTransport transport, BaseSDPSession remote_sdp=None, int sdp_index=0, list codecs=None): cdef int status cdef pj_pool_t *pool cdef pjmedia_endpt *media_endpoint cdef pjmedia_sdp_media *local_media_c cdef pjmedia_sdp_session *local_sdp_c cdef pjmedia_transport_info info cdef pj_sockaddr *addr cdef list global_codecs cdef SDPMediaStream local_media cdef SDPSession local_sdp cdef PJSIPUA ua ua = _get_ua() media_endpoint = ua._pjmedia_endpoint._obj pool = self._pool if self.transport is not None: raise SIPCoreError("VideoTransport.__init__() was already called") if transport is None: raise ValueError("transport argument cannot be None") if sdp_index < 0: raise ValueError("sdp_index argument cannot be negative") if transport.state != "INIT": raise SIPCoreError('RTPTransport object provided is not in the "INIT" state, but in the "%s" state' % transport.state) self.transport = transport transport._get_info(&info) global_codecs = ua._pjmedia_endpoint._get_current_video_codecs() if codecs is None: codecs = global_codecs try: ua._pjmedia_endpoint._set_video_codecs(codecs) addr = &(info.sock_info.rtp_addr_name) with nogil: status = pjmedia_endpt_create_base_sdp(media_endpoint, pool, NULL, addr, &local_sdp_c) if status != 0: raise PJSIPError("Could not generate base SDP", status) with nogil: status = pjmedia_endpt_create_video_sdp(media_endpoint, pool, &info.sock_info, 0, &local_media_c) if status != 0: raise PJSIPError("Could not generate SDP video stream", status) # Create a 'fake' SDP, which only contains the video stream, then the m line is extracted because the full # SDP is built by the Session local_sdp_c.media_count = 1 local_sdp_c.media[0] = local_media_c finally: ua._pjmedia_endpoint._set_video_codecs(global_codecs) local_sdp = SDPSession_create(local_sdp_c) local_media = local_sdp.media[0] if remote_sdp is None: self._is_offer = 1 self.transport.set_LOCAL(local_sdp, 0) else: self._is_offer = 0 if sdp_index != 0: local_sdp.media = [None] * (sdp_index+1) local_sdp.media[sdp_index] = local_media self.transport.set_REMOTE(local_sdp, remote_sdp, sdp_index) self._sdp_info = SDPInfo(local_media, local_sdp, remote_sdp, sdp_index) self.local_video = None self.remote_video = None def __dealloc__(self): cdef PJSIPUA ua cdef Timer timer try: ua = _get_ua() except SIPCoreError: return if self._obj != NULL: self.stop() if self._lock != NULL: pj_mutex_destroy(self._lock) ua.release_memory_pool(self._pool) self._pool = NULL timer = Timer() try: timer.schedule(60, deallocate_weakref, self.weakref) except SIPCoreError: pass cdef PJSIPUA _check_ua(self): cdef PJSIPUA ua try: ua = _get_ua() return ua except: self._obj = NULL self._pool = NULL return None property is_active: def __get__(self): self._check_ua() return bool(self._obj != NULL) property is_started: def __get__(self): return bool(self._is_started) property codec: def __get__(self): self._check_ua() if self._obj == NULL: return None else: - return _pj_str_to_str(self._stream_info.codec_info.encoding_name) + return _pj_str_to_bytes(self._stream_info.codec_info.encoding_name) property sample_rate: def __get__(self): self._check_ua() if self._obj == NULL: return None else: return self._stream_info.codec_info.clock_rate property statistics: def __get__(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_rtcp_stat stat cdef pjmedia_vid_stream *stream cdef dict statistics = dict() cdef PJSIPUA ua ua = self._check_ua() if ua is None: return None with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if stream == NULL: return None with nogil: status = pjmedia_vid_stream_get_stat(stream, &stat) if status != 0: raise PJSIPError("Could not get RTP statistics", status) statistics["rtt"] = _pj_math_stat_to_dict(&stat.rtt) statistics["rx"] = _pjmedia_rtcp_stream_stat_to_dict(&stat.rx) statistics["tx"] = _pjmedia_rtcp_stream_stat_to_dict(&stat.tx) return statistics finally: with nogil: pj_mutex_unlock(lock) def get_local_media(self, BaseSDPSession remote_sdp=None, int index=0, direction="sendrecv"): global valid_sdp_directions cdef int status cdef pj_mutex_t *lock = self._lock cdef object direction_attr cdef SDPAttribute attr cdef SDPSession local_sdp cdef SDPMediaStream local_media cdef pjmedia_sdp_media *c_local_media _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: is_offer = remote_sdp is None if is_offer and direction not in valid_sdp_directions: raise SIPCoreError("Unknown direction: %s" % direction) self._sdp_info.index = index local_sdp = self._sdp_info.local_sdp local_media = self._sdp_info.local_media local_sdp.media = [None] * (index+1) local_sdp.media[index] = local_media self.transport.update_local_sdp(local_sdp, remote_sdp, index) # updating the local SDP might have modified the connection line if local_sdp.connection is not None and local_media.connection is None: local_media.connection = SDPConnection.new(local_sdp.connection) local_media.attributes = [ attr for attr in local_media.attributes if attr.name not in valid_sdp_directions] if is_offer: direction_attr = direction else: if self.direction is None or "recv" in self.direction: direction_attr = "sendrecv" else: direction_attr = "sendonly" local_media.attributes.append(SDPAttribute(direction_attr, "")) for attribute in local_media.attributes: if attribute.name == 'rtcp': attribute.value = attribute.value.split(' ', 1)[0] return local_media finally: with nogil: pj_mutex_unlock(lock) def start(self, BaseSDPSession local_sdp, BaseSDPSession remote_sdp, int sdp_index, int timeout=30): cdef int status cdef object desired_state cdef pj_mutex_t *lock = self._lock cdef pj_pool_t *pool cdef pjmedia_endpt *media_endpoint cdef pjmedia_sdp_media *local_media cdef pjmedia_sdp_session *pj_local_sdp cdef pjmedia_sdp_session *pj_remote_sdp cdef pjmedia_vid_stream *stream cdef pjmedia_vid_stream_info *stream_info cdef pjmedia_transport *transport cdef PJSIPUA ua ua = _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: pool = self._pool media_endpoint = ua._pjmedia_endpoint._obj stream_info = &self._stream_info transport = self.transport._obj if self._is_started: raise SIPCoreError("This VideoTransport was already started once") desired_state = ("LOCAL" if self._is_offer else "REMOTE") if self.transport.state != desired_state: raise SIPCoreError('RTPTransport object provided is not in the "%s" state, but in the "%s" state' % (desired_state, self.transport.state)) if None in (local_sdp, remote_sdp): raise ValueError("SDP arguments cannot be None") pj_local_sdp = local_sdp.get_sdp_session() pj_remote_sdp = remote_sdp.get_sdp_session() if sdp_index < 0: raise ValueError("sdp_index argument cannot be negative") if local_sdp.media[sdp_index].port == 0 or remote_sdp.media[sdp_index].port == 0: raise SIPCoreError("Cannot start a rejected video stream") if timeout < 0: raise ValueError("timeout value cannot be negative") self.transport.set_ESTABLISHED(local_sdp, remote_sdp, sdp_index) with nogil: status = pjmedia_vid_stream_info_from_sdp(stream_info, pool, media_endpoint, pj_local_sdp, pj_remote_sdp, sdp_index) if status != 0: raise PJSIPError("Could not parse SDP for video session", status) if self._stream_info.codec_param == NULL: raise SIPCoreError("Could not parse SDP for video session") self._stream_info.use_ka = 1 with nogil: status = pjmedia_vid_stream_create(media_endpoint, pool, stream_info, transport, NULL, &stream) if status != 0: raise PJSIPError("Could not initialize RTP for video session", status) self._obj = stream with nogil: status = pjmedia_vid_stream_start(stream) if status != 0: with nogil: pjmedia_vid_stream_destroy(stream) self._obj = NULL raise PJSIPError("Could not start RTP for video session", status) with nogil: pjmedia_vid_stream_send_rtcp_sdes(stream) try: local_video = LocalVideoStream_create(stream) remote_video = RemoteVideoStream_create(stream, self._remote_video_event_handler) except PJSIPError: with nogil: pjmedia_vid_stream_destroy(stream) self._obj = NULL self.local_video = None self.remote_video = None raise self.local_video = local_video self.remote_video = remote_video self.update_direction(local_sdp.media[sdp_index].direction) self._sdp_info.local_media = local_sdp.media[sdp_index] self._sdp_info.local_sdp = local_sdp self._sdp_info.remote_sdp = remote_sdp self._sdp_info.index = sdp_index self._is_started = 1 if timeout > 0: self._timer = MediaCheckTimer(timeout) self._timer.schedule(timeout, self._cb_check_rtp, self) finally: with nogil: pj_mutex_unlock(lock) def stop(self): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_vid_stream *stream cdef PJSIPUA ua ua = self._check_ua() if ua is not None: with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if self._timer is not None: self._timer.cancel() self._timer = None if self._obj == NULL: return self._obj = NULL if self.local_video is not None: self.local_video.close() self.local_video = None if self.remote_video is not None: self.remote_video.close() self.remote_video = None with nogil: pjmedia_vid_stream_send_rtcp_bye(stream) pjmedia_vid_stream_destroy(stream) self.transport.set_INIT() finally: if ua is not None: with nogil: pj_mutex_unlock(lock) def update_direction(self, direction): global valid_sdp_directions cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_vid_stream *stream _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if self._obj == NULL: raise SIPCoreError("Stream is not active") if direction not in valid_sdp_directions: raise SIPCoreError("Unknown direction: %s" % direction) self.direction = direction finally: with nogil: pj_mutex_unlock(lock) def update_sdp(self, local_sdp, remote_sdp, index): cdef int status cdef pj_mutex_t *lock = self._lock _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: if self._obj == NULL: raise SIPCoreError("Stream is not active") self._sdp_info.local_media = local_sdp.media[index] self._sdp_info.local_sdp = local_sdp self._sdp_info.remote_sdp = remote_sdp self._sdp_info.index = index finally: with nogil: pj_mutex_unlock(lock) def pause(self, direction="both"): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_vid_stream *stream cdef pjmedia_dir pj_dir _get_ua() if direction not in ("incoming", "outgoing", "both"): raise ValueError("direction can only be one of 'incoming', 'outgoing' or 'both'") if direction == "incoming": pj_dir = PJMEDIA_DIR_RENDER elif direction == "outgoing": pj_dir = PJMEDIA_DIR_CAPTURE else: pj_dir = PJMEDIA_DIR_CAPTURE_RENDER with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if self._obj == NULL: raise SIPCoreError("Stream is not active") with nogil: status = pjmedia_vid_stream_pause(stream, pj_dir) if status != 0: raise PJSIPError("failed to pause video stream", status) finally: with nogil: pj_mutex_unlock(lock) def resume(self, direction="both"): cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_vid_stream *stream cdef pjmedia_dir pj_dir _get_ua() if direction not in ("incoming", "outgoing", "both"): raise ValueError("direction can only be one of 'incoming', 'outgoing' or 'both'") if direction == "incoming": pj_dir = PJMEDIA_DIR_RENDER elif direction == "outgoing": pj_dir = PJMEDIA_DIR_CAPTURE else: pj_dir = PJMEDIA_DIR_CAPTURE_RENDER with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if self._obj == NULL: raise SIPCoreError("Stream is not active") with nogil: status = pjmedia_vid_stream_resume(stream, pj_dir) if status != 0: raise PJSIPError("failed to resume video stream", status) finally: with nogil: pj_mutex_unlock(lock) def send_keyframe(self): cdef pj_mutex_t *lock = self._lock cdef pjmedia_vid_stream *stream _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if stream != NULL: # Do not check for errors, it's OK if we can't send it pjmedia_vid_stream_send_keyframe(stream) finally: with nogil: pj_mutex_unlock(lock) def request_keyframe(self): cdef pj_mutex_t *lock = self._lock cdef pjmedia_vid_stream *stream _get_ua() with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if stream != NULL: # Do not check for errors, it's OK if we can't send it pjmedia_vid_stream_send_rtcp_pli(stream) finally: with nogil: pj_mutex_unlock(lock) cdef int _cb_check_rtp(self, MediaCheckTimer timer) except -1: cdef int status cdef pj_mutex_t *lock = self._lock cdef pjmedia_rtcp_stat stat cdef pjmedia_vid_stream *stream with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: stream = self._obj if stream == NULL: return 0 if self._timer is None: return 0 self._timer = None with nogil: status = pjmedia_vid_stream_get_stat(stream, &stat) if status == 0: if self._packets_received == stat.rx.pkt and self.direction == "sendrecv": _add_event("RTPVideoTransportDidTimeout", dict(obj=self)) self._packets_received = stat.rx.pkt if timer.media_check_interval > 0: self._timer = MediaCheckTimer(timer.media_check_interval) self._timer.schedule(timer.media_check_interval, self._cb_check_rtp, self) finally: with nogil: pj_mutex_unlock(lock) def _remote_video_event_handler(self, str name, object data): if name == "FORMAT_CHANGED": size, framerate = data _add_event("RTPVideoTransportRemoteFormatDidChange", dict(obj=self, size=size, framerate=framerate)) elif name == "RECEIVED_KEYFRAME": _add_event("RTPVideoTransportReceivedKeyFrame", dict(obj=self)) elif name == "MISSED_KEYFRAME": _add_event("RTPVideoTransportMissedKeyFrame", dict(obj=self)) elif name == "REQUESTED_KEYFRAME": _add_event("RTPVideoTransportRequestedKeyFrame", dict(obj=self)) cdef class ICECandidate: def __init__(self, component, cand_type, address, port, priority, rel_addr=''): self.component = component self.type = cand_type self.address = address self.port = port self.priority = priority self.rel_address = rel_addr def __str__(self): return '(%s) %s:%d%s priority=%d type=%s' % (self.component, self.address, self.port, ' rel_addr=%s' % self.rel_address if self.rel_address else '', self.priority, self.type.lower()) cdef ICECandidate ICECandidate_create(pj_ice_sess_cand *cand): cdef char buf[PJ_INET6_ADDRSTRLEN] cdef str address cdef str cand_type cdef int port if cand.type == PJ_ICE_CAND_TYPE_HOST: cand_type = 'HOST' elif cand.type == PJ_ICE_CAND_TYPE_SRFLX: cand_type = 'SRFLX' elif cand.type == PJ_ICE_CAND_TYPE_PRFLX: cand_type = 'PRFLX' elif cand.type == PJ_ICE_CAND_TYPE_RELAYED: cand_type = 'RELAY' else: cand_type = 'UNKNOWN' pj_sockaddr_print(&cand.addr, buf, PJ_INET6_ADDRSTRLEN, 0) address = _buf_to_str(buf) port = pj_sockaddr_get_port(&cand.addr) if pj_sockaddr_has_addr(&cand.rel_addr): pj_sockaddr_print(&cand.rel_addr, buf, PJ_INET6_ADDRSTRLEN, 0) rel_addr = _buf_to_str(buf) else: rel_addr = '' return ICECandidate('RTP' if cand.comp_id==1 else 'RTCP', cand_type, address, port, cand.prio, rel_addr) cdef class ICECheck: def __init__(self, local_candidate, remote_candidate, state, nominated): self.local_candidate = local_candidate self.remote_candidate = remote_candidate self.state = state self.nominated = nominated def __str__(self): return '%s:%d -> %s:%d (%s, %s)' % (self.local_candidate.address, self.local_candidate.port, self.remote_candidate.address, self.remote_candidate.port, self.state.lower(), 'nominated' if self.nominated else 'not nominated') cdef ICECheck ICECheck_create(pj_ice_sess_check *check): cdef str state cdef ICECandidate lcand cdef ICECandidate rcand if check.state == PJ_ICE_SESS_CHECK_STATE_FROZEN: state = 'FROZEN' elif check.state == PJ_ICE_SESS_CHECK_STATE_WAITING: state = 'WAITING' elif check.state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS: state = 'IN_PROGRESS' elif check.state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED: state = 'SUCCEEDED' elif check.state == PJ_ICE_SESS_CHECK_STATE_FAILED: state = 'FAILED' else: state = 'UNKNOWN' lcand = ICECandidate_create(check.lcand) rcand = ICECandidate_create(check.rcand) return ICECheck(lcand, rcand, state, bool(check.nominated)) cdef ICECheck _get_rtp_valid_pair(pj_ice_strans *ice_st): cdef pj_ice_sess_check_ptr_const ice_check ice_check = pj_ice_strans_get_valid_pair(ice_st, 1) if ice_check == NULL: return None return ICECheck_create(ice_check) # helper functions cdef dict _pj_math_stat_to_dict(pj_math_stat *stat): cdef dict retval = dict() retval["count"] = stat.n retval["max"] = stat.max retval["min"] = stat.min retval["last"] = stat.last retval["avg"] = stat.mean return retval cdef dict _pjmedia_rtcp_stream_stat_to_dict(pjmedia_rtcp_stream_stat *stream_stat): cdef dict retval = dict() retval["packets"] = stream_stat.pkt retval["bytes"] = stream_stat.bytes retval["packets_discarded"] = stream_stat.discard retval["packets_lost"] = stream_stat.loss retval["packets_reordered"] = stream_stat.reorder retval["packets_duplicate"] = stream_stat.dup retval["loss_period"] = _pj_math_stat_to_dict(&stream_stat.loss_period) retval["burst_loss"] = bool(stream_stat.loss_type.burst) retval["random_loss"] = bool(stream_stat.loss_type.random) retval["jitter"] = _pj_math_stat_to_dict(&stream_stat.jitter) return retval cdef str _ice_state_to_str(int state): if state == PJ_ICE_STRANS_STATE_NULL: return 'NULL' elif state == PJ_ICE_STRANS_STATE_INIT: return 'GATHERING' elif state == PJ_ICE_STRANS_STATE_READY: return 'GATHERING_COMPLETE' elif state == PJ_ICE_STRANS_STATE_SESS_READY: return 'NEGOTIATION_START' elif state == PJ_ICE_STRANS_STATE_NEGO: return 'NEGOTIATING' elif state == PJ_ICE_STRANS_STATE_RUNNING: return 'RUNNING' elif state == PJ_ICE_STRANS_STATE_FAILED: return 'FAILED' else: return 'UNKNOWN' cdef dict _extract_ice_session_data(pj_ice_sess *ice_sess): cdef dict data = dict() cdef pj_ice_sess_cand *cand cdef pj_ice_sess_check *check # Process local candidates local_candidates = [] for i in range(ice_sess.lcand_cnt): cand = &ice_sess.lcand[i] local_candidates.append(ICECandidate_create(cand)) data['local_candidates'] = local_candidates # Process remote candidates remote_candidates = [] for i in range(ice_sess.rcand_cnt): cand = &ice_sess.rcand[i] remote_candidates.append(ICECandidate_create(cand)) data['remote_candidates'] = remote_candidates # Process valid pairs valid_pairs = [] for i in range(ice_sess.comp_cnt): check = ice_sess.comp[i].valid_check valid_pairs.append(ICECheck_create(check)) data['valid_pairs'] = valid_pairs # Process valid list valid_list = [] for i in range(ice_sess.valid_list.count): check = &ice_sess.valid_list.checks[i] valid_list.append(ICECheck_create(check)) data['valid_list'] = valid_list return data cdef object _extract_rtp_transport(pjmedia_transport *tp): cdef void *rtp_transport_ptr = NULL if tp != NULL: rtp_transport_ptr = tp.user_data if rtp_transport_ptr == NULL: return None return ( rtp_transport_ptr)() # callback functions cdef void _RTPTransport_cb_ice_complete(pjmedia_transport *tp, pj_ice_strans_op op, int status) with gil: # Despite the name this callback is not only called when ICE negotiation ends, it depends on the # op parameter cdef int lock_status cdef double duration cdef pj_ice_strans *ice_st cdef pj_ice_sess *ice_sess cdef pj_time_val tv, start_time cdef pj_mutex_t *lock cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return lock = rtp_transport._lock with nogil: lock_status = pj_mutex_lock(lock) if lock_status != 0: raise PJSIPError("failed to acquire lock", status) try: if op == PJ_ICE_STRANS_OP_NEGOTIATION: if status == 0: ice_st = pjmedia_ice_get_strans(tp) if ice_st == NULL: return ice_sess = pj_ice_strans_get_session(ice_st) if ice_sess == NULL: return start_time = pj_ice_strans_get_start_time(ice_st) pj_gettimeofday(&tv) tv.sec -= start_time.sec tv.msec -= start_time.msec pj_time_val_normalize(&tv) duration = (tv.sec*1000 + tv.msec)/1000.0 data = _extract_ice_session_data(ice_sess) rtp_transport._rtp_valid_pair = _get_rtp_valid_pair(ice_st) _add_event("RTPTransportICENegotiationDidSucceed", dict(obj=rtp_transport, duration=duration, local_candidates=data['local_candidates'], remote_candidates=data['remote_candidates'], valid_pairs=data['valid_pairs'], valid_list=data['valid_list'])) else: rtp_transport._rtp_valid_pair = None _add_event("RTPTransportICENegotiationDidFail", dict(obj=rtp_transport, reason=_pj_status_to_str(status))) elif op == PJ_ICE_STRANS_OP_INIT: if status == 0: rtp_transport.state = "INIT" _add_event("RTPTransportDidInitialize", dict(obj=rtp_transport)) else: rtp_transport.state = "INVALID" _add_event("RTPTransportDidFail", dict(obj=rtp_transport, reason=_pj_status_to_str(status))) else: # silence compiler warning pass finally: with nogil: pj_mutex_unlock(lock) cdef void _RTPTransport_cb_ice_state(pjmedia_transport *tp, pj_ice_strans_state prev, pj_ice_strans_state curr) with gil: cdef int status cdef pj_mutex_t *lock cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return lock = rtp_transport._lock with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: _add_event("RTPTransportICENegotiationStateDidChange", dict(obj=rtp_transport, prev_state=_ice_state_to_str(prev), state=_ice_state_to_str(curr))) finally: with nogil: pj_mutex_unlock(lock) cdef void _RTPTransport_cb_ice_stop(pjmedia_transport *tp, char *reason, int err) with gil: cdef int status cdef pj_mutex_t *lock cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return lock = rtp_transport._lock with nogil: status = pj_mutex_lock(lock) if status != 0: raise PJSIPError("failed to acquire lock", status) try: rtp_transport._rtp_valid_pair = None _reason = reason if _reason != b"media stop requested": _add_event("RTPTransportICENegotiationDidFail", dict(obj=rtp_transport, reason=_reason)) finally: with nogil: pj_mutex_unlock(lock) cdef void _RTPTransport_cb_zrtp_secure_on(pjmedia_transport *tp, char* cipher) with gil: cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return try: rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return _add_event("RTPTransportZRTPSecureOn", dict(obj=rtp_transport, cipher=bytes(cipher))) except: ua._handle_exception(1) cdef void _RTPTransport_cb_zrtp_secure_off(pjmedia_transport *tp) with gil: cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return try: rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return _add_event("RTPTransportZRTPSecureOff", dict(obj=rtp_transport)) except: ua._handle_exception(1) cdef void _RTPTransport_cb_zrtp_show_sas(pjmedia_transport *tp, char* sas, int verified) with gil: cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return try: rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return _add_event("RTPTransportZRTPReceivedSAS", dict(obj=rtp_transport, sas=str(sas), verified=bool(verified))) except: ua._handle_exception(1) cdef void _RTPTransport_cb_zrtp_confirm_goclear(pjmedia_transport *tp) with gil: cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return try: rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return # TODO: not yet implemented by PJSIP's ZRTP transport except: ua._handle_exception(1) cdef void _RTPTransport_cb_zrtp_show_message(pjmedia_transport *tp, int severity, int sub_code) with gil: global zrtp_message_levels, zrtp_error_messages cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return try: rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return level = zrtp_message_levels.get(severity, 1) message = zrtp_error_messages[level].get(sub_code, 'Unknown') _add_event("RTPTransportZRTPLog", dict(obj=rtp_transport, level=level, message=message)) except: ua._handle_exception(1) cdef void _RTPTransport_cb_zrtp_negotiation_failed(pjmedia_transport *tp, int severity, int sub_code) with gil: global zrtp_message_levels, zrtp_error_messages cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return try: rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return level = zrtp_message_levels.get(severity, 1) reason = zrtp_error_messages[level].get(sub_code, 'Unknown') _add_event("RTPTransportZRTPNegotiationFailed", dict(obj=rtp_transport, reason=reason)) except: ua._handle_exception(1) cdef void _RTPTransport_cb_zrtp_not_supported_by_other(pjmedia_transport *tp) with gil: cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return try: rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return _add_event("RTPTransportZRTPNotSupportedByRemote", dict(obj=rtp_transport)) except: ua._handle_exception(1) cdef void _RTPTransport_cb_zrtp_ask_enrollment(pjmedia_transport *tp, int info) with gil: cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return try: rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return # TODO: implement PBX enrollment except: ua._handle_exception(1) cdef void _RTPTransport_cb_zrtp_inform_enrollment(pjmedia_transport *tp, int info) with gil: cdef RTPTransport rtp_transport cdef PJSIPUA ua try: ua = _get_ua() except: return try: rtp_transport = _extract_rtp_transport(tp) if rtp_transport is None: return # TODO: implement PBX enrollment except: ua._handle_exception(1) cdef void _AudioTransport_cb_dtmf(pjmedia_stream *stream, void *user_data, int digit) with gil: cdef AudioTransport audio_stream = ( user_data)() cdef PJSIPUA ua try: ua = _get_ua() except: return if audio_stream is None: return try: _add_event("RTPAudioStreamGotDTMF", dict(obj=audio_stream, digit=chr(digit))) except: ua._handle_exception(1) # globals cdef pjmedia_ice_cb _ice_cb _ice_cb.on_ice_complete = _RTPTransport_cb_ice_complete _ice_cb.on_ice_state = _RTPTransport_cb_ice_state _ice_cb.on_ice_stop = _RTPTransport_cb_ice_stop valid_sdp_directions = ("sendrecv", "sendonly", "recvonly", "inactive") # ZRTP cdef pjmedia_zrtp_cb _zrtp_cb _zrtp_cb.secure_on = _RTPTransport_cb_zrtp_secure_on _zrtp_cb.secure_off = _RTPTransport_cb_zrtp_secure_off _zrtp_cb.show_sas = _RTPTransport_cb_zrtp_show_sas _zrtp_cb.confirm_go_clear = _RTPTransport_cb_zrtp_confirm_goclear _zrtp_cb.show_message = _RTPTransport_cb_zrtp_show_message _zrtp_cb.negotiation_failed = _RTPTransport_cb_zrtp_negotiation_failed _zrtp_cb.not_supported_by_other = _RTPTransport_cb_zrtp_not_supported_by_other _zrtp_cb.ask_enrollment = _RTPTransport_cb_zrtp_ask_enrollment _zrtp_cb.inform_enrollment = _RTPTransport_cb_zrtp_inform_enrollment _zrtp_cb.sign_sas = NULL _zrtp_cb.check_sas_signature = NULL # Keep these aligned with ZrtpCodes.h cdef dict zrtp_message_levels = {1: 'INFO', 2: 'WARNING', 3: 'SEVERE', 4: 'ERROR'} cdef dict zrtp_error_messages = { 'INFO': { 0: "Unknown", 1: "Hello received and prepared a Commit, ready to get peer's hello hash", #InfoHelloReceived 2: "Commit: Generated a public DH key", #InfoCommitDHGenerated 3: "Responder: Commit received, preparing DHPart1", #InfoRespCommitReceived 4: "DH1Part: Generated a public DH key", #InfoDH1DHGenerated 5: "Initiator: DHPart1 received, preparing DHPart2", #InfoInitDH1Received 6: "Responder: DHPart2 received, preparing Confirm1", #InfoRespDH2Received 7: "Initiator: Confirm1 received, preparing Confirm2", #InfoInitConf1Received 8: "Responder: Confirm2 received, preparing Conf2Ack", #InfoRespConf2Received 9: "At least one retained secrets matches - security OK", #InfoRSMatchFound 10: "Entered secure state", #InfoSecureStateOn 11: "No more security for this session", #InfoSecureStateOff }, 'WARNING': { 0: "Unknown", 1: "WarningDHAESmismatch = 1, //!< Commit contains an AES256 cipher but does not offer a Diffie-Helman 4096 - not used DH4096 was discarded", #WarningDHAESmismatch 2: "Received a GoClear message", #WarningGoClearReceived 3: "Hello offers an AES256 cipher but does not offer a Diffie-Helman 4096- not used DH4096 was discarded", #WarningDHShort 4: "No retained shared secrets available - must verify SAS", #WarningNoRSMatch 5: "Internal ZRTP packet checksum mismatch - packet dropped", #WarningCRCmismatch 6: "Dropping packet because SRTP authentication failed!", #WarningSRTPauthError 7: "Dropping packet because SRTP replay check failed!", #WarningSRTPreplayError 8: "Valid retained shared secrets availabe but no matches found - must verify SAS", #WarningNoExpectedRSMatch }, 'SEVERE': { 0: "Unknown", 1: "Hash HMAC check of Hello failed!", #SevereHelloHMACFailed 2: "Hash HMAC check of Commit failed!", #SevereCommitHMACFailed 3: "Hash HMAC check of DHPart1 failed!", #SevereDH1HMACFailed 4: "Hash HMAC check of DHPart2 failed!", #SevereDH2HMACFailed 5: "Cannot send data - connection or peer down?", #SevereCannotSend 6: "Internal protocol error occured!", #SevereProtocolError 7: "Cannot start a timer - internal resources exhausted?", #SevereNoTimer 8: "Too much retries during ZRTP negotiation - connection or peer down?", #SevereTooMuchRetries }, 'ERROR': { 0x00: "Unknown", 0x10: "Malformed packet (CRC OK, but wrong structure)", #MalformedPacket 0x20: "Critical software error", #CriticalSWError 0x30: "Unsupported ZRTP version", #UnsuppZRTPVersion 0x40: "Hello components mismatch", #HelloCompMismatch 0x51: "Hash type not supported", #UnsuppHashType 0x52: "Cipher type not supported", #UnsuppCiphertype 0x53: "Public key exchange not supported", #UnsuppPKExchange 0x54: "SRTP auth. tag not supported", #UnsuppSRTPAuthTag 0x55: "SAS scheme not supported", #UnsuppSASScheme 0x56: "No shared secret available, DH mode required", #NoSharedSecret 0x61: "DH Error: bad pvi or pvr ( == 1, 0, or p-1)", #DHErrorWrongPV 0x62: "DH Error: hvi != hashed data", #DHErrorWrongHVI 0x63: "Received relayed SAS from untrusted MiTM", #SASuntrustedMiTM 0x70: "Auth. Error: Bad Confirm pkt HMAC", #ConfirmHMACWrong 0x80: "Nonce reuse", #NonceReused 0x90: "Equal ZIDs in Hello", #EqualZIDHello 0x100: "GoClear packet received, but not allowed", #GoCleatNotAllowed 0x7fffffff: "Packet ignored", #IgnorePacket } } diff --git a/sipsimple/core/_core.pxd b/sipsimple/core/_core.pxd index 9f21cbc6..d798c2f3 100644 --- a/sipsimple/core/_core.pxd +++ b/sipsimple/core/_core.pxd @@ -1,2639 +1,2640 @@ # cython: language_level=2 cdef extern from *: ctypedef char *char_ptr_const "const char *" enum: PJ_SVN_REV "PJ_SVN_REVISION" # system imports from libc.stdlib cimport malloc, free from libc.string cimport memcpy # Python C imports from cpython.float cimport PyFloat_AsDouble from cpython.ref cimport Py_INCREF, Py_DECREF from cpython.bytes cimport PyBytes_FromString, PyBytes_FromStringAndSize, PyBytes_AsString, PyBytes_Size cdef extern from "Python.h": object PyUnicode_FromString(const char *u) # PJSIP imports cdef extern from "pjlib.h": # constants enum: PJ_ERR_MSG_SIZE enum: PJ_ERRNO_START_SYS PJ_EBUG PJ_ETOOMANY enum: PJ_MAX_OBJ_NAME # init / shutdown int pj_init() nogil void pj_shutdown() nogil # version char *pj_get_version() nogil # string struct pj_str_t: char *ptr int slen ctypedef pj_str_t *pj_str_ptr_const "const pj_str_t *" # errors pj_str_t pj_strerror(int statcode, char *buf, int bufsize) nogil # logging enum: PJ_LOG_MAX_LEVEL enum pj_log_decoration: PJ_LOG_HAS_YEAR PJ_LOG_HAS_MONTH PJ_LOG_HAS_DAY_OF_MON PJ_LOG_HAS_TIME PJ_LOG_HAS_MICRO_SEC PJ_LOG_HAS_SENDER PJ_LOG_HAS_INDENT void pj_log_set_decor(int decor) nogil int pj_log_get_level() nogil void pj_log_set_level(int level) nogil void pj_log_set_log_func(void func(int level, char_ptr_const data, int len)) nogil # memory management struct pj_pool_t struct pj_pool_factory_policy: pass pj_pool_factory_policy pj_pool_factory_default_policy struct pj_pool_factory: pass struct pj_caching_pool: pj_pool_factory factory void pj_caching_pool_init(pj_caching_pool *ch_pool, pj_pool_factory_policy *policy, int max_capacity) nogil void pj_caching_pool_destroy(pj_caching_pool *ch_pool) nogil void *pj_pool_alloc(pj_pool_t *pool, int size) nogil void pj_pool_reset(pj_pool_t *pool) nogil pj_pool_t *pj_pool_create_on_buf(char *name, void *buf, int size) nogil pj_str_t *pj_strdup2_with_null(pj_pool_t *pool, pj_str_t *dst, char *src) nogil void pj_pool_release(pj_pool_t *pool) nogil # threads enum: PJ_THREAD_DESC_SIZE struct pj_mutex_t struct pj_rwmutex_t struct pj_thread_t int pj_mutex_create_simple(pj_pool_t *pool, char *name, pj_mutex_t **mutex) nogil int pj_mutex_create_recursive(pj_pool_t *pool, char *name, pj_mutex_t **mutex) nogil int pj_mutex_lock(pj_mutex_t *mutex) nogil int pj_mutex_unlock(pj_mutex_t *mutex) nogil int pj_mutex_destroy(pj_mutex_t *mutex) nogil int pj_rwmutex_create(pj_pool_t *pool, char *name, pj_rwmutex_t **mutex) nogil int pj_rwmutex_lock_read(pj_rwmutex_t *mutex) nogil int pj_rwmutex_lock_write(pj_rwmutex_t *mutex) nogil int pj_rwmutex_unlock_read(pj_rwmutex_t *mutex) nogil int pj_rwmutex_unlock_write(pj_rwmutex_t *mutex) nogil int pj_rwmutex_destroy(pj_rwmutex_t *mutex) nogil int pj_thread_is_registered() nogil int pj_thread_register(char *thread_name, long *thread_desc, pj_thread_t **thread) nogil # sockets enum: PJ_INET6_ADDRSTRLEN struct pj_ioqueue_t struct pj_addr_hdr: unsigned int sa_family struct pj_sockaddr_in: pass struct pj_sockaddr_in6: pass ctypedef union pj_sockaddr: pj_addr_hdr addr pj_sockaddr_in ipv4 pj_sockaddr_in6 ipv6 ctypedef pj_sockaddr *pj_sockaddr_ptr_const "const pj_sockaddr *" int pj_AF_INET() nogil int pj_AF_INET6() nogil int pj_sockaddr_in_init(pj_sockaddr_in *addr, pj_str_t *cp, int port) nogil int pj_sockaddr_get_port(pj_sockaddr *addr) nogil char *pj_sockaddr_print(pj_sockaddr *addr, char *buf, int size, unsigned int flags) nogil int pj_sockaddr_has_addr(pj_sockaddr *addr) nogil int pj_sockaddr_init(int af, pj_sockaddr *addr, pj_str_t *cp, unsigned int port) nogil int pj_inet_pton(int af, pj_str_t *src, void *dst) nogil # dns struct pj_dns_resolver int pj_dns_resolver_set_ns(pj_dns_resolver *resolver, unsigned count, pj_str_t *servers, int *ports) nogil # time struct pj_time_val: long sec long msec void pj_gettimeofday(pj_time_val *tv) nogil void pj_time_val_normalize(pj_time_val *tv) nogil # timers struct pj_timer_heap_t struct pj_timer_entry: void *user_data int id pj_timer_entry *pj_timer_entry_init(pj_timer_entry *entry, int id, void *user_data, void cb(pj_timer_heap_t *timer_heap, pj_timer_entry *entry) with gil) nogil # lists struct pj_list: void *prev void *next void pj_list_init(pj_list *node) nogil void pj_list_insert_after(pj_list *pos, pj_list *node) nogil # random void pj_srand(unsigned int seed) nogil # maths struct pj_math_stat: int n int max int min int last int mean cdef extern from "pjlib-util.h": # init int pjlib_util_init() nogil cdef extern from "pjnath.h": # init int pjnath_init() nogil # STUN enum: PJ_STUN_PORT struct pj_stun_config: pass struct pj_stun_sock_cfg: pj_sockaddr bound_addr void pj_stun_config_init(pj_stun_config *cfg, pj_pool_factory *factory, unsigned int options, pj_ioqueue_t *ioqueue, pj_timer_heap_t *timer_heap) nogil # NAT detection struct pj_stun_nat_detect_result: int status char *status_text char *nat_type_name ctypedef pj_stun_nat_detect_result *pj_stun_nat_detect_result_ptr_const "const pj_stun_nat_detect_result *" int pj_stun_detect_nat_type(pj_sockaddr_in *server, pj_stun_config *stun_cfg, void *user_data, void pj_stun_nat_detect_cb(void *user_data, pj_stun_nat_detect_result_ptr_const res) with gil) nogil # ICE struct pj_ice_strans struct pj_ice_sess_cand: int type int comp_id int prio pj_sockaddr addr pj_sockaddr rel_addr struct pj_ice_sess_check: pj_ice_sess_cand *lcand pj_ice_sess_cand *rcand int state int nominated ctypedef pj_ice_sess_check *pj_ice_sess_check_ptr_const "const pj_ice_sess_check *" struct pj_ice_sess_comp: pj_ice_sess_check *valid_check struct pj_ice_sess_checklist: int count pj_ice_sess_check *checks struct pj_ice_sess: int comp_cnt pj_ice_sess_comp *comp int lcand_cnt pj_ice_sess_cand *lcand int rcand_cnt pj_ice_sess_cand *rcand pj_ice_sess_checklist valid_list struct pj_ice_strans_cfg_stun: pj_stun_sock_cfg cfg pj_str_t server unsigned int port struct pj_ice_strans_cfg: int af pj_stun_config stun_cfg pj_ice_strans_cfg_stun stun enum pj_ice_strans_op: PJ_ICE_STRANS_OP_INIT PJ_ICE_STRANS_OP_NEGOTIATION enum pj_ice_strans_state: PJ_ICE_STRANS_STATE_NULL PJ_ICE_STRANS_STATE_INIT PJ_ICE_STRANS_STATE_READY PJ_ICE_STRANS_STATE_SESS_READY PJ_ICE_STRANS_STATE_NEGO PJ_ICE_STRANS_STATE_RUNNING PJ_ICE_STRANS_STATE_FAILED enum pj_ice_cand_type: PJ_ICE_CAND_TYPE_HOST PJ_ICE_CAND_TYPE_SRFLX PJ_ICE_CAND_TYPE_PRFLX PJ_ICE_CAND_TYPE_RELAYED enum pj_ice_sess_check_state: PJ_ICE_SESS_CHECK_STATE_FROZEN PJ_ICE_SESS_CHECK_STATE_WAITING PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS PJ_ICE_SESS_CHECK_STATE_SUCCEEDED PJ_ICE_SESS_CHECK_STATE_FAILED struct pjmedia_ice_transport_info: int active pj_ice_strans_state sess_state void pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg) nogil pj_ice_sess* pj_ice_strans_get_session(pj_ice_strans *ice_st) pj_time_val pj_ice_strans_get_start_time(pj_ice_strans *ice_st) pj_ice_strans_state pj_ice_strans_get_state(pj_ice_strans *ice_st) pj_ice_sess_check_ptr_const pj_ice_strans_get_valid_pair(pj_ice_strans *ice_st, unsigned comp_id) cdef extern from "pjmedia.h": enum: PJMEDIA_ENOSNDREC PJMEDIA_ENOSNDPLAY enum: PJMEDIA_AUD_DEFAULT_CAPTURE_DEV PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV PJMEDIA_AUD_DEV_CAP_EC PJMEDIA_AUD_DEV_CAP_EC_TAIL enum: PJMEDIA_VID_DEFAULT_CAPTURE_DEV PJMEDIA_VID_DEFAULT_RENDER_DEV PJMEDIA_VID_INVALID_DEV enum pjmedia_dir: PJMEDIA_DIR_NONE PJMEDIA_DIR_ENCODING PJMEDIA_DIR_DECODING PJMEDIA_DIR_ENCODING_DECODING PJMEDIA_DIR_PLAYBACK = PJMEDIA_DIR_DECODING PJMEDIA_DIR_RENDER = PJMEDIA_DIR_DECODING PJMEDIA_DIR_CAPTURE = PJMEDIA_DIR_ENCODING PJMEDIA_DIR_CAPTURE_PLAYBACK = PJMEDIA_DIR_ENCODING_DECODING PJMEDIA_DIR_CAPTURE_RENDER = PJMEDIA_DIR_ENCODING_DECODING enum pjmedia_type: PJMEDIA_TYPE_NONE PJMEDIA_TYPE_NONEYPE_AUDIO PJMEDIA_TYPE_VIDEO PJMEDIA_TYPE_APPLICATION PJMEDIA_TYPE_UNKNOWN struct pjmedia_rect_size: unsigned int w unsigned int h struct pjmedia_ratio: int num int denum # frame struct pjmedia_frame: void *buf int size ctypedef pjmedia_frame *pjmedia_frame_ptr_const "const pjmedia_frame *" # codec manager struct pjmedia_codec_mgr enum: PJMEDIA_CODEC_MGR_MAX_CODECS struct pjmedia_codec_info: pj_str_t encoding_name unsigned int clock_rate unsigned int channel_cnt int pjmedia_codec_mgr_enum_codecs(pjmedia_codec_mgr *mgr, unsigned int *count, pjmedia_codec_info *info, unsigned int *prio) nogil int pjmedia_codec_mgr_set_codec_priority(pjmedia_codec_mgr *mgr, pj_str_t *codec_id, unsigned int prio) nogil # transport manager struct pjsip_tpmgr struct pjsip_transport_state_info: int status enum pjsip_transport_state: PJSIP_TP_STATE_CONNECTED PJSIP_TP_STATE_DISCONNECTED ctypedef pjsip_transport_state_info *pjsip_transport_state_info_ptr_const "const pjsip_transport_state_info *" ctypedef void (*pjsip_tp_state_callback)(pjsip_transport *tp, pjsip_transport_state state, pjsip_transport_state_info_ptr_const info) with gil int pjsip_tpmgr_set_state_cb(pjsip_tpmgr *mgr, pjsip_tp_state_callback cb) # formats enum pjmedia_format_detail_type: PJMEDIA_FORMAT_DETAIL_NONE PJMEDIA_FORMAT_DETAIL_AUDIO PJMEDIA_FORMAT_DETAIL_VIDEO PJMEDIA_FORMAT_DETAIL_MAX struct pjmedia_audio_format_detail: unsigned int clock_rate unsigned int channel_count unsigned int frame_time_usec unsigned int baseits_per_sample unsigned int avg_bps unsigned int max_bps struct pjmedia_video_format_detail: pjmedia_rect_size size pjmedia_ratio fps unsigned int avg_bps unsigned int max_bps cdef union pjmedia_format_union: pjmedia_audio_format_detail aud pjmedia_video_format_detail vid char user[1] struct pjmedia_format: unsigned int id pjmedia_type type pjmedia_format_detail_type detail_type pjmedia_format_union det ctypedef pjmedia_format *pjmedia_format_ptr_const "const pjmedia_format *" enum: PJMEDIA_CODEC_MAX_FMTP_CNT struct _param: pj_str_t name pj_str_t val struct pjmedia_codec_fmtp: int cnt _param param[PJMEDIA_CODEC_MAX_FMTP_CNT] # video codec parameters enum pjmedia_vid_packing: PJMEDIA_VID_PACKING_PACKETS struct pjmedia_vid_codec_param: pjmedia_dir dir pjmedia_vid_packing packing pjmedia_format enc_fmt pjmedia_codec_fmtp enc_fmtp pjmedia_format dec_fmt pjmedia_codec_fmtp dec_fmtp ctypedef pjmedia_vid_codec_param *pjmedia_vid_codec_param_ptr_const "const pjmedia_vid_codec_param *" # video codec manager enum: PJMEDIA_VID_CODEC_MGR_MAX_CODECS struct pjmedia_vid_codec_info: pj_str_t encoding_name unsigned int pt unsigned int clock_rate unsigned int packings ctypedef pjmedia_vid_codec_info *pjmedia_vid_codec_info_ptr_const "const pjmedia_vid_codec_info *" struct pjmedia_vid_codec_mgr int pjmedia_vid_codec_mgr_create(pj_pool_t *pool, pjmedia_vid_codec_mgr **mgr) nogil void pjmedia_vid_codec_mgr_destroy(pjmedia_vid_codec_mgr *mgr) nogil pjmedia_vid_codec_mgr* pjmedia_vid_codec_mgr_instance() nogil int pjmedia_vid_codec_mgr_enum_codecs(pjmedia_vid_codec_mgr *mgr, unsigned int *count, pjmedia_vid_codec_info *info, unsigned int *prio) nogil int pjmedia_vid_codec_mgr_set_codec_priority(pjmedia_vid_codec_mgr *mgr, pj_str_t *codec_id, unsigned int prio) nogil int pjmedia_vid_codec_mgr_get_default_param(pjmedia_vid_codec_mgr *mgr, pjmedia_vid_codec_info_ptr_const info, pjmedia_vid_codec_param *param) nogil int pjmedia_vid_codec_mgr_set_default_param(pjmedia_vid_codec_mgr *mgr, pjmedia_vid_codec_info_ptr_const info, pjmedia_vid_codec_param_ptr_const param) nogil # video format manager struct pjmedia_video_format_mgr int pjmedia_video_format_mgr_create(pj_pool_t *pool, unsigned int max_fmt, unsigned int options, pjmedia_video_format_mgr **p_mgr) nogil void pjmedia_video_format_mgr_destroy(pjmedia_video_format_mgr *mgr) nogil pjmedia_video_format_mgr* pjmedia_video_format_mgr_instance() nogil # converter manager struct pjmedia_converter_mgr int pjmedia_converter_mgr_create(pj_pool_t *pool, pjmedia_converter_mgr **mgr) nogil void pjmedia_converter_mgr_destroy(pjmedia_converter_mgr *mgr) nogil pjmedia_converter_mgr* pjmedia_converter_mgr_instance() nogil # event manager struct pjmedia_event_mgr enum pjmedia_event_type: PJMEDIA_EVENT_FMT_CHANGED PJMEDIA_EVENT_KEYFRAME_FOUND PJMEDIA_EVENT_KEYFRAME_MISSING PJMEDIA_EVENT_KEYFRAME_REQUESTED struct pjmedia_event_fmt_changed_data: pjmedia_dir dir pjmedia_format new_fmt cdef union pjmedia_event_data_union: pjmedia_event_fmt_changed_data fmt_changed void* ptr struct pjmedia_event: pjmedia_event_type type pjmedia_event_data_union data int pjmedia_event_mgr_create(pj_pool_t *pool, unsigned int options, pjmedia_event_mgr **mgr) nogil void pjmedia_event_mgr_destroy(pjmedia_event_mgr *mgr) nogil pjmedia_event_mgr* pjmedia_event_mgr_instance() nogil ctypedef int pjmedia_event_cb(pjmedia_event *event, void *user_data) int pjmedia_event_subscribe(pjmedia_event_mgr *mgr, pjmedia_event_cb *cb, void *user_data, void *epub) nogil int pjmedia_event_unsubscribe(pjmedia_event_mgr *mgr, pjmedia_event_cb *cb, void *user_data, void *epub) nogil # endpoint struct pjmedia_endpt int pjmedia_endpt_create(pj_pool_factory *pf, pj_ioqueue_t *ioqueue, int worker_cnt, pjmedia_endpt **p_endpt) nogil pj_pool_t *pjmedia_endpt_create_pool(pjmedia_endpt *endpt, char *pool_name, int initial, int increment) nogil int pjmedia_endpt_destroy(pjmedia_endpt *endpt) nogil pj_ioqueue_t *pjmedia_endpt_get_ioqueue(pjmedia_endpt *endpt) nogil pjmedia_codec_mgr *pjmedia_endpt_get_codec_mgr(pjmedia_endpt *endpt) nogil pjsip_tpmgr* pjsip_endpt_get_tpmgr(pjsip_endpoint *endpt) # sound devices struct pjmedia_aud_dev_info: char *name int input_count int output_count struct pjmedia_aud_param: pjmedia_dir dir int play_id int rec_id int clock_rate int channel_count int samples_per_frame int bits_per_sample int flags int ec_enabled int ec_tail_ms struct pjmedia_aud_stream int pjmedia_aud_dev_count() nogil int pjmedia_aud_dev_get_info(int index, pjmedia_aud_dev_info *info) nogil int pjmedia_aud_stream_get_param(pjmedia_aud_stream *strm, pjmedia_aud_param *param) nogil enum pjmedia_aud_dev_event: PJMEDIA_AUD_DEV_DEFAULT_INPUT_CHANGED PJMEDIA_AUD_DEV_DEFAULT_OUTPUT_CHANGED PJMEDIA_AUD_DEV_LIST_WILL_REFRESH PJMEDIA_AUD_DEV_LIST_DID_REFRESH ctypedef void (*pjmedia_aud_dev_observer_callback)(pjmedia_aud_dev_event event) int pjmedia_aud_dev_set_observer_cb(pjmedia_aud_dev_observer_callback cb) nogil int pjmedia_aud_dev_default_param(int index, pjmedia_aud_param *param) nogil int pjmedia_aud_dev_refresh() nogil # sound port struct pjmedia_port_info: pjmedia_format fmt struct pjmedia_port: pjmedia_port_info info struct pjmedia_snd_port struct pjmedia_snd_port_param: pjmedia_aud_param base ctypedef pjmedia_snd_port_param *pjmedia_snd_port_param_ptr_const "const pjmedia_snd_port_param *" int pjmedia_snd_port_create2(pj_pool_t *pool, pjmedia_snd_port_param_ptr_const prm, pjmedia_snd_port **p_port) nogil void pjmedia_snd_port_param_default(pjmedia_snd_port_param *prm) int pjmedia_snd_port_connect(pjmedia_snd_port *snd_port, pjmedia_port *port) nogil int pjmedia_snd_port_disconnect(pjmedia_snd_port *snd_port) nogil int pjmedia_snd_port_set_ec(pjmedia_snd_port *snd_port, pj_pool_t *pool, unsigned int tail_ms, int options) nogil int pjmedia_snd_port_reset_ec_state(pjmedia_snd_port *snd_port) nogil int pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port) nogil pjmedia_aud_stream *pjmedia_snd_port_get_snd_stream(pjmedia_snd_port *snd_port) nogil int pjmedia_null_port_create(pj_pool_t *pool, unsigned int sampling_rate, unsigned int channel_count, unsigned int samples_per_frame, unsigned int bits_per_sample, pjmedia_port **p_port) nogil int pjmedia_mixer_port_create(pj_pool_t *pool, unsigned int sampling_rate, unsigned int channel_count, unsigned int samples_per_frame, unsigned int bits_per_sample, pjmedia_port **p_port) nogil # master port struct pjmedia_master_port int pjmedia_master_port_create(pj_pool_t *pool, pjmedia_port *u_port, pjmedia_port *d_port, unsigned int options, pjmedia_master_port **p_m) nogil int pjmedia_master_port_start(pjmedia_master_port *m) nogil int pjmedia_master_port_destroy(pjmedia_master_port *m, int destroy_ports) nogil # conference bridge enum pjmedia_conf_option: PJMEDIA_CONF_NO_DEVICE struct pjmedia_conf int pjmedia_conf_create(pj_pool_t *pool, int max_slots, int sampling_rate, int channel_count, int samples_per_frame, int bits_per_sample, int options, pjmedia_conf **p_conf) nogil int pjmedia_conf_destroy(pjmedia_conf *conf) nogil pjmedia_port *pjmedia_conf_get_master_port(pjmedia_conf *conf) nogil int pjmedia_conf_add_port(pjmedia_conf *conf, pj_pool_t *pool, pjmedia_port *strm_port, pj_str_t *name, unsigned int *p_slot) nogil int pjmedia_conf_remove_port(pjmedia_conf *conf, unsigned int slot) nogil int pjmedia_conf_connect_port(pjmedia_conf *conf, unsigned int src_slot, unsigned int sink_slot, int level) nogil int pjmedia_conf_disconnect_port(pjmedia_conf *conf, unsigned int src_slot, unsigned int sink_slot) nogil int pjmedia_conf_adjust_rx_level(pjmedia_conf *conf, unsigned slot, int adj_level) nogil int pjmedia_conf_adjust_tx_level(pjmedia_conf *conf, unsigned slot, int adj_level) nogil # video devices enum pjmedia_vid_dev_cap: PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS enum pjmedia_vid_dev_wnd_flag: PJMEDIA_VID_DEV_WND_BORDER PJMEDIA_VID_DEV_WND_RESIZABLE struct pjmedia_vid_dev_info: int id char *name char *driver int dir cdef struct pjmedia_hwnd_win: void *hwnd cdef struct pjmedia_hwnd_x11: void *window void *display cdef struct pjmedia_hwnd_cocoa: void *window cdef struct pjmedia_hwnd_ios: void *window cdef union pjmedia_hwnd_union: pjmedia_hwnd_win win pjmedia_hwnd_x11 x11 pjmedia_hwnd_cocoa cocoa pjmedia_hwnd_ios ios void *window struct pjmedia_vid_dev_hwnd: pjmedia_hwnd_union info struct pjmedia_vid_dev_param: pjmedia_dir dir int cap_id int rend_id unsigned int flags pjmedia_format fmt pjmedia_vid_dev_hwnd window pjmedia_rect_size disp_size int window_hide unsigned int window_flags struct pjmedia_vid_dev_stream int pjmedia_vid_dev_count() nogil int pjmedia_vid_dev_get_info(int index, pjmedia_vid_dev_info *info) nogil int pjmedia_vid_dev_subsys_init(pj_pool_factory *pf) nogil int pjmedia_vid_dev_subsys_shutdown() nogil int pjmedia_vid_dev_default_param(pj_pool_t *pool, int id, pjmedia_vid_dev_param *param) nogil int pjmedia_vid_dev_stream_get_cap(pjmedia_vid_dev_stream *strm, pjmedia_vid_dev_cap cap, void *value) nogil int pjmedia_vid_dev_stream_set_cap(pjmedia_vid_dev_stream *strm, pjmedia_vid_dev_cap cap, void *value) nogil int pjmedia_vid_dev_stream_get_param(pjmedia_vid_dev_stream *strm, pjmedia_vid_dev_param *param) nogil int pjmedia_vid_dev_refresh() nogil int pjmedia_vid_dev_lookup(char_ptr_const drv_name, char_ptr_const dev_name, int *id) # video stream struct pjmedia_vid_stream_info: pjmedia_vid_codec_param *codec_param pjmedia_vid_codec_info codec_info int use_ka struct pjmedia_vid_stream ctypedef pjmedia_vid_stream *pjmedia_vid_stream_ptr_const "const pjmedia_vid_stream *" int pjmedia_vid_stream_get_stat(pjmedia_vid_stream_ptr_const stream, pjmedia_rtcp_stat *stat) nogil int pjmedia_vid_stream_get_info(pjmedia_vid_stream_ptr_const stream, pjmedia_vid_stream_info *info) nogil int pjmedia_vid_stream_info_from_sdp(pjmedia_vid_stream_info *si, pj_pool_t *pool, pjmedia_endpt *endpt, pjmedia_sdp_session *local, pjmedia_sdp_session *remote, unsigned int stream_idx) nogil int pjmedia_vid_stream_create(pjmedia_endpt *endpt, pj_pool_t *pool, pjmedia_vid_stream_info *info, pjmedia_transport *tp, void *user_data, pjmedia_vid_stream **p_stream) nogil int pjmedia_vid_stream_start(pjmedia_vid_stream *stream) nogil int pjmedia_vid_stream_destroy(pjmedia_vid_stream *stream) nogil int pjmedia_vid_stream_get_port(pjmedia_vid_stream *stream, pjmedia_dir dir, pjmedia_port **p_port) nogil int pjmedia_vid_stream_pause(pjmedia_vid_stream *stream, pjmedia_dir dir) nogil int pjmedia_vid_stream_resume(pjmedia_vid_stream *stream, pjmedia_dir dir) nogil int pjmedia_vid_stream_send_keyframe(pjmedia_vid_stream *stream) nogil int pjmedia_vid_stream_send_rtcp_sdes(pjmedia_vid_stream *stream) nogil int pjmedia_vid_stream_send_rtcp_bye(pjmedia_vid_stream *stream) nogil int pjmedia_vid_stream_send_rtcp_pli(pjmedia_vid_stream *stream) nogil # video port struct pjmedia_vid_port struct pjmedia_vid_port_param: pjmedia_vid_dev_param vidparam int active void pjmedia_vid_port_param_default(pjmedia_vid_port_param *prm) nogil ctypedef pjmedia_vid_port_param *pjmedia_vid_port_param_ptr_const "const pjmedia_vid_port_param *" int pjmedia_vid_port_create(pj_pool_t *pool, pjmedia_vid_port_param_ptr_const prm, pjmedia_vid_port **p_vp) nogil int pjmedia_vid_port_start(pjmedia_vid_port *vid_port) nogil int pjmedia_vid_port_is_running(pjmedia_vid_port *vid_port) nogil int pjmedia_vid_port_stop(pjmedia_vid_port *vid_port) nogil int pjmedia_vid_port_connect(pjmedia_vid_port *vid_port, pjmedia_port *port, int destroy) nogil pjmedia_port *pjmedia_vid_port_get_passive_port(pjmedia_vid_port *vid_port) nogil int pjmedia_vid_port_disconnect(pjmedia_vid_port *vid_port) nogil void pjmedia_vid_port_destroy(pjmedia_vid_port *vid_port) nogil pjmedia_vid_dev_stream *pjmedia_vid_port_get_stream(pjmedia_vid_port *vid_port) nogil # video tee int pjmedia_vid_tee_create(pj_pool_t *pool, pjmedia_format_ptr_const fmt, unsigned int max_dst_cnt, pjmedia_port **p_vid_tee) nogil int pjmedia_vid_tee_add_dst_port2(pjmedia_port *vid_tee, unsigned int option, pjmedia_port *port) nogil int pjmedia_vid_tee_remove_dst_port(pjmedia_port *vid_tee, pjmedia_port *port) nogil # sdp enum: PJMEDIA_MAX_SDP_FMT enum: PJMEDIA_MAX_SDP_ATTR enum: PJMEDIA_MAX_SDP_MEDIA enum: PJMEDIA_MAX_SDP_BANDW struct pjmedia_sdp_attr: pj_str_t name pj_str_t value struct pjmedia_sdp_bandw: pj_str_t modifier unsigned int value struct pjmedia_sdp_conn: pj_str_t net_type pj_str_t addr_type pj_str_t addr struct pjmedia_sdp_media_desc: pj_str_t media unsigned int port unsigned int port_count pj_str_t transport unsigned int fmt_count pj_str_t fmt[PJMEDIA_MAX_SDP_FMT] struct pjmedia_sdp_media: pjmedia_sdp_media_desc desc pjmedia_sdp_conn *conn unsigned int attr_count pjmedia_sdp_attr *attr[PJMEDIA_MAX_SDP_ATTR] unsigned int bandw_count pjmedia_sdp_bandw *bandw[PJMEDIA_MAX_SDP_BANDW] struct pjmedia_sdp_session_origin: pj_str_t user unsigned int id unsigned int version pj_str_t net_type pj_str_t addr_type pj_str_t addr struct pjmedia_sdp_session_time: unsigned int start unsigned int stop struct pjmedia_sdp_session: pjmedia_sdp_session_origin origin pj_str_t name pjmedia_sdp_conn *conn pjmedia_sdp_session_time time unsigned int attr_count pjmedia_sdp_attr *attr[PJMEDIA_MAX_SDP_ATTR] unsigned int bandw_count pjmedia_sdp_bandw *bandw[PJMEDIA_MAX_SDP_BANDW] unsigned int media_count pjmedia_sdp_media *media[PJMEDIA_MAX_SDP_MEDIA] ctypedef pjmedia_sdp_session *pjmedia_sdp_session_ptr_const "const pjmedia_sdp_session *" pjmedia_sdp_media *pjmedia_sdp_media_clone(pj_pool_t *pool, pjmedia_sdp_media *rhs) nogil pjmedia_sdp_session *pjmedia_sdp_session_clone(pj_pool_t *pool, pjmedia_sdp_session_ptr_const sdp) nogil int pjmedia_sdp_print(pjmedia_sdp_session_ptr_const sdp, char *buf, int length) int pjmedia_sdp_parse(pj_pool_t *pool, char *buf, int length, pjmedia_sdp_session **p_sdp) nogil # sdp negotiation enum pjmedia_sdp_neg_state: PJMEDIA_SDP_NEG_STATE_NULL PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER PJMEDIA_SDP_NEG_STATE_WAIT_NEGO PJMEDIA_SDP_NEG_STATE_DONE struct pjmedia_sdp_neg int pjmedia_sdp_neg_create_w_local_offer(pj_pool_t *pool, pjmedia_sdp_session_ptr_const local, pjmedia_sdp_neg **neg) nogil int pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool, pjmedia_sdp_session_ptr_const initial, pjmedia_sdp_session_ptr_const remote, pjmedia_sdp_neg **neg) nogil int pjmedia_sdp_neg_set_local_answer(pj_pool_t *pool, pjmedia_sdp_neg *neg, pjmedia_sdp_session_ptr_const local) nogil int pjmedia_sdp_neg_set_remote_answer(pj_pool_t *pool, pjmedia_sdp_neg *neg, pjmedia_sdp_session_ptr_const remote) nogil int pjmedia_sdp_neg_set_remote_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, pjmedia_sdp_session_ptr_const remote) nogil int pjmedia_sdp_neg_get_neg_remote(pjmedia_sdp_neg *neg, pjmedia_sdp_session_ptr_const *remote) nogil int pjmedia_sdp_neg_get_neg_local(pjmedia_sdp_neg *neg, pjmedia_sdp_session_ptr_const *local) nogil int pjmedia_sdp_neg_get_active_remote(pjmedia_sdp_neg *neg, pjmedia_sdp_session_ptr_const *remote) nogil int pjmedia_sdp_neg_get_active_local(pjmedia_sdp_neg *neg, pjmedia_sdp_session_ptr_const *local) nogil int pjmedia_sdp_neg_modify_local_offer (pj_pool_t *pool, pjmedia_sdp_neg *neg, pjmedia_sdp_session_ptr_const local) nogil int pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg) nogil int pjmedia_sdp_neg_negotiate(pj_pool_t *poll, pjmedia_sdp_neg *neg, int allow_asym) nogil pjmedia_sdp_neg_state pjmedia_sdp_neg_get_state(pjmedia_sdp_neg *neg) nogil char *pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state) nogil # transport enum pjmedia_transport_type: PJMEDIA_TRANSPORT_TYPE_ICE PJMEDIA_TRANSPORT_TYPE_SRTP PJMEDIA_TRANSPORT_TYPE_ZRTP struct pjmedia_sock_info: pj_sockaddr rtp_addr_name ctypedef pjmedia_sock_info *pjmedia_sock_info_ptr_const "const pjmedia_sock_info *" struct pjmedia_transport: char *name pjmedia_transport_type type void *user_data struct pjmedia_transport_specific_info: pjmedia_transport_type type char *buffer struct pjmedia_transport_info: pjmedia_sock_info sock_info pj_sockaddr src_rtp_name int specific_info_cnt pjmedia_transport_specific_info *spc_info void pjmedia_transport_info_init(pjmedia_transport_info *info) nogil int pjmedia_transport_udp_create3(pjmedia_endpt *endpt, int af, char *name, pj_str_t *addr, int port, unsigned int options, pjmedia_transport **p_tp) nogil int pjmedia_transport_get_info(pjmedia_transport *tp, pjmedia_transport_info *info) nogil void *pjmedia_transport_info_get_spc_info(pjmedia_transport_info *info, pjmedia_transport_type type) int pjmedia_transport_close(pjmedia_transport *tp) nogil int pjmedia_transport_media_create(pjmedia_transport *tp, pj_pool_t *sdp_pool, unsigned int options, pjmedia_sdp_session *rem_sdp, unsigned int media_index) nogil int pjmedia_transport_encode_sdp(pjmedia_transport *tp, pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp, pjmedia_sdp_session *rem_sdp, unsigned int media_index) nogil int pjmedia_transport_media_start(pjmedia_transport *tp, pj_pool_t *tmp_pool, pjmedia_sdp_session *sdp_local, pjmedia_sdp_session *sdp_remote, unsigned int media_index) nogil int pjmedia_transport_media_stop(pjmedia_transport *tp) nogil int pjmedia_endpt_create_sdp(pjmedia_endpt *endpt, pj_pool_t *pool, unsigned int stream_cnt, pjmedia_sock_info *sock_info, pjmedia_sdp_session **p_sdp) nogil int pjmedia_endpt_create_base_sdp(pjmedia_endpt *endpt, pj_pool_t *pool, pj_str_ptr_const sess_name, pj_sockaddr_ptr_const origin, pjmedia_sdp_session **p_sdp) nogil int pjmedia_endpt_create_audio_sdp(pjmedia_endpt *endpt, pj_pool_t *pool, pjmedia_sock_info_ptr_const si, unsigned int options, pjmedia_sdp_media **p_media) nogil int pjmedia_endpt_create_video_sdp(pjmedia_endpt *endpt, pj_pool_t *pool, pjmedia_sock_info_ptr_const si, unsigned int options, pjmedia_sdp_media **p_media) nogil # SRTP struct pjmedia_srtp_crypto: pj_str_t key pj_str_t name unsigned int flags struct pjmedia_srtp_info: int active pjmedia_srtp_crypto tx_policy enum pjmedia_srtp_use: PJMEDIA_SRTP_MANDATORY struct pjmedia_srtp_setting: pjmedia_srtp_use use void pjmedia_srtp_setting_default(pjmedia_srtp_setting *opt) nogil int pjmedia_transport_srtp_create(pjmedia_endpt *endpt, pjmedia_transport *tp, pjmedia_srtp_setting *opt, pjmedia_transport **p_tp) nogil # ZRTP struct pjmedia_zrtp_info: int active char cipher[128] struct pjmedia_zrtp_cb: void secure_on(pjmedia_transport *tp, char* cipher) with gil void secure_off(pjmedia_transport *tp) with gil void show_sas(pjmedia_transport *tp, char* sas, int verified) with gil void confirm_go_clear(pjmedia_transport *tp) with gil void show_message(pjmedia_transport *tp, int severity, int subCode) with gil void negotiation_failed(pjmedia_transport *tp, int severity, int subCode) with gil void not_supported_by_other(pjmedia_transport *tp) with gil void ask_enrollment(pjmedia_transport *tp, int info) with gil void inform_enrollment(pjmedia_transport *tp, int info) with gil void sign_sas(pjmedia_transport *tp, unsigned char* sas) with gil int check_sas_signature(pjmedia_transport *tp, unsigned char* sas) with gil int pjmedia_transport_zrtp_create(pjmedia_endpt *endpt, pj_timer_heap_t *timer_heap, pjmedia_transport *tp, pjmedia_transport **p_tp, int close_slave) nogil int pjmedia_transport_zrtp_initialize(pjmedia_transport *tp, char_ptr_const zidFilename, int autoEnable, pjmedia_zrtp_cb *zrtp_cb) nogil void pjmedia_transport_zrtp_setSASVerified(pjmedia_transport *tp, int verified) nogil char* pjmedia_transport_zrtp_getPeerName(pjmedia_transport *tp) nogil void pjmedia_transport_zrtp_putPeerName(pjmedia_transport *tp, const char *name) nogil int pjmedia_transport_zrtp_getPeerZid(pjmedia_transport *tp, unsigned char* data) nogil void pjmedia_transport_zrtp_setEnableZrtp(pjmedia_transport *tp, int onOff) nogil char* pjmedia_transport_zrtp_getMultiStreamParameters(pjmedia_transport *tp, int *length) nogil void pjmedia_transport_zrtp_setMultiStreamParameters(pjmedia_transport *tp, const char *parameters, int length, pjmedia_transport *master_tp) nogil # ICE struct pjmedia_ice_cb: void on_ice_complete(pjmedia_transport *tp, pj_ice_strans_op op, int status) with gil void on_ice_state(pjmedia_transport *tp, pj_ice_strans_state prev, pj_ice_strans_state curr) with gil void on_ice_stop(pjmedia_transport *tp, char *reason, int err) with gil int pjmedia_ice_create2(pjmedia_endpt *endpt, char *name, unsigned int comp_cnt, pj_ice_strans_cfg *cfg, pjmedia_ice_cb *cb, unsigned int options, pjmedia_transport **p_tp) nogil pj_ice_strans *pjmedia_ice_get_strans(pjmedia_transport *tp) with gil # stream struct pjmedia_codec_param_setting: unsigned int vad struct pjmedia_codec_param: pjmedia_codec_param_setting setting struct pjmedia_stream_info: pjmedia_codec_info fmt pjmedia_codec_param *param unsigned int tx_event_pt int use_ka struct pjmedia_rtcp_stream_stat_loss_type: unsigned int burst unsigned int random struct pjmedia_rtcp_stream_stat: unsigned int pkt unsigned int bytes unsigned int discard unsigned int loss unsigned int reorder unsigned int dup pj_math_stat loss_period pjmedia_rtcp_stream_stat_loss_type loss_type pj_math_stat jitter struct pjmedia_rtcp_stat: pjmedia_rtcp_stream_stat tx pjmedia_rtcp_stream_stat rx pj_math_stat rtt struct pjmedia_stream int pjmedia_stream_info_from_sdp(pjmedia_stream_info *si, pj_pool_t *pool, pjmedia_endpt *endpt, pjmedia_sdp_session *local, pjmedia_sdp_session *remote, unsigned int stream_idx) nogil int pjmedia_stream_create(pjmedia_endpt *endpt, pj_pool_t *pool, pjmedia_stream_info *info, pjmedia_transport *tp, void *user_data, pjmedia_stream **p_stream) nogil int pjmedia_stream_destroy(pjmedia_stream *stream) nogil int pjmedia_stream_get_port(pjmedia_stream *stream, pjmedia_port **p_port) nogil int pjmedia_stream_start(pjmedia_stream *stream) nogil int pjmedia_stream_dial_dtmf(pjmedia_stream *stream, pj_str_t *ascii_digit) nogil int pjmedia_stream_set_dtmf_callback(pjmedia_stream *stream, void cb(pjmedia_stream *stream, void *user_data, int digit) with gil, void *user_data) nogil int pjmedia_stream_pause(pjmedia_stream *stream, pjmedia_dir dir) nogil int pjmedia_stream_resume(pjmedia_stream *stream, pjmedia_dir dir) nogil int pjmedia_stream_get_stat(pjmedia_stream *stream, pjmedia_rtcp_stat *stat) nogil # wav player enum: PJMEDIA_FILE_NO_LOOP int pjmedia_port_destroy(pjmedia_port *port) nogil int pjmedia_wav_player_port_create(pj_pool_t *pool, char *filename, unsigned int ptime, unsigned int flags, unsigned int buff_size, pjmedia_port **p_port) nogil int pjmedia_wav_player_set_eof_cb(pjmedia_port *port, void *user_data, int cb(pjmedia_port *port, void *usr_data) with gil) nogil int pjmedia_wav_player_port_set_pos(pjmedia_port *port, unsigned int offset) nogil # wav recorder enum pjmedia_file_writer_option: PJMEDIA_FILE_WRITE_PCM int pjmedia_wav_writer_port_create(pj_pool_t *pool, char *filename, unsigned int clock_rate, unsigned int channel_count, unsigned int samples_per_frame, unsigned int bits_per_sample, unsigned int flags, int buff_size, pjmedia_port **p_port) nogil # tone generator enum: PJMEDIA_TONEGEN_MAX_DIGITS struct pjmedia_tone_desc: short freq1 short freq2 short on_msec short off_msec short volume short flags struct pjmedia_tone_digit: char digit short on_msec short off_msec short volume int pjmedia_tonegen_create(pj_pool_t *pool, unsigned int clock_rate, unsigned int channel_count, unsigned int samples_per_frame, unsigned int bits_per_sample, unsigned int options, pjmedia_port **p_port) nogil int pjmedia_tonegen_play(pjmedia_port *tonegen, unsigned int count, pjmedia_tone_desc *tones, unsigned int options) nogil int pjmedia_tonegen_play_digits(pjmedia_port *tonegen, unsigned int count, pjmedia_tone_digit *digits, unsigned int options) nogil int pjmedia_tonegen_stop(pjmedia_port *tonegen) nogil int pjmedia_tonegen_is_busy(pjmedia_port *tonegen) nogil cdef extern from "pjmedia_videodev.h": ctypedef void (*pjmedia_vid_dev_fb_frame_cb)(pjmedia_frame_ptr_const frame, pjmedia_rect_size size, void *user_data) int pjmedia_vid_dev_fb_set_callback(pjmedia_vid_dev_stream *strm, pjmedia_vid_dev_fb_frame_cb cb, void *user_data) cdef extern from "pjmedia-codec.h": # codecs enum: PJMEDIA_SPEEX_NO_NB struct speex_options: unsigned int option int quality int complexity struct ilbc_options: unsigned mode struct pjmedia_audio_codec_config: speex_options speex ilbc_options ilbc void pjmedia_audio_codec_config_default(pjmedia_audio_codec_config *cfg) int pjmedia_codec_register_audio_codecs(pjmedia_endpt *endpt, const pjmedia_audio_codec_config *c) nogil int pjmedia_codec_ffmpeg_vid_init(pjmedia_vid_codec_mgr *mgr, pj_pool_factory *pf) nogil int pjmedia_codec_ffmpeg_vid_deinit() nogil int pjmedia_codec_vpx_init(pjmedia_vid_codec_mgr *mgr, pj_pool_factory *pf) nogil int pjmedia_codec_vpx_deinit() nogil cdef extern from "pjsip.h": # messages enum pjsip_status_code: PJSIP_SC_TSX_TIMEOUT PJSIP_SC_TSX_TRANSPORT_ERROR PJSIP_TLS_EUNKNOWN PJSIP_TLS_EINVMETHOD PJSIP_TLS_ECACERT PJSIP_TLS_ECERTFILE PJSIP_TLS_EKEYFILE PJSIP_TLS_ECIPHER PJSIP_TLS_ECTX enum pjsip_uri_context_e: PJSIP_URI_IN_CONTACT_HDR struct pjsip_param: pj_str_t name pj_str_t value struct pjsip_uri struct pjsip_sip_uri: pj_str_t user pj_str_t passwd pj_str_t host int port pj_str_t user_param pj_str_t method_param pj_str_t transport_param int ttl_param int lr_param pj_str_t maddr_param pjsip_param other_param pjsip_param header_param struct pjsip_name_addr: pj_str_t display pjsip_uri *uri struct pjsip_media_type: pj_str_t type pj_str_t subtype pjsip_param param enum pjsip_method_e: PJSIP_OPTIONS_METHOD PJSIP_CANCEL_METHOD PJSIP_OTHER_METHOD struct pjsip_method: pjsip_method_e id pj_str_t name struct pjsip_host_port: pj_str_t host int port enum pjsip_hdr_e: PJSIP_H_VIA PJSIP_H_CALL_ID PJSIP_H_CONTACT PJSIP_H_CSEQ PJSIP_H_EXPIRES PJSIP_H_FROM struct pjsip_hdr: pjsip_hdr_e type pj_str_t name ctypedef pjsip_hdr *pjsip_hdr_ptr_const "const pjsip_hdr*" struct pjsip_generic_array_hdr: unsigned int count pj_str_t *values struct pjsip_generic_string_hdr: pj_str_t name pj_str_t hvalue struct pjsip_cid_hdr: pj_str_t id struct pjsip_contact_hdr: int star pjsip_uri *uri int q1000 int expires pjsip_param other_param struct pjsip_clen_hdr: int len struct pjsip_ctype_hdr: pjsip_media_type media struct pjsip_cseq_hdr: int cseq pjsip_method method struct pjsip_generic_int_hdr: int ivalue ctypedef pjsip_generic_int_hdr pjsip_expires_hdr struct pjsip_fromto_hdr: pjsip_uri *uri pj_str_t tag pjsip_param other_param struct pjsip_routing_hdr: pjsip_name_addr name_addr pjsip_param other_param ctypedef pjsip_routing_hdr pjsip_route_hdr struct pjsip_retry_after_hdr: int ivalue pjsip_param param pj_str_t comment struct pjsip_via_hdr: pj_str_t transport pjsip_host_port sent_by int ttl_param int rport_param pj_str_t maddr_param pj_str_t recvd_param pj_str_t branch_param pjsip_param other_param pj_str_t comment enum: PJSIP_MAX_ACCEPT_COUNT struct pjsip_msg_body: pjsip_media_type content_type void *data unsigned int len struct pjsip_request_line: pjsip_method method pjsip_uri *uri struct pjsip_status_line: int code pj_str_t reason union pjsip_msg_line: pjsip_request_line req pjsip_status_line status enum pjsip_msg_type_e: PJSIP_REQUEST_MSG PJSIP_RESPONSE_MSG struct pjsip_msg: pjsip_msg_type_e type pjsip_msg_line line pjsip_hdr hdr pjsip_msg_body *body struct pjsip_buffer: char *start char *cur struct pjsip_tx_data_tp_info: char *dst_name int dst_port pjsip_transport *transport struct pjsip_tx_data: pjsip_msg *msg pj_pool_t *pool pjsip_buffer buf pjsip_tx_data_tp_info tp_info struct pjsip_rx_data_tp_info: pj_pool_t *pool pjsip_transport *transport struct pjsip_rx_data_pkt_info: pj_time_val timestamp char *packet int len char *src_name int src_port struct pjsip_rx_data_msg_info: pjsip_msg *msg pjsip_fromto_hdr *from_hdr "from" pjsip_fromto_hdr *to_hdr "to" pjsip_via_hdr *via struct pjsip_rx_data: pjsip_rx_data_pkt_info pkt_info pjsip_rx_data_tp_info tp_info pjsip_rx_data_msg_info msg_info void *pjsip_hdr_clone(pj_pool_t *pool, void *hdr) nogil void pjsip_msg_add_hdr(pjsip_msg *msg, pjsip_hdr *hdr) nogil void *pjsip_msg_find_hdr(pjsip_msg *msg, pjsip_hdr_e type, void *start) nogil void *pjsip_msg_find_hdr_by_name(pjsip_msg *msg, pj_str_t *name, void *start) nogil void *pjsip_msg_find_remove_hdr_by_name(pjsip_msg *msg, pj_str_t *name, void *start) nogil pjsip_generic_string_hdr *pjsip_generic_string_hdr_create(pj_pool_t *pool, pj_str_t *hname, pj_str_t *hvalue) nogil pjsip_contact_hdr *pjsip_contact_hdr_create(pj_pool_t *pool) nogil pjsip_expires_hdr *pjsip_expires_hdr_create(pj_pool_t *pool, int value) nogil pjsip_msg_body *pjsip_msg_body_create(pj_pool_t *pool, pj_str_t *type, pj_str_t *subtype, pj_str_t *text) nogil pjsip_msg_body *pjsip_msg_body_clone(pj_pool_t *pool, const pjsip_msg_body *body) nogil pjsip_route_hdr *pjsip_route_hdr_init(pj_pool_t *pool, void *mem) nogil void pjsip_sip_uri_init(pjsip_sip_uri *url, int secure) nogil int pjsip_tx_data_dec_ref(pjsip_tx_data *tdata) nogil void pjsip_tx_data_add_ref(pjsip_tx_data *tdata) nogil pj_str_t *pjsip_uri_get_scheme(pjsip_uri *uri) nogil void *pjsip_uri_get_uri(pjsip_uri *uri) nogil int pjsip_uri_print(pjsip_uri_context_e context, void *uri, char *buf, unsigned int size) nogil int PJSIP_URI_SCHEME_IS_SIP(pjsip_sip_uri *uri) nogil enum: PJSIP_PARSE_URI_AS_NAMEADDR pjsip_uri *pjsip_parse_uri(pj_pool_t *pool, char *buf, unsigned int size, unsigned int options) nogil void pjsip_method_init_np(pjsip_method *m, pj_str_t *str) nogil pj_str_t *pjsip_get_status_text(int status_code) nogil int pjsip_print_body(pjsip_msg_body *msg_body, char **buf, int *len) # module enum pjsip_module_priority: PJSIP_MOD_PRIORITY_APPLICATION PJSIP_MOD_PRIORITY_DIALOG_USAGE PJSIP_MOD_PRIORITY_TRANSPORT_LAYER struct pjsip_event struct pjsip_transaction struct pjsip_module: pj_str_t name int id int priority int on_rx_request(pjsip_rx_data *rdata) with gil int on_rx_response(pjsip_rx_data *rdata) with gil int on_tx_request(pjsip_tx_data *tdata) with gil int on_tx_response(pjsip_tx_data *tdata) with gil void on_tsx_state(pjsip_transaction *tsx, pjsip_event *event) with gil # endpoint struct pjsip_endpoint int pjsip_endpt_create(pj_pool_factory *pf, char *name, pjsip_endpoint **endpt) nogil void pjsip_endpt_destroy(pjsip_endpoint *endpt) nogil pj_pool_t *pjsip_endpt_create_pool(pjsip_endpoint *endpt, char *pool_name, int initial, int increment) nogil void pjsip_endpt_release_pool(pjsip_endpoint *endpt, pj_pool_t *pool) nogil int pjsip_endpt_handle_events(pjsip_endpoint *endpt, pj_time_val *max_timeout) nogil int pjsip_endpt_register_module(pjsip_endpoint *endpt, pjsip_module *module) nogil int pjsip_endpt_schedule_timer(pjsip_endpoint *endpt, pj_timer_entry *entry, pj_time_val *delay) nogil void pjsip_endpt_cancel_timer(pjsip_endpoint *endpt, pj_timer_entry *entry) nogil enum: PJSIP_H_ACCEPT PJSIP_H_ALLOW PJSIP_H_SUPPORTED pjsip_hdr_ptr_const pjsip_endpt_get_capability(pjsip_endpoint *endpt, int htype, pj_str_t *hname) nogil int pjsip_endpt_add_capability(pjsip_endpoint *endpt, pjsip_module *mod, int htype, pj_str_t *hname, unsigned count, pj_str_t *tags) nogil int pjsip_endpt_create_response(pjsip_endpoint *endpt, pjsip_rx_data *rdata, int st_code, pj_str_t *st_text, pjsip_tx_data **p_tdata) nogil int pjsip_endpt_send_response2(pjsip_endpoint *endpt, pjsip_rx_data *rdata, pjsip_tx_data *tdata, void *token, void *cb) nogil int pjsip_endpt_respond_stateless(pjsip_endpoint *endpt, pjsip_rx_data *rdata, int st_code, pj_str_t *st_text, pjsip_hdr *hdr_list, pjsip_msg_body *body) nogil int pjsip_endpt_create_request(pjsip_endpoint *endpt, pjsip_method *method, pj_str_t *target, pj_str_t *frm, pj_str_t *to, pj_str_t *contact, pj_str_t *call_id, int cseq,pj_str_t *text, pjsip_tx_data **p_tdata) nogil pj_timer_heap_t *pjsip_endpt_get_timer_heap(pjsip_endpoint *endpt) nogil int pjsip_endpt_create_resolver(pjsip_endpoint *endpt, pj_dns_resolver **p_resv) nogil int pjsip_endpt_set_resolver(pjsip_endpoint *endpt, pj_dns_resolver *resv) nogil pj_dns_resolver* pjsip_endpt_get_resolver(pjsip_endpoint *endpt) nogil # transports enum pjsip_ssl_method: PJSIP_TLSV1_METHOD PJSIP_SSLV23_METHOD struct pjsip_transport: char *type_name pj_sockaddr local_addr pjsip_host_port local_name pjsip_host_port remote_name struct pjsip_tpfactory: pjsip_host_port addr_name int destroy(pjsip_tpfactory *factory) nogil struct pjsip_tls_setting: pj_str_t ca_list_file pj_str_t cert_file pj_str_t privkey_file int method int verify_server pj_time_val timeout enum pjsip_tpselector_type: PJSIP_TPSELECTOR_TRANSPORT union pjsip_tpselector_u: pjsip_transport *transport struct pjsip_tpselector: pjsip_tpselector_type type pjsip_tpselector_u u int pjsip_transport_shutdown(pjsip_transport *tp) nogil int pjsip_udp_transport_start(pjsip_endpoint *endpt, pj_sockaddr_in *local, pjsip_host_port *a_name, unsigned int async_cnt, pjsip_transport **p_transport) nogil int pjsip_tcp_transport_start2(pjsip_endpoint *endpt, pj_sockaddr_in *local, pjsip_host_port *a_name, unsigned int async_cnt, pjsip_tpfactory **p_tpfactory) nogil int pjsip_tls_transport_start(pjsip_endpoint *endpt, pjsip_tls_setting *opt, pj_sockaddr_in *local, pjsip_host_port *a_name, unsigned async_cnt, pjsip_tpfactory **p_factory) nogil void pjsip_tls_setting_default(pjsip_tls_setting *tls_opt) nogil int pjsip_transport_shutdown(pjsip_transport *tp) nogil # transaction layer enum pjsip_role_e: PJSIP_ROLE_UAC PJSIP_ROLE_UAS enum pjsip_tsx_state_e: PJSIP_TSX_STATE_TRYING PJSIP_TSX_STATE_PROCEEDING PJSIP_TSX_STATE_COMPLETED PJSIP_TSX_STATE_TERMINATED struct pjsip_transaction: int status_code pj_str_t status_text pjsip_role_e role pjsip_tx_data *last_tx pjsip_tsx_state_e state void **mod_data pjsip_method method int pjsip_tsx_layer_init_module(pjsip_endpoint *endpt) nogil int pjsip_tsx_create_key(pj_pool_t *pool, pj_str_t *key, pjsip_role_e role, pjsip_method *method, pjsip_rx_data *rdata) nogil pjsip_transaction *pjsip_tsx_layer_find_tsx(pj_str_t *key, int lock) nogil int pjsip_tsx_create_uac(pjsip_module *tsx_user, pjsip_tx_data *tdata, pjsip_transaction **p_tsx) nogil int pjsip_tsx_terminate(pjsip_transaction *tsx, int code) nogil int pjsip_tsx_send_msg(pjsip_transaction *tsx, pjsip_tx_data *tdata) nogil pjsip_transaction *pjsip_rdata_get_tsx(pjsip_rx_data *rdata) nogil int pjsip_tsx_create_uas(pjsip_module *tsx_user, pjsip_rx_data *rdata, pjsip_transaction **p_tsx) nogil void pjsip_tsx_recv_msg(pjsip_transaction *tsx, pjsip_rx_data *rdata) nogil # event enum pjsip_event_id_e: PJSIP_EVENT_TSX_STATE PJSIP_EVENT_RX_MSG PJSIP_EVENT_TX_MSG PJSIP_EVENT_TRANSPORT_ERROR PJSIP_EVENT_TIMER union pjsip_event_body_tsx_state_src: pjsip_rx_data *rdata pjsip_tx_data *tdata struct pjsip_event_body_tsx_state: pjsip_event_body_tsx_state_src src pjsip_transaction *tsx pjsip_event_id_e type struct pjsip_event_body_rx_msg: pjsip_rx_data *rdata union pjsip_event_body: pjsip_event_body_tsx_state tsx_state pjsip_event_body_rx_msg rx_msg struct pjsip_event: pjsip_event_id_e type pjsip_event_body body int pjsip_endpt_send_request(pjsip_endpoint *endpt, pjsip_tx_data *tdata, int timeout, void *token, void cb(void *token, pjsip_event *e) with gil) nogil # auth enum: PJSIP_EFAILEDCREDENTIAL enum pjsip_cred_data_type: PJSIP_CRED_DATA_PLAIN_PASSWD PJSIP_CRED_DATA_DIGEST struct pjsip_cred_info: pj_str_t realm pj_str_t scheme pj_str_t username pjsip_cred_data_type data_type pj_str_t data struct pjsip_auth_clt_sess: pass int pjsip_auth_clt_init(pjsip_auth_clt_sess *sess, pjsip_endpoint *endpt, pj_pool_t *pool, unsigned int options) nogil int pjsip_auth_clt_set_credentials(pjsip_auth_clt_sess *sess, int cred_cnt, pjsip_cred_info *c) nogil int pjsip_auth_clt_reinit_req(pjsip_auth_clt_sess *sess, pjsip_rx_data *rdata, pjsip_tx_data *old_request, pjsip_tx_data **new_request) nogil # dialog layer ctypedef pjsip_module pjsip_user_agent struct pjsip_dlg_party: pjsip_contact_hdr *contact pjsip_fromto_hdr *info struct pjsip_dialog: pjsip_auth_clt_sess auth_sess pjsip_cid_hdr *call_id pj_pool_t *pool pjsip_dlg_party local pjsip_dlg_party remote struct pjsip_ua_init_param: pjsip_dialog *on_dlg_forked(pjsip_dialog *first_set, pjsip_rx_data *res) nogil int pjsip_ua_init_module(pjsip_endpoint *endpt, pjsip_ua_init_param *prm) nogil pjsip_user_agent *pjsip_ua_instance() nogil int pjsip_dlg_create_uac(pjsip_user_agent *ua, pj_str_t *local_uri, pj_str_t *local_contact, pj_str_t *remote_uri, pj_str_t *target, pjsip_dialog **p_dlg) nogil int pjsip_dlg_set_route_set(pjsip_dialog *dlg, pjsip_route_hdr *route_set) nogil int pjsip_dlg_create_uas_and_inc_lock(pjsip_user_agent *ua, pjsip_rx_data *rdata, pj_str_t *contact, pjsip_dialog **p_dlg) nogil int pjsip_dlg_terminate(pjsip_dialog *dlg) nogil int pjsip_dlg_set_transport(pjsip_dialog *dlg, pjsip_tpselector *sel) nogil int pjsip_dlg_respond(pjsip_dialog *dlg, pjsip_rx_data *rdata, int st_code, pj_str_t *st_text, pjsip_hdr *hdr_list, pjsip_msg_body *body) nogil int pjsip_dlg_create_response(pjsip_dialog *dlg, pjsip_rx_data *rdata, int st_code, pj_str_t *st_text, pjsip_tx_data **tdata) nogil int pjsip_dlg_modify_response(pjsip_dialog *dlg, pjsip_tx_data *tdata, int st_code, pj_str_t *st_text) nogil int pjsip_dlg_send_response(pjsip_dialog *dlg, pjsip_transaction *tsx, pjsip_tx_data *tdata) nogil void pjsip_dlg_inc_lock(pjsip_dialog *dlg) nogil void pjsip_dlg_dec_lock(pjsip_dialog *dlg) nogil int pjsip_dlg_inc_session(pjsip_dialog *dlg, pjsip_module *mod) nogil int pjsip_dlg_dec_session(pjsip_dialog *dlg, pjsip_module *mod) nogil cdef extern from "pjsip-simple/evsub_msg.h": struct pjsip_event_hdr: pj_str_t event_type pj_str_t id_param pjsip_param other_param struct pjsip_sub_state_hdr: pj_str_t sub_state pj_str_t reason_param int expires_param int retry_after pjsip_param other_param pjsip_event_hdr *pjsip_event_hdr_create(pj_pool_t *pool) nogil cdef extern from "pjsip_simple.h": # subscribe / notify enum: PJSIP_EVSUB_NO_EVENT_ID enum pjsip_evsub_state: PJSIP_EVSUB_STATE_PENDING PJSIP_EVSUB_STATE_ACTIVE PJSIP_EVSUB_STATE_TERMINATED struct pjsip_evsub struct pjsip_evsub_user: void on_evsub_state(pjsip_evsub *sub, pjsip_event *event) with gil void on_tsx_state(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) with gil void on_rx_refresh(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil void on_rx_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text,pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil void on_client_refresh(pjsip_evsub *sub) with gil void on_server_timeout(pjsip_evsub *sub) with gil int pjsip_evsub_init_module(pjsip_endpoint *endpt) nogil int pjsip_evsub_register_pkg(pjsip_module *pkg_mod, pj_str_t *event_name, unsigned int expires, unsigned int accept_cnt, pj_str_t *accept) nogil int pjsip_evsub_create_uac(pjsip_dialog *dlg, pjsip_evsub_user *user_cb, pj_str_t *event, int option, pjsip_evsub **p_evsub) nogil int pjsip_evsub_create_uas(pjsip_dialog *dlg, pjsip_evsub_user *user_cb, pjsip_rx_data *rdata, unsigned int option, pjsip_evsub **p_evsub) nogil int pjsip_evsub_initiate(pjsip_evsub *sub, void *method, unsigned int expires, pjsip_tx_data **p_tdata) nogil int pjsip_evsub_send_request(pjsip_evsub *sub, pjsip_tx_data *tdata) nogil int pjsip_evsub_terminate(pjsip_evsub *sub, int notify) nogil char *pjsip_evsub_get_state_name(pjsip_evsub *sub) nogil void pjsip_evsub_set_mod_data(pjsip_evsub *sub, int mod_id, void *data) nogil void *pjsip_evsub_get_mod_data(pjsip_evsub *sub, int mod_id) nogil void pjsip_evsub_update_expires(pjsip_evsub *sub, int interval) nogil void pjsip_evsub_set_timer(pjsip_evsub *sub, int timer_id, int seconds) nogil pjsip_hdr *pjsip_evsub_get_allow_events_hdr(pjsip_module *m) nogil int pjsip_evsub_notify(pjsip_evsub *sub, pjsip_evsub_state state, pj_str_t *state_str, pj_str_t *reason, pjsip_tx_data **p_tdata) nogil cdef extern from "pjsip_ua.h": # 100rel / PRACK int pjsip_100rel_init_module(pjsip_endpoint *endpt) nogil # invite sessions enum pjsip_inv_option: PJSIP_INV_SUPPORT_100REL enum pjsip_inv_state: PJSIP_INV_STATE_INCOMING PJSIP_INV_STATE_CONFIRMED enum pjmedia_mod_offer_flag: PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE struct pjsip_inv_session: pjsip_inv_state state void **mod_data pjmedia_sdp_neg *neg unsigned sdp_neg_flags int cause pj_str_t cause_text int cancelling pjsip_transaction *invite_tsx struct pjsip_inv_callback: void on_state_changed(pjsip_inv_session *inv, pjsip_event *e) with gil void on_new_session(pjsip_inv_session *inv, pjsip_event *e) with gil void on_tsx_state_changed(pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e) with gil void on_media_update(pjsip_inv_session *inv, int status) with gil int on_rx_reinvite(pjsip_inv_session *inv, pjmedia_sdp_session_ptr_const offer, pjsip_rx_data *rdata) with gil int pjsip_inv_usage_init(pjsip_endpoint *endpt, pjsip_inv_callback *cb) nogil int pjsip_inv_terminate(pjsip_inv_session *inv, int st_code, int notify) nogil int pjsip_inv_end_session(pjsip_inv_session *inv, int st_code, pj_str_t *st_text, pjsip_tx_data **p_tdata) nogil int pjsip_inv_cancel_reinvite(pjsip_inv_session *inv, pjsip_tx_data **p_tdata) nogil int pjsip_inv_send_msg(pjsip_inv_session *inv, pjsip_tx_data *tdata) nogil int pjsip_inv_verify_request(pjsip_rx_data *rdata, unsigned int *options, pjmedia_sdp_session *sdp, pjsip_dialog *dlg, pjsip_endpoint *endpt, pjsip_tx_data **tdata) nogil int pjsip_inv_create_uas(pjsip_dialog *dlg, pjsip_rx_data *rdata, pjmedia_sdp_session *local_sdp, unsigned int options, pjsip_inv_session **p_inv) nogil int pjsip_inv_initial_answer(pjsip_inv_session *inv, pjsip_rx_data *rdata, int st_code, pj_str_t *st_text, pjmedia_sdp_session *sdp, pjsip_tx_data **p_tdata) nogil int pjsip_inv_answer(pjsip_inv_session *inv, int st_code, pj_str_t *st_text, pjmedia_sdp_session *local_sdp, pjsip_tx_data **p_tdata) nogil int pjsip_inv_create_uac(pjsip_dialog *dlg, pjmedia_sdp_session *local_sdp, unsigned int options, pjsip_inv_session **p_inv) nogil int pjsip_inv_invite(pjsip_inv_session *inv, pjsip_tx_data **p_tdata) nogil char *pjsip_inv_state_name(pjsip_inv_state state) nogil int pjsip_inv_reinvite(pjsip_inv_session *inv, pj_str_t *new_contact, pjmedia_sdp_session *new_offer, pjsip_tx_data **p_tdata) nogil int pjsip_create_sdp_body(pj_pool_t *pool, pjmedia_sdp_session *sdp, pjsip_msg_body **p_body) nogil # Replaces struct pjsip_replaces_hdr: pj_str_t call_id pj_str_t to_tag pj_str_t from_tag int early_only pjsip_param other_param pjsip_replaces_hdr *pjsip_replaces_hdr_create(pj_pool_t *pool) nogil int pjsip_replaces_verify_request(pjsip_rx_data *rdata, pjsip_dialog **p_dlg, int lock_dlg, pjsip_tx_data **p_tdata) nogil int pjsip_replaces_init_module(pjsip_endpoint *endpt) nogil # declarations # core.util cdef class frozenlist(object): # attributes cdef int initialized cdef list list cdef long hash cdef class frozendict(object): # attributes cdef int initialized cdef dict dict cdef long hash cdef class PJSTR(object): # attributes cdef pj_str_t pj_str cdef object str # core.lib cdef class PJLIB(object): # attributes cdef int _init_done cdef class PJCachingPool(object): # attributes cdef pj_caching_pool _obj cdef int _init_done cdef class PJSIPEndpoint(object): # attributes cdef pjsip_endpoint *_obj cdef pj_pool_t *_pool cdef pjsip_transport *_udp_transport cdef pjsip_tpfactory *_tcp_transport cdef pjsip_tpfactory *_tls_transport cdef int _tls_verify_server cdef PJSTR _tls_ca_file cdef PJSTR _tls_cert_file cdef PJSTR _tls_privkey_file cdef object _local_ip_used cdef int _tls_timeout # private methods cdef int _make_local_addr(self, pj_sockaddr_in *local_addr, object ip_address, int port) except -1 cdef int _start_udp_transport(self, int port) except -1 cdef int _stop_udp_transport(self) except -1 cdef int _start_tcp_transport(self, int port) except -1 cdef int _stop_tcp_transport(self) except -1 cdef int _start_tls_transport(self, port) except -1 cdef int _stop_tls_transport(self) except -1 cdef int _set_dns_nameservers(self, list servers) except -1 cdef class PJMEDIAEndpoint(object): # attributes cdef pjmedia_endpt *_obj cdef pj_pool_t *_pool cdef int _has_audio_codecs cdef int _has_video cdef int _has_ffmpeg_video cdef int _has_vpx # private methods cdef list _get_codecs(self) cdef list _get_all_codecs(self) cdef list _get_current_codecs(self) cdef int _set_codecs(self, list req_codecs) except -1 cdef list _get_video_codecs(self) cdef list _get_all_video_codecs(self) cdef list _get_current_video_codecs(self) cdef int _set_video_codecs(self, list req_codecs) except -1 cdef void _audio_subsystem_init(self, PJCachingPool caching_pool) cdef void _audio_subsystem_shutdown(self) cdef void _video_subsystem_init(self, PJCachingPool caching_pool) cdef void _video_subsystem_shutdown(self) cdef void _set_h264_options(self, str profile, int level) cdef void _set_video_options(self, tuple max_resolution, int max_framerate, float max_bitrate) # core.helper cdef class BaseCredentials(object): # attributes cdef pjsip_cred_info _credentials # private methods cdef pjsip_cred_info* get_cred_info(self) cdef class Credentials(BaseCredentials): # attributes cdef str _username cdef str _realm cdef str _password cdef bint _digest cdef class FrozenCredentials(BaseCredentials): # attributes cdef int initialized cdef readonly str username cdef readonly str realm cdef readonly str password cdef readonly bint digest cdef class BaseSIPURI(object): pass cdef class SIPURI(BaseSIPURI): # attributes cdef public object user cdef public object password cdef public object host cdef public bint secure cdef public dict parameters cdef public dict headers cdef object _port cdef class FrozenSIPURI(BaseSIPURI): # attributes cdef int initialized cdef readonly object user cdef readonly object password cdef readonly object host cdef readonly object port cdef readonly bint secure cdef readonly frozendict parameters cdef readonly frozendict headers cdef SIPURI SIPURI_create(pjsip_sip_uri *base_uri) cdef FrozenSIPURI FrozenSIPURI_create(pjsip_sip_uri *base_uri) # core.headers cdef class BaseHeader(object): pass cdef class Header(BaseHeader): # attributes cdef str _name cdef str _body cdef class FrozenHeader(BaseHeader): # attributes cdef readonly str name cdef readonly str body cdef class BaseContactHeader(object): pass cdef class ContactHeader(BaseContactHeader): # attributes cdef SIPURI _uri cdef unicode _display_name cdef dict _parameters cdef class FrozenContactHeader(BaseContactHeader): # attributes cdef int initialized cdef readonly FrozenSIPURI uri cdef readonly unicode display_name cdef readonly frozendict parameters cdef class BaseContentTypeHeader(object): pass cdef class ContentTypeHeader(BaseContentTypeHeader): # attributes cdef str _content_type cdef dict _parameters cdef class FrozenContentTypeHeader(BaseContentTypeHeader): # attributes cdef int initialized cdef readonly str _content_type cdef readonly frozendict parameters cdef class BaseIdentityHeader(object): pass cdef class IdentityHeader(BaseIdentityHeader): # attributes cdef SIPURI _uri cdef public unicode display_name cdef dict _parameters cdef class FrozenIdentityHeader(BaseIdentityHeader): # attributes cdef int initialized cdef readonly FrozenSIPURI uri cdef readonly unicode display_name cdef readonly frozendict parameters cdef class FromHeader(IdentityHeader): pass cdef class FrozenFromHeader(FrozenIdentityHeader): pass cdef class ToHeader(IdentityHeader): pass cdef class FrozenToHeader(FrozenIdentityHeader): pass cdef class RouteHeader(IdentityHeader): pass cdef class FrozenRouteHeader(FrozenIdentityHeader): pass cdef class RecordRouteHeader(IdentityHeader): pass cdef class FrozenRecordRouteHeader(FrozenIdentityHeader): pass cdef class BaseRetryAfterHeader(object): pass cdef class RetryAfterHeader(BaseRetryAfterHeader): # attributes cdef public int seconds cdef public str comment cdef dict _parameters cdef class FrozenRetryAfterHeader(BaseRetryAfterHeader): # attributes cdef int initialized cdef readonly int seconds cdef readonly str comment cdef readonly frozendict parameters cdef class BaseViaHeader(object): pass cdef class ViaHeader(BaseViaHeader): # attributes cdef str _transport cdef str _host cdef int _port cdef dict _parameters cdef class FrozenViaHeader(BaseViaHeader): # attributes cdef int initialized cdef readonly str transport cdef readonly str host cdef readonly int port cdef readonly frozendict parameters cdef class BaseWarningHeader(object): pass cdef class WarningHeader(BaseWarningHeader): # attributes cdef int _code cdef str _agent cdef str _text cdef class FrozenWarningHeader(BaseWarningHeader): # attributes cdef int initialized cdef readonly int code cdef readonly str agent cdef readonly str text cdef class BaseEventHeader(object): pass cdef class EventHeader(BaseEventHeader): # attributes cdef public event cdef dict _parameters cdef class FrozenEventHeader(BaseEventHeader): # attributes cdef int initialized cdef readonly str event cdef readonly frozendict parameters cdef class BaseSubscriptionStateHeader(object): pass cdef class SubscriptionStateHeader(BaseSubscriptionStateHeader): # attributes cdef public state cdef dict _parameters cdef class FrozenSubscriptionStateHeader(BaseSubscriptionStateHeader): # attributes cdef int initialized cdef readonly str state cdef readonly frozendict parameters cdef class BaseReasonHeader(object): pass cdef class ReasonHeader(BaseReasonHeader): # attributes cdef public str protocol cdef public dict parameters cdef class FrozenReasonHeader(BaseReasonHeader): # attributes cdef int initialized cdef readonly str protocol cdef readonly frozendict parameters cdef class BaseReferToHeader(object): pass cdef class ReferToHeader(BaseReferToHeader): # attributes cdef public str uri cdef dict _parameters cdef class FrozenReferToHeader(BaseReferToHeader): # attributes cdef int initialized cdef readonly str uri cdef readonly frozendict parameters cdef class BaseSubjectHeader(object): pass cdef class SubjectHeader(BaseSubjectHeader): # attributes cdef public unicode subject cdef class FrozenSubjectHeader(BaseSubjectHeader): # attributes cdef int initialized cdef readonly unicode subject cdef class BaseReplacesHeader(object): pass cdef class ReplacesHeader(BaseReplacesHeader): # attributes cdef public str call_id cdef public str from_tag cdef public str to_tag cdef public int early_only cdef dict _parameters cdef class FrozenReplacesHeader(BaseReplacesHeader): # attributes cdef int initialized cdef readonly str call_id cdef readonly str from_tag cdef readonly str to_tag cdef readonly int early_only cdef readonly frozendict parameters cdef Header Header_create(pjsip_generic_string_hdr *header) cdef FrozenHeader FrozenHeader_create(pjsip_generic_string_hdr *header) cdef ContactHeader ContactHeader_create(pjsip_contact_hdr *header) cdef FrozenContactHeader FrozenContactHeader_create(pjsip_contact_hdr *header) cdef ContentTypeHeader ContentTypeHeader_create(pjsip_ctype_hdr *header) cdef FrozenContentTypeHeader FrozenContentTypeHeader_create(pjsip_ctype_hdr *header) cdef FromHeader FromHeader_create(pjsip_fromto_hdr *header) cdef FrozenFromHeader FrozenFromHeader_create(pjsip_fromto_hdr *header) cdef ToHeader ToHeader_create(pjsip_fromto_hdr *header) cdef FrozenToHeader FrozenToHeader_create(pjsip_fromto_hdr *header) cdef RouteHeader RouteHeader_create(pjsip_routing_hdr *header) cdef FrozenRouteHeader FrozenRouteHeader_create(pjsip_routing_hdr *header) cdef RecordRouteHeader RecordRouteHeader_create(pjsip_routing_hdr *header) cdef FrozenRecordRouteHeader FrozenRecordRouteHeader_create(pjsip_routing_hdr *header) cdef RetryAfterHeader RetryAfterHeader_create(pjsip_retry_after_hdr *header) cdef FrozenRetryAfterHeader FrozenRetryAfterHeader_create(pjsip_retry_after_hdr *header) cdef ViaHeader ViaHeader_create(pjsip_via_hdr *header) cdef FrozenViaHeader FrozenViaHeader_create(pjsip_via_hdr *header) cdef EventHeader EventHeader_create(pjsip_event_hdr *header) cdef FrozenEventHeader FrozenEventHeader_create(pjsip_event_hdr *header) cdef SubscriptionStateHeader SubscriptionStateHeader_create(pjsip_sub_state_hdr *header) cdef FrozenSubscriptionStateHeader FrozenSubscriptionStateHeader_create(pjsip_sub_state_hdr *header) cdef ReferToHeader ReferToHeader_create(pjsip_generic_string_hdr *header) cdef FrozenReferToHeader FrozenReferToHeader_create(pjsip_generic_string_hdr *header) cdef SubjectHeader SubjectHeader_create(pjsip_generic_string_hdr *header) cdef FrozenSubjectHeader FrozenSubjectHeader_create(pjsip_generic_string_hdr *header) cdef ReplacesHeader ReplacesHeader_create(pjsip_replaces_hdr *header) cdef FrozenReplacesHeader FrozenReplacesHeader_create(pjsip_replaces_hdr *header) # core.util cdef int _str_to_pj_str(object string, pj_str_t *pj_str) except -1 +cdef object _pj_str_to_bytes(pj_str_t pj_bytes) cdef object _pj_str_to_str(pj_str_t pj_str) cdef object _pj_status_to_str(int status) cdef object _pj_status_to_def(int status) cdef dict _pjsip_param_to_dict(pjsip_param *param_list) cdef int _dict_to_pjsip_param(object params, pjsip_param *param_list, pj_pool_t *pool) cdef int _pjsip_msg_to_dict(pjsip_msg *msg, dict info_dict) except -1 cdef int _is_valid_ip(int af, object ip) except -1 cdef int _get_ip_version(object ip) except -1 cdef int _add_headers_to_tdata(pjsip_tx_data *tdata, object headers) except -1 cdef int _remove_headers_from_tdata(pjsip_tx_data *tdata, object headers) except -1 cdef int _BaseSIPURI_to_pjsip_sip_uri(BaseSIPURI uri, pjsip_sip_uri *pj_uri, pj_pool_t *pool) except -1 cdef int _BaseRouteHeader_to_pjsip_route_hdr(BaseIdentityHeader header, pjsip_route_hdr *pj_header, pj_pool_t *pool) except -1 # core.ua ctypedef int (*timer_callback)(object, object) except -1 cdef class Timer(object): # attributes cdef int _scheduled cdef double schedule_time cdef timer_callback callback cdef object obj # private methods cdef int schedule(self, float delay, timer_callback callback, object obj) except -1 cdef int cancel(self) except -1 cdef int call(self) except -1 cdef class PJSIPThread(object): # attributes cdef pj_thread_t *_obj cdef long _thread_desc[PJ_THREAD_DESC_SIZE] cdef class PJSIPUA(object): # attributes cdef object _threads cdef object _event_handler cdef list _timers cdef PJLIB _pjlib cdef PJCachingPool _caching_pool cdef PJSIPEndpoint _pjsip_endpoint cdef PJMEDIAEndpoint _pjmedia_endpoint cdef pjsip_module _module cdef PJSTR _module_name cdef pjsip_module _opus_fix_module cdef PJSTR _opus_fix_module_name cdef pjsip_module _trace_module cdef PJSTR _trace_module_name cdef pjsip_module _ua_tag_module cdef PJSTR _ua_tag_module_name cdef pjsip_module _event_module cdef PJSTR _event_module_name cdef int _trace_sip cdef int _detect_sip_loops cdef int _enable_colorbar_device cdef PJSTR _user_agent cdef object _events cdef object _sent_messages cdef object _ip_address cdef int _rtp_port_start cdef int _rtp_port_count cdef int _rtp_port_usable_count cdef int _rtp_port_index cdef pj_stun_config _stun_cfg cdef int _fatal_error cdef set _incoming_events cdef set _incoming_requests cdef pj_rwmutex_t *audio_change_rwlock cdef pj_mutex_t *video_lock cdef list old_devices cdef list old_video_devices cdef object _zrtp_cache # private methods cdef object _get_sound_devices(self, int is_output) cdef object _get_default_sound_device(self, int is_output) cdef object _get_video_devices(self) cdef object _get_default_video_device(self) cdef int _poll_log(self) except -1 cdef int _handle_exception(self, int is_fatal) except -1 cdef int _check_self(self) except -1 cdef int _check_thread(self) except -1 cdef int _add_timer(self, Timer timer) except -1 cdef int _remove_timer(self, Timer timer) except -1 cdef int _cb_rx_request(self, pjsip_rx_data *rdata) except 0 cdef pj_pool_t* create_memory_pool(self, bytes name, int initial_size, int resize_size) cdef void release_memory_pool(self, pj_pool_t* pool) cdef void reset_memory_pool(self, pj_pool_t* pool) cdef int _PJSIPUA_cb_rx_request(pjsip_rx_data *rdata) with gil cdef void _cb_detect_nat_type(void *user_data, pj_stun_nat_detect_result_ptr_const res) with gil cdef int _cb_opus_fix_tx(pjsip_tx_data *tdata) with gil cdef int _cb_trace_rx(pjsip_rx_data *rdata) with gil cdef int _cb_trace_tx(pjsip_tx_data *tdata) with gil cdef int _cb_add_user_agent_hdr(pjsip_tx_data *tdata) with gil cdef int _cb_add_server_hdr(pjsip_tx_data *tdata) with gil cdef PJSIPUA _get_ua() cdef int deallocate_weakref(object weak_ref, object timer) except -1 with gil # core.sound cdef class AudioMixer(object): # attributes cdef int _input_volume cdef int _output_volume cdef bint _muted cdef pj_mutex_t *_lock cdef pj_pool_t *_conf_pool cdef pj_pool_t *_snd_pool cdef pjmedia_conf *_obj cdef pjmedia_master_port *_master_port cdef pjmedia_port *_null_port cdef pjmedia_snd_port *_snd cdef list _connected_slots cdef readonly int ec_tail_length cdef readonly int sample_rate cdef readonly int slot_count cdef readonly int used_slot_count cdef readonly unicode input_device cdef readonly unicode output_device cdef readonly unicode real_input_device cdef readonly unicode real_output_device # private methods cdef void _start_sound_device(self, PJSIPUA ua, unicode input_device, unicode output_device, int ec_tail_length) cdef void _stop_sound_device(self, PJSIPUA ua) cdef int _add_port(self, PJSIPUA ua, pj_pool_t *pool, pjmedia_port *port) except -1 with gil cdef int _remove_port(self, PJSIPUA ua, unsigned int slot) except -1 with gil cdef int _cb_postpoll_stop_sound(self, timer) except -1 cdef class ToneGenerator(object): # attributes cdef int _slot cdef int _volume cdef pj_mutex_t *_lock cdef pj_pool_t *_pool cdef pjmedia_port *_obj cdef Timer _timer cdef readonly AudioMixer mixer # private methods cdef PJSIPUA _get_ua(self, int raise_exception) cdef int _stop(self, PJSIPUA ua) except -1 cdef int _cb_check_done(self, timer) except -1 cdef class RecordingWaveFile(object): # attributes cdef int _slot cdef int _was_started cdef pj_mutex_t *_lock cdef pj_pool_t *_pool cdef pjmedia_port *_port cdef readonly str filename cdef readonly AudioMixer mixer # private methods cdef PJSIPUA _check_ua(self) cdef int _stop(self, PJSIPUA ua) except -1 cdef class WaveFile(object): # attributes cdef object __weakref__ cdef object weakref cdef int _slot cdef int _volume cdef pj_mutex_t *_lock cdef pj_pool_t *_pool cdef pjmedia_port *_port cdef readonly str filename cdef readonly AudioMixer mixer # private methods cdef PJSIPUA _check_ua(self) cdef int _stop(self, PJSIPUA ua, int notify) except -1 cdef int _cb_eof(self, timer) except -1 cdef class MixerPort(object): cdef int _slot cdef int _was_started cdef pj_mutex_t *_lock cdef pj_pool_t *_pool cdef pjmedia_port *_port cdef readonly AudioMixer mixer # private methods cdef PJSIPUA _check_ua(self) cdef int _stop(self, PJSIPUA ua) except -1 cdef int _AudioMixer_dealloc_handler(object obj) except -1 cdef int cb_play_wav_eof(pjmedia_port *port, void *user_data) with gil # core.video cdef class VideoFrame(object): cdef readonly str data cdef readonly int width cdef readonly int height cdef class VideoConsumer(object): cdef pjmedia_port *consumer_port cdef pjmedia_vid_port *_video_port cdef pj_pool_t *_pool cdef pj_mutex_t *_lock cdef int _running cdef int _closed cdef VideoProducer _producer cdef object __weakref__ cdef object weakref cdef void _set_producer(self, VideoProducer producer) cdef class VideoProducer(object): cdef pjmedia_port *producer_port cdef pjmedia_vid_port *_video_port cdef pj_pool_t *_pool cdef pj_mutex_t *_lock cdef int _running cdef int _started cdef int _closed cdef object _consumers cdef void _add_consumer(self, VideoConsumer consumer) cdef void _remove_consumer(self, VideoConsumer consumer) cdef class VideoCamera(VideoProducer): cdef pjmedia_port *_video_tee cdef readonly unicode name cdef readonly unicode real_name cdef void _start(self) cdef void _stop(self) cdef class FrameBufferVideoRenderer(VideoConsumer): cdef pjmedia_vid_dev_stream *_video_stream cdef object _frame_handler cdef _initialize(self, VideoProducer producer) cdef void _destroy_video_port(self) cdef void _start(self) cdef void _stop(self) cdef class LocalVideoStream(VideoConsumer): cdef void _initialize(self, pjmedia_port *media_port) cdef class RemoteVideoStream(VideoProducer): cdef pjmedia_vid_stream *_video_stream cdef object _event_handler cdef void _initialize(self, pjmedia_vid_stream *stream) cdef LocalVideoStream_create(pjmedia_vid_stream *stream) cdef RemoteVideoStream_create(pjmedia_vid_stream *stream, format_change_handler=*) cdef int RemoteVideoStream_on_event(pjmedia_event *event, void *user_data) with gil cdef void _start_video_port(pjmedia_vid_port *port) cdef void _stop_video_port(pjmedia_vid_port *port) # core.event cdef struct _core_event cdef struct _handler_queue cdef int _event_queue_append(_core_event *event) cdef void _cb_log(int level, char_ptr_const data, int len) cdef int _add_event(object event_name, dict params) except -1 cdef list _get_clear_event_queue() cdef int _add_handler(int func(object obj) except -1, object obj, _handler_queue *queue) except -1 cdef int _remove_handler(object obj, _handler_queue *queue) except -1 cdef int _process_handler_queue(PJSIPUA ua, _handler_queue *queue) except -1 # core.request cdef class EndpointAddress(object): # attributes cdef readonly bytes ip cdef readonly int port cdef class Request(object): # attributes cdef readonly object state cdef PJSTR _method cdef readonly EndpointAddress peer_address cdef readonly FrozenCredentials credentials cdef readonly FrozenFromHeader from_header cdef readonly FrozenToHeader to_header cdef readonly FrozenSIPURI request_uri cdef readonly FrozenContactHeader contact_header cdef readonly FrozenRouteHeader route_header cdef PJSTR _call_id cdef readonly int cseq cdef readonly frozenlist extra_headers cdef PJSTR _content_type cdef PJSTR _content_subtype cdef PJSTR _body cdef pjsip_tx_data *_tdata cdef pjsip_transaction *_tsx cdef pjsip_auth_clt_sess _auth cdef pjsip_route_hdr _route_header cdef int _need_auth cdef pj_timer_entry _timer cdef int _timer_active cdef int _expire_rest cdef object _expire_time cdef object _timeout # private methods cdef PJSIPUA _get_ua(self) cdef int _cb_tsx_state(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1 cdef int _cb_timer(self, PJSIPUA ua) except -1 cdef class IncomingRequest(object): # attributes cdef readonly str state cdef pjsip_transaction *_tsx cdef pjsip_tx_data *_tdata cdef readonly EndpointAddress peer_address # methods cdef int init(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1 cdef void _Request_cb_tsx_state(pjsip_transaction *tsx, pjsip_event *event) with gil cdef void _Request_cb_timer(pj_timer_heap_t *timer_heap, pj_timer_entry *entry) with gil # core.referral cdef class Referral(object): # attributes cdef pjsip_evsub *_obj cdef pjsip_dialog *_dlg cdef pjsip_route_hdr _route_header cdef pj_list _route_set cdef int _create_subscription cdef readonly object state cdef pj_timer_entry _timeout_timer cdef int _timeout_timer_active cdef pj_timer_entry _refresh_timer cdef int _refresh_timer_active cdef readonly EndpointAddress peer_address cdef readonly FrozenFromHeader from_header cdef readonly FrozenToHeader to_header cdef readonly FrozenReferToHeader refer_to_header cdef readonly FrozenRouteHeader route_header cdef readonly FrozenCredentials credentials cdef readonly FrozenContactHeader local_contact_header cdef readonly FrozenContactHeader remote_contact_header cdef readonly int refresh cdef readonly frozenlist extra_headers cdef pj_time_val _request_timeout cdef int _want_end cdef int _term_code cdef object _term_reason # private methods cdef PJSIPUA _get_ua(self) cdef int _update_contact_header(self, BaseContactHeader contact_header) except -1 cdef int _cancel_timers(self, PJSIPUA ua, int cancel_timeout, int cancel_refresh) except -1 cdef int _send_refer(self, PJSIPUA ua, pj_time_val *timeout, FrozenReferToHeader refer_to_header, frozenlist extra_headers) except -1 cdef int _send_subscribe(self, PJSIPUA ua, int expires, pj_time_val *timeout, frozenlist extra_headers) except -1 cdef int _cb_state(self, PJSIPUA ua, object state, int code, str reason) except -1 cdef int _cb_got_response(self, PJSIPUA ua, pjsip_rx_data *rdata, str method) except -1 cdef int _cb_notify(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1 cdef int _cb_timeout_timer(self, PJSIPUA ua) cdef int _cb_refresh_timer(self, PJSIPUA ua) cdef class IncomingReferral(object): cdef pjsip_evsub *_obj cdef pjsip_dialog *_dlg cdef pjsip_tx_data *_initial_response cdef pjsip_transaction *_initial_tsx cdef pj_time_val _expires_time cdef int _create_subscription cdef readonly str state cdef readonly EndpointAddress peer_address cdef readonly FrozenContactHeader local_contact_header cdef readonly FrozenContactHeader remote_contact_header cdef PJSTR _content cdef int init(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1 cdef PJSIPUA _get_ua(self, int raise_exception) cdef int _set_content(self, int code, str reason) except -1 cdef int _set_state(self, str state) except -1 cdef int _send_initial_response(self, int code) except -1 cdef int _send_notify(self) except -1 cdef int _terminate(self, PJSIPUA ua, int do_cleanup) except -1 cdef int _cb_rx_refresh(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1 cdef int _cb_server_timeout(self, PJSIPUA ua) except -1 cdef int _cb_tsx(self, PJSIPUA ua, pjsip_event *event) except -1 cdef void _Referral_cb_state(pjsip_evsub *sub, pjsip_event *event) with gil cdef void _Referral_cb_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil cdef void _Referral_cb_refresh(pjsip_evsub *sub) with gil cdef void _IncomingReferral_cb_rx_refresh(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil cdef void _IncomingReferral_cb_server_timeout(pjsip_evsub *sub) with gil cdef void _IncomingReferral_cb_tsx(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) with gil # core.subscription cdef class Subscription(object): # attributes cdef pjsip_evsub *_obj cdef pjsip_dialog *_dlg cdef pjsip_route_hdr _route_header cdef pj_list _route_set cdef pj_timer_entry _timeout_timer cdef int _timeout_timer_active cdef pj_timer_entry _refresh_timer cdef int _refresh_timer_active cdef readonly object state cdef readonly EndpointAddress peer_address cdef readonly FrozenFromHeader from_header cdef readonly FrozenToHeader to_header cdef readonly FrozenContactHeader contact_header cdef readonly object event cdef readonly FrozenRouteHeader route_header cdef readonly FrozenCredentials credentials cdef readonly int refresh cdef readonly frozenlist extra_headers cdef readonly object body cdef readonly object content_type cdef readonly str call_id cdef pj_time_val _subscribe_timeout cdef int _want_end cdef int _term_code cdef object _term_reason cdef int _expires # private methods cdef PJSIPUA _get_ua(self) cdef int _cancel_timers(self, PJSIPUA ua, int cancel_timeout, int cancel_refresh) except -1 cdef int _send_subscribe(self, PJSIPUA ua, int expires, pj_time_val *timeout, object extra_headers, object content_type, object body) except -1 cdef int _cb_state(self, PJSIPUA ua, object state, int code, object reason, dict headers) except -1 cdef int _cb_got_response(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1 cdef int _cb_notify(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1 cdef int _cb_timeout_timer(self, PJSIPUA ua) cdef int _cb_refresh_timer(self, PJSIPUA ua) cdef class IncomingSubscription(object): # attributes cdef pjsip_evsub *_obj cdef pjsip_dialog *_dlg cdef PJSTR _content_type cdef PJSTR _content_subtype cdef PJSTR _content cdef pjsip_tx_data *_initial_response cdef pjsip_transaction *_initial_tsx cdef int _expires cdef readonly str state cdef readonly str event cdef readonly str call_id cdef readonly EndpointAddress peer_address # methods cdef int _set_state(self, str state) except -1 cdef PJSIPUA _get_ua(self, int raise_exception) cdef int init(self, PJSIPUA ua, pjsip_rx_data *rdata, str event) except -1 cdef int _send_initial_response(self, int code) except -1 cdef int _send_notify(self, str reason=*) except -1 cdef int _terminate(self, PJSIPUA ua, str reason, int do_cleanup) except -1 cdef int _cb_rx_refresh(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1 cdef int _cb_server_timeout(self, PJSIPUA ua) except -1 cdef int _cb_tsx(self, PJSIPUA ua, pjsip_event *event) except -1 cdef void _Subscription_cb_state(pjsip_evsub *sub, pjsip_event *event) with gil cdef void _Subscription_cb_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil cdef void _Subscription_cb_refresh(pjsip_evsub *sub) with gil cdef void _IncomingSubscription_cb_rx_refresh(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil cdef void _IncomingSubscription_cb_server_timeout(pjsip_evsub *sub) with gil cdef void _IncomingSubscription_cb_tsx(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) with gil # core.sdp cdef class BaseSDPConnection(object): # attributes cdef pjmedia_sdp_conn _sdp_connection # private methods cdef pjmedia_sdp_conn* get_sdp_connection(self) cdef class SDPConnection(BaseSDPConnection): # attributes cdef str _address cdef str _net_type cdef str _address_type cdef class FrozenSDPConnection(BaseSDPConnection): # attributes cdef int initialized cdef readonly str address cdef readonly str net_type cdef readonly str address_type cdef class SDPAttributeList(list): pass cdef class FrozenSDPAttributeList(frozenlist): pass cdef class SDPBandwidthInfoList(list): pass cdef class FrozenSDPBandwidthInfoList(frozenlist): pass cdef class BaseSDPSession(object): # attributes cdef pjmedia_sdp_session _sdp_session # private methods cdef pjmedia_sdp_session* get_sdp_session(self) cdef class SDPSession(BaseSDPSession): # attributes cdef str _address cdef str _user cdef str _net_type cdef str _address_type cdef str _name cdef str _info cdef SDPConnection _connection cdef list _attributes cdef list _bandwidth_info cdef list _media # private methods cdef int _update(self) except -1 cdef class FrozenSDPSession(BaseSDPSession): # attributes cdef int initialized cdef readonly str address cdef readonly unsigned int id cdef readonly unsigned int version cdef readonly str user cdef readonly str net_type cdef readonly str address_type cdef readonly str name cdef readonly str info cdef readonly FrozenSDPConnection connection cdef readonly int start_time cdef readonly int stop_time cdef readonly FrozenSDPAttributeList attributes cdef readonly FrozenSDPBandwidthInfoList bandwidth_info cdef readonly frozenlist media cdef class BaseSDPMediaStream(object): # attributes cdef pjmedia_sdp_media _sdp_media # private methods cdef pjmedia_sdp_media* get_sdp_media(self) cdef class SDPMediaStream(BaseSDPMediaStream): # attributes cdef str _media cdef str _transport cdef list _formats cdef list _codec_list cdef str _info cdef SDPConnection _connection cdef SDPAttributeList _attributes cdef SDPBandwidthInfoList _bandwidth_info # private methods cdef int _update(self, SDPMediaStream media) except -1 cdef class FrozenSDPMediaStream(BaseSDPMediaStream): # attributes cdef int initialized cdef readonly str media cdef readonly int port cdef readonly str transport cdef readonly int port_count cdef readonly frozenlist formats cdef readonly frozenlist codec_list cdef readonly str info cdef readonly FrozenSDPConnection connection cdef readonly FrozenSDPAttributeList attributes cdef readonly FrozenSDPBandwidthInfoList bandwidth_info cdef class BaseSDPAttribute(object): # attributes cdef pjmedia_sdp_attr _sdp_attribute # private methods cdef pjmedia_sdp_attr* get_sdp_attribute(self) cdef class SDPAttribute(BaseSDPAttribute): # attributes cdef str _name cdef str _value cdef class FrozenSDPAttribute(BaseSDPAttribute): # attributes cdef int initialized cdef readonly str name cdef readonly str value cdef class BaseSDPBandwidthInfo(object): # attributes cdef pjmedia_sdp_bandw _sdp_bandwidth_info # private methods cdef pjmedia_sdp_bandw* get_sdp_bandwidth_info(self) cdef class SDPBandwidthInfo(BaseSDPBandwidthInfo): # attributes cdef str _modifier cdef int _value cdef class FrozenSDPBandwidthInfo(BaseSDPBandwidthInfo): # attributes cdef int initialized cdef readonly str modifier cdef readonly int value cdef SDPSession SDPSession_create(pjmedia_sdp_session_ptr_const pj_session) cdef FrozenSDPSession FrozenSDPSession_create(pjmedia_sdp_session_ptr_const pj_session) cdef SDPMediaStream SDPMediaStream_create(pjmedia_sdp_media *pj_media) cdef FrozenSDPMediaStream FrozenSDPMediaStream_create(pjmedia_sdp_media *pj_media) cdef SDPConnection SDPConnection_create(pjmedia_sdp_conn *pj_conn) cdef FrozenSDPConnection FrozenSDPConnection_create(pjmedia_sdp_conn *pj_conn) cdef SDPAttribute SDPAttribute_create(pjmedia_sdp_attr *pj_attr) cdef FrozenSDPAttribute FrozenSDPAttribute_create(pjmedia_sdp_attr *pj_attr) cdef SDPBandwidthInfo SDPBandwidthInfo_create(pjmedia_sdp_bandw *pj_bandw) cdef FrozenSDPBandwidthInfo FrozenSDPBandwidthInfo_create(pjmedia_sdp_bandw *pj_bandw) cdef class SDPNegotiator(object): # attributes cdef pjmedia_sdp_neg* _neg cdef pj_pool_t *_pool # core.invitation cdef class SDPPayloads: cdef readonly FrozenSDPSession proposed_local cdef readonly FrozenSDPSession proposed_remote cdef readonly FrozenSDPSession active_local cdef readonly FrozenSDPSession active_remote cdef class StateCallbackTimer(Timer): cdef object state cdef object sub_state cdef object rdata cdef object tdata cdef object originator cdef class SDPCallbackTimer(Timer): cdef int status cdef object active_local cdef object active_remote cdef class TransferStateCallbackTimer(Timer): cdef object state cdef object code cdef object reason cdef class TransferResponseCallbackTimer(Timer): cdef object method cdef object rdata cdef class TransferRequestCallbackTimer(Timer): cdef object rdata cdef class Invitation(object): # attributes cdef object __weakref__ cdef object weakref cdef int _sdp_neg_status cdef int _failed_response cdef pj_list _route_set cdef pj_mutex_t *_lock cdef pjsip_inv_session *_invite_session cdef pjsip_evsub *_transfer_usage cdef pjsip_role_e _transfer_usage_role cdef pjsip_dialog *_dialog cdef pjsip_route_hdr _route_header cdef pjsip_transaction *_reinvite_transaction cdef PJSTR _sipfrag_payload cdef Timer _timer cdef Timer _transfer_timeout_timer cdef Timer _transfer_refresh_timer cdef readonly str call_id cdef readonly str direction cdef readonly str remote_user_agent cdef readonly str state cdef readonly str sub_state cdef readonly str transport cdef readonly str transfer_state cdef readonly EndpointAddress peer_address cdef readonly FrozenCredentials credentials cdef readonly FrozenContactHeader local_contact_header cdef readonly FrozenContactHeader remote_contact_header cdef readonly FrozenFromHeader from_header cdef readonly FrozenToHeader to_header cdef readonly FrozenSIPURI request_uri cdef readonly FrozenRouteHeader route_header cdef readonly SDPPayloads sdp # private methods cdef int init_incoming(self, PJSIPUA ua, pjsip_rx_data *rdata, unsigned int inv_options) except -1 cdef int process_incoming_transfer(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1 cdef int process_incoming_options(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1 cdef PJSIPUA _check_ua(self) cdef int _do_dealloc(self) except -1 cdef int _update_contact_header(self, BaseContactHeader contact_header) except -1 cdef int _fail(self, PJSIPUA ua) except -1 cdef int _cb_state(self, StateCallbackTimer timer) except -1 cdef int _cb_sdp_done(self, SDPCallbackTimer timer) except -1 cdef int _cb_timer_disconnect(self, timer) except -1 cdef int _cb_postpoll_fail(self, timer) except -1 cdef int _start_incoming_transfer(self, timer) except -1 cdef int _terminate_transfer(self) except -1 cdef int _terminate_transfer_uac(self) except -1 cdef int _terminate_transfer_uas(self) except -1 cdef int _set_transfer_state(self, str state) except -1 cdef int _set_sipfrag_payload(self, int code, str reason) except -1 cdef int _send_notify(self) except -1 cdef int _transfer_cb_timeout_timer(self, timer) except -1 cdef int _transfer_cb_refresh_timer(self, timer) except -1 cdef int _transfer_cb_state(self, TransferStateCallbackTimer timer) except -1 cdef int _transfer_cb_response(self, TransferResponseCallbackTimer timer) except -1 cdef int _transfer_cb_notify(self, TransferRequestCallbackTimer timer) except -1 cdef int _transfer_cb_server_timeout(self, timer) except -1 cdef void _Invitation_cb_state(pjsip_inv_session *inv, pjsip_event *e) with gil cdef void _Invitation_cb_sdp_done(pjsip_inv_session *inv, int status) with gil cdef int _Invitation_cb_rx_reinvite(pjsip_inv_session *inv, pjmedia_sdp_session_ptr_const offer, pjsip_rx_data *rdata) with gil cdef void _Invitation_cb_tsx_state_changed(pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e) with gil cdef void _Invitation_cb_new(pjsip_inv_session *inv, pjsip_event *e) with gil cdef void _Invitation_transfer_cb_state(pjsip_evsub *sub, pjsip_event *event) with gil cdef void _Invitation_transfer_cb_tsx(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) with gil cdef void _Invitation_transfer_cb_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil cdef void _Invitation_transfer_cb_refresh(pjsip_evsub *sub) with gil cdef void _Invitation_transfer_in_cb_rx_refresh(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil cdef void _Invitation_transfer_in_cb_server_timeout(pjsip_evsub *sub) with gil # core.mediatransport cdef class ICECandidate(object): # attributes cdef readonly str component cdef readonly str type cdef readonly str address cdef readonly int port cdef readonly int priority cdef readonly str rel_address cdef class ICECheck(object): cdef readonly ICECandidate local_candidate cdef readonly ICECandidate remote_candidate cdef readonly str state cdef readonly int nominated cdef class RTPTransport(object): # attributes cdef object __weakref__ cdef object weakref cdef int _af cdef pj_mutex_t *_lock cdef pj_pool_t *_pool cdef pjmedia_transport *_obj cdef pjmedia_transport *_wrapped_transport cdef ICECheck _rtp_valid_pair cdef str _encryption cdef readonly object ice_stun_address cdef readonly object ice_stun_port cdef readonly object state cdef readonly object use_ice # private methods cdef PJSIPUA _check_ua(self) cdef void _get_info(self, pjmedia_transport_info *info) cdef int _init_local_sdp(self, BaseSDPSession local_sdp, BaseSDPSession remote_sdp, int sdp_index) cdef int _ice_active(self) cdef class MediaCheckTimer(Timer): # attributes cdef int media_check_interval cdef class SDPInfo(object): # attributes cdef BaseSDPMediaStream _local_media cdef BaseSDPSession _local_sdp cdef BaseSDPSession _remote_sdp cdef public int index cdef class AudioTransport(object): # attributes cdef object __weakref__ cdef object weakref cdef int _is_offer cdef int _is_started cdef int _slot cdef int _volume cdef unsigned int _packets_received cdef unsigned int _vad cdef pj_mutex_t *_lock cdef pj_pool_t *_pool cdef pjmedia_stream *_obj cdef pjmedia_stream_info _stream_info cdef Timer _timer cdef readonly object direction cdef readonly AudioMixer mixer cdef readonly RTPTransport transport cdef SDPInfo _sdp_info # private methods cdef PJSIPUA _check_ua(self) cdef int _cb_check_rtp(self, MediaCheckTimer timer) except -1 cdef class VideoTransport(object): # attributes cdef object __weakref__ cdef object weakref cdef int _is_offer cdef int _is_started cdef unsigned int _packets_received cdef pj_mutex_t *_lock cdef pj_pool_t *_pool cdef pjmedia_vid_stream *_obj cdef pjmedia_vid_stream_info _stream_info cdef Timer _timer cdef readonly object direction cdef readonly RTPTransport transport cdef SDPInfo _sdp_info cdef readonly LocalVideoStream local_video cdef readonly RemoteVideoStream remote_video # private methods cdef PJSIPUA _check_ua(self) cdef int _cb_check_rtp(self, MediaCheckTimer timer) except -1 cdef void _RTPTransport_cb_ice_complete(pjmedia_transport *tp, pj_ice_strans_op op, int status) with gil cdef void _RTPTransport_cb_ice_state(pjmedia_transport *tp, pj_ice_strans_state prev, pj_ice_strans_state curr) with gil cdef void _RTPTransport_cb_ice_stop(pjmedia_transport *tp, char *reason, int err) with gil cdef void _RTPTransport_cb_zrtp_secure_on(pjmedia_transport *tp, char* cipher) with gil cdef void _RTPTransport_cb_zrtp_secure_off(pjmedia_transport *tp) with gil cdef void _RTPTransport_cb_zrtp_show_sas(pjmedia_transport *tp, char* sas, int verified) with gil cdef void _RTPTransport_cb_zrtp_confirm_goclear(pjmedia_transport *tp) with gil cdef void _RTPTransport_cb_zrtp_show_message(pjmedia_transport *tp, int severity, int subCode) with gil cdef void _RTPTransport_cb_zrtp_negotiation_failed(pjmedia_transport *tp, int severity, int subCode) with gil cdef void _RTPTransport_cb_zrtp_not_supported_by_other(pjmedia_transport *tp) with gil cdef void _RTPTransport_cb_zrtp_ask_enrollment(pjmedia_transport *tp, int info) with gil cdef void _RTPTransport_cb_zrtp_inform_enrollment(pjmedia_transport *tp, int info) with gil cdef void _AudioTransport_cb_dtmf(pjmedia_stream *stream, void *user_data, int digit) with gil cdef ICECandidate ICECandidate_create(pj_ice_sess_cand *cand) cdef ICECheck ICECheck_create(pj_ice_sess_check *check) cdef str _ice_state_to_str(int state) cdef dict _extract_ice_session_data(pj_ice_sess *ice_sess) cdef object _extract_rtp_transport(pjmedia_transport *tp) cdef dict _pj_math_stat_to_dict(pj_math_stat *stat) cdef dict _pjmedia_rtcp_stream_stat_to_dict(pjmedia_rtcp_stream_stat *stream_stat) diff --git a/sipsimple/core/_core.referral.pxi b/sipsimple/core/_core.referral.pxi index 5c213372..f3d8c619 100644 --- a/sipsimple/core/_core.referral.pxi +++ b/sipsimple/core/_core.referral.pxi @@ -1,1003 +1,1003 @@ import re cdef class Referral: expire_warning_time = 30 def __cinit__(self, *args, **kwargs): self.state = "NULL" pj_timer_entry_init(&self._timeout_timer, 0, self, _Referral_cb_timer) self._timeout_timer_active = 0 pj_timer_entry_init(&self._refresh_timer, 1, self, _Referral_cb_timer) self._refresh_timer_active = 0 self.extra_headers = frozenlist() self.peer_address = None self._create_subscription = 1 self.local_contact_header = None self.remote_contact_header = None def __init__(self, SIPURI request_uri not None, FromHeader from_header not None, ToHeader to_header not None, ReferToHeader refer_to_header not None, ContactHeader contact_header not None, RouteHeader route_header not None, Credentials credentials=None): global _refer_cb global _refer_event cdef PJSTR from_header_str cdef PJSTR to_header_str cdef PJSTR contact_str cdef PJSTR request_uri_str cdef pjsip_cred_info *cred_info cdef PJSIPUA ua = _get_ua() cdef int status if self._obj != NULL or self.state != "NULL": raise SIPCoreError("Referral.__init__() was already called") self.local_contact_header = FrozenContactHeader.new(contact_header) self.route_header = FrozenRouteHeader.new(route_header) self.route_header.uri.parameters.dict["lr"] = None # always send lr parameter in Route header self.route_header.uri.parameters.dict["hide"] = None # always hide Route header if credentials is not None: self.credentials = FrozenCredentials.new(credentials) from_header_parameters = from_header.parameters.copy() from_header_parameters.pop("tag", None) from_header.parameters = {} from_header_str = PJSTR(from_header.body) to_header_parameters = to_header.parameters.copy() to_header_parameters.pop("tag", None) to_header.parameters = {} to_header_str = PJSTR(to_header.body) contact_str = PJSTR(str(contact_header.body)) request_uri_str = PJSTR(str(request_uri)) with nogil: status = pjsip_dlg_create_uac(pjsip_ua_instance(), &from_header_str.pj_str, &contact_str.pj_str, &to_header_str.pj_str, &request_uri_str.pj_str, &self._dlg) if status != 0: raise PJSIPError("Could not create dialog for REFER", status) # Increment dialog session count so that it's never destroyed by PJSIP with nogil: status = pjsip_dlg_inc_session(self._dlg, &ua._module) if contact_header.expires is not None: self._dlg.local.contact.expires = contact_header.expires if contact_header.q is not None: self._dlg.local.contact.q1000 = int(contact_header.q*1000) contact_parameters = contact_header.parameters.copy() contact_parameters.pop("q", None) contact_parameters.pop("expires", None) _dict_to_pjsip_param(contact_parameters, &self._dlg.local.contact.other_param, self._dlg.pool) _dict_to_pjsip_param(from_header_parameters, &self._dlg.local.info.other_param, self._dlg.pool) _dict_to_pjsip_param(to_header_parameters, &self._dlg.remote.info.other_param, self._dlg.pool) self.from_header = FrozenFromHeader_create(self._dlg.local.info) self.to_header = FrozenToHeader.new(to_header) self.refer_to_header = FrozenReferToHeader.new(refer_to_header) with nogil: status = pjsip_evsub_create_uac(self._dlg, &_refer_cb, &_refer_event.pj_str, PJSIP_EVSUB_NO_EVENT_ID, &self._obj) if status != 0: raise PJSIPError("Could not create REFER", status) pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, self) _BaseRouteHeader_to_pjsip_route_hdr(self.route_header, &self._route_header, self._dlg.pool) pj_list_init( &self._route_set) pj_list_insert_after( &self._route_set, &self._route_header) with nogil: status = pjsip_dlg_set_route_set(self._dlg, &self._route_set) if status != 0: raise PJSIPError("Could not set route on REFER", status) if self.credentials is not None: cred_info = self.credentials.get_cred_info() with nogil: status = pjsip_auth_clt_set_credentials(&self._dlg.auth_sess, 1, cred_info) if status != 0: raise PJSIPError("Could not set credentials for REFER", status) def __dealloc__(self): cdef PJSIPUA ua = self._get_ua() if ua is not None: self._cancel_timers(ua, 1, 1) if self._obj != NULL: pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) with nogil: pjsip_evsub_terminate(self._obj, 0) self._obj = NULL if self._dlg != NULL: with nogil: pjsip_dlg_dec_session(self._dlg, &ua._module) self._dlg = NULL def send_refer(self, int create_subscription=1, list extra_headers not None=list(), object timeout=None): cdef PJSIPUA ua = self._get_ua() with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state != "NULL": raise SIPCoreError('This method may only be called in the "NULL" state') if timeout is not None: if timeout <= 0: raise ValueError("Timeout value cannot be negative") self._request_timeout.sec = int(timeout) self._request_timeout.msec = (timeout * 1000) % 1000 else: self._request_timeout.sec = 0 self._request_timeout.msec = 0 if extra_headers is not None: self.extra_headers = frozenlist([header.frozen_type.new(header) for header in extra_headers]) self._create_subscription = create_subscription self._send_refer(ua, &self._request_timeout, self.refer_to_header, self.extra_headers) _add_event("SIPReferralWillStart", dict(obj=self)) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) def refresh(self, ContactHeader contact_header=None, list extra_headers not None=list(), object timeout=None): cdef PJSIPUA ua = self._get_ua() with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state not in ("ACCEPTED", "ACTIVE", "PENDING"): raise SIPCoreError('This method may only be called in the "ACCEPTED", "ACTIVE" or "PENDING" states') if timeout is not None: if timeout <= 0: raise ValueError("Timeout value cannot be negative") self._request_timeout.sec = int(timeout) self._request_timeout.msec = (timeout * 1000) % 1000 else: self._request_timeout.sec = 0 self._request_timeout.msec = 0 if contact_header is not None: self._update_contact_header(contact_header) if extra_headers is not None: self.extra_headers = frozenlist([header.frozen_type.new(header) for header in extra_headers]) self._send_subscribe(ua, 600, &self._request_timeout, self.extra_headers) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) def end(self, object timeout=None): cdef pj_time_val end_timeout cdef PJSIPUA ua = self._get_ua() with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state == "TERMINATED": return if self.state == "NULL": raise SIPCoreError('This method may not be called in the "NULL" state') if timeout is not None: if timeout <= 0: raise ValueError("Timeout value cannot be negative") end_timeout.sec = int(timeout) end_timeout.msec = (timeout * 1000) % 1000 else: end_timeout.sec = 0 end_timeout.msec = 0 self._want_end = 1 self._cancel_timers(ua, 1, 1) _add_event("SIPReferralWillEnd", dict(obj=self)) try: self._send_subscribe(ua, 0, &end_timeout, frozenlist([])) except PJSIPError, e: self._term_reason = e.args[0] if self._obj != NULL: pjsip_evsub_terminate(self._obj, 1) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) cdef PJSIPUA _get_ua(self): cdef PJSIPUA ua try: ua = _get_ua() except SIPCoreError: self._obj = NULL self._timeout_timer_active = 0 self._refresh_timer_active = 0 self.state = "TERMINATED" return None else: return ua cdef int _update_contact_header(self, BaseContactHeader contact_header) except -1: # The PJSIP functions called here don't do much, so there is no need to call them # without the gil. cdef pj_str_t contact_str_pj cdef pjsip_uri *contact contact_str = str(contact_header.uri) if contact_header.display_name: contact_str = "%s <%s>" % (contact_header.display_name.encode('utf-8'), contact_str) pj_strdup2_with_null(self._dlg.pool, &contact_str_pj, contact_str) contact = pjsip_parse_uri(self._dlg.pool, contact_str_pj.ptr, contact_str_pj.slen, PJSIP_PARSE_URI_AS_NAMEADDR) if contact == NULL: raise SIPCoreError("Not a valid Contact header: %s" % contact_str) self._dlg.local.contact = pjsip_contact_hdr_create(self._dlg.pool) self._dlg.local.contact.uri = contact if contact_header.expires is not None: self._dlg.local.contact.expires = contact_header.expires if contact_header.q is not None: self._dlg.local.contact.q1000 = int(contact_header.q*1000) parameters = contact_header.parameters.copy() parameters.pop("q", None) parameters.pop("expires", None) _dict_to_pjsip_param(parameters, &self._dlg.local.contact.other_param, self._dlg.pool) self.local_contact_header = FrozenContactHeader.new(contact_header) return 0 cdef int _cancel_timers(self, PJSIPUA ua, int cancel_timeout, int cancel_refresh) except -1: if cancel_timeout and self._timeout_timer_active: pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._timeout_timer) self._timeout_timer_active = 0 if cancel_refresh and self._refresh_timer_active: pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._refresh_timer) self._refresh_timer_active = 0 cdef int _send_refer(self, PJSIPUA ua, pj_time_val *timeout, FrozenReferToHeader refer_to_header, frozenlist extra_headers) except -1: global _refer_method cdef pjsip_method refer_method cdef pjsip_tx_data *tdata cdef int status pjsip_method_init_np(&refer_method, &_refer_method.pj_str) with nogil: status = pjsip_evsub_initiate(self._obj, &refer_method, -1, &tdata) if status != 0: raise PJSIPError("Could not create REFER message", status) _add_headers_to_tdata(tdata, [refer_to_header, Header('Referred-By', str(self.from_header.uri))]) _add_headers_to_tdata(tdata, extra_headers) if not self._create_subscription: _add_headers_to_tdata(tdata, [Header('Refer-Sub', 'false')]) # We can't remove the Event header or PJSIP will fail to match responses to this request _remove_headers_from_tdata(tdata, ["Expires"]) with nogil: status = pjsip_evsub_send_request(self._obj, tdata) if status != 0: raise PJSIPError("Could not send REFER message", status) if timeout.sec or timeout.msec: status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._timeout_timer, timeout) if status == 0: self._timeout_timer_active = 1 cdef int _send_subscribe(self, PJSIPUA ua, int expires, pj_time_val *timeout, frozenlist extra_headers) except -1: cdef pjsip_tx_data *tdata cdef int status with nogil: status = pjsip_evsub_initiate(self._obj, NULL, expires, &tdata) if status != 0: raise PJSIPError("Could not create SUBSCRIBE message", status) _add_headers_to_tdata(tdata, extra_headers) with nogil: status = pjsip_evsub_send_request(self._obj, tdata) if status != 0: raise PJSIPError("Could not send SUBSCRIBE message", status) self._cancel_timers(ua, 1, 0) if timeout.sec or timeout.msec: status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._timeout_timer, timeout) if status == 0: self._timeout_timer_active = 1 cdef int _cb_state(self, PJSIPUA ua, object state, int code, str reason) except -1: # PJSIP holds the dialog lock when this callback is entered cdef object prev_state = self.state cdef int status self.state = state if state == "ACCEPTED" and prev_state == "SENT": _add_event("SIPReferralDidStart", dict(obj=self)) if not self._create_subscription: # Terminate the subscription self._want_end = 1 _add_event("SIPReferralWillEnd", dict(obj=self)) with nogil: pjsip_evsub_terminate(self._obj, 1) elif state == "TERMINATED": pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) self._cancel_timers(ua, 1, 1) self._obj = NULL if self._want_end: _add_event("SIPReferralDidEnd", dict(obj=self)) else: if self._term_reason is not None: _add_event("SIPReferralDidFail", dict(obj=self, code=self._term_code, reason=self._term_reason)) elif code/100 == 2: _add_event("SIPReferralDidEnd", dict(obj=self)) else: _add_event("SIPReferralDidFail", dict(obj=self, code=code, reason=reason)) if prev_state != state: _add_event("SIPReferralChangedState", dict(obj=self, prev_state=prev_state, state=state)) cdef int _cb_got_response(self, PJSIPUA ua, pjsip_rx_data *rdata, str method) except -1: # PJSIP holds the dialog lock when this callback is entered global _refer_sub_hdr_name cdef int expires cdef int status cdef dict event_dict = dict() cdef pj_time_val refresh cdef pjsip_generic_int_hdr *expires_hdr cdef pjsip_generic_string_hdr *refer_sub_header self.to_header = FrozenToHeader_create(rdata.msg_info.to_hdr) if self.state != "TERMINATED" and not self._want_end: self._cancel_timers(ua, 1, 0) if method == "REFER": refer_sub_header = pjsip_msg_find_hdr_by_name(rdata.msg_info.msg, &_refer_sub_hdr_name.pj_str, NULL); if not self._create_subscription: if not (refer_sub_header != NULL and _pj_str_to_str(refer_sub_header.hvalue) == "false"): self._create_subscription = 1 elif method == "SUBSCRIBE": # For the REFER method the expires value will be taken from the NOTIFY Subscription-State header expires_hdr = pjsip_msg_find_hdr(rdata.msg_info.msg, PJSIP_H_EXPIRES, NULL) if expires_hdr != NULL and not self._refresh_timer_active: expires = expires_hdr.ivalue refresh.sec = max(1, expires - self.expire_warning_time, expires/2) refresh.msec = 0 status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._refresh_timer, &refresh) if status == 0: self._refresh_timer_active = 1 if self.state != "TERMINATED": _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) try: self.remote_contact_header = event_dict['headers']['Contact'][0] except LookupError: pass cdef int _cb_notify(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: # PJSIP holds the dialog lock when this callback is entered global _subscription_state_hdr_name cdef pjsip_sub_state_hdr *sub_state_hdr cdef pj_time_val refresh cdef int expires cdef dict event_dict = dict() cdef dict notify_dict = dict(obj=self) sub_state_hdr = pjsip_msg_find_hdr_by_name(rdata.msg_info.msg, &_subscription_state_hdr_name.pj_str, NULL) if self.state != "TERMINATED" and sub_state_hdr != NULL and sub_state_hdr.expires_param > 0 and not self._refresh_timer_active: expires = sub_state_hdr.expires_param refresh.sec = max(1, expires - self.expire_warning_time, expires/2) refresh.msec = 0 status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._refresh_timer, &refresh) if status == 0: self._refresh_timer_active = 1 _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) if self.state != "TERMINATED": try: self.remote_contact_header = event_dict['headers']['Contact'][0] except LookupError: pass notify_dict["request_uri"] = event_dict["request_uri"] notify_dict["from_header"] = event_dict["headers"].get("From", None) notify_dict["to_header"] = event_dict["headers"].get("To", None) notify_dict["headers"] = event_dict["headers"] notify_dict["body"] = event_dict["body"] content_type = notify_dict["headers"].get("Content-Type", None) notify_dict["content_type"] = content_type.content_type if content_type else None event = notify_dict["headers"].get("Event", None) notify_dict["event"] = event.event if event else None _add_event("SIPReferralGotNotify", notify_dict) cdef int _cb_timeout_timer(self, PJSIPUA ua): # Timer callback, dialog lock is not held by PJSIP global sip_status_messages with nogil: pjsip_dlg_inc_lock(self._dlg) try: self._term_code = PJSIP_SC_TSX_TIMEOUT self._term_reason = sip_status_messages[PJSIP_SC_TSX_TIMEOUT] if self._obj != NULL: with nogil: pjsip_evsub_terminate(self._obj, 1) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) cdef int _cb_refresh_timer(self, PJSIPUA ua): # Timer callback, dialog lock is not held by PJSIP with nogil: pjsip_dlg_inc_lock(self._dlg) try: self._send_subscribe(ua, 600, &self._request_timeout, self.extra_headers) except PJSIPError, e: self._term_reason = e.args[0] if self._obj != NULL: with nogil: pjsip_evsub_terminate(self._obj, 1) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) cdef class IncomingReferral: def __cinit__(self): self.state = None self.peer_address = None self._create_subscription = 1 self.local_contact_header = None self.remote_contact_header = None def __dealloc__(self): cdef PJSIPUA ua = self._get_ua(0) self._initial_response = NULL self._initial_tsx = NULL if self._obj != NULL: pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) with nogil: pjsip_evsub_terminate(self._obj, 0) self._obj = NULL if self._dlg != NULL and ua is not None: with nogil: pjsip_dlg_dec_session(self._dlg, &ua._module) self._dlg = NULL cdef int init(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: global _incoming_refer_subs_cb global _event_hdr_name global _refer_event global _refer_to_hdr_name global _refer_sub_hdr_name cdef int status cdef str transport cdef FrozenSIPURI request_uri cdef FrozenContactHeader contact_header cdef PJSTR contact_str cdef dict event_dict cdef pjsip_generic_string_hdr *refer_to_header cdef pjsip_generic_string_hdr *refer_sub_header cdef pjsip_tpselector tp_sel cdef pjsip_event_hdr *event_header refer_to_header = pjsip_msg_find_hdr_by_name(rdata.msg_info.msg, &_refer_to_hdr_name.pj_str, NULL); if refer_to_header == NULL: with nogil: status = pjsip_endpt_create_response(ua._pjsip_endpoint._obj, rdata, 400, NULL, &self._initial_response) if status != 0: raise PJSIPError("Could not create response", status) with nogil: status = pjsip_endpt_send_response2(ua._pjsip_endpoint._obj, rdata, self._initial_response, NULL, NULL) if status != 0: with nogil: pjsip_tx_data_dec_ref(self._initial_response) raise PJSIPError("Could not send response", status) return 0 # If there is a Ref-Sub header and it contains 'false', don't establish a subscription refer_sub_header = pjsip_msg_find_hdr_by_name(rdata.msg_info.msg, &_refer_sub_hdr_name.pj_str, NULL); if refer_sub_header != NULL and _pj_str_to_str(refer_sub_header.hvalue) == "false": self._create_subscription = 0 self._set_state("incoming") self.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) event_dict = dict(obj=self, prev_state=self.state, state="incoming") _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) try: self.remote_contact_header = event_dict['headers']['Contact'][0] except LookupError: # Contact header is required with nogil: status = pjsip_endpt_create_response(ua._pjsip_endpoint._obj, rdata, 400, NULL, &self._initial_response) if status != 0: raise PJSIPError("Could not create response", status) with nogil: status = pjsip_endpt_send_response2(ua._pjsip_endpoint._obj, rdata, self._initial_response, NULL, NULL) if status != 0: with nogil: pjsip_tx_data_dec_ref(self._initial_response) raise PJSIPError("Could not send response", status) return 0 event_dict["refer_to"] = event_dict["headers"].get("Refer-To") transport = rdata.tp_info.transport.type_name.lower() request_uri = event_dict["request_uri"] if _is_valid_ip(pj_AF_INET(), request_uri.host): self.local_contact_header = FrozenContactHeader(request_uri) else: self.local_contact_header = FrozenContactHeader(FrozenSIPURI(host=_pj_str_to_str(rdata.tp_info.transport.local_name.host), user=request_uri.user, port=rdata.tp_info.transport.local_name.port, parameters=(frozendict(transport=transport) if transport != "udp" else frozendict()))) contact_str = PJSTR(self.local_contact_header.body) with nogil: status = pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(), rdata, &contact_str.pj_str, &self._dlg) if status != 0: with nogil: status = pjsip_endpt_create_response(ua._pjsip_endpoint._obj, rdata, 400, NULL, &self._initial_response) if status != 0: raise PJSIPError("Could not create response", status) with nogil: status = pjsip_endpt_send_response2(ua._pjsip_endpoint._obj, rdata, self._initial_response, NULL, NULL) if status != 0: with nogil: pjsip_tx_data_dec_ref(self._initial_response) raise PJSIPError("Could not send response", status) return 0 # Increment dialog session count so that it's never destroyed by PJSIP with nogil: status = pjsip_dlg_inc_session(self._dlg, &ua._module) if status != 0: pjsip_dlg_dec_lock(self._dlg) raise PJSIPError("Could not increment dialog session count", status) # PJSIP event framework needs an Event header, even if it's not needed for REFER, so we insert a fake one event_header = pjsip_msg_find_hdr_by_name(rdata.msg_info.msg, &_event_hdr_name.pj_str, NULL) if event_header == NULL: event_header = pjsip_event_hdr_create(rdata.tp_info.pool) event_header.event_type = _refer_event.pj_str pjsip_msg_add_hdr(rdata.msg_info.msg, event_header) self._initial_tsx = pjsip_rdata_get_tsx(rdata) with nogil: status = pjsip_evsub_create_uas(self._dlg, &_incoming_refer_subs_cb, rdata, 0, &self._obj) pjsip_dlg_dec_lock(self._dlg) if status != 0: with nogil: pjsip_tsx_terminate(self._initial_tsx, 500) self._initial_tsx = NULL self._dlg = NULL raise PJSIPError("Could not create incoming REFER session", status) pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, self) with nogil: status = pjsip_dlg_create_response(self._dlg, rdata, 500, NULL, &self._initial_response) if status != 0: with nogil: pjsip_tsx_terminate(self._initial_tsx, 500) self._initial_tsx = NULL raise PJSIPError("Could not create response for incoming REFER", status) _add_event("SIPIncomingReferralGotRefer", event_dict) return 0 def accept(self, int code=202, int duration=180): cdef PJSIPUA ua = self._get_ua(1) with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state != "incoming": raise SIPCoreInvalidStateError('Can only accept an incoming REFER in the "incoming" state, '+ 'object is currently in the "%s" state' % self.state) pjsip_evsub_update_expires(self._obj, duration) self._send_initial_response(code) self._set_state("active") if not self._create_subscription: pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) with nogil: pjsip_evsub_terminate(self._obj, 0) self._obj = NULL self._set_state("terminated") _add_event("SIPIncomingReferralDidEnd", dict(obj=self)) else: self._set_content(100, "Trying") self._send_notify() finally: with nogil: pjsip_dlg_dec_lock(self._dlg) def reject(self, int code): cdef PJSIPUA ua = self._get_ua(1) with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state != "incoming": raise SIPCoreInvalidStateError('Can only reject an incoming REFER in the "incoming" state, '+ 'object is currently in the "%s" state' % self.state) if not (300 <= code < 700): raise ValueError("Invalid negative SIP response code: %d" % code) self._send_initial_response(code) pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) with nogil: pjsip_evsub_terminate(self._obj, 0) self._obj = NULL self._set_state("terminated") _add_event("SIPIncomingReferralDidEnd", dict(obj=self)) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) def send_notify(self, int code, str status=None): cdef PJSIPUA ua = self._get_ua(1) cdef str content with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state != "active": raise SIPCoreInvalidStateError('Can only send NOTIFY for a REFER session in the "active" state, ' 'object is currently in the "%s" state' % self.state) self._set_content(code, status) self._send_notify() finally: with nogil: pjsip_dlg_dec_lock(self._dlg) def end(self, int code, str status=None): cdef PJSIPUA ua = self._get_ua(0) with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state == "terminated": return if self.state not in ("pending", "active"): raise SIPCoreInvalidStateError('Can only end an incoming REFER session in the "pending" or '+ '"active" state, object is currently in the "%s" state' % self.state) self._set_content(code, status) self._terminate(ua, 1) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) cdef PJSIPUA _get_ua(self, int raise_exception): cdef PJSIPUA ua try: ua = _get_ua() except SIPCoreError: self._obj = NULL self._set_state("terminated") if raise_exception: raise else: return None else: return ua cdef int _set_content(self, int code, str reason) except -1: cdef str content if reason is None: try: reason = sip_status_messages[code] except IndexError: reason = "Unknown" content = "SIP/2.0 %d %s\r\n" % (code, reason) self._content = PJSTR(content) cdef int _set_state(self, str state) except -1: cdef str prev_state prev_state = self.state self.state = state if prev_state != state and prev_state is not None: _add_event("SIPIncomingReferralChangedState", dict(obj=self, prev_state=prev_state, state=state)) cdef int _send_initial_response(self, int code) except -1: cdef int status with nogil: status = pjsip_dlg_modify_response(self._dlg, self._initial_response, code, NULL) if status != 0: raise PJSIPError("Could not modify response", status) # pjsip_dlg_modify_response() increases ref count unncessarily with nogil: pjsip_tx_data_dec_ref(self._initial_response) if not self._create_subscription: _add_headers_to_tdata(self._initial_response, [Header('Refer-Sub', 'false')]) with nogil: status = pjsip_dlg_send_response(self._dlg, self._initial_tsx, self._initial_response) if status != 0: raise PJSIPError("Could not send response", status) self._initial_response = NULL self._initial_tsx = NULL cdef int _send_notify(self) except -1: cdef pjsip_evsub_state state cdef pj_str_t *reason_p cdef pjsip_tx_data *tdata cdef int status cdef dict _sipfrag_version = dict(version="2.0") - cdef PJSTR _content_type = PJSTR("message") - cdef PJSTR _content_subtype = PJSTR("sipfrag") - cdef PJSTR reason = PJSTR("noresource") + cdef PJSTR _content_type = PJSTR(b"message") + cdef PJSTR _content_subtype = PJSTR(b"sipfrag") + cdef PJSTR reason = PJSTR(b"noresource") reason_p = NULL if self.state == "pending": state = PJSIP_EVSUB_STATE_PENDING elif self.state == "active": state = PJSIP_EVSUB_STATE_ACTIVE else: state = PJSIP_EVSUB_STATE_TERMINATED reason_p = &reason.pj_str with nogil: status = pjsip_evsub_notify(self._obj, state, NULL, reason_p, &tdata) if status != 0: raise PJSIPError("Could not create NOTIFY request", status) if self.state in ("active", "terminated"): tdata.msg.body = pjsip_msg_body_create(tdata.pool, &_content_type.pj_str, &_content_subtype.pj_str, &self._content.pj_str) _dict_to_pjsip_param(_sipfrag_version, &tdata.msg.body.content_type.param, tdata.pool) with nogil: status = pjsip_evsub_send_request(self._obj, tdata) if status != 0: raise PJSIPError("Could not send NOTIFY request", status) event_dict = dict(obj=self) _pjsip_msg_to_dict(tdata.msg, event_dict) _add_event("SIPIncomingReferralSentNotify", event_dict) return 0 cdef int _terminate(self, PJSIPUA ua, int do_cleanup) except -1: cdef int status self._set_state("terminated") self._send_notify() if do_cleanup: pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) self._obj = NULL _add_event("SIPIncomingReferralDidEnd", dict(obj=self)) cdef int _cb_rx_refresh(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: # PJSIP holds the dialog lock when this callback is entered cdef int status cdef pjsip_expires_hdr *expires_header cdef int expires cdef dict event_dict event_dict = dict(obj=self) _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) expires_header = pjsip_msg_find_hdr(rdata.msg_info.msg, PJSIP_H_EXPIRES, NULL) if expires_header == NULL: self._expires_time.sec = 600 self._expires_time.msec = 0 else: if expires_header.ivalue == 0: _add_event("SIPIncomingReferralGotUnsubscribe", event_dict) # cleanup will be done by _cb_tsx self._terminate(ua, 0) return 200 else: expires = min(expires_header.ivalue, 600) self._expires_time.sec = expires self._expires_time.msec = 0 _add_event("SIPIncomingReferralGotRefreshingSubscribe", event_dict) # Last NOTIFY will be resent self._send_notify() if self.state == "active": return 200 else: return 202 cdef int _cb_server_timeout(self, PJSIPUA ua) except -1: # PJSIP holds the dialog lock when this callback is entered self._terminate(ua, 1) cdef int _cb_tsx(self, PJSIPUA ua, pjsip_event *event) except -1: # PJSIP holds the dialog lock when this callback is entered cdef pjsip_rx_data *rdata cdef dict event_dict cdef int status_code if (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.tsx.role == PJSIP_ROLE_UAC and _pj_str_to_str(event.body.tsx_state.tsx.method.name) == "NOTIFY" and event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_COMPLETED): event_dict = dict(obj=self) rdata = event.body.tsx_state.src.rdata if rdata != NULL: if self.peer_address is None: self.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: self.peer_address.ip = rdata.pkt_info.src_name self.peer_address.port = rdata.pkt_info.src_port status_code = event.body.tsx_state.tsx.status_code if event.body.tsx_state.type==PJSIP_EVENT_RX_MSG and status_code/100==2: _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) try: self.remote_contact_header = event_dict['headers']['Contact'][0] except LookupError: pass _add_event("SIPIncomingReferralNotifyDidSucceed", event_dict) else: if event.body.tsx_state.type == PJSIP_EVENT_RX_MSG: _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) else: event_dict["code"] = status_code event_dict["reason"] = _pj_str_to_str(event.body.tsx_state.tsx.status_text) _add_event("SIPIncomingReferralNotifyDidFail", event_dict) if status_code in (408, 481) or status_code/100==7: # PJSIP will terminate the subscription and the dialog will be destroyed self._terminate(ua, 1) elif (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.tsx.role == PJSIP_ROLE_UAC and _pj_str_to_str(event.body.tsx_state.tsx.method.name) == "NOTIFY" and event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_TERMINATED): event_dict = dict(obj=self) status_code = event.body.tsx_state.tsx.status_code if status_code == 408: # Local timeout, PJSIP will terminate the subscription and the dialog will be destroyed event_dict["code"] = status_code event_dict["reason"] = _pj_str_to_str(event.body.tsx_state.tsx.status_text) _add_event("SIPIncomingReferralNotifyDidFail", event_dict) self._terminate(ua, 1) elif (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.tsx.role == PJSIP_ROLE_UAS and _pj_str_to_str(event.body.tsx_state.tsx.method.name) == "REFER" and event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_COMPLETED and event.body.tsx_state.type == PJSIP_EVENT_TX_MSG): event_dict = dict(obj=self) _pjsip_msg_to_dict(event.body.tsx_state.src.tdata.msg, event_dict) _add_event("SIPIncomingReferralAnsweredRefer", event_dict) if self.state == "terminated" and self._obj != NULL: pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) self._obj = NULL cdef void _Referral_cb_state(pjsip_evsub *sub, pjsip_event *event) with gil: cdef void *referral_void cdef Referral referral cdef object state cdef int code = 0 cdef dict event_dict = dict() cdef str reason = None cdef pjsip_rx_data *rdata = NULL cdef PJSIPUA ua try: ua = _get_ua() except: return try: referral_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if referral_void == NULL: return referral = referral_void state = pjsip_evsub_get_state_name(sub) if (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and (event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_COMPLETED or event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_TERMINATED)): if state == "TERMINATED": if event.body.tsx_state.tsx.role == PJSIP_ROLE_UAC: code = event.body.tsx_state.tsx.status_code reason = _pj_str_to_str(event.body.tsx_state.tsx.status_text) else: reason = "Referral has expired" if event.body.tsx_state.type == PJSIP_EVENT_RX_MSG and _pj_str_to_str(event.body.tsx_state.tsx.method.name) == "NOTIFY": # Extract code and reason from the sipfrag payload rdata = event.body.tsx_state.src.rdata if rdata != NULL: _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) if event_dict.get('body', None) is not None: match = sipfrag_re.match(event_dict['body']) if match: code = int(match.group('code')) reason = match.group('reason') referral._cb_state(ua, state, code, reason) except: ua._handle_exception(1) cdef void _Referral_cb_tsx(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) with gil: cdef void *referral_void cdef Referral referral cdef pjsip_rx_data *rdata cdef PJSIPUA ua try: ua = _get_ua() except: return try: referral_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if referral_void == NULL: return referral = referral_void if (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.type == PJSIP_EVENT_RX_MSG and event.body.tsx_state.tsx.role == PJSIP_ROLE_UAC and event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_COMPLETED and _pj_str_to_str(event.body.tsx_state.tsx.method.name) in ("REFER", "SUBSCRIBE") and event.body.tsx_state.tsx.status_code/100 == 2): rdata = event.body.tsx_state.src.rdata if rdata != NULL: if referral.peer_address is None: referral.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: referral.peer_address.ip = rdata.pkt_info.src_name referral.peer_address.port = rdata.pkt_info.src_port - referral._cb_got_response(ua, rdata, _pj_str_to_str(event.body.tsx_state.tsx.method.name)) + referral._cb_got_response(ua, rdata, _pj_str_to_bytes(event.body.tsx_state.tsx.method.name)) except: ua._handle_exception(1) cdef void _Referral_cb_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil: cdef void *referral_void cdef Referral referral cdef PJSIPUA ua try: ua = _get_ua() except: return try: referral_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if referral_void == NULL: return referral = referral_void if rdata != NULL: if referral.peer_address is None: referral.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: referral.peer_address.ip = rdata.pkt_info.src_name referral.peer_address.port = rdata.pkt_info.src_port referral._cb_notify(ua, rdata) except: ua._handle_exception(1) cdef void _Referral_cb_refresh(pjsip_evsub *sub) with gil: # We want to handle the refresh timer oursevles, ignore the PJSIP provided timer pass cdef void _Referral_cb_timer(pj_timer_heap_t *timer_heap, pj_timer_entry *entry) with gil: cdef Referral referral cdef PJSIPUA ua try: ua = _get_ua() except: return try: if entry.user_data != NULL: referral = entry.user_data if entry.id == 1: referral._refresh_timer_active = 0 referral._cb_refresh_timer(ua) else: referral._timeout_timer_active = 0 referral._cb_timeout_timer(ua) except: ua._handle_exception(1) cdef void _IncomingReferral_cb_rx_refresh(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil: cdef void *referral_void cdef IncomingReferral referral cdef PJSIPUA ua try: ua = _get_ua() except: return try: referral_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if referral_void == NULL: p_st_code[0] = 481 return referral = referral_void if rdata != NULL: if referral.peer_address is None: referral.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: referral.peer_address.ip = rdata.pkt_info.src_name referral.peer_address.port = rdata.pkt_info.src_port p_st_code[0] = referral._cb_rx_refresh(ua, rdata) except: ua._handle_exception(1) cdef void _IncomingReferral_cb_server_timeout(pjsip_evsub *sub) with gil: cdef void *referral_void cdef IncomingReferral referral cdef PJSIPUA ua try: ua = _get_ua() except: return try: referral_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if referral_void == NULL: return referral = referral_void referral._cb_server_timeout(ua) except: ua._handle_exception(1) cdef void _IncomingReferral_cb_tsx(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) with gil: cdef void *referral_void cdef IncomingReferral referral cdef PJSIPUA ua try: ua = _get_ua() except: return try: referral_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if referral_void == NULL: return referral = referral_void referral._cb_tsx(ua, event) except: ua._handle_exception(1) # Globals # cdef pjsip_evsub_user _refer_cb _refer_cb.on_evsub_state = _Referral_cb_state _refer_cb.on_tsx_state = _Referral_cb_tsx _refer_cb.on_rx_notify = _Referral_cb_notify _refer_cb.on_client_refresh = _Referral_cb_refresh cdef pjsip_evsub_user _incoming_refer_subs_cb _incoming_refer_subs_cb.on_rx_refresh = _IncomingReferral_cb_rx_refresh _incoming_refer_subs_cb.on_server_timeout = _IncomingReferral_cb_server_timeout _incoming_refer_subs_cb.on_tsx_state = _IncomingReferral_cb_tsx sipfrag_re = re.compile(r'^SIP/2\.0\s+(?P\d{3})\s+(?P[ a-zA-Z0-9_-]+)') -cdef PJSTR _refer_method = PJSTR("REFER") -cdef PJSTR _refer_event = PJSTR("refer") -cdef PJSTR _refer_to_hdr_name = PJSTR("Refer-To") -cdef PJSTR _refer_sub_hdr_name = PJSTR("Refer-Sub") -cdef PJSTR _subscription_state_hdr_name = PJSTR("Subscription-State") +cdef PJSTR _refer_method = PJSTR(b"REFER") +cdef PJSTR _refer_event = PJSTR(b"refer") +cdef PJSTR _refer_to_hdr_name = PJSTR(b"Refer-To") +cdef PJSTR _refer_sub_hdr_name = PJSTR(b"Refer-Sub") +cdef PJSTR _subscription_state_hdr_name = PJSTR(b"Subscription-State") diff --git a/sipsimple/core/_core.request.pxi b/sipsimple/core/_core.request.pxi index 38e53a21..80aa5c9b 100644 --- a/sipsimple/core/_core.request.pxi +++ b/sipsimple/core/_core.request.pxi @@ -1,493 +1,507 @@ from datetime import datetime, timedelta cdef class EndpointAddress: def __init__(self, ip, port): self.ip = ip self.port = port def __repr__(self): return "%s(%r, %r)" % (self.__class__.__name__, self.ip, self.port) def __str__(self): return "%s:%d" % (self.ip, self.port) cdef class Request: expire_warning_time = 30 # properties property method: def __get__(self): return self._method.str property call_id: def __get__(self): return self._call_id.str property content_type: def __get__(self): if self._content_type is None: return None else: return "/".join([self._content_type.str, self._content_subtype.str]) property body: def __get__(self): if self._body is None: return None else: return self._body.str property expires_in: def __get__(self): cdef object dt self._get_ua() if self.state != "EXPIRING" or self._expire_time is None: return 0 else: dt = self._expire_time - datetime.now() return max(0, dt.seconds) # public methods def __cinit__(self, *args, **kwargs): self.state = "INIT" self.peer_address = None pj_timer_entry_init(&self._timer, 0, self, _Request_cb_timer) self._timer_active = 0 def __init__(self, method, SIPURI request_uri not None, FromHeader from_header not None, ToHeader to_header not None, RouteHeader route_header not None, Credentials credentials=None, ContactHeader contact_header=None, call_id=None, cseq=None, object extra_headers=None, content_type=None, body=None): cdef pjsip_method method_pj cdef PJSTR from_header_str cdef PJSTR to_header_str cdef PJSTR request_uri_str cdef PJSTR contact_header_str cdef pj_str_t *contact_header_pj = NULL cdef pj_str_t *call_id_pj = NULL cdef object content_type_spl cdef pjsip_hdr *hdr cdef pjsip_contact_hdr *contact_hdr cdef pjsip_cid_hdr *cid_hdr cdef pjsip_cseq_hdr *cseq_hdr cdef int status + cdef dict event_dict cdef PJSIPUA ua = _get_ua() if self._tsx != NULL or self.state != "INIT": raise SIPCoreError("Request.__init__() was already called") if cseq is not None and cseq < 0: raise ValueError("cseq argument cannot be negative") if extra_headers is not None: header_names = set([header.name for header in extra_headers]) if "Route" in header_names: raise ValueError("Route should be specified with route_header argument, not extra_headers") if "Content-Type" in header_names: raise ValueError("Content-Type should be specified with content_type argument, not extra_headers") else: header_names = () if content_type is not None and body is None: raise ValueError("Cannot specify a content_type without a body") if content_type is None and body is not None: raise ValueError("Cannot specify a body without a content_type") - self._method = PJSTR(method) + self._method = PJSTR(method.encode()) pjsip_method_init_np(&method_pj, &self._method.pj_str) if credentials is not None: self.credentials = FrozenCredentials.new(credentials) - from_header_str = PJSTR(from_header.body) + from_header_str = PJSTR(from_header.body.encode()) self.to_header = FrozenToHeader.new(to_header) - to_header_str = PJSTR(to_header.body) + to_header_str = PJSTR(to_header.body.encode()) + struri = str(request_uri) self.request_uri = FrozenSIPURI.new(request_uri) - request_uri_str = PJSTR(str(request_uri)) + request_uri_str = PJSTR(struri.encode()) self.route_header = FrozenRouteHeader.new(route_header) + #self.route_header = FrozenRouteHeader(FrozenSIPURI(host=route_header.uri.host)) self.route_header.uri.parameters.dict["lr"] = None # always send lr parameter in Route header self.route_header.uri.parameters.dict["hide"] = None # always hide Route header if contact_header is not None: self.contact_header = FrozenContactHeader.new(contact_header) contact_parameters = contact_header.parameters.copy() contact_parameters.pop("q", None) contact_parameters.pop("expires", None) contact_header.parameters = {} - contact_header_str = PJSTR(contact_header.body) + contact_header_str = PJSTR(contact_header.body.encode()) contact_header_pj = &contact_header_str.pj_str if call_id is not None: - self._call_id = PJSTR(call_id) + self._call_id = PJSTR(call_id.encode()) call_id_pj = &self._call_id.pj_str if cseq is None: self.cseq = -1 else: self.cseq = cseq if extra_headers is None: self.extra_headers = frozenlist() else: self.extra_headers = frozenlist([header.frozen_type.new(header) for header in extra_headers]) if body is not None: content_type_spl = content_type.split("/", 1) - self._content_type = PJSTR(content_type_spl[0]) - self._content_subtype = PJSTR(content_type_spl[1]) + self._content_type = PJSTR(content_type_spl[0].encode()) + self._content_subtype = PJSTR(content_type_spl[1].encode()) self._body = PJSTR(body) + status = pjsip_endpt_create_request(ua._pjsip_endpoint._obj, &method_pj, &request_uri_str.pj_str, &from_header_str.pj_str, &to_header_str.pj_str, contact_header_pj, call_id_pj, self.cseq, NULL, &self._tdata) if status != 0: raise PJSIPError("Could not create request", status) if body is not None: self._tdata.msg.body = pjsip_msg_body_create(self._tdata.pool, &self._content_type.pj_str, &self._content_subtype.pj_str, &self._body.pj_str) + + status = _BaseRouteHeader_to_pjsip_route_hdr(self.route_header, &self._route_header, self._tdata.pool) + pjsip_msg_add_hdr(self._tdata.msg, &self._route_header) + hdr = ( &self._tdata.msg.hdr).next while hdr != &self._tdata.msg.hdr: - if _pj_str_to_str(hdr.name) in header_names: - raise ValueError("Cannot override %s header value in extra_headers" % _pj_str_to_str(hdr.name)) + hdr_name = _pj_str_to_str(hdr.name) + if hdr_name in header_names: + raise ValueError("Cannot override %s header value in extra_headers" % _pj_str_to_bytes(hdr.name)) + if hdr.type == PJSIP_H_CONTACT: contact_hdr = hdr _dict_to_pjsip_param(contact_parameters, &contact_hdr.other_param, self._tdata.pool) elif hdr.type == PJSIP_H_CALL_ID: cid_hdr = hdr - self._call_id = PJSTR(_pj_str_to_str(cid_hdr.id)) + self._call_id = PJSTR(_pj_str_to_bytes(cid_hdr.id)) elif hdr.type == PJSIP_H_CSEQ: cseq_hdr = hdr self.cseq = cseq_hdr.cseq elif hdr.type == PJSIP_H_FROM: self.from_header = FrozenFromHeader_create( hdr) else: pass hdr = ( hdr).next - _BaseRouteHeader_to_pjsip_route_hdr(self.route_header, &self._route_header, self._tdata.pool) - pjsip_msg_add_hdr(self._tdata.msg, &self._route_header) + _add_headers_to_tdata(self._tdata, self.extra_headers) + + event_dict = dict(obj=self) + _pjsip_msg_to_dict(self._tdata.msg, event_dict) + print('Request dict %s' % event_dict) + if self.credentials is not None: status = pjsip_auth_clt_init(&self._auth, ua._pjsip_endpoint._obj, self._tdata.pool, 0) if status != 0: raise PJSIPError("Could not init authentication credentials", status) status = pjsip_auth_clt_set_credentials(&self._auth, 1, self.credentials.get_cred_info()) if status != 0: raise PJSIPError("Could not set authentication credentials", status) self._need_auth = 1 else: self._need_auth = 0 status = pjsip_tsx_create_uac(&ua._module, self._tdata, &self._tsx) if status != 0: raise PJSIPError("Could not create transaction for request", status) self._tsx.mod_data[ua._module.id] = self def __dealloc__(self): cdef PJSIPUA ua = self._get_ua() if self._tsx != NULL: self._tsx.mod_data[ua._module.id] = NULL if self._tsx.state < PJSIP_TSX_STATE_COMPLETED: pjsip_tsx_terminate(self._tsx, 500) self._tsx = NULL if self._tdata != NULL: pjsip_tx_data_dec_ref(self._tdata) self._tdata = NULL if self._timer_active: pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._timer) self._timer_active = 0 def send(self, timeout=None): cdef pj_time_val timeout_pj cdef int status cdef PJSIPUA ua = self._get_ua() if self.state != "INIT": raise SIPCoreError('This method may only be called in the "INIT" state, current state is "%s"' % self.state) if timeout is not None: if timeout <= 0: raise ValueError("Timeout value cannot be negative") timeout_pj.sec = int(timeout) timeout_pj.msec = (timeout * 1000) % 1000 self._timeout = timeout status = pjsip_tsx_send_msg(self._tsx, self._tdata) if status != 0: raise PJSIPError("Could not send request", status) pjsip_tx_data_add_ref(self._tdata) if timeout: status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._timer, &timeout_pj) if status == 0: self._timer_active = 1 self.state = "IN_PROGRESS" def end(self): cdef PJSIPUA ua = self._get_ua() if self.state == "IN_PROGRESS": pjsip_tsx_terminate(self._tsx, 408) elif self.state == "EXPIRING": pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._timer) self._timer_active = 0 self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) # private methods cdef PJSIPUA _get_ua(self): cdef PJSIPUA ua try: ua = _get_ua() except SIPCoreError: self._tsx = NULL self._tdata = NULL self._timer_active = 0 self.state = "TERMINATED" return None else: return ua cdef int _cb_tsx_state(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: cdef pjsip_tx_data *tdata_auth cdef pjsip_transaction *tsx_auth cdef pjsip_cseq_hdr *cseq cdef dict event_dict cdef int expires = -1 cdef SIPURI contact_uri cdef dict contact_params cdef pj_time_val timeout_pj cdef int status if rdata != NULL: self.to_header = FrozenToHeader_create(rdata.msg_info.to_hdr) if self.peer_address is None: self.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: self.peer_address.ip = rdata.pkt_info.src_name self.peer_address.port = rdata.pkt_info.src_port if self._tsx.state == PJSIP_TSX_STATE_PROCEEDING: if rdata == NULL: return 0 event_dict = dict(obj=self) _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) _add_event("SIPRequestGotProvisionalResponse", event_dict) elif self._tsx.state == PJSIP_TSX_STATE_COMPLETED: if self._timer_active: pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._timer) self._timer_active = 0 if self._need_auth and self._tsx.status_code in [401, 407]: self._need_auth = 0 status = pjsip_auth_clt_reinit_req(&self._auth, rdata, self._tdata, &tdata_auth) if status != 0: _add_event("SIPRequestDidFail", dict(obj=self, code=0, reason="Could not add auth data to request %s" % _pj_status_to_str(status))) self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) return 0 cseq = pjsip_msg_find_hdr(tdata_auth.msg, PJSIP_H_CSEQ, NULL) if cseq != NULL: cseq.cseq += 1 self.cseq = cseq.cseq status = pjsip_tsx_create_uac(&ua._module, tdata_auth, &tsx_auth) if status != 0: pjsip_tx_data_dec_ref(tdata_auth) _add_event("SIPRequestDidFail", dict(obj=self, code=0, reason="Could not create transaction for request with auth %s" % _pj_status_to_str(status))) self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) return 0 self._tsx.mod_data[ua._module.id] = NULL self._tsx = tsx_auth self._tsx.mod_data[ua._module.id] = self status = pjsip_tsx_send_msg(self._tsx, tdata_auth) if status != 0: pjsip_tx_data_dec_ref(tdata_auth) _add_event("SIPRequestDidFail", dict(obj=self, code=0, reason="Could not send request with auth %s" % _pj_status_to_str(status))) self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) return 0 elif self._timeout is not None: timeout_pj.sec = int(self._timeout) timeout_pj.msec = (self._timeout * 1000) % 1000 status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._timer, &timeout_pj) if status == 0: self._timer_active = 1 else: event_dict = dict(obj=self) if rdata != NULL: # This shouldn't happen, but safety fist! _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) if self._tsx.status_code / 100 == 2: if rdata != NULL: if "Expires" in event_dict["headers"]: expires = event_dict["headers"]["Expires"] elif self.contact_header is not None: for contact_header in event_dict["headers"].get("Contact", []): if contact_header.uri == self.contact_header.uri and contact_header.expires is not None: expires = contact_header.expires if expires == -1: expires = 0 for header in self.extra_headers: if header.name == "Expires": try: expires = int(header.body) except ValueError: pass break event_dict["expires"] = expires self._expire_time = datetime.now() + timedelta(seconds=expires) _add_event("SIPRequestDidSucceed", event_dict) else: expires = 0 _add_event("SIPRequestDidFail", event_dict) if expires == 0: self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) else: timeout_pj.sec = max(1, expires - self.expire_warning_time, expires/2) timeout_pj.msec = 0 status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._timer, &timeout_pj) if status == 0: self._timer_active = 1 self.state = "EXPIRING" self._expire_rest = max(1, expires - timeout_pj.sec) else: self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) elif self._tsx.state == PJSIP_TSX_STATE_TERMINATED: if self.state == "IN_PROGRESS": if self._timer_active: pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._timer) self._timer_active = 0 _add_event("SIPRequestDidFail", dict(obj=self, code=self._tsx.status_code, - reason=_pj_str_to_str(self._tsx.status_text))) + reason=_pj_str_to_bytes(self._tsx.status_text))) self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) self._tsx.mod_data[ua._module.id] = NULL self._tsx = NULL else: pass cdef int _cb_timer(self, PJSIPUA ua) except -1: cdef pj_time_val expires cdef int status if self.state == "IN_PROGRESS": pjsip_tsx_terminate(self._tsx, 408) elif self.state == "EXPIRING": if self._expire_rest > 0: _add_event("SIPRequestWillExpire", dict(obj=self, expires=self._expire_rest)) expires.sec = self._expire_rest expires.msec = 0 self._expire_rest = 0 status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._timer, &expires) if status == 0: self._timer_active = 1 else: self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) else: self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) return 0 cdef class IncomingRequest: def __cinit__(self, *args, **kwargs): self.peer_address = None def __dealloc__(self): cdef PJSIPUA ua try: ua = _get_ua() except SIPCoreError: return if self._tsx != NULL: pjsip_tsx_terminate(self._tsx, 500) self._tsx = NULL if self._tdata != NULL: pjsip_tx_data_dec_ref(self._tdata) self._tdata = NULL def answer(self, int code, str reason=None, object extra_headers=None): cdef bytes reason_bytes cdef dict event_dict cdef int status cdef PJSIPUA ua = _get_ua() if self.state != "incoming": raise SIPCoreInvalidStateError('Can only answer an incoming request in the "incoming" state, ' 'object is currently in the "%s" state' % self.state) if code < 200 or code >= 700: raise ValueError("Invalid SIP final response code: %d" % code) self._tdata.msg.line.status.code = code if reason is None: self._tdata.msg.line.status.reason = pjsip_get_status_text(code)[0] else: reason_bytes = reason.encode() pj_strdup2_with_null(self._tdata.pool, &self._tdata.msg.line.status.reason, reason_bytes) if extra_headers is not None: _add_headers_to_tdata(self._tdata, extra_headers) event_dict = dict(obj=self) _pjsip_msg_to_dict(self._tdata.msg, event_dict) status = pjsip_tsx_send_msg(self._tsx, self._tdata) if status != 0: raise PJSIPError("Could not send response", status) self.state = "answered" self._tdata = NULL self._tsx = NULL _add_event("SIPIncomingRequestSentResponse", event_dict) cdef int init(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: cdef dict event_dict cdef int status status = pjsip_endpt_create_response(ua._pjsip_endpoint._obj, rdata, 500, NULL, &self._tdata) if status != 0: raise PJSIPError("Could not create response", status) status = pjsip_tsx_create_uas(&ua._module, rdata, &self._tsx) if status != 0: pjsip_tx_data_dec_ref(self._tdata) self._tdata = NULL raise PJSIPError("Could not create transaction for incoming request", status) pjsip_tsx_recv_msg(self._tsx, rdata) self.state = "incoming" self.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) event_dict = dict(obj=self) _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) _add_event("SIPIncomingRequestGotRequest", event_dict) # callback functions cdef void _Request_cb_tsx_state(pjsip_transaction *tsx, pjsip_event *event) with gil: cdef PJSIPUA ua cdef void *req_ptr cdef Request req cdef pjsip_rx_data *rdata = NULL try: ua = _get_ua() except: return try: req_ptr = tsx.mod_data[ua._module.id] if req_ptr != NULL: req = req_ptr if event.type == PJSIP_EVENT_RX_MSG: rdata = event.body.rx_msg.rdata elif event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.type == PJSIP_EVENT_RX_MSG: rdata = event.body.tsx_state.src.rdata req._cb_tsx_state(ua, rdata) except: ua._handle_exception(1) cdef void _Request_cb_timer(pj_timer_heap_t *timer_heap, pj_timer_entry *entry) with gil: cdef PJSIPUA ua cdef Request req try: ua = _get_ua() except: return try: if entry.user_data != NULL: req = entry.user_data req._timer_active = 0 req._cb_timer(ua) except: ua._handle_exception(1) diff --git a/sipsimple/core/_core.sdp.pxi b/sipsimple/core/_core.sdp.pxi index 49cf3741..f907348b 100644 --- a/sipsimple/core/_core.sdp.pxi +++ b/sipsimple/core/_core.sdp.pxi @@ -1,1227 +1,1227 @@ import re from application.python.descriptor import WriteOnceAttribute cdef object BaseSDPSession_richcmp(object self, object other, int op) with gil: cdef int eq = 1 if op not in [2,3]: return NotImplemented if not isinstance(other, BaseSDPSession): return NotImplemented for attr in ("id", "version", "user", "net_type", "address_type", "address", "address", "name", "connection", "start_time", "stop_time", "attributes", "bandwidth_info", "media"): if getattr(self, attr) != getattr(other, attr): eq = 0 break if op == 2: return bool(eq) else: return not eq cdef pjmedia_sdp_session* _parse_sdp_session(str sdp): cdef int status cdef pjmedia_sdp_session *sdp_session status = pjmedia_sdp_parse(_get_ua()._pjsip_endpoint._pool, _str_as_str(sdp), _str_as_size(sdp), &sdp_session) if status != 0: raise PJSIPError("failed to parse SDP", status) return sdp_session cdef class BaseSDPSession: def __init__(self, *args, **kwargs): raise TypeError("BaseSDPSession cannot be instantiated directly") def __repr__(self): return "%s(%r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r)" % (self.__class__.__name__, self.address, self.id, self.version, self.user, self.net_type, self.address_type, self.name, self.connection, self.start_time, self.stop_time, self.attributes, self.bandwidth_info, self.media) def __str__(self): cdef char cbuf[2048] cdef int buf_len buf_len = pjmedia_sdp_print(self.get_sdp_session(), cbuf, sizeof(cbuf)) if buf_len > -1: return _pj_buf_len_to_str(cbuf, buf_len) return '' def __richcmp__(self, other, op): return BaseSDPSession_richcmp(self, other, op) cdef pjmedia_sdp_session* get_sdp_session(self): self._sdp_session.media_count = len(self.media) for index, m in enumerate(self.media): if m is not None: self._sdp_session.media[index] = (m).get_sdp_media() else: self._sdp_session.media[index] = NULL self._sdp_session.attr_count = len(self.attributes) for index, attr in enumerate(self.attributes): self._sdp_session.attr[index] = (attr).get_sdp_attribute() self._sdp_session.bandw_count = len(self.bandwidth_info) for index, info in enumerate(self.bandwidth_info): self._sdp_session.bandw[index] = (info).get_sdp_bandwidth_info() return &self._sdp_session property has_ice_attributes: def __get__(self): return set([attr.name for attr in self.attributes]).issuperset(['ice-pwd', 'ice-ufrag']) cdef class SDPSession(BaseSDPSession): def __init__(self, str address not None, object id=None, object version=None, str user not None="-", str net_type not None="IN", str address_type not None="IP4", str name not None=" ", SDPConnection connection=None, unsigned long start_time=0, unsigned long stop_time=0, list attributes=None, list bandwidth_info=None, list media=None): cdef unsigned int version_id = 2208988800UL cdef pj_time_val tv pj_gettimeofday(&tv) version_id += tv.sec self.address = address self.id = id if id is not None else version_id self.version = version if version is not None else version_id self.user = user self.net_type = net_type self.address_type = address_type self.name = name self.connection = connection self.start_time = start_time self.stop_time = stop_time self.attributes = attributes if attributes is not None else [] self.bandwidth_info = bandwidth_info if bandwidth_info is not None else [] self.media = media if media is not None else [] @classmethod def new(cls, BaseSDPSession sdp_session): connection = SDPConnection.new(sdp_session.connection) if (sdp_session.connection is not None) else None attributes = [SDPAttribute.new(attr) for attr in sdp_session.attributes] bandwidth_info = [SDPBandwidthInfo.new(info) for info in sdp_session.bandwidth_info] media = [SDPMediaStream.new(m) if m is not None else None for m in sdp_session.media] return cls(sdp_session.address, sdp_session.id, sdp_session.version, sdp_session.user, sdp_session.net_type, sdp_session.address_type, sdp_session.name, connection, sdp_session.start_time, sdp_session.stop_time, attributes, bandwidth_info, media) @classmethod def parse(cls, str sdp): cdef pjmedia_sdp_session *sdp_session sdp_session = _parse_sdp_session(sdp) return SDPSession_create(sdp_session) property address: def __get__(self): return self._address def __set__(self, str address not None): - _str_to_pj_str(address, &self._sdp_session.origin.addr) + _str_to_pj_str(address.encode(), &self._sdp_session.origin.addr) self._address = address property id: def __get__(self): return self._sdp_session.origin.id def __set__(self, unsigned int id): self._sdp_session.origin.id = id property version: def __get__(self): return self._sdp_session.origin.version def __set__(self, unsigned int version): self._sdp_session.origin.version = version property user: def __get__(self): return self._user def __set__(self, str user not None): - _str_to_pj_str(user, &self._sdp_session.origin.user) + _str_to_pj_str(user.encode(), &self._sdp_session.origin.user) self._user = user property net_type: def __get__(self): return self._net_type def __set__(self, str net_type not None): - _str_to_pj_str(net_type, &self._sdp_session.origin.net_type) + _str_to_pj_str(net_type.encode(), &self._sdp_session.origin.net_type) self._net_type = net_type property address_type: def __get__(self): return self._address_type def __set__(self, str address_type not None): - _str_to_pj_str(address_type, &self._sdp_session.origin.addr_type) + _str_to_pj_str(address_type.encode(), &self._sdp_session.origin.addr_type) self._address_type = address_type property name: def __get__(self): return self._name def __set__(self, str name not None): - _str_to_pj_str(name, &self._sdp_session.name) + _str_to_pj_str(name.encode(), &self._sdp_session.name) self._name = name property connection: def __get__(self): return self._connection def __set__(self, SDPConnection connection): if connection is None: self._sdp_session.conn = NULL else: self._sdp_session.conn = connection.get_sdp_connection() self._connection = connection property start_time: def __get__(self): return self._sdp_session.time.start def __set__(self, unsigned long start_time): self._sdp_session.time.start = start_time property stop_time: def __get__(self): return self._sdp_session.time.stop def __set__(self, unsigned long stop_time): self._sdp_session.time.stop = stop_time property attributes: def __get__(self): return self._attributes def __set__(self, list attributes not None): if len(attributes) > PJMEDIA_MAX_SDP_ATTR: raise SIPCoreError("Too many attributes") for attr in attributes: if not isinstance(attr, SDPAttribute): raise TypeError("Items in SDPSession attribute list must be SDPAttribute instancess") if not isinstance(attributes, SDPAttributeList): attributes = SDPAttributeList(attributes) self._attributes = attributes property bandwidth_info: def __get__(self): return self._bandwidth_info def __set__(self, list infos not None): if len(infos) > PJMEDIA_MAX_SDP_BANDW: raise SIPCoreError("Too many bandwidth info attributes") for info in infos: if not isinstance(info, SDPBandwidthInfo): raise TypeError("Items in SDPSession attribute list must be SDPBandwidthInfo instancess") if not isinstance(infos, SDPBandwidthInfoList): infos = SDPBandwidthInfoList(infos) self._bandwidth_info = infos property media: def __get__(self): return self._media def __set__(self, list media not None): if len(media) > PJMEDIA_MAX_SDP_MEDIA: raise SIPCoreError("Too many media objects") for m in media: if m is not None and not isinstance(m, SDPMediaStream): raise TypeError("Items in SDPSession media list must be SDPMediaStream instancess") self._media = media cdef int _update(self) except -1: cdef SDPSession session cdef SDPMediaStream media, old_media session = SDPSession_create(&(self)._sdp_session) if len(self._media) != len(session._media): raise ValueError("Number of media streams in SDPSession got changed") if len(self._attributes) > len(session._attributes): raise ValueError("Number of attributes in SDPSession got reduced") for attr in ("id", "version", "user", "net_type", "address_type", "address", "name", "start_time", "stop_time"): setattr(self, attr, getattr(session, attr)) if session._connection is None: self.connection = None elif self._connection is None or self._connection != session._connection: self.connection = session._connection for index, attribute in enumerate(session._attributes): try: old_attribute = self._attributes[index] except IndexError: self._attributes.append(attribute) else: if old_attribute != attribute: self._attributes[index] = attribute for index, info in enumerate(session._bandwidth_info): try: old_info = self._bandwidth_info[index] except IndexError: self._bandwidth_info.append(info) else: if old_info != info: self._bandwidth_info[index] = info for index, media in enumerate(session._media): old_media = self._media[index] if old_media is not None: old_media._update(media) cdef class FrozenSDPSession(BaseSDPSession): def __init__(self, str address not None, object id=None, object version=None, str user not None="-", str net_type not None="IN", str address_type not None="IP4", str name not None=" ", FrozenSDPConnection connection=None, unsigned long start_time=0, unsigned long stop_time=0, frozenlist attributes not None=frozenlist(), frozenlist bandwidth_info not None=frozenlist(), frozenlist media not None=frozenlist()): cdef unsigned int version_id = 2208988800UL cdef pj_time_val tv if not self.initialized: if len(attributes) > PJMEDIA_MAX_SDP_ATTR: raise SIPCoreError("Too many attributes") for attr in attributes: if not isinstance(attr, FrozenSDPAttribute): raise TypeError("Items in FrozenSDPSession attribute list must be FrozenSDPAttribute instances") if len(bandwidth_info) > PJMEDIA_MAX_SDP_BANDW: raise SIPCoreError("Too many bandwidth info attributes") for info in bandwidth_info: if not isinstance(info, FrozenSDPBandwidthInfo): raise TypeError("Items in FrozenSDPSession bandwidth info attribute list must be FrozenSDPBandwidthInfo instances") if len(media) > PJMEDIA_MAX_SDP_MEDIA: raise SIPCoreError("Too many media objects") for m in media: if not isinstance(m, FrozenSDPMediaStream): raise TypeError("Items in FrozenSDPSession media list must be FrozenSDPMediaStream instancess") pj_gettimeofday(&tv) version_id += tv.sec self.address = address - _str_to_pj_str(address, &self._sdp_session.origin.addr) + _str_to_pj_str(address.encode(), &self._sdp_session.origin.addr) self.id = id if id is not None else version_id self._sdp_session.origin.id = id if id is not None else version_id self.version = version if version is not None else version_id self._sdp_session.origin.version = version if version is not None else version_id self.user = user - _str_to_pj_str(user, &self._sdp_session.origin.user) + _str_to_pj_str(user.encode(), &self._sdp_session.origin.user) self.net_type = net_type - _str_to_pj_str(net_type, &self._sdp_session.origin.net_type) + _str_to_pj_str(net_type.encode(), &self._sdp_session.origin.net_type) self.address_type = address_type - _str_to_pj_str(address_type, &self._sdp_session.origin.addr_type) + _str_to_pj_str(address_type.encode(), &self._sdp_session.origin.addr_type) self.name = name - _str_to_pj_str(name, &self._sdp_session.name) + _str_to_pj_str(name.encode(), &self._sdp_session.name) self.connection = connection if connection is None: self._sdp_session.conn = NULL else: self._sdp_session.conn = connection.get_sdp_connection() self.start_time = start_time self._sdp_session.time.start = start_time self.stop_time = stop_time self._sdp_session.time.stop = stop_time self.attributes = FrozenSDPAttributeList(attributes) if not isinstance(attributes, FrozenSDPAttributeList) else attributes self.bandwidth_info = FrozenSDPBandwidthInfoList(bandwidth_info) if not isinstance(bandwidth_info, FrozenSDPBandwidthInfo) else bandwidth_info self.media = media self.initialized = 1 @classmethod def new(cls, BaseSDPSession sdp_session): if isinstance(sdp_session, FrozenSDPSession): return sdp_session connection = FrozenSDPConnection.new(sdp_session.connection) if (sdp_session.connection is not None) else None attributes = frozenlist([FrozenSDPAttribute.new(attr) for attr in sdp_session.attributes]) bandwidth_info = frozenlist([FrozenSDPBandwidthInfo.new(info) for info in sdp_session.bandwidth_info]) media = frozenlist([FrozenSDPMediaStream.new(m) for m in sdp_session.media]) return cls(sdp_session.address, sdp_session.id, sdp_session.version, sdp_session.user, sdp_session.net_type, sdp_session.address_type, sdp_session.name, connection, sdp_session.start_time, sdp_session.stop_time, attributes, bandwidth_info, media) @classmethod def parse(cls, str sdp): cdef pjmedia_sdp_session *sdp_session sdp_session = _parse_sdp_session(sdp) return FrozenSDPSession_create(sdp_session) def __hash__(self): return hash((self.address, self.id, self.version, self.user, self.net_type, self.address_type, self.name, self.connection, self.start_time, self.stop_time, self.attributes, self.bandwidth_info, self.media)) def __richcmp__(self, other, op): return BaseSDPSession_richcmp(self, other, op) class MediaCodec(object): name = WriteOnceAttribute() rate = WriteOnceAttribute() def __init__(self, name, rate): self.name = name self.rate = int(rate) def __repr__(self): return "%s(%r, %r)" % (self.__class__.__name__, self.name, self.rate) def __str__(self): return "%s/%s" % (self.name, self.rate) def __hash__(self): return hash(self.name) def __eq__(self, other): if isinstance(other, MediaCodec): return self.name.lower() == other.name.lower() and self.rate == other.rate elif isinstance(other, basestring): if '/' in other: return self.__str__().lower() == other.lower() else: return self.name.lower() == other.lower() return False def __ne__(self, other): return not self.__eq__(other) cdef object BaseSDPMediaStream_richcmp(object self, object other, int op) with gil: cdef int eq = 1 if op not in [2,3]: return NotImplemented if not isinstance(other, BaseSDPMediaStream): return NotImplemented for attr in ("media", "port", "port_count", "transport", "formats", "connection", "attributes", "bandwidth_info"): if getattr(self, attr) != getattr(other, attr): eq = 0 break if op == 2: return bool(eq) else: return not eq cdef class BaseSDPMediaStream: rtpmap_re = re.compile(r"""^(?P\d+)\s+(?P[-\w]+)/(?P\d+)(?:/\w+)?$""", re.IGNORECASE | re.MULTILINE) rtp_mappings = { 0: MediaCodec('PCMU', 8000), 3: MediaCodec('GSM', 8000), 4: MediaCodec('G723', 8000), 5: MediaCodec('DVI4', 8000), 6: MediaCodec('DVI4', 16000), 7: MediaCodec('LPC', 8000), 8: MediaCodec('PCMA', 8000), 9: MediaCodec('G722', 8000), 10: MediaCodec('L16', 44100), # 2 channels 11: MediaCodec('L16', 44100), # 1 channel 12: MediaCodec('QCELP', 8000), 13: MediaCodec('CN', 8000), 14: MediaCodec('MPA', 90000), 15: MediaCodec('G728', 8000), 16: MediaCodec('DVI4', 11025), 17: MediaCodec('DVI4', 22050), 18: MediaCodec('G729', 8000)} def __init__(self, *args, **kwargs): raise TypeError("BaseSDPMediaStream cannot be instantiated directly") def __repr__(self): return "%s(%r, %r, %r, %r, %r, %r, %r, %r)" % (self.__class__.__name__, self.media, self.port, self.transport, self.port_count, self.formats, self.connection, self.attributes, self.bandwidth_info) def __richcmp__(self, other, op): return BaseSDPMediaStream_richcmp(self, other, op) property direction: def __get__(self): for attribute in self.attributes: if attribute.name in ("sendrecv", "sendonly", "recvonly", "inactive"): return attribute.name return "sendrecv" property has_ice_attributes: def __get__(self): return set([attr.name for attr in self.attributes]).issuperset(['ice-pwd', 'ice-ufrag']) property has_ice_candidates: def __get__(self): return 'candidate' in self.attributes cdef pjmedia_sdp_media* get_sdp_media(self): self._sdp_media.attr_count = len(self.attributes) for index, attr in enumerate(self.attributes): self._sdp_media.attr[index] = (attr).get_sdp_attribute() self._sdp_media.bandw_count = len(self.bandwidth_info) for index, info in enumerate(self.bandwidth_info): self._sdp_media.bandw[index] = (info).get_sdp_bandwidth_info() return &self._sdp_media cdef class SDPMediaStream(BaseSDPMediaStream): def __init__(self, str media not None, int port, str transport not None, int port_count=1, list formats=None, SDPConnection connection=None, list attributes=None, list bandwidth_info=None): self.media = media self.port = port self.transport = transport self.port_count = port_count self.formats = formats if formats is not None else [] self.connection = connection self.attributes = attributes if attributes is not None else [] self.bandwidth_info = bandwidth_info if bandwidth_info is not None else [] @classmethod def new(cls, BaseSDPMediaStream sdp_media): connection = SDPConnection.new(sdp_media.connection) if (sdp_media.connection is not None) else None attributes = [SDPAttribute.new(attr) for attr in sdp_media.attributes] bandwidth_info = [SDPBandwidthInfo.new(bi) for bi in sdp_media.bandwidth_info] return cls(sdp_media.media, sdp_media.port, sdp_media.transport, sdp_media.port_count, list(sdp_media.formats), connection, attributes, bandwidth_info) property media: def __get__(self): return self._media def __set__(self, str media not None): - _str_to_pj_str(media, &self._sdp_media.desc.media) + _str_to_pj_str(media.encode(), &self._sdp_media.desc.media) self._media = media property port: def __get__(self): return self._sdp_media.desc.port def __set__(self, int port): self._sdp_media.desc.port = port property transport: def __get__(self): return self._transport def __set__(self, str transport not None): - _str_to_pj_str(transport, &self._sdp_media.desc.transport) + _str_to_pj_str(transport.encode(), &self._sdp_media.desc.transport) self._transport = transport property port_count: def __get__(self): return self._sdp_media.desc.port_count def __set__(self, int port_count): self._sdp_media.desc.port_count = port_count property formats: def __get__(self): return self._formats def __set__(self, list formats not None): if len(formats) > PJMEDIA_MAX_SDP_FMT: raise SIPCoreError("Too many formats") self._sdp_media.desc.fmt_count = len(formats) for index, format in enumerate(formats): - _str_to_pj_str(format, &self._sdp_media.desc.fmt[index]) + _str_to_pj_str(format.encode(), &self._sdp_media.desc.fmt[index]) self._formats = formats property codec_list: def __get__(self): return self._codec_list property connection: def __get__(self): return self._connection def __set__(self, SDPConnection connection): if connection is None: self._sdp_media.conn = NULL else: self._sdp_media.conn = connection.get_sdp_connection() self._connection = connection property attributes: def __get__(self): return self._attributes def __set__(self, list attributes not None): if len(attributes) > PJMEDIA_MAX_SDP_ATTR: raise SIPCoreError("Too many attributes") for attr in attributes: if not isinstance(attr, SDPAttribute): raise TypeError("Items in SDPMediaStream attribute list must be SDPAttribute instances") if not isinstance(attributes, SDPAttributeList): attributes = SDPAttributeList(attributes) self._attributes = attributes if self._media in ("audio", "video"): rtp_mappings = self.rtp_mappings.copy() rtpmap_lines = '\n'.join([attr.value for attr in attributes if attr.name=='rtpmap']) # iterators are not supported -Dan rtpmap_codecs = dict([(int(type), MediaCodec(name, rate)) for type, name, rate in self.rtpmap_re.findall(rtpmap_lines)]) rtp_mappings.update(rtpmap_codecs) self._codec_list = [rtp_mappings.get(int(format), MediaCodec('Unknown', 0)) for format in self.formats] else: self._codec_list = list() property bandwidth_info: def __get__(self): return self._bandwidth_info def __set__(self, list infos not None): if len(infos) > PJMEDIA_MAX_SDP_BANDW: raise SIPCoreError("Too many bandwidth information attributes") for info in infos: if not isinstance(info, SDPBandwidthInfo): raise TypeError("Items in SDPMediaStream bandwidth_info list must be SDPBandwidthInfo instances") if not isinstance(infos, SDPBandwidthInfoList): infos = SDPBandwidthInfoList(infos) self._bandwidth_info = infos cdef int _update(self, SDPMediaStream media) except -1: if len(self._attributes) > len(media._attributes): raise ValueError("Number of attributes in SDPMediaStream got reduced") if len(self._bandwidth_info) > len(media._bandwidth_info): raise ValueError("Number of bandwidth info attributes in SDPMediaStream got reduced") for attr in ("media", "port", "transport", "port_count", "formats"): setattr(self, attr, getattr(media, attr)) if media._connection is None: self.connection = None elif self._connection is None or self._connection != media.connection: self.connection = media._connection for index, attribute in enumerate(media._attributes): try: old_attribute = self._attributes[index] except IndexError: self._attributes.append(attribute) else: if old_attribute != attribute: self._attributes[index] = attribute for index, info in enumerate(media._bandwidth_info): try: old_info = self._bandwidth_info[index] except IndexError: self._bandwidth_info.append(info) else: if old_info != info: self._bandwidth_info[index] = info cdef class FrozenSDPMediaStream(BaseSDPMediaStream): def __init__(self, str media not None, int port, str transport not None, int port_count=1, frozenlist formats not None=frozenlist(), FrozenSDPConnection connection=None, frozenlist attributes not None=frozenlist(), frozenlist bandwidth_info not None=frozenlist()): if not self.initialized: if len(formats) > PJMEDIA_MAX_SDP_FMT: raise SIPCoreError("Too many formats") if len(attributes) > PJMEDIA_MAX_SDP_ATTR: raise SIPCoreError("Too many attributes") for attr in attributes: if not isinstance(attr, FrozenSDPAttribute): raise TypeError("Items in FrozenSDPMediaStream attribute list must be FrozenSDPAttribute instances") if len(bandwidth_info) > PJMEDIA_MAX_SDP_BANDW: raise SIPCoreError("Too many bandwidth info attributes") for info in bandwidth_info: if not isinstance(info, FrozenSDPBandwidthInfo): raise TypeError("Items in FrozenSDPMediaStream bandwidth info list must be FrozenSDPBandwidthInfo instances") self.media = media - _str_to_pj_str(media, &self._sdp_media.desc.media) + _str_to_pj_str(media.encode(), &self._sdp_media.desc.media) self.port = port self._sdp_media.desc.port = port self.transport = transport - _str_to_pj_str(transport, &self._sdp_media.desc.transport) + _str_to_pj_str(transport.encode(), &self._sdp_media.desc.transport) self.port_count = port_count self._sdp_media.desc.port_count = port_count self.formats = formats self._sdp_media.desc.fmt_count = len(self.formats) for index, format in enumerate(self.formats): - _str_to_pj_str(format, &self._sdp_media.desc.fmt[index]) + _str_to_pj_str(format.encode(), &self._sdp_media.desc.fmt[index]) self.connection = connection if connection is None: self._sdp_media.conn = NULL else: self._sdp_media.conn = connection.get_sdp_connection() self.attributes = FrozenSDPAttributeList(attributes) if not isinstance(attributes, FrozenSDPAttributeList) else attributes if self.media in ("audio", "video"): rtp_mappings = self.rtp_mappings.copy() rtpmap_lines = '\n'.join([attr.value for attr in attributes if attr.name=='rtpmap']) # iterators are not supported -Dan rtpmap_codecs = dict([(int(type), MediaCodec(name, rate)) for type, name, rate in self.rtpmap_re.findall(rtpmap_lines)]) rtp_mappings.update(rtpmap_codecs) self.codec_list = frozenlist([rtp_mappings.get(int(format) if format.isdigit() else None, MediaCodec('Unknown', 0)) for format in self.formats]) else: self.codec_list = frozenlist() self.bandwidth_info = FrozenSDPBandwidthInfoList(bandwidth_info) if not isinstance(bandwidth_info, FrozenSDPBandwidthInfoList) else bandwidth_info self.initialized = 1 @classmethod def new(cls, BaseSDPMediaStream sdp_media): if isinstance(sdp_media, FrozenSDPMediaStream): return sdp_media connection = FrozenSDPConnection.new(sdp_media.connection) if (sdp_media.connection is not None) else None attributes = frozenlist([FrozenSDPAttribute.new(attr) for attr in sdp_media.attributes]) bandwidth_info = frozenlist([FrozenSDPBandwidthInfo.new(info) for info in sdp_media.bandwidth_info]) return cls(sdp_media.media, sdp_media.port, sdp_media.transport, sdp_media.port_count, frozenlist(sdp_media.formats), connection, attributes, bandwidth_info) def __hash__(self): return hash((self.media, self.port, self.transport, self.port_count, self.formats, self.connection, self.attributes, self.bandwidth_info)) def __richcmp__(self, other, op): return BaseSDPMediaStream_richcmp(self, other, op) cdef object BaseSDPConnection_richcmp(object self, object other, int op) with gil: cdef int eq = 1 if op not in [2,3]: return NotImplemented if not isinstance(other, BaseSDPConnection): return NotImplemented for attr in ("net_type", "address_type", "address"): if getattr(self, attr) != getattr(other, attr): eq = 0 break if op == 2: return bool(eq) else: return not eq cdef class BaseSDPConnection: def __init__(self, *args, **kwargs): raise TypeError("BaseSDPConnection cannot be instantiated directly") def __repr__(self): return "%s(%r, %r, %r)" % (self.__class__.__name__, self.address, self.net_type, self.address_type) def __richcmp__(self, other, op): return BaseSDPConnection_richcmp(self, other, op) cdef pjmedia_sdp_conn* get_sdp_connection(self): return &self._sdp_connection cdef class SDPConnection(BaseSDPConnection): def __init__(self, str address not None, str net_type not None="IN", str address_type not None="IP4"): self.address = address self.net_type = net_type self.address_type = address_type @classmethod def new(cls, BaseSDPConnection sdp_connection): return cls(sdp_connection.address, sdp_connection.net_type, sdp_connection.address_type) property address: def __get__(self): return self._address def __set__(self, str address not None): - _str_to_pj_str(address, &self._sdp_connection.addr) + _str_to_pj_str(address.encode(), &self._sdp_connection.addr) self._address = address property net_type: def __get__(self): return self._net_type def __set__(self, str net_type not None): - _str_to_pj_str(net_type, &self._sdp_connection.net_type) + _str_to_pj_str(net_type.encode(), &self._sdp_connection.net_type) self._net_type = net_type property address_type: def __get__(self): return self._address_type def __set__(self, str address_type not None): - _str_to_pj_str(address_type, &self._sdp_connection.addr_type) + _str_to_pj_str(address_type.encode(), &self._sdp_connection.addr_type) self._address_type = address_type cdef class FrozenSDPConnection(BaseSDPConnection): def __init__(self, str address not None, str net_type not None="IN", str address_type not None="IP4"): if not self.initialized: - _str_to_pj_str(address, &self._sdp_connection.addr) - _str_to_pj_str(net_type, &self._sdp_connection.net_type) - _str_to_pj_str(address_type, &self._sdp_connection.addr_type) + _str_to_pj_str(address.encode(), &self._sdp_connection.addr) + _str_to_pj_str(net_type.encode(), &self._sdp_connection.net_type) + _str_to_pj_str(address_type.encode(), &self._sdp_connection.addr_type) self.address = address self.net_type = net_type self.address_type = address_type self.initialized = 1 @classmethod def new(cls, BaseSDPConnection sdp_connection): if isinstance(sdp_connection, FrozenSDPConnection): return sdp_connection return cls(sdp_connection.address, sdp_connection.net_type, sdp_connection.address_type) def __hash__(self): return hash((self.address, self.net_type, self.address_type)) def __richcmp__(self, other, op): return BaseSDPConnection_richcmp(self, other, op) cdef class SDPAttributeList(list): def __contains__(self, item): if isinstance(item, BaseSDPAttribute): return list.__contains__(self, item) else: return item in [attr.name for attr in self] def getall(self, name): return [attr.value for attr in self if attr.name == name] def getfirst(self, name, default=None): for attr in self: if attr.name == name: return attr.value return default cdef class FrozenSDPAttributeList(frozenlist): def __contains__(self, item): if isinstance(item, BaseSDPAttribute): return list.__contains__(self, item) else: return item in [attr.name for attr in self] def getall(self, name): return [attr.value for attr in self if attr.name == name] def getfirst(self, name, default=None): for attr in self: if attr.name == name: return attr.value return default cdef object BaseSDPAttribute_richcmp(object self, object other, int op) with gil: cdef int eq = 1 if op not in [2,3]: return NotImplemented if not isinstance(other, BaseSDPAttribute): return NotImplemented for attr in ("name", "value"): if getattr(self, attr) != getattr(other, attr): eq = 0 break if op == 2: return bool(eq) else: return not eq cdef class BaseSDPAttribute: def __init__(self, *args, **kwargs): raise TypeError("BaseSDPAttribute cannot be instantiated directly") def __repr__(self): return "%s(%r, %r)" % (self.__class__.__name__, self.name, self.value) def __richcmp__(self, other, op): return BaseSDPAttribute_richcmp(self, other, op) cdef pjmedia_sdp_attr* get_sdp_attribute(self): return &self._sdp_attribute cdef class SDPAttribute(BaseSDPAttribute): def __init__(self, str name not None, str value not None): self.name = name self.value = value @classmethod def new(cls, BaseSDPAttribute sdp_attribute): return cls(sdp_attribute.name, sdp_attribute.value) property name: def __get__(self): return self._name def __set__(self, str name not None): - _str_to_pj_str(name, &self._sdp_attribute.name) + _str_to_pj_str(name.encode(), &self._sdp_attribute.name) self._name = name property value: def __get__(self): return self._value def __set__(self, str value not None): - _str_to_pj_str(value, &self._sdp_attribute.value) + _str_to_pj_str(value.encode(), &self._sdp_attribute.value) self._value = value cdef class FrozenSDPAttribute(BaseSDPAttribute): def __init__(self, str name not None, str value not None): if not self.initialized: - _str_to_pj_str(name, &self._sdp_attribute.name) - _str_to_pj_str(value, &self._sdp_attribute.value) + _str_to_pj_str(name.encode(), &self._sdp_attribute.name) + _str_to_pj_str(value.encode(), &self._sdp_attribute.value) self.name = name self.value = value self.initialized = 1 @classmethod def new(cls, BaseSDPAttribute sdp_attribute): if isinstance(sdp_attribute, FrozenSDPAttribute): return sdp_attribute return cls(sdp_attribute.name, sdp_attribute.value) def __hash__(self): return hash((self.name, self.value)) def __richcmp__(self, other, op): return BaseSDPAttribute_richcmp(self, other, op) cdef class SDPBandwidthInfoList(list): def __contains__(self, item): if isinstance(item, BaseSDPBandwidthInfo): return list.__contains__(self, item) else: - return item in [attr.name for attr in self] + return item in [attr.name.decode() for attr in self] cdef class FrozenSDPBandwidthInfoList(frozenlist): def __contains__(self, item): if isinstance(item, BaseSDPBandwidthInfo): return list.__contains__(self, item) else: - return item in [info.modifier for info in self] + return item in [info.modifier.decode() for info in self] cdef object BaseSDPBandwidthInfo_richcmp(object self, object other, int op) with gil: cdef int eq = 1 if op not in [2,3]: return NotImplemented if not isinstance(other, BaseSDPBandwidthInfo): return NotImplemented for attr in ("modifier", "value"): if getattr(self, attr) != getattr(other, attr): eq = 0 break if op == 2: return bool(eq) else: return not eq cdef class BaseSDPBandwidthInfo: def __init__(self, *args, **kwargs): raise TypeError("BaseSDPBandwidthInfo cannot be instantiated directly") def __repr__(self): return "%s(%r, %r)" % (self.__class__.__name__, self.modifier, self.value) def __richcmp__(self, other, op): return BaseSDPBandwidthInfo_richcmp(self, other, op) cdef pjmedia_sdp_bandw* get_sdp_bandwidth_info(self): return &self._sdp_bandwidth_info cdef class SDPBandwidthInfo(BaseSDPBandwidthInfo): def __init__(self, str modifier not None, object value not None): self.modifier = modifier self.value = value @classmethod def new(cls, BaseSDPBandwidthInfo sdp_bandwidth_info): return cls(sdp_bandwidth_info.modifier, sdp_bandwidth_info.value) property modifier: def __get__(self): return self._modifier def __set__(self, str modifier not None): - _str_to_pj_str(modifier, &self._sdp_bandwidth_info.modifier) + _str_to_pj_str(modifier.encode(), &self._sdp_bandwidth_info.modifier) self._modifier = modifier property value: def __get__(self): return self._value def __set__(self, object value not None): self._value = value self._sdp_bandwidth_info.value = self._value cdef class FrozenSDPBandwidthInfo(BaseSDPBandwidthInfo): def __init__(self, str modifier not None, object value not None): if not self.initialized: - _str_to_pj_str(modifier, &self._sdp_bandwidth_info.modifier) + _str_to_pj_str(modifier.encode(), &self._sdp_bandwidth_info.modifier) self.modifier = modifier self._sdp_bandwidth_info.value = value self.value = value self.initialized = 1 @classmethod def new(cls, BaseSDPBandwidthInfo sdp_bandwidth_info): if isinstance(sdp_bandwidth_info, FrozenSDPBandwidthInfo): return sdp_bandwidth_info return cls(sdp_bandwidth_info.modifier, sdp_bandwidth_info.value) def __hash__(self): return hash((self.modifier, self.value)) def __richcmp__(self, other, op): return BaseSDPBandwidthInfo_richcmp(self, other, op) # Factory functions # cdef SDPSession SDPSession_create(pjmedia_sdp_session_ptr_const pj_session): cdef SDPConnection connection = None cdef int i if pj_session.conn != NULL: connection = SDPConnection_create(pj_session.conn) return SDPSession(_pj_str_to_str(pj_session.origin.addr), pj_session.origin.id, pj_session.origin.version, _pj_str_to_str(pj_session.origin.user), _pj_str_to_str(pj_session.origin.net_type), _pj_str_to_str(pj_session.origin.addr_type), _pj_str_to_str(pj_session.name), connection, pj_session.time.start, pj_session.time.stop, [SDPAttribute_create(pj_session.attr[i]) for i in range(pj_session.attr_count)], [SDPBandwidthInfo_create(pj_session.bandw[i]) for i in range(pj_session.bandw_count)], [SDPMediaStream_create(pj_session.media[i]) if pj_session.media[i] != NULL else None for i in range(pj_session.media_count)]) cdef FrozenSDPSession FrozenSDPSession_create(pjmedia_sdp_session_ptr_const pj_session): cdef FrozenSDPConnection connection = None cdef int i if pj_session.conn != NULL: connection = FrozenSDPConnection_create(pj_session.conn) return FrozenSDPSession(_pj_str_to_str(pj_session.origin.addr), pj_session.origin.id, pj_session.origin.version, _pj_str_to_str(pj_session.origin.user), _pj_str_to_str(pj_session.origin.net_type), _pj_str_to_str(pj_session.origin.addr_type), _pj_str_to_str(pj_session.name), connection, pj_session.time.start, pj_session.time.stop, frozenlist([FrozenSDPAttribute_create(pj_session.attr[i]) for i in range(pj_session.attr_count)]), frozenlist([FrozenSDPBandwidthInfo_create(pj_session.bandw[i]) for i in range(pj_session.bandw_count)]), frozenlist([FrozenSDPMediaStream_create(pj_session.media[i]) if pj_session.media[i] != NULL else None for i in range(pj_session.media_count)])) cdef SDPMediaStream SDPMediaStream_create(pjmedia_sdp_media *pj_media): cdef SDPConnection connection = None cdef int i if pj_media.conn != NULL: connection = SDPConnection_create(pj_media.conn) return SDPMediaStream(_pj_str_to_str(pj_media.desc.media), pj_media.desc.port, _pj_str_to_str(pj_media.desc.transport), pj_media.desc.port_count, [_pj_str_to_str(pj_media.desc.fmt[i]) for i in range(pj_media.desc.fmt_count)], connection, [SDPAttribute_create(pj_media.attr[i]) for i in range(pj_media.attr_count)], [SDPBandwidthInfo_create(pj_media.bandw[i]) for i in range(pj_media.bandw_count)]) cdef FrozenSDPMediaStream FrozenSDPMediaStream_create(pjmedia_sdp_media *pj_media): cdef FrozenSDPConnection connection = None cdef int i if pj_media.conn != NULL: connection = FrozenSDPConnection_create(pj_media.conn) return FrozenSDPMediaStream(_pj_str_to_str(pj_media.desc.media), pj_media.desc.port, _pj_str_to_str(pj_media.desc.transport), pj_media.desc.port_count, frozenlist([_pj_str_to_str(pj_media.desc.fmt[i]) for i in range(pj_media.desc.fmt_count)]), connection, frozenlist([FrozenSDPAttribute_create(pj_media.attr[i]) for i in range(pj_media.attr_count)]), frozenlist([FrozenSDPBandwidthInfo_create(pj_media.bandw[i]) for i in range(pj_media.bandw_count)])) cdef SDPConnection SDPConnection_create(pjmedia_sdp_conn *pj_conn): return SDPConnection(_pj_str_to_str(pj_conn.addr), _pj_str_to_str(pj_conn.net_type), _pj_str_to_str(pj_conn.addr_type)) cdef FrozenSDPConnection FrozenSDPConnection_create(pjmedia_sdp_conn *pj_conn): return FrozenSDPConnection(_pj_str_to_str(pj_conn.addr), _pj_str_to_str(pj_conn.net_type), _pj_str_to_str(pj_conn.addr_type)) cdef SDPAttribute SDPAttribute_create(pjmedia_sdp_attr *pj_attr): return SDPAttribute(_pj_str_to_str(pj_attr.name), _pj_str_to_str(pj_attr.value)) cdef FrozenSDPAttribute FrozenSDPAttribute_create(pjmedia_sdp_attr *pj_attr): return FrozenSDPAttribute(_pj_str_to_str(pj_attr.name), _pj_str_to_str(pj_attr.value)) cdef SDPBandwidthInfo SDPBandwidthInfo_create(pjmedia_sdp_bandw *pj_bandw): return SDPBandwidthInfo(_pj_str_to_str(pj_bandw.modifier), int(pj_bandw.value)) cdef FrozenSDPBandwidthInfo FrozenSDPBandwidthInfo_create(pjmedia_sdp_bandw *pj_bandw): return FrozenSDPBandwidthInfo(_pj_str_to_str(pj_bandw.modifier), int(pj_bandw.value)) # SDP negotiator # cdef class SDPNegotiator: def __cinit__(self, *args, **kwargs): cdef pj_pool_t *pool cdef bytes pool_name cdef PJSIPUA ua ua = _get_ua() pool_name = b"SDPNegotiator_%d" % id(self) pool = ua.create_memory_pool(pool_name, 4096, 4096) self._pool = pool self._neg = NULL def __dealloc__(self): cdef PJSIPUA ua try: ua = _get_ua() except: return ua.release_memory_pool(self._pool) self._pool = NULL @classmethod def create_with_local_offer(cls, BaseSDPSession sdp_session): cdef int status cdef pjmedia_sdp_neg *neg cdef pj_pool_t *pool cdef SDPNegotiator obj obj = cls() pool = obj._pool status = pjmedia_sdp_neg_create_w_local_offer(pool, sdp_session.get_sdp_session(), &neg) if status != 0: raise PJSIPError("failed to create SDPNegotiator with local offer", status) obj._neg = neg return obj @classmethod def create_with_remote_offer(cls, BaseSDPSession sdp_session): cdef int status cdef pjmedia_sdp_neg *neg cdef pj_pool_t *pool cdef SDPNegotiator obj obj = cls() pool = obj._pool status = pjmedia_sdp_neg_create_w_remote_offer(pool, NULL, sdp_session.get_sdp_session(), &neg) if status != 0: raise PJSIPError("failed to create SDPNegotiator with remote offer", status) obj._neg = neg return obj def __repr__(self): return "%s, state=%s" % (self.__class__.__name__, self.state) property state: def __get__(self): if self._neg == NULL: return None return _buf_to_str(pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_get_state(self._neg))) property active_local: def __get__(self): cdef int status cdef pjmedia_sdp_session_ptr_const sdp if self._neg == NULL: return None status = pjmedia_sdp_neg_get_active_local(self._neg, &sdp) if status != 0: return None return FrozenSDPSession_create(sdp) property active_remote: def __get__(self): cdef int status cdef pjmedia_sdp_session_ptr_const sdp if self._neg == NULL: return None status = pjmedia_sdp_neg_get_active_remote(self._neg, &sdp) if status != 0: return None return FrozenSDPSession_create(sdp) property current_local: def __get__(self): cdef int status cdef pjmedia_sdp_session_ptr_const sdp if self._neg == NULL: return None status = pjmedia_sdp_neg_get_neg_local(self._neg, &sdp) if status != 0: return None return FrozenSDPSession_create(sdp) property current_remote: def __get__(self): cdef int status cdef pjmedia_sdp_session_ptr_const sdp if self._neg == NULL: return None status = pjmedia_sdp_neg_get_neg_remote(self._neg, &sdp) if status != 0: return None return FrozenSDPSession_create(sdp) def _check_self(self): if self._neg == NULL: raise RuntimeError('SDPNegotiator was not properly initialized') def set_local_answer(self, BaseSDPSession sdp_session): self._check_self() cdef int status cdef pj_pool_t *pool = self._pool status = pjmedia_sdp_neg_set_local_answer(pool, self._neg, sdp_session.get_sdp_session()) if status != 0: raise PJSIPError("failed to set local answer", status) def set_local_offer(self, BaseSDPSession sdp_session): # PJSIP has an asymmetric API here. This function will modify the local session with the new SDP and treat it as a local offer. self._check_self() cdef int status cdef pj_pool_t *pool = self._pool status = pjmedia_sdp_neg_modify_local_offer(pool, self._neg, sdp_session.get_sdp_session()) if status != 0: raise PJSIPError("failed to set local offer", status) def set_remote_answer(self, BaseSDPSession sdp_session): self._check_self() cdef int status cdef pj_pool_t *pool = self._pool status = pjmedia_sdp_neg_set_remote_answer(pool, self._neg, sdp_session.get_sdp_session()) if status != 0: raise PJSIPError("failed to set remote answer", status) def set_remote_offer(self, BaseSDPSession sdp_session): self._check_self() cdef int status cdef pj_pool_t *pool = self._pool status = pjmedia_sdp_neg_set_remote_offer(pool, self._neg, sdp_session.get_sdp_session()) if status != 0: raise PJSIPError("failed to set remote offer", status) def cancel_offer(self): self._check_self() cdef int status status = pjmedia_sdp_neg_cancel_offer(self._neg) if status != 0: raise PJSIPError("failed to cancel offer", status) def negotiate(self): self._check_self() cdef int status cdef pj_pool_t *pool = self._pool status = pjmedia_sdp_neg_negotiate(pool, self._neg, 0) if status != 0: raise PJSIPError("SDP negotiation failed", status) diff --git a/sipsimple/core/_core.subscription.pxi b/sipsimple/core/_core.subscription.pxi index 34f29945..8f37761e 100644 --- a/sipsimple/core/_core.subscription.pxi +++ b/sipsimple/core/_core.subscription.pxi @@ -1,959 +1,959 @@ import re cdef class Subscription: expire_warning_time = 30 #public methods def __cinit__(self, *args, **kwargs): self.state = "NULL" pj_timer_entry_init(&self._timeout_timer, 0, self, _Subscription_cb_timer) self._timeout_timer_active = 0 pj_timer_entry_init(&self._refresh_timer, 1, self, _Subscription_cb_timer) self._refresh_timer_active = 0 self.extra_headers = frozenlist() self.peer_address = None self.call_id = None def __init__(self, SIPURI request_uri not None, FromHeader from_header not None, ToHeader to_header not None, ContactHeader contact_header not None, object event, RouteHeader route_header not None, Credentials credentials=None, int refresh=300): global _subs_cb cdef PJSTR from_header_str cdef PJSTR to_header_str cdef PJSTR contact_str cdef PJSTR request_uri_str cdef pj_str_t event_pj cdef pjsip_cred_info *cred_info cdef PJSIPUA ua = _get_ua() cdef int status if self._obj != NULL or self.state != "NULL": raise SIPCoreError("Subscription.__init__() was already called") if refresh <= 0: raise ValueError("refresh argument needs to be a non-negative integer") - if event not in ua._events.iterkeys(): + if event not in ua._events.keys(): raise ValueError('Unknown event "%s"' % event) self.contact_header = FrozenContactHeader.new(contact_header) self.event = event self.route_header = FrozenRouteHeader.new(route_header) self.route_header.uri.parameters.dict["lr"] = None # always send lr parameter in Route header self.route_header.uri.parameters.dict["hide"] = None # always hide Route header if credentials is not None: self.credentials = FrozenCredentials.new(credentials) self.refresh = refresh from_header_parameters = from_header.parameters.copy() from_header_parameters.pop("tag", None) from_header.parameters = {} - from_header_str = PJSTR(from_header.body) + from_header_str = PJSTR(from_header.body.encode()) to_header_parameters = to_header.parameters.copy() to_header_parameters.pop("tag", None) to_header.parameters = {} - to_header_str = PJSTR(to_header.body) - contact_str = PJSTR(str(contact_header.body)) - request_uri_str = PJSTR(str(request_uri)) + to_header_str = PJSTR(to_header.body.encode()) + contact_str = PJSTR(str(contact_header.body).encode()) + request_uri_str = PJSTR(str(request_uri).encode()) _str_to_pj_str(self.event, &event_pj) with nogil: status = pjsip_dlg_create_uac(pjsip_ua_instance(), &from_header_str.pj_str, &contact_str.pj_str, &to_header_str.pj_str, &request_uri_str.pj_str, &self._dlg) if status != 0: raise PJSIPError("Could not create dialog for SUBSCRIBE", status) # Increment dialog session count so that it's never destroyed by PJSIP with nogil: status = pjsip_dlg_inc_session(self._dlg, &ua._module) if status != 0: raise PJSIPError("Could not increment dialog session count", status) self.call_id = _pj_str_to_str(self._dlg.call_id.id) if contact_header.expires is not None: self._dlg.local.contact.expires = contact_header.expires if contact_header.q is not None: self._dlg.local.contact.q1000 = int(contact_header.q*1000) contact_parameters = contact_header.parameters.copy() contact_parameters.pop("q", None) contact_parameters.pop("expires", None) _dict_to_pjsip_param(contact_parameters, &self._dlg.local.contact.other_param, self._dlg.pool) _dict_to_pjsip_param(from_header_parameters, &self._dlg.local.info.other_param, self._dlg.pool) _dict_to_pjsip_param(to_header_parameters, &self._dlg.remote.info.other_param, self._dlg.pool) self.from_header = FrozenFromHeader_create(self._dlg.local.info) self.to_header = FrozenToHeader.new(to_header) with nogil: status = pjsip_evsub_create_uac(self._dlg, &_subs_cb, &event_pj, PJSIP_EVSUB_NO_EVENT_ID, &self._obj) if status != 0: raise PJSIPError("Could not create SUBSCRIBE", status) pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, self) _BaseRouteHeader_to_pjsip_route_hdr(self.route_header, &self._route_header, self._dlg.pool) pj_list_init( &self._route_set) pj_list_insert_after( &self._route_set, &self._route_header) with nogil: status = pjsip_dlg_set_route_set(self._dlg, &self._route_set) if status != 0: raise PJSIPError("Could not set route on SUBSCRIBE", status) if self.credentials is not None: cred_info = self.credentials.get_cred_info() with nogil: status = pjsip_auth_clt_set_credentials(&self._dlg.auth_sess, 1, cred_info) if status != 0: raise PJSIPError("Could not set credentials for SUBSCRIBE", status) def __dealloc__(self): cdef PJSIPUA ua = self._get_ua() if ua is not None: self._cancel_timers(ua, 1, 1) if self._obj != NULL: pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) with nogil: pjsip_evsub_terminate(self._obj, 0) self._obj = NULL if self._dlg != NULL and ua is not None: with nogil: pjsip_dlg_dec_session(self._dlg, &ua._module) self._dlg = NULL def subscribe(self, list extra_headers not None=list(), object content_type=None, object body=None, object timeout=None): cdef object prev_state = self.state cdef PJSIPUA ua = self._get_ua() with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state == "TERMINATED": raise SIPCoreError('This method may not be called in the "TERMINATED" state') if (content_type is not None and body is None) or (content_type is None and body is not None): raise ValueError("Both or none of content_type and body arguments need to be specified") if timeout is not None: if timeout <= 0: raise ValueError("Timeout value cannot be negative") self._subscribe_timeout.sec = int(timeout) self._subscribe_timeout.msec = (timeout * 1000) % 1000 else: self._subscribe_timeout.sec = 0 self._subscribe_timeout.msec = 0 if extra_headers is not None: self.extra_headers = frozenlist([header.frozen_type.new(header) for header in extra_headers]) self.content_type = content_type self.body = body self._send_subscribe(ua, self.refresh, &self._subscribe_timeout, self.extra_headers, content_type, body) self._cancel_timers(ua, 0, 1) if prev_state == "NULL": _add_event("SIPSubscriptionWillStart", dict(obj=self)) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) def end(self, object timeout=None): cdef pj_time_val end_timeout cdef PJSIPUA ua = self._get_ua() with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state == "TERMINATED": return if self.state == "NULL": raise SIPCoreError('This method may not be called in the "NULL" state') if timeout is not None: if timeout <= 0: raise ValueError("Timeout value cannot be negative") end_timeout.sec = int(timeout) end_timeout.msec = (timeout * 1000) % 1000 else: end_timeout.sec = 0 end_timeout.msec = 0 self._want_end = 1 self._cancel_timers(ua, 1, 1) _add_event("SIPSubscriptionWillEnd", dict(obj=self)) try: self._send_subscribe(ua, 0, &end_timeout, self.extra_headers, None, None) except PJSIPError, e: self._term_reason = e.args[0] if self._obj != NULL: with nogil: pjsip_evsub_terminate(self._obj, 1) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) # private methods cdef PJSIPUA _get_ua(self): cdef PJSIPUA ua try: ua = _get_ua() except SIPCoreError: self._obj = NULL self._timeout_timer_active = 0 self._refresh_timer_active = 0 self.state = "TERMINATED" return None else: return ua cdef int _cancel_timers(self, PJSIPUA ua, int cancel_timeout, int cancel_refresh) except -1: if cancel_timeout and self._timeout_timer_active: pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._timeout_timer) self._timeout_timer_active = 0 if cancel_refresh and self._refresh_timer_active: pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._refresh_timer) self._refresh_timer_active = 0 cdef int _send_subscribe(self, PJSIPUA ua, int expires, pj_time_val *timeout, object extra_headers, object content_type, object body) except -1: cdef pjsip_tx_data *tdata cdef pj_str_t body_pj cdef object content_type_spl cdef PJSTR content_type_str cdef PJSTR content_subtype_str cdef int status if body is not None: content_type_spl = content_type.split("/") if len(content_type_spl) != 2: raise ValueError('Supplied content_type argument does not contain a "/" character') - content_type_str = PJSTR(content_type_spl[0]) - content_subtype_str = PJSTR(content_type_spl[1]) - _str_to_pj_str(body, &body_pj) + content_type_str = PJSTR(content_type_spl[0].encode()) + content_subtype_str = PJSTR(content_type_spl[1].encode()) + _str_to_pj_str(body.encode(), &body_pj) with nogil: status = pjsip_evsub_initiate(self._obj, NULL, expires, &tdata) if status != 0: raise PJSIPError("Could not create SUBSCRIBE message", status) _add_headers_to_tdata(tdata, extra_headers) if body is not None: tdata.msg.body = pjsip_msg_body_create(tdata.pool, &content_type_str.pj_str, &content_subtype_str.pj_str, &body_pj) with nogil: status = pjsip_evsub_send_request(self._obj, tdata) if status != 0: raise PJSIPError("Could not send SUBSCRIBE message", status) self._cancel_timers(ua, 1, 0) if timeout.sec or timeout.msec: status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._timeout_timer, timeout) if status == 0: self._timeout_timer_active = 1 self._expires = self.refresh # callback methods cdef int _cb_state(self, PJSIPUA ua, object state, int code, object reason, dict headers) except -1: # PJSIP holds the dialog lock when this callback is entered cdef object prev_state = self.state cdef int expires cdef int status cdef pj_time_val end_timeout self.state = state if state == "ACCEPTED" and prev_state == "SENT": try: contact_header = headers['Contact'][0] except LookupError: self._term_code = 1400 self._term_reason = "Contact header missing" with nogil: pjsip_evsub_terminate(self._obj, 1) return 0 _add_event("SIPSubscriptionDidStart", dict(obj=self)) try: expires = int(headers["Expires"]) except (KeyError, ValueError): return 0 if expires == 0: self._want_end = 1 self._cancel_timers(ua, 1, 1) end_timeout.sec = 1 end_timeout.msec = 0 _add_event("SIPSubscriptionWillEnd", dict(obj=self)) try: self._send_subscribe(ua, 0, &end_timeout, self.extra_headers, None, None) except PJSIPError, e: self._term_reason = e.args[0] if self._obj != NULL: with nogil: pjsip_evsub_terminate(self._obj, 1) return 0 elif state == "TERMINATED": pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) self._cancel_timers(ua, 1, 1) self._obj = NULL if self._want_end: _add_event("SIPSubscriptionDidEnd", dict(obj=self)) else: min_expires = headers.get('Min-Expires') if self._term_reason is not None: _add_event("SIPSubscriptionDidFail", dict(obj=self, code=self._term_code, reason=self._term_reason, min_expires=min_expires)) else: subscription_state = headers.get('Subscription-State') if subscription_state is not None and subscription_state.state == 'terminated': reason = subscription_state.reason _add_event("SIPSubscriptionDidFail", dict(obj=self, code=code, reason=reason, min_expires=min_expires)) if prev_state != state: _add_event("SIPSubscriptionChangedState", dict(obj=self, prev_state=prev_state, state=state)) cdef int _cb_got_response(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: # PJSIP holds the dialog lock when this callback is entered cdef dict event_dict = dict() cdef int expires = self._expires cdef int status cdef pj_time_val refresh _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) self.to_header = FrozenToHeader_create(rdata.msg_info.to_hdr) if self.state != "TERMINATED": try: contact_header = event_dict["headers"]["Contact"][0] except LookupError: return 0 try: expires = int(event_dict["headers"]["Expires"]) except (KeyError, ValueError): expires = self._expires if expires == 0: return 0 if self.state != "TERMINATED" and not self._want_end: self._cancel_timers(ua, 1, 0) refresh.sec = max(1, expires - self.expire_warning_time, expires/2) refresh.msec = 0 status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._refresh_timer, &refresh) if status == 0: self._refresh_timer_active = 1 cdef int _cb_notify(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: # PJSIP holds the dialog lock when this callback is entered cdef dict event_dict = dict() cdef dict notify_dict = dict(obj=self) _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) body = event_dict["body"] content_type = event_dict["headers"].get("Content-Type", None) event = event_dict["headers"].get("Event", None) if event is None or event.event != self.event or (body is not None and content_type is not None and content_type.content_type not in ua.events[event.event]): return 0 notify_dict["request_uri"] = event_dict["request_uri"] notify_dict["from_header"] = event_dict["headers"].get("From", None) notify_dict["to_header"] = event_dict["headers"].get("To", None) notify_dict["headers"] = event_dict["headers"] notify_dict["body"] = body notify_dict["content_type"] = content_type.content_type if content_type and body else None notify_dict["event"] = event.event _add_event("SIPSubscriptionGotNotify", notify_dict) cdef int _cb_timeout_timer(self, PJSIPUA ua): # Timer callback, dialog lock is not held by PJSIP global sip_status_messages with nogil: pjsip_dlg_inc_lock(self._dlg) try: self._term_code = PJSIP_SC_TSX_TIMEOUT self._term_reason = sip_status_messages[PJSIP_SC_TSX_TIMEOUT] if self._obj != NULL: with nogil: pjsip_evsub_terminate(self._obj, 1) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) cdef int _cb_refresh_timer(self, PJSIPUA ua): # Timer callback, dialog lock is not held by PJSIP with nogil: pjsip_dlg_inc_lock(self._dlg) try: self._send_subscribe(ua, self.refresh, &self._subscribe_timeout, self.extra_headers, self.content_type, self.body) except PJSIPError, e: self._term_reason = e.args[0] if self._obj != NULL: with nogil: pjsip_evsub_terminate(self._obj, 1) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) cdef class IncomingSubscription: # properties property content_type: def __get__(self): if self._content_type is None: return None return "%s/%s" % (self._content_type.str, self._content_subtype.str) property content: def __get__(self): if self._content is None: return None return self._content.str def __cinit__(self): self.state = None self.peer_address = None self.call_id = None def __dealloc__(self): cdef PJSIPUA ua = self._get_ua(0) self._initial_response = NULL self._initial_tsx = NULL if self._obj != NULL: pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) with nogil: pjsip_evsub_terminate(self._obj, 0) self._obj = NULL if self._dlg != NULL and ua is not None: with nogil: pjsip_dlg_dec_session(self._dlg, &ua._module) self._dlg = NULL cdef int init(self, PJSIPUA ua, pjsip_rx_data *rdata, str event) except -1: global _incoming_subs_cb cdef int status cdef str transport cdef FrozenSIPURI request_uri cdef FrozenContactHeader contact_header cdef PJSTR contact_str cdef dict event_dict cdef pjsip_expires_hdr *expires_header cdef char *error_message expires_header = pjsip_msg_find_hdr(rdata.msg_info.msg, PJSIP_H_EXPIRES, NULL) if expires_header == NULL: self._expires = 3600 else: self._expires = min(expires_header.ivalue, 3600) self._set_state("incoming") self.event = event self.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) event_dict = dict(obj=self) _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) transport = rdata.tp_info.transport.type_name.lower() request_uri = event_dict["request_uri"] - if _is_valid_ip(pj_AF_INET(), request_uri.host): + if _is_valid_ip(pj_AF_INET(), request_uri.host.encode()): contact_header = FrozenContactHeader(request_uri) else: contact_header = FrozenContactHeader(FrozenSIPURI(host=_pj_str_to_str(rdata.tp_info.transport.local_name.host), user=request_uri.user, port=rdata.tp_info.transport.local_name.port, parameters=(frozendict(transport=transport) if transport != "udp" else frozendict()))) - contact_str = PJSTR(str(contact_header.body)) + contact_str = PJSTR(str(contact_header.body).encode()) with nogil: status = pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(), rdata, &contact_str.pj_str, &self._dlg) if status != 0: error_message = "Could not create dialog for incoming SUBSCRIBE" else: pjsip_dlg_inc_session(self._dlg, &ua._module) # Increment dialog session count so it's never destroyed by PJSIP # setting the transport to rdata.tp_info.transport doesn't work as the NOTIFY has to be sent to the Contact URI and the transports can conflict if status != 0: raise PJSIPError(error_message, status) self._initial_tsx = pjsip_rdata_get_tsx(rdata) self.call_id = _pj_str_to_str(self._dlg.call_id.id) with nogil: status = pjsip_evsub_create_uas(self._dlg, &_incoming_subs_cb, rdata, 0, &self._obj) pjsip_dlg_dec_lock(self._dlg) if status != 0: pjsip_tsx_terminate(self._initial_tsx, 500) self._initial_tsx = NULL self._dlg = NULL error_message = "Could not create incoming SUBSCRIBE session" else: pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, self) status = pjsip_dlg_create_response(self._dlg, rdata, 500, NULL, &self._initial_response) if status != 0: pjsip_tsx_terminate(self._initial_tsx, 500) self._initial_tsx = NULL error_message = "Could not create response for incoming SUBSCRIBE" if status != 0: raise PJSIPError(error_message, status) _add_event("SIPIncomingSubscriptionGotSubscribe", event_dict) return 0 def reject(self, int code): cdef PJSIPUA ua = self._get_ua(1) with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state != "incoming": raise SIPCoreInvalidStateError('Can only reject an incoming SUBSCRIBE in the "incoming" state, '+ 'object is currently in the "%s" state' % self.state) if not (300 <= code < 700): raise ValueError("Invalid negative SIP response code: %d" % code) self._send_initial_response(code) pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) with nogil: pjsip_evsub_terminate(self._obj, 0) self._obj = NULL self._set_state("terminated") _add_event("SIPIncomingSubscriptionDidEnd", dict(obj=self)) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) def accept_pending(self): cdef PJSIPUA ua = self._get_ua(1) with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state != "incoming": raise SIPCoreInvalidStateError('Can only accept an incoming SUBSCRIBE as pending in the "incoming" state, '+ 'object is currently in the "%s" state' % self.state) self._send_initial_response(202) self._set_state("pending") if self._expires > 0: self._send_notify() else: # cleanup will be done by _cb_tsx self._terminate(ua, "timeout", 0) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) def accept(self, str content_type=None, str content=None): global _re_content_type cdef object content_type_match cdef PJSIPUA ua = self._get_ua(1) with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state not in ("incoming", "pending"): raise SIPCoreInvalidStateError('Can only accept an incoming SUBSCRIBE in the "incoming" or "pending" state, object is currently in the "%s" state' % self.state) if (content_type is None and content is not None) or (content_type is not None and content is None): raise ValueError('Either both or neither of the "content_type" and "content" arguments should be specified') if content_type is not None: content_type_match = _re_content_type.match(content_type) if content_type_match is None: raise ValueError("content_type parameter is not properly formatted") - self._content_type = PJSTR(content_type_match.group(1)) - self._content_subtype = PJSTR(content_type_match.group(2)) - self._content = PJSTR(content) + self._content_type = PJSTR(content_type_match.group(1).encode()) + self._content_subtype = PJSTR(content_type_match.group(2).encode()) + self._content = PJSTR(content.encode()) if self.state == "incoming": self._send_initial_response(200) self._set_state("active") if self._expires > 0: self._send_notify() else: # cleanup will be done by _cb_tsx self._terminate(ua, "timeout", 0) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) def push_content(self, str content_type not None, str content not None): global _re_content_type cdef object content_type_match cdef PJSIPUA ua = self._get_ua(1) with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state != "active": raise SIPCoreInvalidStateError('Can only push the content for a SUBSCRIBE session in the "active" state, ' 'object is currently in the "%s" state' % self.state) content_type_match = _re_content_type.match(content_type) if content_type_match is None: raise ValueError("content_type parameter is not properly formatted") - self._content_type = PJSTR(content_type_match.group(1)) - self._content_subtype = PJSTR(content_type_match.group(2)) - self._content = PJSTR(content) + self._content_type = PJSTR(content_type_match.group(1).encode()) + self._content_subtype = PJSTR(content_type_match.group(2).encode()) + self._content = PJSTR(content.encode()) self._send_notify() finally: with nogil: pjsip_dlg_dec_lock(self._dlg) def end(self, reason=None): cdef PJSIPUA ua = self._get_ua(0) with nogil: pjsip_dlg_inc_lock(self._dlg) try: if self.state == "terminated": return if self.state not in ("pending", "active"): raise SIPCoreInvalidStateError('Can only end an incoming SUBSCRIBE session in the "pending" or '+ '"active" state, object is currently in the "%s" state' % self.state) self._terminate(ua, reason, 1) finally: with nogil: pjsip_dlg_dec_lock(self._dlg) cdef int _set_state(self, str state) except -1: cdef str prev_state prev_state = self.state self.state = state if prev_state != state and prev_state is not None: _add_event("SIPIncomingSubscriptionChangedState", dict(obj=self, prev_state=prev_state, state=state)) cdef PJSIPUA _get_ua(self, int raise_exception): cdef PJSIPUA ua try: ua = _get_ua() except SIPCoreError: self._obj = NULL self._initial_response = NULL self._initial_tsx = NULL self._set_state("terminated") if raise_exception: raise else: return None else: return ua cdef int _send_initial_response(self, int code) except -1: cdef PJSIPUA ua = self._get_ua(1) cdef int status with nogil: status = pjsip_dlg_modify_response(self._dlg, self._initial_response, code, NULL) if status != 0: raise PJSIPError("Could not modify response", status) # pjsip_dlg_modify_response() increases ref count unncessarily with nogil: pjsip_tx_data_dec_ref(self._initial_response) if code / 100 == 2: pjsip_msg_add_hdr(self._initial_response.msg, pjsip_expires_hdr_create(self._initial_response.pool, self._expires)) with nogil: status = pjsip_dlg_send_response(self._dlg, self._initial_tsx, self._initial_response) if status != 0: raise PJSIPError("Could not send response", status) self._initial_response = NULL self._initial_tsx = NULL if self._expires > 0: with nogil: # Start TIMER_TYPE_UAS_TIMEOUT, which PJSIP doesn't do for the initial SUBSCRIBE pjsip_evsub_set_timer(self._obj, 2, self._expires) cdef int _send_notify(self, str reason=None) except -1: cdef pjsip_evsub_state state cdef pj_str_t reason_pj cdef pj_str_t *reason_p cdef pjsip_tx_data *tdata cdef int status reason_p = NULL if self.state == "pending": state = PJSIP_EVSUB_STATE_PENDING elif self.state == "active": state = PJSIP_EVSUB_STATE_ACTIVE else: state = PJSIP_EVSUB_STATE_TERMINATED if reason is not None: - _str_to_pj_str(reason, &reason_pj) + _str_to_pj_str(reason.encode(), &reason_pj) reason_p = &reason_pj with nogil: status = pjsip_evsub_notify(self._obj, state, NULL, reason_p, &tdata) if status != 0: raise PJSIPError("Could not create NOTIFY request", status) if self.state == "active" and None not in (self._content_type, self._content_subtype, self._content): tdata.msg.body = pjsip_msg_body_create(tdata.pool, &self._content_type.pj_str, &self._content_subtype.pj_str, &self._content.pj_str) with nogil: status = pjsip_evsub_send_request(self._obj, tdata) if status != 0: raise PJSIPError("Could not send NOTIFY request", status) event_dict = dict(obj=self) _pjsip_msg_to_dict(tdata.msg, event_dict) _add_event("SIPIncomingSubscriptionSentNotify", event_dict) return 0 cdef int _terminate(self, PJSIPUA ua, str reason, int do_cleanup) except -1: cdef int status self._set_state("terminated") try: self._send_notify(reason) except SIPCoreError: pass if do_cleanup: pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) self._obj = NULL _add_event("SIPIncomingSubscriptionDidEnd", dict(obj=self)) # callback methods cdef int _cb_rx_refresh(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: # PJSIP holds the dialog lock when this callback is entered cdef int status cdef pjsip_expires_hdr *expires_header cdef int expires cdef dict event_dict event_dict = dict(obj=self) _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) expires_header = pjsip_msg_find_hdr(rdata.msg_info.msg, PJSIP_H_EXPIRES, NULL) if expires_header == NULL: self._expires = 3600 else: if expires_header.ivalue == 0: _add_event("SIPIncomingSubscriptionGotUnsubscribe", event_dict) # cleanup will be done by _cb_tsx self._terminate(ua, None, 0) return 200 else: self._expires = min(expires_header.ivalue, 3600) _add_event("SIPIncomingSubscriptionGotRefreshingSubscribe", event_dict) try: self._send_notify() except SIPCoreError, e: _add_event("SIPIncomingSubscriptionNotifyDidFail", dict(obj=self, code=0, reason=e.args[0])) if self.state == "active": return 200 else: return 202 cdef int _cb_server_timeout(self, PJSIPUA ua) except -1: # PJSIP holds the dialog lock when this callback is entered _add_event("SIPIncomingSubscriptionDidTimeout", dict(obj=self)) self._terminate(ua, "timeout", 1) cdef int _cb_tsx(self, PJSIPUA ua, pjsip_event *event) except -1: # PJSIP holds the dialog lock when this callback is entered cdef pjsip_rx_data *rdata cdef dict event_dict cdef int status_code if (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.tsx.role == PJSIP_ROLE_UAC and _pj_str_to_str(event.body.tsx_state.tsx.method.name) == "NOTIFY" and event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_COMPLETED): event_dict = dict(obj=self) rdata = event.body.tsx_state.src.rdata if rdata != NULL: if self.peer_address is None: self.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: self.peer_address.ip = rdata.pkt_info.src_name self.peer_address.port = rdata.pkt_info.src_port status_code = event.body.tsx_state.tsx.status_code if event.body.tsx_state.type==PJSIP_EVENT_RX_MSG and status_code/100==2: _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) _add_event("SIPIncomingSubscriptionNotifyDidSucceed", event_dict) else: if event.body.tsx_state.type == PJSIP_EVENT_RX_MSG: _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) else: event_dict["code"] = status_code event_dict["reason"] = _pj_str_to_str(event.body.tsx_state.tsx.status_text) _add_event("SIPIncomingSubscriptionNotifyDidFail", event_dict) if status_code in (408, 481) or status_code/100==7: # PJSIP will terminate the subscription and the dialog will be destroyed self._terminate(ua, None, 1) elif (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.tsx.role == PJSIP_ROLE_UAC and _pj_str_to_str(event.body.tsx_state.tsx.method.name) == "NOTIFY" and event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_TERMINATED): event_dict = dict(obj=self) status_code = event.body.tsx_state.tsx.status_code if status_code == 408: # Local timeout, PJSIP will terminate the subscription and the dialog will be destroyed event_dict["code"] = status_code event_dict["reason"] = _pj_str_to_str(event.body.tsx_state.tsx.status_text) _add_event("SIPIncomingSubscriptionNotifyDidFail", event_dict) self._terminate(ua, None, 1) elif (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.tsx.role == PJSIP_ROLE_UAS and _pj_str_to_str(event.body.tsx_state.tsx.method.name) == "SUBSCRIBE" and event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_COMPLETED and event.body.tsx_state.type == PJSIP_EVENT_TX_MSG): event_dict = dict(obj=self) _pjsip_msg_to_dict(event.body.tsx_state.src.tdata.msg, event_dict) _add_event("SIPIncomingSubscriptionAnsweredSubscribe", event_dict) if self.state == "terminated" and self._obj != NULL: pjsip_evsub_set_mod_data(self._obj, ua._event_module.id, NULL) self._obj = NULL # callback functions cdef void _Subscription_cb_state(pjsip_evsub *sub, pjsip_event *event) with gil: cdef void *subscription_void cdef Subscription subscription cdef object state cdef int code = 0 cdef object reason = None cdef pjsip_rx_data *rdata = NULL cdef PJSIPUA ua try: ua = _get_ua() except: return try: subscription_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if subscription_void == NULL: return subscription = subscription_void state = pjsip_evsub_get_state_name(sub) if (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and (event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_COMPLETED or event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_TERMINATED)): if state == "TERMINATED": if event.body.tsx_state.tsx.role == PJSIP_ROLE_UAC: code = event.body.tsx_state.tsx.status_code reason = _pj_str_to_str(event.body.tsx_state.tsx.status_text) else: code = 0 reason = None if event.body.tsx_state.type == PJSIP_EVENT_RX_MSG and _pj_str_to_str(event.body.tsx_state.tsx.method.name) in ("SUBSCRIBE", "NOTIFY"): rdata = event.body.tsx_state.src.rdata headers_dict = dict() if rdata != NULL: rdata_dict = dict() _pjsip_msg_to_dict(rdata.msg_info.msg, rdata_dict) headers_dict = rdata_dict.get('headers', {}) subscription._cb_state(ua, state, code, reason, headers_dict) except: ua._handle_exception(1) cdef void _Subscription_cb_tsx(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) with gil: cdef void *subscription_void cdef Subscription subscription cdef pjsip_rx_data *rdata cdef PJSIPUA ua try: ua = _get_ua() except: return try: subscription_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if subscription_void == NULL: return subscription = subscription_void if (event != NULL and event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.type == PJSIP_EVENT_RX_MSG and event.body.tsx_state.tsx.role == PJSIP_ROLE_UAC and event.body.tsx_state.tsx.state == PJSIP_TSX_STATE_COMPLETED and _pj_str_to_str(event.body.tsx_state.tsx.method.name) == "SUBSCRIBE" and event.body.tsx_state.tsx.status_code / 100 == 2): rdata = event.body.tsx_state.src.rdata if rdata != NULL: if subscription.peer_address is None: subscription.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: subscription.peer_address.ip = rdata.pkt_info.src_name subscription.peer_address.port = rdata.pkt_info.src_port subscription._cb_got_response(ua, rdata) except: ua._handle_exception(1) cdef void _Subscription_cb_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil: cdef void *subscription_void cdef Subscription subscription cdef PJSIPUA ua try: ua = _get_ua() except: return try: subscription_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if subscription_void == NULL: return subscription = subscription_void if rdata != NULL: if subscription.peer_address is None: subscription.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: subscription.peer_address.ip = rdata.pkt_info.src_name subscription.peer_address.port = rdata.pkt_info.src_port subscription._cb_notify(ua, rdata) except: ua._handle_exception(1) cdef void _Subscription_cb_refresh(pjsip_evsub *sub) with gil: # We want to handle the refresh timer oursevles, ignore the PJSIP provided timer pass cdef void _Subscription_cb_timer(pj_timer_heap_t *timer_heap, pj_timer_entry *entry) with gil: cdef Subscription subscription cdef PJSIPUA ua try: ua = _get_ua() except: return try: if entry.user_data != NULL: subscription = entry.user_data if subscription._dlg == NULL: return if entry.id == 1: subscription._refresh_timer_active = 0 subscription._cb_refresh_timer(ua) else: subscription._timeout_timer_active = 0 subscription._cb_timeout_timer(ua) except: ua._handle_exception(1) cdef void _IncomingSubscription_cb_rx_refresh(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) with gil: cdef void *subscription_void cdef IncomingSubscription subscription cdef PJSIPUA ua try: ua = _get_ua() except: return try: subscription_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if subscription_void == NULL: p_st_code[0] = 481 return subscription = subscription_void if rdata != NULL: if subscription.peer_address is None: subscription.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: subscription.peer_address.ip = rdata.pkt_info.src_name subscription.peer_address.port = rdata.pkt_info.src_port p_st_code[0] = subscription._cb_rx_refresh(ua, rdata) except: ua._handle_exception(1) cdef void _IncomingSubscription_cb_server_timeout(pjsip_evsub *sub) with gil: cdef void *subscription_void cdef IncomingSubscription subscription cdef PJSIPUA ua try: ua = _get_ua() except: return try: subscription_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if subscription_void == NULL: return subscription = subscription_void subscription._cb_server_timeout(ua) except: ua._handle_exception(1) cdef void _IncomingSubscription_cb_tsx(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) with gil: cdef void *subscription_void cdef IncomingSubscription subscription cdef PJSIPUA ua try: ua = _get_ua() except: return try: subscription_void = pjsip_evsub_get_mod_data(sub, ua._event_module.id) if subscription_void == NULL: return subscription = subscription_void subscription._cb_tsx(ua, event) except: ua._handle_exception(1) # globals cdef pjsip_evsub_user _subs_cb _subs_cb.on_evsub_state = _Subscription_cb_state _subs_cb.on_tsx_state = _Subscription_cb_tsx _subs_cb.on_rx_notify = _Subscription_cb_notify _subs_cb.on_client_refresh = _Subscription_cb_refresh cdef pjsip_evsub_user _incoming_subs_cb _incoming_subs_cb.on_rx_refresh = _IncomingSubscription_cb_rx_refresh _incoming_subs_cb.on_server_timeout = _IncomingSubscription_cb_server_timeout _incoming_subs_cb.on_tsx_state = _IncomingSubscription_cb_tsx _re_content_type = re.compile("^([a-zA-Z0-9\-.!%*_+`'~]+)\/([a-zA-Z0-9\-.!%*_+`'~]+)$") diff --git a/sipsimple/core/_core.ua.pxi b/sipsimple/core/_core.ua.pxi index 8901c1f0..361d87b3 100644 --- a/sipsimple/core/_core.ua.pxi +++ b/sipsimple/core/_core.ua.pxi @@ -1,1228 +1,1244 @@ import errno import heapq import re import random import sys import time import traceback import os import tempfile cdef class Timer: cdef int schedule(self, float delay, timer_callback callback, object obj) except -1: cdef PJSIPUA ua = _get_ua() if delay < 0: raise ValueError("delay must be a non-negative number") if callback == NULL: raise ValueError("callback must be non-NULL") if self._scheduled: raise RuntimeError("already scheduled") self.schedule_time = PyFloat_AsDouble(time.time() + delay) self.callback = callback self.obj = obj ua._add_timer(self) self._scheduled = 1 return 0 cdef int cancel(self) except -1: cdef PJSIPUA ua = _get_ua() if not self._scheduled: return 0 ua._remove_timer(self) self._scheduled = 0 return 0 cdef int call(self) except -1: self._scheduled = 0 self.callback(self.obj, self) def __richcmp__(self, other, op): cdef double diff if not isinstance(self, Timer) or not isinstance(other, Timer): return NotImplemented diff = (self).schedule_time - (other).schedule_time if op == 0: # < return diff < 0.0 elif op == 1: # <= return diff <= 0.0 elif op == 2: # == return diff == 0.0 elif op == 3: # != return diff != 0.0 elif op == 4: # > return diff > 0.0 elif op == 5: # >= return diff >= 0.0 return cdef class PJSIPUA: def __cinit__(self, *args, **kwargs): global _ua if _ua != NULL: raise SIPCoreError("Can only have one PJSUPUA instance at the same time") _ua = self self._threads = [] self._timers = list() self._events = {} self._incoming_events = set() self._incoming_requests = set() self._sent_messages = set() def __init__(self, event_handler, *args, **kwargs): global _event_queue_lock - cdef str event - cdef str method + cdef object event + cdef object method cdef list accept_types cdef int status - cdef PJSTR message_method = PJSTR("MESSAGE") - cdef PJSTR refer_method = PJSTR("REFER") - cdef PJSTR str_norefersub = PJSTR("norefersub") - cdef PJSTR str_gruu = PJSTR("gruu") + + cdef PJSTR message_method = PJSTR(b"MESSAGE") + cdef PJSTR refer_method = PJSTR(b"REFER") + cdef PJSTR str_norefersub = PJSTR(b"norefersub") + cdef PJSTR str_gruu = PJSTR(b"gruu") + self._event_handler = event_handler if kwargs["log_level"] < 0 or kwargs["log_level"] > PJ_LOG_MAX_LEVEL: raise ValueError("Log level should be between 0 and %d" % PJ_LOG_MAX_LEVEL) pj_log_set_level(kwargs["log_level"]) pj_log_set_decor(PJ_LOG_HAS_YEAR | PJ_LOG_HAS_MONTH | PJ_LOG_HAS_DAY_OF_MON | PJ_LOG_HAS_TIME | PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_SENDER | PJ_LOG_HAS_INDENT) pj_log_set_log_func(_cb_log) self._pjlib = PJLIB() pj_srand(random.getrandbits(32)) # rely on python seed for now self._caching_pool = PJCachingPool() self._pjmedia_endpoint = PJMEDIAEndpoint(self._caching_pool) self._pjsip_endpoint = PJSIPEndpoint(self._caching_pool, kwargs["ip_address"], kwargs["udp_port"], kwargs["tcp_port"], kwargs["tls_port"], kwargs["tls_verify_server"], kwargs["tls_ca_file"], kwargs["tls_cert_file"], kwargs["tls_privkey_file"], kwargs["tls_timeout"]) status = pj_mutex_create_simple(self._pjsip_endpoint._pool, "event_queue_lock", &_event_queue_lock) if status != 0: raise PJSIPError("Could not initialize event queue mutex", status) - self._ip_address = kwargs["ip_address"] - self.codecs = kwargs["codecs"] - self.video_codecs = kwargs["video_codecs"] - self._module_name = PJSTR("mod-core") + + self._ip_address = kwargs["ip_address"].encode() if kwargs["ip_address"] else None + self.codecs = list(codec.encode() for codec in kwargs["codecs"]) + self.video_codecs = list(codec.encode() for codec in kwargs["video_codecs"]) + + self._module_name = PJSTR(b"mod-core") self._module.name = self._module_name.pj_str self._module.id = -1 self._module.priority = PJSIP_MOD_PRIORITY_APPLICATION self._module.on_rx_request = _PJSIPUA_cb_rx_request self._module.on_tsx_state = _Request_cb_tsx_state status = pjsip_endpt_register_module(self._pjsip_endpoint._obj, &self._module) if status != 0: raise PJSIPError("Could not load application module", status) + status = pjsip_endpt_add_capability(self._pjsip_endpoint._obj, &self._module, PJSIP_H_ALLOW, NULL, 1, &message_method.pj_str) if status != 0: raise PJSIPError("Could not add MESSAGE method to supported methods", status) status = pjsip_endpt_add_capability(self._pjsip_endpoint._obj, &self._module, PJSIP_H_ALLOW, NULL, 1, &refer_method.pj_str) if status != 0: raise PJSIPError("Could not add REFER method to supported methods", status) status = pjsip_endpt_add_capability(self._pjsip_endpoint._obj, NULL, PJSIP_H_SUPPORTED, NULL, 1, &str_norefersub.pj_str) if status != 0: raise PJSIPError("Could not add 'norefsub' to Supported header", status) status = pjsip_endpt_add_capability(self._pjsip_endpoint._obj, NULL, PJSIP_H_SUPPORTED, NULL, 1, &str_gruu.pj_str) if status != 0: raise PJSIPError("Could not add 'gruu' to Supported header", status) - self._trace_sip = int(bool(kwargs["trace_sip"])) - self._detect_sip_loops = int(bool(kwargs["detect_sip_loops"])) - self._enable_colorbar_device = int(bool(kwargs["enable_colorbar_device"])) - self._opus_fix_module_name = PJSTR("mod-core-opus-fix") + + self._opus_fix_module_name = PJSTR(b"mod-core-opus-fix") self._opus_fix_module.name = self._opus_fix_module_name.pj_str self._opus_fix_module.id = -1 self._opus_fix_module.priority = PJSIP_MOD_PRIORITY_TRANSPORT_LAYER+1 self._opus_fix_module.on_rx_request = _cb_opus_fix_rx self._opus_fix_module.on_rx_response = _cb_opus_fix_rx self._opus_fix_module.on_tx_request = _cb_opus_fix_tx self._opus_fix_module.on_tx_response = _cb_opus_fix_tx status = pjsip_endpt_register_module(self._pjsip_endpoint._obj, &self._opus_fix_module) if status != 0: raise PJSIPError("Could not load opus-fix module", status) - self._trace_module_name = PJSTR("mod-core-sip-trace") + + self._trace_module_name = PJSTR(b"mod-core-sip-trace") self._trace_module.name = self._trace_module_name.pj_str self._trace_module.id = -1 self._trace_module.priority = 0 self._trace_module.on_rx_request = _cb_trace_rx self._trace_module.on_rx_response = _cb_trace_rx self._trace_module.on_tx_request = _cb_trace_tx self._trace_module.on_tx_response = _cb_trace_tx status = pjsip_endpt_register_module(self._pjsip_endpoint._obj, &self._trace_module) if status != 0: raise PJSIPError("Could not load sip trace module", status) - self._ua_tag_module_name = PJSTR("mod-core-ua-tag") + + self._ua_tag_module_name = PJSTR(b"mod-core-ua-tag") self._ua_tag_module.name = self._ua_tag_module_name.pj_str self._ua_tag_module.id = -1 self._ua_tag_module.priority = PJSIP_MOD_PRIORITY_TRANSPORT_LAYER+1 self._ua_tag_module.on_tx_request = _cb_add_user_agent_hdr self._ua_tag_module.on_tx_response = _cb_add_server_hdr status = pjsip_endpt_register_module(self._pjsip_endpoint._obj, &self._ua_tag_module) if status != 0: raise PJSIPError("Could not load User-Agent/Server header tagging module", status) - self._event_module_name = PJSTR("mod-core-events") + + self._event_module_name = PJSTR(b"mod-core-events") self._event_module.name = self._event_module_name.pj_str self._event_module.id = -1 self._event_module.priority = PJSIP_MOD_PRIORITY_DIALOG_USAGE status = pjsip_endpt_register_module(self._pjsip_endpoint._obj, &self._event_module) if status != 0: raise PJSIPError("Could not load events module", status) + + self._trace_sip = int(bool(kwargs["trace_sip"])) + self._detect_sip_loops = int(bool(kwargs["detect_sip_loops"])) + self._enable_colorbar_device = int(bool(kwargs["enable_colorbar_device"])) + self._user_agent = PJSTR(kwargs["user_agent"].encode()) + self.rtp_port_range = kwargs["rtp_port_range"] + self.zrtp_cache = kwargs["zrtp_cache"].encode() if kwargs["zrtp_cache"] else None + status = pjmedia_aud_dev_set_observer_cb(_cb_audio_dev_process_event); if status != 0: raise PJSIPError("Could not set audio_change callbacks", status) + status = pj_rwmutex_create(self._pjsip_endpoint._pool, "ua_audio_change_rwlock", &self.audio_change_rwlock) if status != 0: raise PJSIPError("Could not initialize audio change rwmutex", status) + status = pj_mutex_create_recursive(self._pjsip_endpoint._pool, "ua_video_lock", &self.video_lock) if status != 0: raise PJSIPError("Could not initialize video mutex", status) - self._user_agent = PJSTR(kwargs["user_agent"]) + for event, accept_types in kwargs["events"].iteritems(): self.add_event(event, accept_types) + for event in kwargs["incoming_events"]: - if event not in self._events.iterkeys(): + if event not in self._events.keys(): raise ValueError('Event "%s" is not known' % event) self._incoming_events.add(event) + for method in kwargs["incoming_requests"]: - method = method.upper() - if method in ("ACK", "BYE", "INVITE", "REFER", "SUBSCRIBE"): + if method in (b"ACK", b"BYE", b"INVITE", b"REFER", b"SUBSCRIBE"): raise ValueError('Handling incoming "%s" requests is not allowed' % method) self._incoming_requests.add(method) - self.rtp_port_range = kwargs["rtp_port_range"] - self.zrtp_cache = kwargs["zrtp_cache"] + pj_stun_config_init(&self._stun_cfg, &self._caching_pool._obj.factory, 0, pjmedia_endpt_get_ioqueue(self._pjmedia_endpoint._obj), pjsip_endpt_get_timer_heap(self._pjsip_endpoint._obj)) property trace_sip: def __get__(self): self._check_self() return bool(self._trace_sip) def __set__(self, value): self._check_self() self._trace_sip = int(bool(value)) property detect_sip_loops: def __get__(self): self._check_self() return bool(self._detect_sip_loops) def __set__(self, value): self._check_self() self._detect_sip_loops = int(bool(value)) property enable_colorbar_device: def __get__(self): self._check_self() return bool(self._enable_colorbar_device) def __set__(self, value): self._check_self() self._enable_colorbar_device = int(bool(value)) self.refresh_video_devices() property events: def __get__(self): self._check_self() return self._events.copy() property ip_address: def __get__(self): self._check_self() return self._ip_address def add_event(self, object event, list accept_types): cdef pj_str_t event_pj cdef pj_str_t accept_types_pj[PJSIP_MAX_ACCEPT_COUNT] cdef int index cdef object accept_type cdef int accept_cnt = len(accept_types) cdef int status self._check_self() if accept_cnt == 0: raise SIPCoreError("Need at least one of accept_types") if accept_cnt > PJSIP_MAX_ACCEPT_COUNT: raise SIPCoreError("Too many accept_types") _str_to_pj_str(event, &event_pj) for index, accept_type in enumerate(accept_types): _str_to_pj_str(accept_type, &accept_types_pj[index]) status = pjsip_evsub_register_pkg(&self._event_module, &event_pj, 3600, accept_cnt, accept_types_pj) if status != 0: raise PJSIPError("Could not register event package", status) self._events[event] = accept_types[:] property incoming_events: def __get__(self): self._check_self() return self._incoming_events.copy() - def add_incoming_event(self, str event): + def add_incoming_event(self, object event): self._check_self() - if event not in self._events.iterkeys(): + if event not in self._events.keys(): raise ValueError('Event "%s" is not known' % event) self._incoming_events.add(event) - def remove_incoming_event(self, str event): + def remove_incoming_event(self, object event): self._check_self() - if event not in self._events.iterkeys(): + if event not in self._events.keys(): raise ValueError('Event "%s" is not known' % event) self._incoming_events.discard(event) property incoming_requests: def __get__(self): self._check_self() return self._incoming_requests.copy() - def add_incoming_request(self, object value): - cdef str method + def add_incoming_request(self, object method): self._check_self() - method = value.upper() - if method in ("ACK", "BYE", "INVITE", "REFER", "SUBSCRIBE"): + if method in (b"ACK", b"BYE", b"INVITE", b"REFER", b"SUBSCRIBE"): raise ValueError('Handling incoming "%s" requests is not allowed' % method) self._incoming_requests.add(method) - def remove_incoming_request(self, object value): - cdef str method + def remove_incoming_request(self, object method): self._check_self() - method = value.upper() - if method in ("ACK", "BYE", "INVITE", "REFER", "SUBSCRIBE"): + if method in (b"ACK", b"BYE", b"INVITE", b"REFER", b"SUBSCRIBE"): raise ValueError('Handling incoming "%s" requests is not allowed' % method) self._incoming_requests.discard(method) cdef pj_pool_t* create_memory_pool(self, bytes name, int initial_size, int resize_size): cdef pj_pool_t *pool cdef char *c_pool_name cdef pjsip_endpoint *endpoint c_pool_name = name endpoint = self._pjsip_endpoint._obj with nogil: pool = pjsip_endpt_create_pool(endpoint, c_pool_name, initial_size, resize_size) if pool == NULL: raise SIPCoreError("Could not allocate memory pool") return pool cdef void release_memory_pool(self, pj_pool_t* pool): cdef pjsip_endpoint *endpoint endpoint = self._pjsip_endpoint._obj if pool != NULL: with nogil: pjsip_endpt_release_pool(endpoint, pool) cdef void reset_memory_pool(self, pj_pool_t* pool): if pool != NULL: with nogil: pj_pool_reset(pool) cdef object _get_sound_devices(self, int is_output): cdef int count cdef pjmedia_aud_dev_info info cdef list retval = list() cdef int status with nogil: status = pj_rwmutex_lock_read(self.audio_change_rwlock) if status != 0: raise PJSIPError('Could not acquire audio_change_rwlock', status) try: for i in range(pjmedia_aud_dev_count()): with nogil: status = pjmedia_aud_dev_get_info(i, &info) if status != 0: raise PJSIPError("Could not get audio device info", status) if is_output: count = info.output_count else: count = info.input_count if count: retval.append(decode_device_name(info.name)) return retval finally: pj_rwmutex_unlock_read(self.audio_change_rwlock) cdef object _get_default_sound_device(self, int is_output): cdef pjmedia_aud_dev_info info cdef int dev_id cdef int status with nogil: status = pj_rwmutex_lock_read(self.audio_change_rwlock) if status != 0: raise SIPCoreError('Could not acquire audio_change_rwlock', status) try: if is_output: dev_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV else: dev_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV with nogil: status = pjmedia_aud_dev_get_info(dev_id, &info) if status != 0: raise PJSIPError("Could not get audio device info", status) return decode_device_name(info.name) finally: pj_rwmutex_unlock_read(self.audio_change_rwlock) property default_output_device: def __get__(self): self._check_self() return self._get_default_sound_device(1) property default_input_device: def __get__(self): self._check_self() return self._get_default_sound_device(0) property output_devices: def __get__(self): self._check_self() return self._get_sound_devices(1) property input_devices: def __get__(self): self._check_self() return self._get_sound_devices(0) property sound_devices: def __get__(self): self._check_self() cdef int count cdef pjmedia_aud_dev_info info cdef list retval = list() cdef int status with nogil: status = pj_rwmutex_lock_read(self.audio_change_rwlock) if status != 0: raise SIPCoreError('Could not acquire audio_change_rwlock', status) try: for i in range(pjmedia_aud_dev_count()): with nogil: status = pjmedia_aud_dev_get_info(i, &info) if status == 0: retval.append(decode_device_name(info.name)) return retval finally: pj_rwmutex_unlock_read(self.audio_change_rwlock) def refresh_sound_devices(self): self._check_self() cdef int status cdef dict event_dict self.old_devices = self.sound_devices with nogil: status = pj_rwmutex_lock_write(self.audio_change_rwlock) if status != 0: raise SIPCoreError('Could not acquire audio_change_rwlock', status) with nogil: pjmedia_aud_dev_refresh() status = pj_rwmutex_unlock_write(self.audio_change_rwlock) if status != 0: raise SIPCoreError('Could not release audio_change_rwlock', status) event_dict = dict() event_dict["old_devices"] = self.old_devices event_dict["new_devices"] = self.sound_devices _add_event("AudioDevicesDidChange", event_dict) cdef object _get_video_devices(self): cdef pjmedia_vid_dev_info info cdef list retval = list() cdef int direction cdef int status for i in range(pjmedia_vid_dev_count()): with nogil: status = pjmedia_vid_dev_get_info(i, &info) if status != 0: raise PJSIPError("Could not get video device info", status) direction = info.dir if direction in (PJMEDIA_DIR_CAPTURE, PJMEDIA_DIR_CAPTURE_PLAYBACK): if (not self._enable_colorbar_device and bytes(info.driver) == "Colorbar") or bytes(info.driver) == "Null": continue retval.append(decode_device_name(info.name)) return retval cdef object _get_default_video_device(self): cdef pjmedia_vid_dev_info info cdef int status with nogil: status = pjmedia_vid_dev_get_info(PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &info) if status != 0: raise PJSIPError("Could not get default video device info", status) if (not self._enable_colorbar_device and bytes(info.driver) == "Colorbar") or bytes(info.driver) == "Null": raise SIPCoreError("Could not get default video device") return decode_device_name(info.name) def refresh_video_devices(self): self._check_self() cdef int status cdef dict event_dict self.old_video_devices = self.video_devices with nogil: pjmedia_vid_dev_refresh() event_dict = dict() event_dict["old_devices"] = self.old_video_devices event_dict["new_devices"] = self.video_devices _add_event("VideoDevicesDidChange", event_dict) property default_video_device: def __get__(self): self._check_self() return self._get_default_video_device() property video_devices: def __get__(self): self._check_self() return self._get_video_devices() property available_codecs: def __get__(self): self._check_self() return self._pjmedia_endpoint._get_all_codecs() property codecs: def __get__(self): self._check_self() return self._pjmedia_endpoint._get_current_codecs() def __set__(self, value): self._check_self() self._pjmedia_endpoint._set_codecs(value) property available_video_codecs: def __get__(self): self._check_self() return self._pjmedia_endpoint._get_all_video_codecs() property video_codecs: def __get__(self): self._check_self() return self._pjmedia_endpoint._get_current_video_codecs() def __set__(self, value): self._check_self() self._pjmedia_endpoint._set_video_codecs(value) property udp_port: def __get__(self): self._check_self() if self._pjsip_endpoint._udp_transport == NULL: return None return self._pjsip_endpoint._udp_transport.local_name.port def set_udp_port(self, value): cdef int port self._check_self() if value is None: if self._pjsip_endpoint._udp_transport == NULL: return self._pjsip_endpoint._stop_udp_transport() else: port = value if not (0 <= port <= 65535): raise ValueError("Not a valid UDP port: %d" % value) if self._pjsip_endpoint._udp_transport != NULL: if port == self._pjsip_endpoint._udp_transport.local_name.port: return self._pjsip_endpoint._stop_udp_transport() self._pjsip_endpoint._start_udp_transport(port) property tcp_port: def __get__(self): self._check_self() if self._pjsip_endpoint._tcp_transport == NULL: return None return self._pjsip_endpoint._tcp_transport.addr_name.port def set_tcp_port(self, value): cdef int port self._check_self() if value is None: if self._pjsip_endpoint._tcp_transport == NULL: return self._pjsip_endpoint._stop_tcp_transport() else: port = value if not (0 <= port <= 65535): raise ValueError("Not a valid TCP port: %d" % value) if self._pjsip_endpoint._tcp_transport != NULL: if port == self._pjsip_endpoint._tcp_transport.addr_name.port: return self._pjsip_endpoint._stop_tcp_transport() self._pjsip_endpoint._start_tcp_transport(port) property tls_port: def __get__(self): self._check_self() if self._pjsip_endpoint._tls_transport == NULL: return None return self._pjsip_endpoint._tls_transport.addr_name.port property rtp_port_range: def __get__(self): self._check_self() return (self._rtp_port_start, self._rtp_port_start + self._rtp_port_count) def __set__(self, value): cdef int _rtp_port_start cdef int _rtp_port_stop cdef int _rtp_port_count cdef int _rtp_port_usable_count cdef int port self._check_self() for port in value: if not (0 <= port <= 65535): raise SIPCoreError("RTP port range values should be between 0 and 65535") _rtp_port_start, _rtp_port_stop = value _rtp_port_count = _rtp_port_stop - _rtp_port_start _rtp_port_usable_count = _rtp_port_count - _rtp_port_count % 2 # we need an even number of ports, so we won't use the last one if an odd number is provided if _rtp_port_usable_count < 2: raise SIPCoreError("RTP port range should contain at least 2 ports") self._rtp_port_start = _rtp_port_start self._rtp_port_count = _rtp_port_count self._rtp_port_usable_count = _rtp_port_usable_count self._rtp_port_index = 0 property user_agent: def __get__(self): self._check_self() return self._user_agent.str def __set__(self, value): self._check_self() - self._user_agent = PJSTR("value") + self._user_agent = PJSTR(b"value") property log_level: def __get__(self): self._check_self() return pj_log_get_level() def __set__(self, value): self._check_self() if value < 0 or value > PJ_LOG_MAX_LEVEL: raise ValueError("Log level should be between 0 and %d" % PJ_LOG_MAX_LEVEL) pj_log_set_level(value) property tls_verify_server: def __get__(self): self._check_self() return bool(self._pjsip_endpoint._tls_verify_server) property tls_ca_file: def __get__(self): self._check_self() if self._pjsip_endpoint._tls_ca_file is None: return None else: return self._pjsip_endpoint._tls_ca_file.str property tls_cert_file: def __get__(self): self._check_self() if self._pjsip_endpoint._tls_cert_file is None: return None else: return self._pjsip_endpoint._tls_cert_file.str property tls_privkey_file: def __get__(self): self._check_self() if self._pjsip_endpoint._tls_privkey_file is None: return None else: return self._pjsip_endpoint._tls_privkey_file.str property tls_timeout: def __get__(self): self._check_self() return self._pjsip_endpoint._tls_timeout def set_tls_options(self, port=None, verify_server=False, ca_file=None, cert_file=None, privkey_file=None, int timeout=3000): cdef int c_port self._check_self() if port is None: if self._pjsip_endpoint._tls_transport == NULL: return self._pjsip_endpoint._stop_tls_transport() else: c_port = port if not (0 <= c_port <= 65535): raise ValueError("Not a valid TCP port: %d" % port) if ca_file is not None and not os.path.isfile(ca_file): raise ValueError("Cannot find the specified CA file: %s" % ca_file) if cert_file is not None and not os.path.isfile(cert_file): raise ValueError("Cannot find the specified certificate file: %s" % cert_file) if privkey_file is not None and not os.path.isfile(privkey_file): raise ValueError("Cannot find the specified private key file: %s" % privkey_file) if timeout < 0: raise ValueError("Invalid TLS timeout value: %d" % timeout) if self._pjsip_endpoint._tls_transport != NULL: self._pjsip_endpoint._stop_tls_transport() self._pjsip_endpoint._tls_verify_server = int(bool(verify_server)) if ca_file is None: self._pjsip_endpoint._tls_ca_file = None else: self._pjsip_endpoint._tls_ca_file = PJSTR(ca_file.encode(sys.getfilesystemencoding())) if cert_file is None: self._pjsip_endpoint._tls_cert_file = None else: self._pjsip_endpoint._tls_cert_file = PJSTR(cert_file.encode(sys.getfilesystemencoding())) if privkey_file is None: self._pjsip_endpoint._tls_privkey_file = None else: self._pjsip_endpoint._tls_privkey_file = PJSTR(privkey_file.encode(sys.getfilesystemencoding())) self._pjsip_endpoint._tls_timeout = timeout self._pjsip_endpoint._start_tls_transport(c_port) def detect_nat_type(self, stun_server_address, stun_server_port=PJ_STUN_PORT, object user_data=None): cdef pj_str_t stun_server_address_pj cdef pj_sockaddr_in stun_server cdef int status self._check_self() - if not _is_valid_ip(pj_AF_INET(), stun_server_address): + if not _is_valid_ip(pj_AF_INET(), stun_server_address.encode()): raise ValueError("Not a valid IPv4 address: %s" % stun_server_address) - _str_to_pj_str(stun_server_address, &stun_server_address_pj) + _str_to_pj_str(stun_server_address.encode(), &stun_server_address_pj) status = pj_sockaddr_in_init(&stun_server, &stun_server_address_pj, stun_server_port) if status != 0: raise PJSIPError("Could not init STUN server address", status) status = pj_stun_detect_nat_type(&stun_server, &self._stun_cfg, user_data, _cb_detect_nat_type) if status != 0: raise PJSIPError("Could not start NAT type detection", status) Py_INCREF(user_data) def set_nameservers(self, list nameservers): self._check_self() return self._pjsip_endpoint._set_dns_nameservers([n for n in nameservers if _re_ipv4.match(n)]) def set_h264_options(self, profile, level): self._check_self() self._pjmedia_endpoint._set_h264_options(str(profile), int(level.replace('.', ''))) def set_video_options(self, max_resolution, int max_framerate, object max_bitrate): self._check_self() self._pjmedia_endpoint._set_video_options(tuple(max_resolution), max_framerate, max_bitrate or 0.0) property zrtp_cache: def __get__(self): self._check_self() return self._zrtp_cache def __set__(self, value): self._check_self() if value is None: value = os.path.join(tempfile.gettempdir(), 'zrtp_cache_%d.db' % os.getpid()) self._zrtp_cache = value def __dealloc__(self): self.dealloc() def dealloc(self): global _ua, _dealloc_handler_queue, _event_queue_lock if _ua == NULL: return self._check_thread() pjmedia_aud_dev_set_observer_cb(NULL) if self.audio_change_rwlock != NULL: pj_rwmutex_destroy(self.audio_change_rwlock) self.audio_change_rwlock = NULL if self.video_lock != NULL: pj_mutex_destroy(self.video_lock) self.video_lock = NULL _process_handler_queue(self, &_dealloc_handler_queue) if _event_queue_lock != NULL: pj_mutex_lock(_event_queue_lock) pj_mutex_destroy(_event_queue_lock) _event_queue_lock = NULL self._pjsip_endpoint = None self._pjmedia_endpoint = None self._caching_pool = None self._pjlib = None _ua = NULL self._poll_log() cdef int _poll_log(self) except -1: cdef object event_name cdef dict event_params cdef list events events = _get_clear_event_queue() for event_name, event_params in events: self._event_handler(event_name, **event_params) def poll(self): global _post_poll_handler_queue cdef int status cdef double now cdef object retval = None cdef float max_timeout cdef pj_time_val pj_max_timeout cdef list timers cdef Timer timer self._check_self() max_timeout = 0.100 while self._timers: if not (self._timers[0])._scheduled: # timer was cancelled heapq.heappop(self._timers) else: max_timeout = min(max((self._timers[0]).schedule_time - time.time(), 0.0), max_timeout) break pj_max_timeout.sec = int(max_timeout) pj_max_timeout.msec = int(max_timeout * 1000) % 1000 with nogil: status = pjsip_endpt_handle_events(self._pjsip_endpoint._obj, &pj_max_timeout) IF UNAME_SYSNAME == "Darwin": if status not in [0, PJ_ERRNO_START_SYS + errno.EBADF]: raise PJSIPError("Error while handling events", status) ELSE: if status != 0: raise PJSIPError("Error while handling events", status) _process_handler_queue(self, &_post_poll_handler_queue) timers = list() now = time.time() while self._timers: if not (self._timers[0])._scheduled: # timer was cancelled heapq.heappop(self._timers) elif (self._timers[0]).schedule_time <= now: # timer needs to be processed timer = heapq.heappop(self._timers) timers.append(timer) else: break for timer in timers: timer.call() self._poll_log() if self._fatal_error: return True else: return False cdef int _handle_exception(self, int is_fatal) except -1: cdef object exc_type cdef object exc_val cdef object exc_tb exc_type, exc_val, exc_tb = sys.exc_info() if is_fatal: self._fatal_error = is_fatal _add_event("SIPEngineGotException", dict(type=exc_type, value=exc_val, traceback="".join(traceback.format_exception(exc_type, exc_val, exc_tb)))) return 0 cdef int _check_self(self) except -1: global _ua if _ua == NULL: raise SIPCoreError("The PJSIPUA is no longer running") self._check_thread() cdef int _check_thread(self) except -1: if not pj_thread_is_registered(): self._threads.append(PJSIPThread()) return 0 cdef int _add_timer(self, Timer timer) except -1: heapq.heappush(self._timers, timer) return 0 cdef int _remove_timer(self, Timer timer) except -1: # Don't remove it from the heap, just mark it as not scheduled timer._scheduled = 0 return 0 cdef int _cb_rx_request(self, pjsip_rx_data *rdata) except 0: global _event_hdr_name cdef int status cdef int bad_request cdef pjsip_tx_data *tdata = NULL cdef pjsip_hdr_ptr_const hdr_add cdef IncomingRequest request cdef Invitation inv cdef IncomingSubscription sub cdef IncomingReferral ref cdef list extra_headers cdef dict event_dict cdef dict message_params cdef pj_str_t tsx_key cdef pjsip_via_hdr *top_via cdef pjsip_via_hdr *via cdef pjsip_transaction *tsx = NULL cdef unsigned int options = PJSIP_INV_SUPPORT_100REL cdef pjsip_event_hdr *event_hdr - cdef object method_name = _pj_str_to_str(rdata.msg_info.msg.line.req.method.name) - if method_name != "ACK": + cdef object method_name = _pj_str_to_bytes(rdata.msg_info.msg.line.req.method.name) + if method_name != b"ACK": if self._detect_sip_loops: # Temporarily trick PJSIP into believing the last Via header is actually the first top_via = via = rdata.msg_info.via while True: rdata.msg_info.via = via via = pjsip_msg_find_hdr(rdata.msg_info.msg, PJSIP_H_VIA, ( via).next) if via == NULL: break status = pjsip_tsx_create_key(rdata.tp_info.pool, &tsx_key, PJSIP_ROLE_UAC, &rdata.msg_info.msg.line.req.method, rdata) rdata.msg_info.via = top_via if status != 0: raise PJSIPError("Could not generate transaction key for incoming request", status) tsx = pjsip_tsx_layer_find_tsx(&tsx_key, 0) if tsx != NULL: status = pjsip_endpt_create_response(self._pjsip_endpoint._obj, rdata, 482, NULL, &tdata) if status != 0: raise PJSIPError("Could not create response", status) elif method_name in self._incoming_requests: request = IncomingRequest() request.init(self, rdata) - elif method_name == "OPTIONS": + elif method_name == b"OPTIONS": status = pjsip_endpt_create_response(self._pjsip_endpoint._obj, rdata, 200, NULL, &tdata) if status != 0: raise PJSIPError("Could not create response", status) for hdr_type in [PJSIP_H_ALLOW, PJSIP_H_ACCEPT, PJSIP_H_SUPPORTED]: hdr_add = pjsip_endpt_get_capability(self._pjsip_endpoint._obj, hdr_type, NULL) if hdr_add != NULL: pjsip_msg_add_hdr(tdata.msg, pjsip_hdr_clone(tdata.pool, hdr_add)) - elif method_name == "INVITE": + elif method_name == b"INVITE": status = pjsip_inv_verify_request(rdata, &options, NULL, NULL, self._pjsip_endpoint._obj, &tdata) if status == 0: inv = Invitation() inv.init_incoming(self, rdata, options) - elif method_name == "SUBSCRIBE": + elif method_name == b"SUBSCRIBE": event_hdr = pjsip_msg_find_hdr_by_name(rdata.msg_info.msg, &_event_hdr_name.pj_str, NULL) - if event_hdr == NULL or _pj_str_to_str(event_hdr.event_type) not in self._incoming_events: + if event_hdr == NULL or _pj_str_to_bytes(event_hdr.event_type) not in self._incoming_events: status = pjsip_endpt_create_response(self._pjsip_endpoint._obj, rdata, 489, NULL, &tdata) if status != 0: raise PJSIPError("Could not create response", status) else: sub = IncomingSubscription() - sub.init(self, rdata, _pj_str_to_str(event_hdr.event_type)) - elif method_name == "REFER": + sub.init(self, rdata, _pj_str_to_bytes(event_hdr.event_type)) + elif method_name == b"REFER": ref = IncomingReferral() ref.init(self, rdata) - elif method_name == "MESSAGE": + elif method_name == b"MESSAGE": bad_request = 0 extra_headers = list() message_params = dict() event_dict = dict() _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) message_params["request_uri"] = event_dict["request_uri"] message_params["from_header"] = event_dict["headers"].get("From", None) message_params["to_header"] = event_dict["headers"].get("To", None) message_params["headers"] = event_dict["headers"] message_params["body"] = event_dict["body"] content_type = message_params["headers"].get("Content-Type", None) if content_type is not None: message_params["content_type"] = content_type.content_type if message_params["headers"].get("Content-Length", 0) > 0 and message_params["body"] is None: bad_request = 1 extra_headers.append(WarningHeader(399, "local", "Missing body")) else: message_params["content_type"] = None if message_params["headers"].get("Content-Length", 0) > 0 and message_params["body"] is None: bad_request = 1 extra_headers.append(WarningHeader(399, "local", "Missing Content-Type header")) if bad_request: status = pjsip_endpt_create_response(self._pjsip_endpoint._obj, rdata, 400, NULL, &tdata) if status != 0: raise PJSIPError("Could not create response", status) _add_headers_to_tdata(tdata, extra_headers) else: _add_event("SIPEngineGotMessage", message_params) status = pjsip_endpt_create_response(self._pjsip_endpoint._obj, rdata, 200, NULL, &tdata) if status != 0: raise PJSIPError("Could not create response", status) - elif method_name != "ACK": + elif method_name != b"ACK": status = pjsip_endpt_create_response(self._pjsip_endpoint._obj, rdata, 405, NULL, &tdata) if status != 0: raise PJSIPError("Could not create response", status) if tdata != NULL: status = pjsip_endpt_send_response2(self._pjsip_endpoint._obj, rdata, tdata, NULL, NULL) if status != 0: pjsip_tx_data_dec_ref(tdata) raise PJSIPError("Could not send response", status) return 1 cdef class PJSIPThread: def __cinit__(self): - cdef object thread_name = "python_%d" % id(self) + str_id = "python_%d" % id(self) + cdef object thread_name = str_id.encode() cdef int status status = pj_thread_register(thread_name, self._thread_desc, &self._obj) if status != 0: raise PJSIPError("Error while registering thread", status) # callback functions cdef void _cb_audio_dev_process_event(pjmedia_aud_dev_event event) with gil: cdef PJSIPUA ua event_dict = dict() try: ua = _get_ua() except: return try: if event in (PJMEDIA_AUD_DEV_DEFAULT_INPUT_CHANGED, PJMEDIA_AUD_DEV_DEFAULT_OUTPUT_CHANGED): event_dict["changed_input"] = event == PJMEDIA_AUD_DEV_DEFAULT_INPUT_CHANGED event_dict["changed_output"] = event == PJMEDIA_AUD_DEV_DEFAULT_OUTPUT_CHANGED _add_event("DefaultAudioDeviceDidChange", event_dict) elif event == PJMEDIA_AUD_DEV_LIST_WILL_REFRESH: ua.old_devices = ua.sound_devices with nogil: status = pj_rwmutex_lock_write(ua.audio_change_rwlock) if status != 0: raise SIPCoreError('Could not acquire audio_change_rwlock for writing', status) elif event == PJMEDIA_AUD_DEV_LIST_DID_REFRESH: with nogil: status = pj_rwmutex_unlock_write(ua.audio_change_rwlock) if status != 0: raise SIPCoreError('Could not release the audio_change_rwlock', status) event_dict["old_devices"] = ua.old_devices event_dict["new_devices"] = ua.sound_devices _add_event("AudioDevicesDidChange", event_dict) except: ua._handle_exception(1) cdef void _cb_detect_nat_type(void *user_data, pj_stun_nat_detect_result_ptr_const res) with gil: cdef PJSIPUA ua cdef dict event_dict cdef object user_data_obj = user_data Py_DECREF(user_data_obj) try: ua = _get_ua() except: return try: event_dict = dict() event_dict["succeeded"] = res.status == 0 event_dict["user_data"] = user_data_obj if res.status == 0: event_dict["nat_type"] = res.nat_type_name else: event_dict["error"] = res.status_text _add_event("SIPEngineDetectedNATType", event_dict) except: ua._handle_exception(0) cdef int _PJSIPUA_cb_rx_request(pjsip_rx_data *rdata) with gil: cdef PJSIPUA ua try: ua = _get_ua() except: return 0 try: return ua._cb_rx_request(rdata) except: ua._handle_exception(0) cdef int _cb_opus_fix_tx(pjsip_tx_data *tdata) with gil: cdef PJSIPUA ua cdef pjsip_msg_body *body cdef pjsip_msg_body *new_body cdef pjmedia_sdp_session *sdp cdef pjmedia_sdp_media *media cdef pjmedia_sdp_attr *attr cdef int i cdef int j cdef pj_str_t new_value try: ua = _get_ua() except: return 0 try: if tdata != NULL and tdata.msg != NULL: body = tdata.msg.body if body != NULL and _pj_str_to_str(body.content_type.type).lower() == "application" and _pj_str_to_str(body.content_type.subtype).lower() == "sdp": new_body = pjsip_msg_body_clone(tdata.pool, body) sdp = new_body.data for i in range(sdp.media_count): media = sdp.media[i] if _pj_str_to_str(media.desc.media).lower() != "audio": continue for j in range(media.attr_count): attr = media.attr[j] if _pj_str_to_str(attr.name).lower() != "rtpmap": continue attr_value = _pj_str_to_str(attr.value).lower() pos = attr_value.find("opus") if pos == -1: continue # this is the opus rtpmap attribute opus_line = attr_value[:pos] + "opus/48000/2" new_value.slen = len(opus_line) new_value.ptr = pj_pool_alloc(tdata.pool, new_value.slen) memcpy(new_value.ptr, PyBytes_AsString(opus_line), new_value.slen) attr.value = new_value break tdata.msg.body = new_body except: ua._handle_exception(0) return 0 cdef int _cb_opus_fix_rx(pjsip_rx_data *rdata) with gil: cdef PJSIPUA ua cdef pjsip_msg_body *body cdef int pos1 cdef int pos2 cdef char *body_ptr try: ua = _get_ua() except: return 0 try: if rdata != NULL and rdata.msg_info.msg != NULL: body = rdata.msg_info.msg.body if body != NULL and _pj_str_to_str(body.content_type.type).lower() == "application" and _pj_str_to_str(body.content_type.subtype).lower() == "sdp": body_ptr = body.data - body_str = _pj_buf_len_to_str(body_ptr, body.len).lower() + body_str = _pj_buf_len_to_str(body_ptr, body.len).decode().lower() pos1 = body_str.find("opus/48000") if pos1 != -1: pos2 = body_str.find("opus/48000/2") if pos2 != -1: memcpy(body_ptr + pos2 + 11, '1', 1) else: # old opus, we must make it fail memcpy(body_ptr + pos1 + 5, 'XXXXX', 5) except: ua._handle_exception(0) return 0 cdef int _cb_trace_rx(pjsip_rx_data *rdata) with gil: cdef PJSIPUA ua try: ua = _get_ua() except: return 0 try: if ua._trace_sip: _add_event("SIPEngineSIPTrace", - dict(received=True, source_ip=rdata.pkt_info.src_name, source_port=rdata.pkt_info.src_port, + dict(received=True, + source_ip=rdata.pkt_info.src_name.decode(), + source_port=rdata.pkt_info.src_port, destination_ip=_pj_str_to_str(rdata.tp_info.transport.local_name.host), destination_port=rdata.tp_info.transport.local_name.port, - data=_pj_buf_len_to_str(rdata.pkt_info.packet, rdata.pkt_info.len), - transport=rdata.tp_info.transport.type_name)) + data=_pj_buf_len_to_str(rdata.pkt_info.packet, rdata.pkt_info.len).decode(), + transport=rdata.tp_info.transport.type_name.decode())) except: ua._handle_exception(0) return 0 cdef int _cb_trace_tx(pjsip_tx_data *tdata) with gil: cdef PJSIPUA ua try: ua = _get_ua() except: return 0 try: if ua._trace_sip: _add_event("SIPEngineSIPTrace", dict(received=False, source_ip=_pj_str_to_str(tdata.tp_info.transport.local_name.host), - source_port=tdata.tp_info.transport.local_name.port, destination_ip=tdata.tp_info.dst_name, + source_port=tdata.tp_info.transport.local_name.port, + destination_ip=tdata.tp_info.dst_name.decode(), destination_port=tdata.tp_info.dst_port, - data=_pj_buf_len_to_str(tdata.buf.start, tdata.buf.cur - tdata.buf.start), - transport=tdata.tp_info.transport.type_name)) + data=_pj_buf_len_to_str(tdata.buf.start, tdata.buf.cur - tdata.buf.start).decode(), + transport=tdata.tp_info.transport.type_name.decode())) except: ua._handle_exception(0) return 0 cdef int _cb_add_user_agent_hdr(pjsip_tx_data *tdata) with gil: cdef PJSIPUA ua cdef pjsip_hdr *hdr cdef void *found_hdr try: ua = _get_ua() except: return 0 try: found_hdr = pjsip_msg_find_hdr_by_name(tdata.msg, &_user_agent_hdr_name.pj_str, NULL) if found_hdr == NULL: hdr = pjsip_generic_string_hdr_create(tdata.pool, &_user_agent_hdr_name.pj_str, &ua._user_agent.pj_str) if hdr == NULL: raise SIPCoreError('Could not add "User-Agent" header to outgoing request') pjsip_msg_add_hdr(tdata.msg, hdr) except: ua._handle_exception(0) return 0 cdef int _cb_add_server_hdr(pjsip_tx_data *tdata) with gil: cdef PJSIPUA ua cdef pjsip_hdr *hdr cdef void *found_hdr try: ua = _get_ua() except: return 0 try: found_hdr = pjsip_msg_find_hdr_by_name(tdata.msg, &_server_hdr_name.pj_str, NULL) if found_hdr == NULL: hdr = pjsip_generic_string_hdr_create(tdata.pool, &_server_hdr_name.pj_str, &ua._user_agent.pj_str) if hdr == NULL: raise SIPCoreError('Could not add "Server" header to outgoing response') pjsip_msg_add_hdr(tdata.msg, hdr) except: ua._handle_exception(0) return 0 # functions cdef PJSIPUA _get_ua(): global _ua cdef PJSIPUA ua if _ua == NULL: raise SIPCoreError("PJSIPUA is not instantiated") ua = _ua ua._check_thread() return ua cdef int deallocate_weakref(object weak_ref, object timer) except -1 with gil: Py_DECREF(weak_ref) # globals cdef void *_ua = NULL -cdef PJSTR _user_agent_hdr_name = PJSTR("User-Agent") -cdef PJSTR _server_hdr_name = PJSTR("Server") -cdef PJSTR _event_hdr_name = PJSTR("Event") +cdef PJSTR _user_agent_hdr_name = PJSTR(b"User-Agent") +cdef PJSTR _server_hdr_name = PJSTR(b"Server") +cdef PJSTR _event_hdr_name = PJSTR(b"Event") cdef object _re_ipv4 = re.compile(r"^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$") diff --git a/sipsimple/core/_core.util.pxi b/sipsimple/core/_core.util.pxi index b862d7c5..53d9f9ae 100644 --- a/sipsimple/core/_core.util.pxi +++ b/sipsimple/core/_core.util.pxi @@ -1,422 +1,433 @@ import platform import re import sys from application.version import Version cdef class PJSTR: def __cinit__(self, str): self.str = str _str_to_pj_str(str, &self.pj_str) def __str__(self): return self.str - cdef class SIPStatusMessages: cdef object _default_status def __cinit__(self, *args, **kwargs): self._default_status = _pj_str_to_str(pjsip_get_status_text(0)[0]) def __getitem__(self, int val): cdef object _status _status = _pj_str_to_str(pjsip_get_status_text(val)[0]) if _status == self._default_status: raise IndexError("Unknown SIP response code: %d" % val) return _status cdef class frozenlist: def __cinit__(self, *args, **kw): self.list = list() self.initialized = 0 self.hash = 0 def __init__(self, *args, **kw): if not self.initialized: self.list = list(*args, **kw) self.initialized = 1 self.hash = hash(tuple(self.list)) def __reduce__(self): return (self.__class__, (self.list,), None) def __repr__(self): return "frozenlist(%r)" % self.list def __len__(self): return self.list.__len__() def __hash__(self): return self.hash def __iter__(self): return self.list.__iter__() def __cmp__(self, frozenlist other): return self.list.__cmp__(other.list) def __richcmp__(frozenlist self, other, op): if isinstance(other, frozenlist): other = (other).list if op == 0: return self.list.__cmp__(other) < 0 elif op == 1: return self.list.__cmp__(other) <= 0 elif op == 2: return self.list.__eq__(other) elif op == 3: return self.list.__ne__(other) elif op == 4: return self.list.__cmp__(other) > 0 elif op == 5: return self.list.__cmp__(other) >= 0 else: return NotImplemented def __contains__(self, item): return self.list.__contains__(item) def __getitem__(self, key): return self.list.__getitem__(key) def __add__(first, second): if isinstance(first, frozenlist): first = (first).list if isinstance(second, frozenlist): second = (second).list return frozenlist(first+second) def __mul__(first, second): if isinstance(first, frozenlist): first = (first).list if isinstance(second, frozenlist): second = (second).list return frozenlist(first*second) def __reversed__(self): return self.list.__reversed__() def count(self, elem): return self.list.count(elem) def index(self, elem): return self.list.index(elem) cdef class frozendict: def __cinit__(self, *args, **kw): self.dict = dict() self.initialized = 0 def __init__(self, *args, **kw): if not self.initialized: self.dict = dict(*args, **kw) self.initialized = 1 self.hash = hash(tuple(self.dict.iteritems())) def __reduce__(self): return (self.__class__, (self.dict,), None) def __repr__(self): return "frozendict(%r)" % self.dict def __len__(self): return self.dict.__len__() def __hash__(self): return self.hash def __iter__(self): return self.dict.__iter__() def __cmp__(self, frozendict other): return self.dict.__cmp__(other.dict) def __richcmp__(frozendict self, other, op): if isinstance(other, frozendict): other = (other).dict if op == 0: return self.dict.__cmp__(other) < 0 elif op == 1: return self.dict.__cmp__(other) <= 0 elif op == 2: return self.dict.__eq__(other) elif op == 3: return self.dict.__ne__(other) elif op == 4: return self.dict.__cmp__(other) > 0 elif op == 5: return self.dict.__cmp__(other) >= 0 else: return NotImplemented def __contains__(self, item): return self.dict.__contains__(item) def __getitem__(self, key): return self.dict.__getitem__(key) def copy(self): return self def get(self, *args): return self.dict.get(*args) def has_key(self, key): return self.dict.has_key(key) def items(self): - return self.dict.items() + return list(self.dict.items()) def iteritems(self): - return self.dict.iteritems() + return list(self.dict.items()) def iterkeys(self): - return self.dict.iterkeys() + return list(self.dict.keys()) def itervalues(self): - return self.dict.itervalues() + return list(self.dict.values()) def keys(self): - return self.dict.keys() + return list(self.dict.keys()) def values(self): - return self.dict.values() + return list(self.dict.values()) # functions cdef int _str_to_pj_str(object string, pj_str_t *pj_str) except -1: - # Feed data from Python to PJSIP - # TODO: convert to Python3 - bytes_string = string.encode() - pj_str.ptr = PyBytes_AsString(bytes_string) - pj_str.slen = len(bytes_string) - print("Encoded STR %s to PJS %s" % (string, pj_str.ptr)) + #print('Convert %s (%s)' % (string, type(string))) + pj_str.ptr = PyBytes_AsString(string) + pj_str.slen = len(string) + +cdef object _pj_str_to_bytes(pj_str_t pj_str): + return PyBytes_FromStringAndSize(pj_str.ptr, pj_str.slen) cdef object _pj_str_to_str(pj_str_t pj_str): - # Feed data from PJSIP to the Python - bytes_string = PyBytes_FromStringAndSize(pj_str.ptr, pj_str.slen) - string = bytes_string.decode() - print("Decoded PJS %s to STR %s" % (pj_str.ptr, string)) - return string + return PyBytes_FromStringAndSize(pj_str.ptr, pj_str.slen).decode() cdef object _pj_buf_len_to_str(object buf, int buf_len): - # TODO: convert to Python3 return PyBytes_FromStringAndSize(buf, buf_len) cdef object _buf_to_str(object buf): - # TODO: convert to Python3 return PyBytes_FromString(buf) cdef object _str_as_str(object string): - # TODO: convert to Python3 return PyBytes_AsString(string) cdef object _str_as_size(object string): - # TODO: convert to Python3 return PyBytes_Size(string) cdef object _pj_status_to_str(int status): cdef char buf[PJ_ERR_MSG_SIZE] return _pj_str_to_str(pj_strerror(status, buf, PJ_ERR_MSG_SIZE)) cdef object _pj_status_to_def(int status): return _re_pj_status_str_def.match(_pj_status_to_str(status)).group(1) cdef dict _pjsip_param_to_dict(pjsip_param *param_list): cdef pjsip_param *param cdef dict retval = dict() param = ( param_list).next while param != param_list: if param.value.slen == 0: retval[_pj_str_to_str(param.name)] = None else: retval[_pj_str_to_str(param.name)] = _pj_str_to_str(param.value) param = ( param).next return retval cdef int _dict_to_pjsip_param(object params, pjsip_param *param_list, pj_pool_t *pool): cdef pjsip_param *param = NULL for name, value in params.iteritems(): param = pj_pool_alloc(pool, sizeof(pjsip_param)) if param == NULL: return -1 _str_to_pj_str(name, ¶m.name) if value is None: param.value.slen = 0 else: _str_to_pj_str(value, ¶m.value) pj_list_insert_after( param_list, param) return 0 cdef int _pjsip_msg_to_dict(pjsip_msg *msg, dict info_dict) except -1: cdef pjsip_msg_body *body cdef pjsip_hdr *header cdef pjsip_generic_array_hdr *array_header cdef pjsip_ctype_hdr *ctype_header cdef pjsip_cseq_hdr *cseq_header cdef char *buf cdef int buf_len, i, status headers = {} header = ( &msg.hdr).next while header != &msg.hdr: header_name = _pj_str_to_str(header.name) header_data = None multi_header = False if header_name in ("Accept", "Allow", "Require", "Supported", "Unsupported", "Allow-Events"): array_header = header header_data = [] for i from 0 <= i < array_header.count: - header_data.append(_pj_str_to_str(array_header.values[i])) + pass + # TODO crash here + #header_data.append(_pj_str_to_str(array_header.values[i])) elif header_name == "Contact": multi_header = True header_data = FrozenContactHeader_create( header) elif header_name == "Content-Length": header_data = ( header).len elif header_name == "Content-Type": header_data = FrozenContentTypeHeader_create( header) elif header_name == "CSeq": cseq_header = header - header_data = (cseq_header.cseq, _pj_str_to_str(cseq_header.method.name)) + hvalue = _pj_str_to_str(cseq_header.method.name) + header_data = (cseq_header.cseq, hvalue) elif header_name in ("Expires", "Max-Forwards", "Min-Expires"): header_data = ( header).ivalue elif header_name == "From": header_data = FrozenFromHeader_create( header) elif header_name == "To": header_data = FrozenToHeader_create( header) elif header_name == "Route": multi_header = True header_data = FrozenRouteHeader_create( header) elif header_name == "Reason": value = _pj_str_to_str((header).hvalue) protocol, sep, params_str = value.partition(';') params = frozendict([(name, value or None) for name, sep, value in [param.partition('=') for param in params_str.split(';')]]) header_data = FrozenReasonHeader(protocol, params) elif header_name == "Record-Route": multi_header = True header_data = FrozenRecordRouteHeader_create( header) elif header_name == "Retry-After": header_data = FrozenRetryAfterHeader_create( header) elif header_name == "Via": multi_header = True header_data = FrozenViaHeader_create( header) elif header_name == "Warning": match = _re_warning_hdr.match(_pj_str_to_str((header).hvalue)) if match is not None: warning_params = match.groupdict() warning_params['code'] = int(warning_params['code']) header_data = FrozenWarningHeader(**warning_params) elif header_name == "Event": header_data = FrozenEventHeader_create( header) elif header_name == "Subscription-State": header_data = FrozenSubscriptionStateHeader_create( header) elif header_name == "Refer-To": header_data = FrozenReferToHeader_create( header) elif header_name == "Subject": header_data = FrozenSubjectHeader_create( header) elif header_name == "Replaces": header_data = FrozenReplacesHeader_create( header) # skip the following headers: elif header_name not in ("Authorization", "Proxy-Authenticate", "Proxy-Authorization", "WWW-Authenticate"): - header_data = FrozenHeader(header_name, _pj_str_to_str(( header).hvalue)) + hvalue = ( header).hvalue + header_value = _pj_str_to_str(hvalue) + header_data = FrozenHeader(header_name, header_value) + if header_data is not None: if multi_header: headers.setdefault(header_name, []).append(header_data) else: if header_name not in headers: headers[header_name] = header_data header = ( header).next info_dict["headers"] = headers body = msg.body if body == NULL: info_dict["body"] = None else: status = pjsip_print_body(body, &buf, &buf_len) if status != 0: info_dict["body"] = None else: - info_dict["body"] = _pj_buf_len_to_str(buf, buf_len) + info_dict["body"] = _pj_buf_len_to_str(buf, buf_len).decode() if msg.type == PJSIP_REQUEST_MSG: info_dict["method"] = _pj_str_to_str(msg.line.req.method.name) # You need to call pjsip_uri_get_uri on the request URI if the message is for transmitting, # but it isn't required if message is one received. Otherwise, a seg fault occurs. Don't ask. info_dict["request_uri"] = FrozenSIPURI_create(pjsip_uri_get_uri(msg.line.req.uri)) else: info_dict["code"] = msg.line.status.code info_dict["reason"] = _pj_str_to_str(msg.line.status.reason) return 0 cdef int _is_valid_ip(int af, object ip) except -1: cdef char buf[16] cdef pj_str_t src cdef int status _str_to_pj_str(ip, &src) status = pj_inet_pton(af, &src, buf) if status == 0: return 1 else: return 0 cdef int _get_ip_version(object ip) except -1: if _is_valid_ip(pj_AF_INET(), ip): return pj_AF_INET() elif _is_valid_ip(pj_AF_INET6(), ip): return pj_AF_INET() else: return 0 cdef int _add_headers_to_tdata(pjsip_tx_data *tdata, object headers) except -1: cdef pj_str_t name_pj, value_pj cdef pjsip_hdr *hdr for header in headers: - _str_to_pj_str(header.name, &name_pj) - _str_to_pj_str(header.body, &value_pj) + hb = header.name.encode() + bb = header.body.encode() + _str_to_pj_str(hb, &name_pj) + _str_to_pj_str(bb, &value_pj) hdr = pjsip_generic_string_hdr_create(tdata.pool, &name_pj, &value_pj) pjsip_msg_add_hdr(tdata.msg, hdr) cdef int _remove_headers_from_tdata(pjsip_tx_data *tdata, object headers) except -1: cdef pj_str_t header_name_pj cdef pjsip_hdr *hdr for header in headers: _str_to_pj_str(header, &header_name_pj) hdr = pjsip_msg_find_remove_hdr_by_name(tdata.msg, &header_name_pj, NULL) +cdef int _BaseRouteHeader_to_pjsip_route_hdr(BaseIdentityHeader header, pjsip_route_hdr *pj_header, pj_pool_t *pool) except -1: + cdef pjsip_param *param + cdef pjsip_sip_uri *sip_uri + pjsip_route_hdr_init(NULL, pj_header) + sip_uri = pj_pool_alloc(pool, sizeof(pjsip_sip_uri)) + _BaseSIPURI_to_pjsip_sip_uri(header.uri, sip_uri, pool) + + pj_header.name_addr.uri = sip_uri + if header.display_name: + _str_to_pj_str(header.display_name, &pj_header.name_addr.display) + _dict_to_pjsip_param(header.parameters, &pj_header.other_param, pool) + return 0 + cdef int _BaseSIPURI_to_pjsip_sip_uri(BaseSIPURI uri, pjsip_sip_uri *pj_uri, pj_pool_t *pool) except -1: cdef pjsip_param *param pjsip_sip_uri_init(pj_uri, uri.secure) if uri.user: - _str_to_pj_str(uri.user, &pj_uri.user) + _str_to_pj_str(uri.user.encode(), &pj_uri.user) if uri.password: - _str_to_pj_str(uri.password, &pj_uri.passwd) + _str_to_pj_str(uri.password.encode(), &pj_uri.passwd) if uri.host: - _str_to_pj_str(uri.host, &pj_uri.host) + _str_to_pj_str(uri.host.encode(), &pj_uri.host) if uri.port: pj_uri.port = uri.port + for name, value in uri.parameters.iteritems(): + #print('Parse parameter %s (%s): %s (%s)' % (name, type(name), value, type(value))) + if value is not None: + try: + int(value) + except ValueError: + value = value.encode() + + name = name.encode() + if name == "lr": pj_uri.lr_param = 1 elif name == "maddr": _str_to_pj_str(value, &pj_uri.maddr_param) elif name == "method": _str_to_pj_str(value, &pj_uri.method_param) elif name == "transport": _str_to_pj_str(value, &pj_uri.transport_param) elif name == "ttl": pj_uri.ttl_param = int(value) elif name == "user": _str_to_pj_str(value, &pj_uri.user_param) else: param = pj_pool_alloc(pool, sizeof(pjsip_param)) _str_to_pj_str(name, ¶m.name) if value is None: param.value.slen = 0 else: _str_to_pj_str(value, ¶m.value) pj_list_insert_after( &pj_uri.other_param, param) _dict_to_pjsip_param(uri.headers, &pj_uri.header_param, pool) return 0 -cdef int _BaseRouteHeader_to_pjsip_route_hdr(BaseIdentityHeader header, pjsip_route_hdr *pj_header, pj_pool_t *pool) except -1: - cdef pjsip_param *param - cdef pjsip_sip_uri *sip_uri - pjsip_route_hdr_init(NULL, pj_header) - sip_uri = pj_pool_alloc(pool, sizeof(pjsip_sip_uri)) - _BaseSIPURI_to_pjsip_sip_uri(header.uri, sip_uri, pool) - pj_header.name_addr.uri = sip_uri - if header.display_name: - _str_to_pj_str(header.display_name.encode('utf-8'), &pj_header.name_addr.display) - _dict_to_pjsip_param(header.parameters, &pj_header.other_param, pool) - return 0 def _get_device_name_encoding(): if sys.platform == 'win32': encoding = 'mbcs' elif sys.platform.startswith('linux2') and Version.parse(platform.release()) < Version(2,6,31): encoding = 'latin1' else: encoding = 'utf-8' return encoding _device_name_encoding = _get_device_name_encoding() def decode_device_name(device_name): # ignore decoding errors, some systems (I'm looking at you, OSX), seem to misbehave return device_name.decode(_device_name_encoding, 'ignore') # globals cdef object _re_pj_status_str_def = re.compile("^.*\((.*)\)$") cdef object _re_warning_hdr = re.compile('(?P[0-9]{3}) (?P.*?) "(?P.*?)"') sip_status_messages = SIPStatusMessages() diff --git a/sipsimple/core/_engine.py b/sipsimple/core/_engine.py index 9c2c99b5..b136aef7 100644 --- a/sipsimple/core/_engine.py +++ b/sipsimple/core/_engine.py @@ -1,138 +1,138 @@ """ Implements a mechanism for starting the SIP core engine based on PJSIP (http://pjsip.org) stack. """ __all__ = ["Engine"] import sys import traceback import atexit from application.notification import NotificationCenter, NotificationData from application.python.types import Singleton from threading import Thread, RLock from sipsimple import log, __version__ from sipsimple.core._core import PJSIPUA, PJ_VERSION, PJ_SVN_REVISION, SIPCoreError class Engine(Thread, metaclass=Singleton): default_start_options = {"ip_address": None, "udp_port": 0, "tcp_port": None, "tls_port": None, "tls_verify_server": False, "tls_ca_file": None, "tls_cert_file": None, "tls_privkey_file": None, "tls_timeout": 3000, - "user_agent": "sipsimple-%s-pjsip-%s-r%s" % (__version__, PJ_VERSION, PJ_SVN_REVISION), + "user_agent": "sipsimple-%s-pjsip-%s-r%s" % (__version__, PJ_VERSION, PJ_SVN_REVISION), "log_level": 0, "trace_sip": False, "detect_sip_loops": True, "rtp_port_range": (50000, 50500), "zrtp_cache": None, "codecs": ["G722", "speex", "PCMU", "PCMA"], "video_codecs": ["H264", "H263-1998", "VP8"], "enable_colorbar_device": False, - "events": {"conference": ["application/conference-info+xml"], - "message-summary": ["application/simple-message-summary"], - "presence": ["multipart/related", "application/rlmi+xml", "application/pidf+xml"], - "presence.winfo": ["application/watcherinfo+xml"], - "dialog": ["multipart/related", "application/rlmi+xml", "application/dialog-info+xml"], - "dialog.winfo": ["application/watcherinfo+xml"], - "refer": ["message/sipfrag;version=2.0"], - "xcap-diff": ["application/xcap-diff+xml"]}, + "events": {b"conference": [b"application/conference-info+xml"], + b"message-summary": [b"application/simple-message-summary"], + b"presence": [b"multipart/related", b"application/rlmi+xml", b"application/pidf+xml"], + b"presence.winfo": [b"application/watcherinfo+xml"], + b"dialog": [b"multipart/related", b"application/rlmi+xml", b"application/dialog-info+xml"], + b"dialog.winfo": [b"application/watcherinfo+xml"], + b"refer": [b"message/sipfrag;version=2.0"], + b"xcap-diff": [b"application/xcap-diff+xml"]}, "incoming_events": set(), "incoming_requests": set()} def __init__(self): self.notification_center = NotificationCenter() self._thread_started = False self._thread_stopping = False self._lock = RLock() self._options = None atexit.register(self.stop) super(Engine, self).__init__() self.daemon = True @property def is_running(self): return (hasattr(self, "_ua") and hasattr(self, "_thread_started") and self._thread_started and not self._thread_stopping) def __dir__(self): if hasattr(self, '_ua'): ua_attributes = [attr for attr in dir(self._ua) if not attr.startswith('__') and attr != 'poll'] else: ua_attributes = [] return sorted(set(dir(self.__class__) + list(self.__dict__.keys()) + ua_attributes)) def __getattr__(self, attr): if attr not in ["_ua", "poll"] and hasattr(self, "_ua") and attr in dir(self._ua): return getattr(self._ua, attr) raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, attr)) def __setattr__(self, attr, value): if attr not in ["_ua", "poll"] and hasattr(self, "_ua") and attr in dir(self._ua): setattr(self._ua, attr, value) return object.__setattr__(self, attr, value) def start(self, **kwargs): with self._lock: if self._thread_started: raise SIPCoreError("Worker thread was already started once") self._options = kwargs self._thread_started = True super(Engine, self).start() def stop(self): with self._lock: if self._thread_stopping: return if self._thread_started: self._thread_stopping = True # worker thread def run(self): self.notification_center.post_notification('SIPEngineWillStart', sender=self) init_options = Engine.default_start_options.copy() init_options.update(self._options) try: self._ua = PJSIPUA(self._handle_event, **init_options) except Exception: log.exception('Exception occurred while starting the Engine') exc_type, exc_val, exc_tb = sys.exc_info() exc_tb = "".join(traceback.format_exception(exc_type, exc_val, exc_tb)) self.notification_center.post_notification('SIPEngineGotException', sender=self, data=NotificationData(type=exc_type, value=exc_val, traceback=exc_tb)) self.notification_center.post_notification('SIPEngineDidFail', sender=self) return else: self.notification_center.post_notification('SIPEngineDidStart', sender=self) failed = False while not self._thread_stopping: try: failed = self._ua.poll() except: log.exception('Exception occurred while running the Engine') exc_type, exc_val, exc_tb = sys.exc_info() self.notification_center.post_notification('SIPEngineGotException', sender=self, data=NotificationData(type=exc_type, value=exc_val, traceback="".join(traceback.format_exception(exc_type, exc_val, exc_tb)))) failed = True if failed: self.notification_center.post_notification('SIPEngineDidFail', sender=self) break if not failed: self.notification_center.post_notification('SIPEngineWillEnd', sender=self) self._ua.dealloc() del self._ua self.notification_center.post_notification('SIPEngineDidEnd', sender=self) def _handle_event(self, event_name, **kwargs): sender = kwargs.pop("obj", None) if sender is None: sender = self self.notification_center.post_notification(event_name, sender, NotificationData(**kwargs)) diff --git a/sipsimple/core/_primitives.py b/sipsimple/core/_primitives.py index 5f5eeeda..bde146eb 100644 --- a/sipsimple/core/_primitives.py +++ b/sipsimple/core/_primitives.py @@ -1,314 +1,316 @@ """ Implements a high-level mechanism for SIP methods that can be used for non-session based operations like REGISTER, SUBSCRIBE, PUBLISH and MESSAGE. """ __all__ = ["Message", "Registration", "Publication", "PublicationError", "PublicationETagError"] from threading import RLock from application.notification import IObserver, NotificationCenter, NotificationData from application.python import Null from zope.interface import implementer from sipsimple.core._core import ContactHeader, Header, Request, RouteHeader, SIPCoreError, SIPURI, ToHeader @implementer(IObserver) class Registration(object): def __init__(self, from_header, credentials=None, duration=300, extra_headers=None): self.from_header = from_header self.credentials = credentials self.duration = duration self.extra_headers = extra_headers or [] self._current_request = None self._last_request = None self._unregistering = False self._lock = RLock() is_registered = property(lambda self: self._last_request is not None) contact_uri = property(lambda self: None if self._last_request is None else self._last_request.contact_uri) expires_in = property(lambda self: 0 if self._last_request is None else self._last_request.expires_in) peer_address = property(lambda self: None if self._last_request is None else self._last_request.peer_address) def register(self, contact_header, route_header, timeout=None): with self._lock: try: self._make_and_send_request(contact_header, route_header, timeout, True) except SIPCoreError as e: notification_center = NotificationCenter() notification_center.post_notification('SIPRegistrationDidFail', sender=self, data=NotificationData(code=0, reason=e.args[0], route_header=route_header)) def end(self, timeout=None): with self._lock: if self._last_request is None: return notification_center = NotificationCenter() notification_center.post_notification('SIPRegistrationWillEnd', sender=self) try: self._make_and_send_request(ContactHeader.new(self._last_request.contact_header), RouteHeader.new(self._last_request.route_header), timeout, False) except SIPCoreError as e: notification_center.post_notification('SIPRegistrationDidNotEnd', sender=self, data=NotificationData(code=0, reason=e.args[0])) def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_SIPRequestDidSucceed(self, notification): request = notification.sender with self._lock: if request is not self._current_request: return self._current_request = None if self._unregistering: if self._last_request is not None: self._last_request.end() self._last_request = None notification.center.post_notification('SIPRegistrationDidEnd', sender=self, data=NotificationData(expired=False)) else: self._last_request = request try: contact_header_list = notification.data.headers["Contact"] except KeyError: contact_header_list = [] notification.center.post_notification('SIPRegistrationDidSucceed', sender=self, data=NotificationData(code=notification.data.code, reason=notification.data.reason, contact_header=request.contact_header, contact_header_list=contact_header_list, expires_in=notification.data.expires, route_header=request.route_header)) def _NH_SIPRequestDidFail(self, notification): request = notification.sender with self._lock: if request is not self._current_request: return self._current_request = None if self._unregistering: notification.center.post_notification('SIPRegistrationDidNotEnd', sender=self, data=NotificationData(code=notification.data.code, reason=notification.data.reason)) else: if hasattr(notification.data, 'headers'): min_expires = notification.data.headers.get('Min-Expires', None) else: min_expires = None notification.center.post_notification('SIPRegistrationDidFail', sender=self, data=NotificationData(code=notification.data.code, reason=notification.data.reason, route_header=request.route_header, min_expires=min_expires)) def _NH_SIPRequestWillExpire(self, notification): with self._lock: if notification.sender is not self._last_request: return notification.center.post_notification('SIPRegistrationWillExpire', sender=self, data=NotificationData(expires=notification.data.expires)) def _NH_SIPRequestDidEnd(self, notification): request = notification.sender with self._lock: notification.center.remove_observer(self, sender=request) if request is not self._last_request: return self._last_request = None if self._current_request is not None: self._current_request.end() self._current_request = None notification.center.post_notification('SIPRegistrationDidEnd', sender=self, data=NotificationData(expired=True)) def _make_and_send_request(self, contact_header, route_header, timeout, do_register): notification_center = NotificationCenter() prev_request = self._current_request or self._last_request if prev_request is not None: call_id = prev_request.call_id cseq = prev_request.cseq + 1 else: call_id = None cseq = 1 extra_headers = [] extra_headers.append(Header("Expires", str(int(self.duration) if do_register else 0))) extra_headers.extend(self.extra_headers) - request = Request("REGISTER", SIPURI(self.from_header.uri.host), self.from_header, ToHeader.new(self.from_header), route_header, + uri = SIPURI(self.from_header.uri.host) + to_header = ToHeader.new(self.from_header) + request = Request("REGISTER", uri, self.from_header, to_header, route_header, credentials=self.credentials, contact_header=contact_header, call_id=call_id, cseq=cseq, extra_headers=extra_headers) notification_center.add_observer(self, sender=request) if self._current_request is not None: # we are trying to send something already, cancel whatever it is self._current_request.end() self._current_request = None try: request.send(timeout=timeout) except: notification_center.remove_observer(self, sender=request) raise self._unregistering = not do_register self._current_request = request @implementer(IObserver) class Message(object): def __init__(self, from_header, to_header, route_header, content_type, body, credentials=None, extra_headers=None): - self._request = Request("MESSAGE", to_header.uri, from_header, to_header, route_header, credentials=credentials, extra_headers=extra_headers, content_type=content_type, body=body) + self._request = Request("MESSAGE", to_header.uri, from_header, to_header, route_header, credentials=credentials, extra_headers=extra_headers, content_type=content_type, body=body.encode()) self._lock = RLock() from_header = property(lambda self: self._request.from_header) to_header = property(lambda self: self._request.to_header) route_header = property(lambda self: self._request.route_header) content_type = property(lambda self: self._request.content_type) body = property(lambda self: self._request.body) credentials = property(lambda self: self._request.credentials) is_sent = property(lambda self: self._request.state != "INIT") in_progress = property(lambda self: self._request.state == "IN_PROGRESS") peer_address = property(lambda self: self._request.peer_address) def send(self, timeout=None): notification_center = NotificationCenter() with self._lock: if self.is_sent: raise RuntimeError("This MESSAGE was already sent") notification_center.add_observer(self, sender=self._request) try: self._request.send(timeout) except: notification_center.remove_observer(self, sender=self._request) raise def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_SIPRequestDidSucceed(self, notification): if notification.data.expires: # this shouldn't happen really notification.sender.end() notification.center.post_notification('SIPMessageDidSucceed', sender=self, data=notification.data) def _NH_SIPRequestDidFail(self, notification): notification.center.post_notification('SIPMessageDidFail', sender=self, data=notification.data) def _NH_SIPRequestDidEnd(self, notification): notification.center.remove_observer(self, sender=notification.sender) class PublicationError(Exception): pass class PublicationETagError(PublicationError): pass @implementer(IObserver) class Publication(object): def __init__(self, from_header, event, content_type, credentials=None, duration=300, extra_headers=None): self.from_header = from_header self.event = event self.content_type = content_type self.credentials = credentials self.duration = duration self.extra_headers = extra_headers or [] self._last_etag = None self._current_request = None self._last_request = None self._unpublishing = False self._lock = RLock() is_published = property(lambda self: self._last_request is not None) expires_in = property(lambda self: 0 if self._last_request is None else self._last_request.expires_in) peer_address = property(lambda self: None if self._last_request is None else self._last_request.peer_address) def publish(self, body, route_header, timeout=None): with self._lock: if body is None: if self._last_request is None: raise ValueError("Need body for initial PUBLISH") elif self._last_etag is None: raise PublicationETagError("Cannot refresh, last ETag was invalid") self._make_and_send_request(body, route_header, timeout, True) def end(self, timeout=None): with self._lock: if self._last_request is None: return notification_center = NotificationCenter() notification_center.post_notification('SIPPublicationWillEnd', sender=self) try: self._make_and_send_request(None, RouteHeader.new(self._last_request.route_header), timeout, False) except SIPCoreError as e: notification_center.post_notification('SIPPublicationDidNotEnd', sender=self, data=NotificationData(code=0, reason=e.args[0])) def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_SIPRequestDidSucceed(self, notification): request = notification.sender with self._lock: if request is not self._current_request: return self._current_request = None if self._unpublishing: if self._last_request is not None: self._last_request.end() self._last_request = None self._last_etag = None notification.center.post_notification('SIPPublicationDidEnd', sender=self, data=NotificationData(expired=False)) else: self._last_request = request self._last_etag = notification.data.headers["SIP-ETag"].body if "SIP-ETag" in notification.data.headers else None # TODO: add more data? notification.center.post_notification('SIPPublicationDidSucceed', sender=self, data=NotificationData(code=notification.data.code, reason=notification.data.reason, expires_in=notification.data.expires, route_header=request.route_header)) def _NH_SIPRequestDidFail(self, notification): request = notification.sender with self._lock: if request is not self._current_request: return self._current_request = None if notification.data.code == 412: self._last_etag = None if self._unpublishing: notification.center.post_notification('SIPPublicationDidNotEnd', sender=self, data=NotificationData(code=notification.data.code, reason=notification.data.reason)) else: notification.center.post_notification('SIPPublicationDidFail', sender=self, data=NotificationData(code=notification.data.code, reason=notification.data.reason, route_header=request.route_header)) def _NH_SIPRequestWillExpire(self, notification): with self._lock: if notification.sender is not self._last_request: return notification.center.post_notification('SIPPublicationWillExpire', sender=self, data=NotificationData(expires=notification.data.expires)) def _NH_SIPRequestDidEnd(self, notification): with self._lock: notification.center.remove_observer(self, sender=notification.sender) if notification.sender is not self._last_request: return self._last_request = None if self._current_request is not None: self._current_request.end() self._current_request = None self._last_etag = None notification.center.post_notification('SIPPublicationDidEnd', sender=self, data=NotificationData(expired=True)) def _make_and_send_request(self, body, route_header, timeout, do_publish): notification_center = NotificationCenter() extra_headers = [] extra_headers.append(Header("Event", self.event)) extra_headers.append(Header("Expires", str(int(self.duration) if do_publish else 0))) if self._last_etag is not None: extra_headers.append(Header("SIP-If-Match", self._last_etag)) extra_headers.extend(self.extra_headers) content_type = (self.content_type if body is not None else None) request = Request("PUBLISH", self.from_header.uri, self.from_header, ToHeader.new(self.from_header), route_header, credentials=self.credentials, cseq=1, extra_headers=extra_headers, content_type=content_type, body=body) notification_center.add_observer(self, sender=request) if self._current_request is not None: # we are trying to send something already, cancel whatever it is self._current_request.end() self._current_request = None try: request.send(timeout=timeout) except: notification_center.remove_observer(self, sender=request) raise self._unpublishing = not do_publish self._current_request = request diff --git a/sipsimple/session.py b/sipsimple/session.py index c7fdad82..714c043b 100644 --- a/sipsimple/session.py +++ b/sipsimple/session.py @@ -1,2730 +1,2730 @@ """ Implements an asynchronous notification based mechanism for establishment, modification and termination of sessions using Session Initiation Protocol (SIP) standardized in RFC3261. """ __all__ = ['Session', 'SessionManager'] import random from threading import RLock from time import time from application.notification import IObserver, Notification, NotificationCenter, NotificationData from application.python import Null, limit from application.python.decorator import decorator, preserve_signature from application.python.types import Singleton from application.system import host from eventlib import api, coros, proc from twisted.internet import reactor from zope.interface import implementer from sipsimple import log from sipsimple.account import AccountManager, BonjourAccount from sipsimple.configuration.settings import SIPSimpleSettings from sipsimple.core import DialogID, Engine, Invitation, Referral, Subscription, PJSIPError, SIPCoreError, SIPCoreInvalidStateError, SIPURI, sip_status_messages, sipfrag_re from sipsimple.core import ContactHeader, FromHeader, Header, ReasonHeader, ReferToHeader, ReplacesHeader, RouteHeader, ToHeader, WarningHeader from sipsimple.core import SDPConnection, SDPMediaStream, SDPSession from sipsimple.core import PublicGRUU, PublicGRUUIfAvailable, NoGRUU from sipsimple.lookup import DNSLookup, DNSLookupError from sipsimple.payloads import ParserError from sipsimple.payloads.conference import ConferenceDocument from sipsimple.streams import MediaStreamRegistry, InvalidStreamError, UnknownStreamError from sipsimple.threading import run_in_twisted_thread from sipsimple.threading.green import Command, run_in_green_thread from sipsimple.util import ISOTimestamp class InvitationDisconnectedError(Exception): def __init__(self, invitation, data): self.invitation = invitation self.data = data class MediaStreamDidNotInitializeError(Exception): def __init__(self, stream, data): self.stream = stream self.data = data class MediaStreamDidFailError(Exception): def __init__(self, stream, data): self.stream = stream self.data = data class SubscriptionError(Exception): def __init__(self, error, timeout, **attributes): self.error = error self.timeout = timeout self.attributes = attributes class SIPSubscriptionDidFail(Exception): def __init__(self, data): self.data = data class InterruptSubscription(Exception): pass class TerminateSubscription(Exception): pass class ReferralError(Exception): def __init__(self, error, code=0): self.error = error self.code = code class TerminateReferral(Exception): pass class SIPReferralDidFail(Exception): def __init__(self, data): self.data = data class IllegalStateError(RuntimeError): pass class IllegalDirectionError(RuntimeError): pass class SIPInvitationTransferDidFail(Exception): def __init__(self, data): self.data = data @decorator def transition_state(required_state, new_state): def state_transitioner(func): @preserve_signature(func) def wrapper(obj, *args, **kwargs): with obj._lock: if obj.state != required_state: raise IllegalStateError('cannot call %s in %s state' % (func.__name__, obj.state)) obj.state = new_state return func(obj, *args, **kwargs) return wrapper return state_transitioner @decorator def check_state(required_states): def state_checker(func): @preserve_signature(func) def wrapper(obj, *args, **kwargs): if obj.state not in required_states: raise IllegalStateError('cannot call %s in %s state' % (func.__name__, obj.state)) return func(obj, *args, **kwargs) return wrapper return state_checker @decorator def check_transfer_state(direction, state): def state_checker(func): @preserve_signature(func) def wrapper(obj, *args, **kwargs): if obj.transfer_handler.direction != direction: raise IllegalDirectionError('cannot transfer in %s direction' % obj.transfer_handler.direction) if obj.transfer_handler.state != state: raise IllegalStateError('cannot transfer in %s state' % obj.transfer_handler.state) return func(obj, *args, **kwargs) return wrapper return state_checker class AddParticipantOperation(object): pass class RemoveParticipantOperation(object): pass @implementer(IObserver) class ReferralHandler(object): def __init__(self, session, participant_uri, operation): self.participant_uri = participant_uri if not isinstance(self.participant_uri, SIPURI): if not self.participant_uri.startswith(('sip:', 'sips:')): self.participant_uri = 'sip:%s' % self.participant_uri try: self.participant_uri = SIPURI.parse(self.participant_uri) except SIPCoreError: notification_center = NotificationCenter() if operation is AddParticipantOperation: notification_center.post_notification('SIPConferenceDidNotAddParticipant', sender=session, data=NotificationData(participant=self.participant_uri, code=0, reason='invalid participant URI')) else: notification_center.post_notification('SIPConferenceDidNotRemoveParticipant', sender=session, data=NotificationData(participant=self.participant_uri, code=0, reason='invalid participant URI')) return self.session = session self.operation = operation self.active = False self.route = None self._channel = coros.queue() self._referral = None def start(self): notification_center = NotificationCenter() if not self.session.remote_focus: if self.operation is AddParticipantOperation: notification_center.post_notification('SIPConferenceDidNotAddParticipant', sender=self.session, data=NotificationData(participant=self.participant_uri, code=0, reason='remote endpoint is not a focus')) else: notification_center.post_notification('SIPConferenceDidNotRemoveParticipant', sender=self.session, data=NotificationData(participant=self.participant_uri, code=0, reason='remote endpoint is not a focus')) self.session = None return notification_center.add_observer(self, sender=self.session) notification_center.add_observer(self, name='NetworkConditionsDidChange') proc.spawn(self._run) def _run(self): notification_center = NotificationCenter() settings = SIPSimpleSettings() try: # Lookup routes account = self.session.account if account is BonjourAccount(): uri = SIPURI.new(self.session._invitation.remote_contact_header.uri) elif account.sip.outbound_proxy is not None and account.sip.outbound_proxy.transport in settings.sip.transport_list: uri = SIPURI(host=account.sip.outbound_proxy.host, port=account.sip.outbound_proxy.port, parameters={'transport': account.sip.outbound_proxy.transport}) elif account.sip.always_use_my_proxy: uri = SIPURI(host=account.id.domain) else: uri = SIPURI.new(self.session.remote_identity.uri) lookup = DNSLookup() try: routes = lookup.lookup_sip_proxy(uri, settings.sip.transport_list).wait() except DNSLookupError as e: timeout = random.uniform(15, 30) raise ReferralError(error='DNS lookup failed: %s' % e) target_uri = SIPURI.new(self.session.remote_identity.uri) timeout = time() + 30 for route in routes: self.route = route remaining_time = timeout - time() if remaining_time > 0: try: contact_uri = account.contact[NoGRUU, route] except KeyError: continue refer_to_header = ReferToHeader(str(self.participant_uri)) refer_to_header.parameters['method'] = 'INVITE' if self.operation is AddParticipantOperation else 'BYE' referral = Referral(target_uri, FromHeader(account.uri, account.display_name), ToHeader(target_uri), refer_to_header, ContactHeader(contact_uri), RouteHeader(route.uri), account.credentials) notification_center.add_observer(self, sender=referral) try: referral.send_refer(timeout=limit(remaining_time, min=1, max=5)) except SIPCoreError: notification_center.remove_observer(self, sender=referral) timeout = 5 raise ReferralError(error='Internal error') self._referral = referral try: while True: notification = self._channel.wait() if notification.name == 'SIPReferralDidStart': break except SIPReferralDidFail as e: notification_center.remove_observer(self, sender=referral) self._referral = None if e.data.code in (403, 405): raise ReferralError(error=sip_status_messages[e.data.code], code=e.data.code) else: # Otherwise just try the next route continue else: break else: self.route = None raise ReferralError(error='No more routes to try') # At this point it is subscribed. Handle notifications and ending/failures. try: self.active = True while True: notification = self._channel.wait() if notification.name == 'SIPReferralGotNotify': if notification.data.event == 'refer' and notification.data.body: match = sipfrag_re.match(notification.data.body) if match: code = int(match.group('code')) reason = match.group('reason') if code/100 > 2: continue if self.operation is AddParticipantOperation: notification_center.post_notification('SIPConferenceGotAddParticipantProgress', sender=self.session, data=NotificationData(participant=self.participant_uri, code=code, reason=reason)) else: notification_center.post_notification('SIPConferenceGotRemoveParticipantProgress', sender=self.session, data=NotificationData(participant=self.participant_uri, code=code, reason=reason)) elif notification.name == 'SIPReferralDidEnd': break except SIPReferralDidFail as e: notification_center.remove_observer(self, sender=self._referral) raise ReferralError(error=e.data.reason, code=e.data.code) else: notification_center.remove_observer(self, sender=self._referral) if self.operation is AddParticipantOperation: notification_center.post_notification('SIPConferenceDidAddParticipant', sender=self.session, data=NotificationData(participant=self.participant_uri)) else: notification_center.post_notification('SIPConferenceDidRemoveParticipant', sender=self.session, data=NotificationData(participant=self.participant_uri)) finally: self.active = False except TerminateReferral: if self._referral is not None: try: self._referral.end(timeout=2) except SIPCoreError: pass else: try: while True: notification = self._channel.wait() if notification.name == 'SIPReferralDidEnd': break except SIPReferralDidFail: pass finally: notification_center.remove_observer(self, sender=self._referral) if self.operation is AddParticipantOperation: notification_center.post_notification('SIPConferenceDidNotAddParticipant', sender=self.session, data=NotificationData(participant=self.participant_uri, code=0, reason='error')) else: notification_center.post_notification('SIPConferenceDidNotRemoveParticipant', sender=self.session, data=NotificationData(participant=self.participant_uri, code=0, reason='error')) except ReferralError as e: if self.operation is AddParticipantOperation: notification_center.post_notification('SIPConferenceDidNotAddParticipant', sender=self.session, data=NotificationData(participant=self.participant_uri, code=e.code, reason=e.error)) else: notification_center.post_notification('SIPConferenceDidNotRemoveParticipant', sender=self.session, data=NotificationData(participant=self.participant_uri, code=e.code, reason=e.error)) finally: notification_center.remove_observer(self, sender=self.session) notification_center.remove_observer(self, name='NetworkConditionsDidChange') self.session = None self._referral = None def _refresh(self): try: contact_header = ContactHeader(self.session.account.contact[NoGRUU, self.route]) except KeyError: pass else: try: self._referral.refresh(contact_header=contact_header, timeout=2) except (SIPCoreError, SIPCoreInvalidStateError): pass @run_in_twisted_thread def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_SIPReferralDidStart(self, notification): self._channel.send(notification) def _NH_SIPReferralDidEnd(self, notification): self._channel.send(notification) def _NH_SIPReferralDidFail(self, notification): self._channel.send_exception(SIPReferralDidFail(notification.data)) def _NH_SIPReferralGotNotify(self, notification): self._channel.send(notification) def _NH_SIPSessionDidFail(self, notification): self._channel.send_exception(TerminateReferral()) def _NH_SIPSessionWillEnd(self, notification): self._channel.send_exception(TerminateReferral()) def _NH_NetworkConditionsDidChange(self, notification): if self.active: self._refresh() @implementer(IObserver) class ConferenceHandler(object): def __init__(self, session): self.session = session self.active = False self.subscribed = False self._command_proc = None self._command_channel = coros.queue() self._data_channel = coros.queue() self._subscription = None self._subscription_proc = None self._subscription_timer = None notification_center = NotificationCenter() notification_center.add_observer(self, sender=self.session) notification_center.add_observer(self, name='NetworkConditionsDidChange') self._command_proc = proc.spawn(self._run) @run_in_green_thread def add_participant(self, participant_uri): referral_handler = ReferralHandler(self.session, participant_uri, AddParticipantOperation) referral_handler.start() @run_in_green_thread def remove_participant(self, participant_uri): referral_handler = ReferralHandler(self.session, participant_uri, RemoveParticipantOperation) referral_handler.start() def _run(self): while True: command = self._command_channel.wait() handler = getattr(self, '_CH_%s' % command.name) handler(command) def _activate(self): self.active = True command = Command('subscribe') self._command_channel.send(command) return command def _deactivate(self): self.active = False command = Command('unsubscribe') self._command_channel.send(command) return command def _resubscribe(self): command = Command('subscribe') self._command_channel.send(command) return command def _terminate(self): notification_center = NotificationCenter() notification_center.remove_observer(self, sender=self.session) notification_center.remove_observer(self, name='NetworkConditionsDidChange') self._deactivate() command = Command('terminate') self._command_channel.send(command) command.wait() self.session = None def _CH_subscribe(self, command): if self._subscription_timer is not None and self._subscription_timer.active(): self._subscription_timer.cancel() self._subscription_timer = None if self._subscription_proc is not None: subscription_proc = self._subscription_proc subscription_proc.kill(InterruptSubscription) subscription_proc.wait() self._subscription_proc = proc.spawn(self._subscription_handler, command) def _CH_unsubscribe(self, command): # Cancel any timer which would restart the subscription process if self._subscription_timer is not None and self._subscription_timer.active(): self._subscription_timer.cancel() self._subscription_timer = None if self._subscription_proc is not None: subscription_proc = self._subscription_proc subscription_proc.kill(TerminateSubscription) subscription_proc.wait() self._subscription_proc = None command.signal() def _CH_terminate(self, command): command.signal() raise proc.ProcExit() def _subscription_handler(self, command): notification_center = NotificationCenter() settings = SIPSimpleSettings() try: # Lookup routes account = self.session.account if account is BonjourAccount(): uri = SIPURI.new(self.session._invitation.remote_contact_header.uri) elif account.sip.outbound_proxy is not None and account.sip.outbound_proxy.transport in settings.sip.transport_list: uri = SIPURI(host=account.sip.outbound_proxy.host, port=account.sip.outbound_proxy.port, parameters={'transport': account.sip.outbound_proxy.transport}) elif account.sip.always_use_my_proxy: uri = SIPURI(host=account.id.domain) else: uri = SIPURI.new(self.session.remote_identity.uri) lookup = DNSLookup() try: routes = lookup.lookup_sip_proxy(uri, settings.sip.transport_list).wait() except DNSLookupError as e: timeout = random.uniform(15, 30) raise SubscriptionError(error='DNS lookup failed: %s' % e, timeout=timeout) target_uri = SIPURI.new(self.session.remote_identity.uri) default_interval = 600 if account is BonjourAccount() else account.sip.subscribe_interval refresh_interval = getattr(command, 'refresh_interval', default_interval) timeout = time() + 30 for route in routes: remaining_time = timeout - time() if remaining_time > 0: try: contact_uri = account.contact[NoGRUU, route] except KeyError: continue subscription = Subscription(target_uri, FromHeader(account.uri, account.display_name), ToHeader(target_uri), ContactHeader(contact_uri), 'conference', RouteHeader(route.uri), credentials=account.credentials, refresh=refresh_interval) notification_center.add_observer(self, sender=subscription) try: subscription.subscribe(timeout=limit(remaining_time, min=1, max=5)) except SIPCoreError: notification_center.remove_observer(self, sender=subscription) timeout = 5 raise SubscriptionError(error='Internal error', timeout=timeout) self._subscription = subscription try: while True: notification = self._data_channel.wait() if notification.sender is subscription and notification.name == 'SIPSubscriptionDidStart': break except SIPSubscriptionDidFail as e: notification_center.remove_observer(self, sender=subscription) self._subscription = None if e.data.code == 407: # Authentication failed, so retry the subscription in some time timeout = random.uniform(60, 120) raise SubscriptionError(error='Authentication failed', timeout=timeout) elif e.data.code == 423: # Get the value of the Min-Expires header timeout = random.uniform(60, 120) if e.data.min_expires is not None and e.data.min_expires > refresh_interval: raise SubscriptionError(error='Interval too short', timeout=timeout, min_expires=e.data.min_expires) else: raise SubscriptionError(error='Interval too short', timeout=timeout) elif e.data.code in (405, 406, 489, 1400): command.signal(e) return else: # Otherwise just try the next route continue else: self.subscribed = True command.signal() break else: # There are no more routes to try, reschedule the subscription timeout = random.uniform(60, 180) raise SubscriptionError(error='No more routes to try', timeout=timeout) # At this point it is subscribed. Handle notifications and ending/failures. try: while True: notification = self._data_channel.wait() if notification.sender is not self._subscription: continue if notification.name == 'SIPSubscriptionGotNotify': if notification.data.event == 'conference' and notification.data.body: try: conference_info = ConferenceDocument.parse(notification.data.body) except ParserError: pass else: notification_center.post_notification('SIPSessionGotConferenceInfo', sender=self.session, data=NotificationData(conference_info=conference_info)) elif notification.name == 'SIPSubscriptionDidEnd': break except SIPSubscriptionDidFail: self._command_channel.send(Command('subscribe')) notification_center.remove_observer(self, sender=self._subscription) except InterruptSubscription as e: if not self.subscribed: command.signal(e) if self._subscription is not None: notification_center.remove_observer(self, sender=self._subscription) try: self._subscription.end(timeout=2) except SIPCoreError: pass except TerminateSubscription as e: if not self.subscribed: command.signal(e) if self._subscription is not None: try: self._subscription.end(timeout=2) except SIPCoreError: pass else: try: while True: notification = self._data_channel.wait() if notification.sender is self._subscription and notification.name == 'SIPSubscriptionDidEnd': break except SIPSubscriptionDidFail: pass finally: notification_center.remove_observer(self, sender=self._subscription) except SubscriptionError as e: if 'min_expires' in e.attributes: command = Command('subscribe', command.event, refresh_interval=e.attributes['min_expires']) else: command = Command('subscribe', command.event) self._subscription_timer = reactor.callLater(e.timeout, self._command_channel.send, command) finally: self.subscribed = False self._subscription = None self._subscription_proc = None @run_in_twisted_thread def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_SIPSubscriptionDidStart(self, notification): self._data_channel.send(notification) def _NH_SIPSubscriptionDidEnd(self, notification): self._data_channel.send(notification) def _NH_SIPSubscriptionDidFail(self, notification): self._data_channel.send_exception(SIPSubscriptionDidFail(notification.data)) def _NH_SIPSubscriptionGotNotify(self, notification): self._data_channel.send(notification) def _NH_SIPSessionDidStart(self, notification): if self.session.remote_focus: self._activate() @run_in_green_thread def _NH_SIPSessionDidFail(self, notification): self._terminate() @run_in_green_thread def _NH_SIPSessionDidEnd(self, notification): self._terminate() def _NH_SIPSessionDidRenegotiateStreams(self, notification): if self.session.remote_focus and not self.active: self._activate() elif not self.session.remote_focus and self.active: self._deactivate() def _NH_NetworkConditionsDidChange(self, notification): if self.active: self._resubscribe() class TransferInfo(object): def __init__(self, referred_by=None, replaced_dialog_id=None): self.referred_by = referred_by self.replaced_dialog_id = replaced_dialog_id @implementer(IObserver) class TransferHandler(object): def __init__(self, session): self.state = None self.direction = None self.new_session = None self.session = session notification_center = NotificationCenter() notification_center.add_observer(self, sender=self.session) notification_center.add_observer(self, sender=self.session._invitation) self._command_channel = coros.queue() self._data_channel = coros.queue() self._proc = proc.spawn(self._run) def _run(self): while True: command = self._command_channel.wait() handler = getattr(self, '_CH_%s' % command.name) handler(command) self.direction = None self.state = None def _CH_incoming_transfer(self, command): self.direction = 'incoming' notification_center = NotificationCenter() refer_to_hdr = command.data.headers.get('Refer-To') target = SIPURI.parse(refer_to_hdr.uri) referred_by_hdr = command.data.headers.get('Referred-By', None) if referred_by_hdr is not None: origin = referred_by_hdr.body else: origin = str(self.session.remote_identity.uri) try: while True: try: notification = self._data_channel.wait() except SIPInvitationTransferDidFail: self.state = 'failed' return else: if notification.name == 'SIPInvitationTransferDidStart': self.state = 'starting' refer_to_uri = SIPURI.new(target) refer_to_uri.headers = {} refer_to_uri.parameters = {} notification_center.post_notification('SIPSessionTransferNewIncoming', self.session, NotificationData(transfer_destination=refer_to_uri)) elif notification.name == 'SIPSessionTransferDidStart': break elif notification.name == 'SIPSessionTransferDidFail': self.state = 'failed' try: self.session._invitation.notify_transfer_progress(notification.data.code, notification.data.reason) except SIPCoreError: return while True: try: notification = self._data_channel.wait() except SIPInvitationTransferDidFail: return self.state = 'started' transfer_info = TransferInfo(referred_by=origin) try: replaces_hdr = target.headers.pop('Replaces') call_id, rest = replaces_hdr.split(';', 1) params = dict((item.split('=') for item in rest.split(';'))) to_tag = params.get('to-tag') from_tag = params.get('from-tag') except (KeyError, ValueError): pass else: transfer_info.replaced_dialog_id = DialogID(call_id, local_tag=from_tag, remote_tag=to_tag) settings = SIPSimpleSettings() account = self.session.account if account is BonjourAccount(): uri = target elif account.sip.outbound_proxy is not None and account.sip.outbound_proxy.transport in settings.sip.transport_list: uri = SIPURI(host=account.sip.outbound_proxy.host, port=account.sip.outbound_proxy.port, parameters={'transport': account.sip.outbound_proxy.transport}) elif account.sip.always_use_my_proxy: uri = SIPURI(host=account.id.domain) else: uri = target lookup = DNSLookup() try: routes = lookup.lookup_sip_proxy(uri, settings.sip.transport_list).wait() except DNSLookupError as e: self.state = 'failed' notification_center.post_notification('SIPSessionTransferDidFail', sender=self.session, data=NotificationData(code=0, reason="DNS lookup failed: {}".format(e))) try: self.session._invitation.notify_transfer_progress(480) except SIPCoreError: return while True: try: self._data_channel.wait() except SIPInvitationTransferDidFail: return return self.new_session = Session(account) notification_center = NotificationCenter() notification_center.add_observer(self, sender=self.new_session) self.new_session.connect(ToHeader(target), routes=routes, streams=[MediaStreamRegistry.AudioStream()], transfer_info=transfer_info) while True: try: notification = self._data_channel.wait() except SIPInvitationTransferDidFail: return if notification.name == 'SIPInvitationTransferDidEnd': return except proc.ProcExit: if self.new_session is not None: notification_center.remove_observer(self, sender=self.new_session) self.new_session = None raise def _CH_outgoing_transfer(self, command): self.direction = 'outgoing' notification_center = NotificationCenter() self.state = 'starting' while True: try: notification = self._data_channel.wait() except SIPInvitationTransferDidFail as e: self.state = 'failed' notification_center.post_notification('SIPSessionTransferDidFail', sender=self.session, data=NotificationData(code=e.data.code, reason=e.data.reason)) return if notification.name == 'SIPInvitationTransferDidStart': self.state = 'started' notification_center.post_notification('SIPSessionTransferDidStart', sender=self.session) elif notification.name == 'SIPInvitationTransferDidEnd': self.state = 'ended' self.session.end() notification_center.post_notification('SIPSessionTransferDidEnd', sender=self.session) return def _terminate(self): notification_center = NotificationCenter() notification_center.remove_observer(self, sender=self.session._invitation) notification_center.remove_observer(self, sender=self.session) self._proc.kill() self._proc = None self._command_channel = None self._data_channel = None self.session = None @run_in_twisted_thread def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_SIPInvitationTransferNewIncoming(self, notification): self._command_channel.send(Command('incoming_transfer', data=notification.data)) def _NH_SIPInvitationTransferNewOutgoing(self, notification): self._command_channel.send(Command('outgoing_transfer', data=notification.data)) def _NH_SIPInvitationTransferDidStart(self, notification): self._data_channel.send(notification) def _NH_SIPInvitationTransferDidFail(self, notification): self._data_channel.send_exception(SIPInvitationTransferDidFail(notification.data)) def _NH_SIPInvitationTransferDidEnd(self, notification): self._data_channel.send(notification) def _NH_SIPInvitationTransferGotNotify(self, notification): if notification.data.event == 'refer' and notification.data.body: match = sipfrag_re.match(notification.data.body) if match: code = int(match.group('code')) reason = match.group('reason') notification.center.post_notification('SIPSessionTransferGotProgress', sender=self.session, data=NotificationData(code=code, reason=reason)) def _NH_SIPSessionTransferDidStart(self, notification): if notification.sender is self.session and self.state == 'starting': self._data_channel.send(notification) def _NH_SIPSessionTransferDidFail(self, notification): if notification.sender is self.session and self.state == 'starting': self._data_channel.send(notification) def _NH_SIPSessionGotRingIndication(self, notification): if notification.sender is self.new_session and self.session is not None: try: self.session._invitation.notify_transfer_progress(180) except SIPCoreError: pass def _NH_SIPSessionGotProvisionalResponse(self, notification): if notification.sender is self.new_session and self.session is not None: try: self.session._invitation.notify_transfer_progress(notification.data.code, notification.data.reason) except SIPCoreError: pass def _NH_SIPSessionDidStart(self, notification): if notification.sender is self.new_session: notification.center.remove_observer(self, sender=notification.sender) self.new_session = None if self.session is not None: notification.center.post_notification('SIPSessionTransferDidEnd', sender=self.session) if self.state == 'started': try: self.session._invitation.notify_transfer_progress(200) except SIPCoreError: pass self.state = 'ended' self.session.end() def _NH_SIPSessionDidEnd(self, notification): if notification.sender is self.new_session: # If any stream fails to start we won't get SIPSessionDidFail, we'll get here instead notification.center.remove_observer(self, sender=notification.sender) self.new_session = None if self.session is not None: notification.center.post_notification('SIPSessionTransferDidFail', sender=self.session, data=NotificationData(code=500, reason='internal error')) if self.state == 'started': try: self.session._invitation.notify_transfer_progress(500) except SIPCoreError: pass self.state = 'failed' else: self._terminate() def _NH_SIPSessionDidFail(self, notification): if notification.sender is self.new_session: notification.center.remove_observer(self, sender=notification.sender) self.new_session = None if self.session is not None: notification.center.post_notification('SIPSessionTransferDidFail', sender=self.session, data=NotificationData(code=notification.data.code or 500, reason=notification.data.reason)) if self.state == 'started': try: self.session._invitation.notify_transfer_progress(notification.data.code or 500, notification.data.reason) except SIPCoreError: pass self.state = 'failed' else: self._terminate() class OptionalTag(str): def __eq__(self, other): return other is None or super(OptionalTag, self).__eq__(other) def __ne__(self, other): return not self == other def __repr__(self): return '{}({})'.format(self.__class__.__name__, super(OptionalTag, self).__repr__()) @implementer(IObserver) class SessionReplaceHandler(object): def __init__(self, session): self.session = session def start(self): notification_center = NotificationCenter() notification_center.add_observer(self, sender=self.session) notification_center.add_observer(self, sender=self.session.replaced_session) def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_SIPSessionDidStart(self, notification): notification.center.remove_observer(self, sender=self.session) notification.center.remove_observer(self, sender=self.session.replaced_session) self.session.replaced_session.end() self.session.replaced_session = None self.session = None def _NH_SIPSessionDidFail(self, notification): if notification.sender is self.session: notification.center.remove_observer(self, sender=self.session) notification.center.remove_observer(self, sender=self.session.replaced_session) self.session.replaced_session = None self.session = None _NH_SIPSessionDidEnd = _NH_SIPSessionDidFail @implementer(IObserver) class Session(object): media_stream_timeout = 15 short_reinvite_timeout = 5 def __init__(self, account): self.account = account self.direction = None self.end_time = None self.on_hold = False self.proposed_streams = None self.route = None self.state = None self.start_time = None self.streams = None self.transport = None self.local_focus = False self.remote_focus = False self.greenlet = None self.conference = None self.replaced_session = None self.transfer_handler = None self.transfer_info = None self._channel = coros.queue() self._hold_in_progress = False self._invitation = None self._local_identity = None self._remote_identity = None self._lock = RLock() def init_incoming(self, invitation, data): notification_center = NotificationCenter() remote_sdp = invitation.sdp.proposed_remote self.proposed_streams = [] if remote_sdp: for index, media_stream in enumerate(remote_sdp.media): if media_stream.port != 0: for stream_type in MediaStreamRegistry: try: stream = stream_type.new_from_sdp(self, remote_sdp, index) except UnknownStreamError: continue except InvalidStreamError as e: log.error("Invalid stream: {}".format(e)) break except Exception as e: log.exception("Exception occurred while setting up stream from SDP: {}".format(e)) break else: stream.index = index self.proposed_streams.append(stream) break self.direction = 'incoming' self.state = 'incoming' self.transport = invitation.transport self._invitation = invitation self.conference = ConferenceHandler(self) self.transfer_handler = TransferHandler(self) if 'isfocus' in invitation.remote_contact_header.parameters: self.remote_focus = True if 'Referred-By' in data.headers or 'Replaces' in data.headers: self.transfer_info = TransferInfo() if 'Referred-By' in data.headers: self.transfer_info.referred_by = data.headers['Referred-By'].body if 'Replaces' in data.headers: replaces_header = data.headers.get('Replaces') # Because we only allow the remote tag to be optional, it can only match established dialogs and early outgoing dialogs, but not early incoming dialogs, # which according to RFC3891 should be rejected with 481 (which will happen automatically by never matching them). if replaces_header.early_only or replaces_header.from_tag == '0': replaced_dialog_id = DialogID(replaces_header.call_id, local_tag=replaces_header.to_tag, remote_tag=OptionalTag(replaces_header.from_tag)) else: replaced_dialog_id = DialogID(replaces_header.call_id, local_tag=replaces_header.to_tag, remote_tag=replaces_header.from_tag) session_manager = SessionManager() try: replaced_session = next(session for session in session_manager.sessions if session.dialog_id == replaced_dialog_id) except StopIteration: invitation.send_response(481) return else: # Any matched dialog at this point is either established, terminated or early outgoing. if replaced_session.state in ('terminating', 'terminated'): invitation.send_response(603) return elif replaced_session.dialog_id.remote_tag is not None and replaces_header.early_only: # The replaced dialog is established, but the early-only flag is set invitation.send_response(486) return self.replaced_session = replaced_session self.transfer_info.replaced_dialog_id = replaced_dialog_id replace_handler = SessionReplaceHandler(self) replace_handler.start() notification_center.add_observer(self, sender=invitation) notification_center.post_notification('SIPSessionNewIncoming', sender=self, data=NotificationData(streams=self.proposed_streams[:], headers=data.headers)) @transition_state(None, 'connecting') @run_in_green_thread def connect(self, to_header, routes, streams, is_focus=False, transfer_info=None, extra_headers=None): self.greenlet = api.getcurrent() notification_center = NotificationCenter() settings = SIPSimpleSettings() connected = False received_code = 0 received_reason = None unhandled_notifications = [] extra_headers = extra_headers or [] if {'to', 'from', 'via', 'contact', 'route', 'record-route'}.intersection(header.name.lower() for header in extra_headers): raise RuntimeError('invalid header in extra_headers: To, From, Via, Contact, Route and Record-Route headers are not allowed') self.direction = 'outgoing' self.proposed_streams = streams self.route = routes[0] self.transport = self.route.transport self.local_focus = is_focus self._invitation = Invitation() - self._local_identity = FromHeader(self.account.uri, self.account.display_name) + self._local_identity = FromHeader(self.account.uri, self.account.display_name.decode()) self._remote_identity = to_header self.conference = ConferenceHandler(self) self.transfer_handler = TransferHandler(self) self.transfer_info = transfer_info notification_center.add_observer(self, sender=self._invitation) notification_center.post_notification('SIPSessionNewOutgoing', sender=self, data=NotificationData(streams=streams[:])) for stream in self.proposed_streams: notification_center.add_observer(self, sender=stream) stream.initialize(self, direction='outgoing') try: wait_count = len(self.proposed_streams) while wait_count > 0: notification = self._channel.wait() if notification.name == 'MediaStreamDidInitialize': wait_count -= 1 try: contact_uri = self.account.contact[PublicGRUUIfAvailable, self.route] local_ip = host.outgoing_ip_for(self.route.address) if local_ip is None: raise ValueError("could not get outgoing IP address") except (KeyError, ValueError) as e: for stream in self.proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self._fail(originator='local', code=480, reason=sip_status_messages[480], error=str(e)) return connection = SDPConnection(local_ip) local_sdp = SDPSession(local_ip, name=settings.user_agent) for index, stream in enumerate(self.proposed_streams): stream.index = index media = stream.get_local_media(remote_sdp=None, index=index) if media.connection is None or (media.connection is not None and not media.has_ice_attributes and not media.has_ice_candidates): media.connection = connection local_sdp.media.append(media) - from_header = FromHeader(self.account.uri, self.account.display_name) + from_header = FromHeader(self.account.uri, self.account.display_name.decode()) route_header = RouteHeader(self.route.uri) contact_header = ContactHeader(contact_uri) if is_focus: contact_header.parameters['isfocus'] = None if self.transfer_info is not None: if self.transfer_info.referred_by is not None: extra_headers.append(Header('Referred-By', self.transfer_info.referred_by)) if self.transfer_info.replaced_dialog_id is not None: dialog_id = self.transfer_info.replaced_dialog_id extra_headers.append(ReplacesHeader(dialog_id.call_id, dialog_id.local_tag, dialog_id.remote_tag)) self._invitation.send_invite(to_header.uri, from_header, to_header, route_header, contact_header, local_sdp, self.account.credentials, extra_headers) try: with api.timeout(settings.sip.invite_timeout): while True: notification = self._channel.wait() if notification.name == 'SIPInvitationGotSDPUpdate': if notification.data.succeeded: local_sdp = notification.data.local_sdp remote_sdp = notification.data.remote_sdp break else: for stream in self.proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self._fail(originator='remote', code=0, reason=None, error='SDP negotiation failed: %s' % notification.data.error) return elif notification.name == 'SIPInvitationChangedState': if notification.data.state == 'early': if notification.data.code == 180: notification_center.post_notification('SIPSessionGotRingIndication', self) notification_center.post_notification('SIPSessionGotProvisionalResponse', self, NotificationData(code=notification.data.code, reason=notification.data.reason)) elif notification.data.state == 'connecting': received_code = notification.data.code received_reason = notification.data.reason elif notification.data.state == 'connected': if not connected: connected = True notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='INVITE', code=received_code, reason=received_reason)) else: unhandled_notifications.append(notification) elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) except api.TimeoutError: self.end() return notification_center.post_notification('SIPSessionWillStart', sender=self) stream_map = dict((stream.index, stream) for stream in self.proposed_streams) for index, local_media in enumerate(local_sdp.media): remote_media = remote_sdp.media[index] stream = stream_map[index] if remote_media.port: # TODO: check if port is also 0 in local_sdp. In that case PJSIP disabled the stream because # negotiation failed. If there are more streams, however, the negotiation is considered successful as a # whole, so while we built a normal SDP, PJSIP modified it and sent it to the other side. That's kind io # OK, but we cannot really start the stream. -Saul stream.start(local_sdp, remote_sdp, index) else: notification_center.remove_observer(self, sender=stream) self.proposed_streams.remove(stream) del stream_map[stream.index] stream.deactivate() stream.end() removed_streams = [stream for stream in self.proposed_streams if stream.index >= len(local_sdp.media)] for stream in removed_streams: notification_center.remove_observer(self, sender=stream) self.proposed_streams.remove(stream) del stream_map[stream.index] stream.deactivate() stream.end() invitation_notifications = [] with api.timeout(self.media_stream_timeout): wait_count = len(self.proposed_streams) while wait_count > 0: notification = self._channel.wait() if notification.name == 'MediaStreamDidStart': wait_count -= 1 elif notification.name == 'SIPInvitationChangedState': invitation_notifications.append(notification) for notification in invitation_notifications: self._channel.send(notification) while not connected or self._channel: notification = self._channel.wait() if notification.name == 'SIPInvitationChangedState': if notification.data.state == 'early': if notification.data.code == 180: notification_center.post_notification('SIPSessionGotRingIndication', self) notification_center.post_notification('SIPSessionGotProvisionalResponse', self, NotificationData(code=notification.data.code, reason=notification.data.reason)) elif notification.data.state == 'connecting': received_code = notification.data.code received_reason = notification.data.reason elif notification.data.state == 'connected': if not connected: connected = True notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='INVITE', code=received_code, reason=received_reason)) else: unhandled_notifications.append(notification) elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) except (MediaStreamDidNotInitializeError, MediaStreamDidFailError, api.TimeoutError) as e: for stream in self.proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() if isinstance(e, api.TimeoutError): error = 'media stream timed-out while starting' elif isinstance(e, MediaStreamDidNotInitializeError): error = 'media stream did not initialize: %s' % e.data.reason else: error = 'media stream failed: %s' % e.data.reason self._fail(originator='local', code=0, reason=None, error=error) except InvitationDisconnectedError as e: notification_center.remove_observer(self, sender=self._invitation) for stream in self.proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self.state = 'terminated' # As weird as it may sound, PJSIP accepts a BYE even without receiving a final response to the INVITE if e.data.prev_state in ('connecting', 'connected') or getattr(e.data, 'method', None) == 'BYE': notification_center.post_notification('SIPSessionWillEnd', self, NotificationData(originator=e.data.originator)) if e.data.originator == 'remote': notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method=e.data.method, code=200, reason=sip_status_messages[200])) self.end_time = ISOTimestamp.now() notification_center.post_notification('SIPSessionDidEnd', self, NotificationData(originator=e.data.originator, end_reason=e.data.disconnect_reason)) else: if e.data.originator == 'remote': notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='INVITE', code=e.data.code, reason=e.data.reason)) code = e.data.code reason = e.data.reason elif e.data.disconnect_reason == 'timeout': code = 408 reason = 'timeout' else: # TODO: we should know *exactly* when there are set -Saul code = getattr(e.data, 'code', 0) reason = getattr(e.data, 'reason', 'Session disconnected') if e.data.originator == 'remote' and code // 100 == 3: redirect_identities = e.data.headers.get('Contact', []) else: redirect_identities = None notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator=e.data.originator, code=code, reason=reason, failure_reason=e.data.disconnect_reason, redirect_identities=redirect_identities)) self.greenlet = None except SIPCoreError as e: for stream in self.proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self._fail(originator='local', code=0, reason=None, error='SIP core error: %s' % str(e)) else: self.greenlet = None self.state = 'connected' self.streams = self.proposed_streams self.proposed_streams = None self.start_time = ISOTimestamp.now() any_stream_ice = any(getattr(stream, 'ice_active', False) for stream in self.streams) if any_stream_ice: self._reinvite_after_ice() notification_center.post_notification('SIPSessionDidStart', self, NotificationData(streams=self.streams[:])) for notification in unhandled_notifications: self.handle_notification(notification) if self._hold_in_progress: self._send_hold() def _reinvite_after_ice(self): # This function does not do any error checking, it's designed to be called at the end of connect and add_stream self.state = 'sending_proposal' self.greenlet = api.getcurrent() notification_center = NotificationCenter() local_sdp = SDPSession.new(self._invitation.sdp.active_local) local_sdp.version += 1 for index, stream in enumerate(self.streams): local_sdp.media[index] = stream.get_local_media(remote_sdp=None, index=index) self._invitation.send_reinvite(sdp=local_sdp) received_invitation_state = False received_sdp_update = False try: with api.timeout(self.short_reinvite_timeout): while not received_invitation_state or not received_sdp_update: notification = self._channel.wait() if notification.name == 'SIPInvitationGotSDPUpdate': received_sdp_update = True if notification.data.succeeded: local_sdp = notification.data.local_sdp remote_sdp = notification.data.remote_sdp for index, stream in enumerate(self.streams): stream.update(local_sdp, remote_sdp, index) else: return elif notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected' and notification.data.sub_state == 'normal': received_invitation_state = True notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='INVITE', code=notification.data.code, reason=notification.data.reason)) elif notification.data.state == 'disconnected': self.end() return except Exception: pass finally: self.state = 'connected' self.greenlet = None @check_state(['incoming', 'received_proposal']) @run_in_green_thread def send_ring_indication(self): try: self._invitation.send_response(180) except SIPCoreInvalidStateError: pass # The INVITE session might have already been cancelled; ignore the error @transition_state('incoming', 'accepting') @run_in_green_thread def accept(self, streams, is_focus=False, extra_headers=None): self.greenlet = api.getcurrent() notification_center = NotificationCenter() settings = SIPSimpleSettings() self.local_focus = is_focus connected = False unhandled_notifications = [] extra_headers = extra_headers or [] if {'to', 'from', 'via', 'contact', 'route', 'record-route'}.intersection(header.name.lower() for header in extra_headers): raise RuntimeError('invalid header in extra_headers: To, From, Via, Contact, Route and Record-Route headers are not allowed') if self.proposed_streams: for stream in self.proposed_streams: if stream in streams: notification_center.add_observer(self, sender=stream) stream.initialize(self, direction='incoming') else: for index, stream in enumerate(streams): notification_center.add_observer(self, sender=stream) stream.index = index stream.initialize(self, direction='outgoing') self.proposed_streams = streams wait_count = len(self.proposed_streams) try: while wait_count > 0: notification = self._channel.wait() if notification.name == 'MediaStreamDidInitialize': wait_count -= 1 remote_sdp = self._invitation.sdp.proposed_remote sdp_connection = remote_sdp.connection or next((media.connection for media in remote_sdp.media if media.connection is not None)) local_ip = host.outgoing_ip_for(sdp_connection.address) if sdp_connection.address != '0.0.0.0' else sdp_connection.address if local_ip is None: for stream in self.proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self._fail(originator='local', code=500, reason=sip_status_messages[500], error='could not get local IP address') return connection = SDPConnection(local_ip) local_sdp = SDPSession(local_ip, name=settings.user_agent) if remote_sdp: stream_map = dict((stream.index, stream) for stream in self.proposed_streams) for index, media in enumerate(remote_sdp.media): stream = stream_map.get(index, None) if stream is not None: media = stream.get_local_media(remote_sdp=remote_sdp, index=index) if not media.has_ice_attributes and not media.has_ice_candidates: media.connection = connection else: media = SDPMediaStream.new(media) media.connection = connection media.port = 0 media.attributes = [] media.bandwidth_info = [] local_sdp.media.append(media) else: for index, stream in enumerate(self.proposed_streams): stream.index = index media = stream.get_local_media(remote_sdp=None, index=index) if media.connection is None or (media.connection is not None and not media.has_ice_attributes and not media.has_ice_candidates): media.connection = connection local_sdp.media.append(media) contact_header = ContactHeader.new(self._invitation.local_contact_header) try: local_contact_uri = self.account.contact[PublicGRUU, self._invitation.transport] except KeyError: pass else: contact_header.uri = local_contact_uri if is_focus: contact_header.parameters['isfocus'] = None self._invitation.send_response(200, contact_header=contact_header, sdp=local_sdp, extra_headers=extra_headers) notification_center.post_notification('SIPSessionWillStart', sender=self) # Local and remote SDPs will be set after the 200 OK is sent while True: notification = self._channel.wait() if notification.name == 'SIPInvitationGotSDPUpdate': if notification.data.succeeded: local_sdp = notification.data.local_sdp remote_sdp = notification.data.remote_sdp break else: if not connected: # we could not have got a SIPInvitationGotSDPUpdate if we did not get an ACK connected = True notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=200, reason=sip_status_messages[200], ack_received=True)) for stream in self.proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self._fail(originator='remote', code=0, reason=None, error='SDP negotiation failed: %s' % notification.data.error) return elif notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected': if not connected: connected = True notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=200, reason=sip_status_messages[200], ack_received=True)) elif notification.data.prev_state == 'connected': unhandled_notifications.append(notification) elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) wait_count = 0 stream_map = dict((stream.index, stream) for stream in self.proposed_streams) for index, local_media in enumerate(local_sdp.media): remote_media = remote_sdp.media[index] stream = stream_map.get(index, None) if stream is not None: if remote_media.port: wait_count += 1 stream.start(local_sdp, remote_sdp, index) else: notification_center.remove_observer(self, sender=stream) self.proposed_streams.remove(stream) del stream_map[stream.index] stream.deactivate() stream.end() removed_streams = [stream for stream in self.proposed_streams if stream.index >= len(local_sdp.media)] for stream in removed_streams: notification_center.remove_observer(self, sender=stream) self.proposed_streams.remove(stream) del stream_map[stream.index] stream.deactivate() stream.end() with api.timeout(self.media_stream_timeout): while wait_count > 0 or not connected or self._channel: notification = self._channel.wait() if notification.name == 'MediaStreamDidStart': wait_count -= 1 elif notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected': if not connected: connected = True notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=200, reason='OK', ack_received=True)) elif notification.data.prev_state == 'connected': unhandled_notifications.append(notification) elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) else: unhandled_notifications.append(notification) except (MediaStreamDidNotInitializeError, MediaStreamDidFailError, api.TimeoutError) as e: if self._invitation.state == 'connecting': ack_received = False if isinstance(e, api.TimeoutError) and wait_count == 0 else 'unknown' # pjsip's invite session object does not inform us whether the ACK was received or not notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=200, reason='OK', ack_received=ack_received)) elif self._invitation.state == 'connected' and not connected: # we didn't yet get to process the SIPInvitationChangedState (state -> connected) notification notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=200, reason='OK', ack_received=True)) for stream in self.proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() reason_header = None if isinstance(e, api.TimeoutError): if wait_count > 0: error = 'media stream timed-out while starting' else: error = 'No ACK received' reason_header = ReasonHeader('SIP') reason_header.cause = 500 reason_header.text = 'Missing ACK' elif isinstance(e, MediaStreamDidNotInitializeError): error = 'media stream did not initialize: %s' % e.data.reason reason_header = ReasonHeader('SIP') reason_header.cause = 500 reason_header.text = 'media stream did not initialize' else: error = 'media stream failed: %s' % e.data.reason reason_header = ReasonHeader('SIP') reason_header.cause = 500 reason_header.text = 'media stream failed to start' self.start_time = ISOTimestamp.now() if self._invitation.state in ('incoming', 'early'): self._fail(originator='local', code=500, reason=sip_status_messages[500], error=error, reason_header=reason_header) else: self._fail(originator='local', code=0, reason=None, error=error, reason_header=reason_header) except InvitationDisconnectedError as e: notification_center.remove_observer(self, sender=self._invitation) for stream in self.proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self.state = 'terminated' if e.data.prev_state in ('incoming', 'early'): notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=487, reason='Session Cancelled', ack_received='unknown')) notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator='remote', code=487, reason='Session Cancelled', failure_reason=e.data.disconnect_reason, redirect_identities=None)) elif e.data.prev_state == 'connecting' and e.data.disconnect_reason == 'missing ACK': notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=200, reason='OK', ack_received=False)) notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator='local', code=200, reason=sip_status_messages[200], failure_reason=e.data.disconnect_reason, redirect_identities=None)) else: notification_center.post_notification('SIPSessionWillEnd', self, NotificationData(originator='remote')) notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method=getattr(e.data, 'method', 'INVITE'), code=200, reason='OK')) self.end_time = ISOTimestamp.now() notification_center.post_notification('SIPSessionDidEnd', self, NotificationData(originator='remote', end_reason=e.data.disconnect_reason)) self.greenlet = None except SIPCoreInvalidStateError: # the only reason for which this error can be thrown is if invitation.send_response was called after the INVITE session was cancelled by the remote party notification_center.remove_observer(self, sender=self._invitation) for stream in self.proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self.greenlet = None self.state = 'terminated' notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=487, reason='Session Cancelled', ack_received='unknown')) notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator='remote', code=487, reason='Session Cancelled', failure_reason='user request', redirect_identities=None)) except SIPCoreError as e: for stream in self.proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self._fail(originator='local', code=500, reason=sip_status_messages[500], error='SIP core error: %s' % str(e)) else: self.greenlet = None self.state = 'connected' self.streams = self.proposed_streams self.proposed_streams = None self.start_time = ISOTimestamp.now() notification_center.post_notification('SIPSessionDidStart', self, NotificationData(streams=self.streams[:])) for notification in unhandled_notifications: self.handle_notification(notification) if self._hold_in_progress: self._send_hold() finally: self.greenlet = None @transition_state('incoming', 'terminating') @run_in_green_thread def reject(self, code=603, reason=None): self.greenlet = api.getcurrent() notification_center = NotificationCenter() try: self._invitation.send_response(code, reason) with api.timeout(1): while True: notification = self._channel.wait() if notification.name == 'SIPInvitationChangedState': if notification.data.state == 'disconnected': ack_received = notification.data.disconnect_reason != 'missing ACK' notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=code, reason=sip_status_messages[code], ack_received=ack_received)) break except SIPCoreInvalidStateError: # the only reason for which this error can be thrown is if invitation.send_response was called after the INVITE session was cancelled by the remote party self.greenlet = None except SIPCoreError as e: self._fail(originator='local', code=500, reason=sip_status_messages[500], error='SIP core error: %s' % str(e)) except api.TimeoutError: notification_center.remove_observer(self, sender=self._invitation) self.greenlet = None self.state = 'terminated' notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=code, reason=sip_status_messages[code], ack_received=False)) notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator='local', code=code, reason=sip_status_messages[code], failure_reason='timeout', redirect_identities=None)) else: notification_center.remove_observer(self, sender=self._invitation) self.greenlet = None self.state = 'terminated' notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator='local', code=code, reason=sip_status_messages[code], failure_reason='user request', redirect_identities=None)) finally: self.greenlet = None @transition_state('received_proposal', 'accepting_proposal') @run_in_green_thread def accept_proposal(self, streams): self.greenlet = api.getcurrent() notification_center = NotificationCenter() unhandled_notifications = [] streams = [stream for stream in streams if stream in self.proposed_streams] for stream in streams: notification_center.add_observer(self, sender=stream) stream.initialize(self, direction='incoming') try: wait_count = len(streams) while wait_count > 0: notification = self._channel.wait() if notification.name == 'MediaStreamDidInitialize': wait_count -= 1 local_sdp = SDPSession.new(self._invitation.sdp.active_local) local_sdp.version += 1 remote_sdp = self._invitation.sdp.proposed_remote connection = SDPConnection(local_sdp.address) stream_map = dict((stream.index, stream) for stream in streams) for index, media in enumerate(remote_sdp.media): stream = stream_map.get(index, None) if stream is not None: media = stream.get_local_media(remote_sdp=remote_sdp, index=index) if not media.has_ice_attributes and not media.has_ice_candidates: media.connection = connection if index < len(local_sdp.media): local_sdp.media[index] = media else: local_sdp.media.append(media) elif index >= len(local_sdp.media): # actually == is sufficient media = SDPMediaStream.new(media) media.connection = connection media.port = 0 media.attributes = [] media.bandwidth_info = [] local_sdp.media.append(media) self._invitation.send_response(200, sdp=local_sdp) prev_on_hold_streams = set(stream for stream in self.streams if stream.hold_supported and stream.on_hold_by_remote) received_invitation_state = False received_sdp_update = False while not received_invitation_state or not received_sdp_update: notification = self._channel.wait() if notification.name == 'SIPInvitationGotSDPUpdate': received_sdp_update = True if notification.data.succeeded: local_sdp = notification.data.local_sdp remote_sdp = notification.data.remote_sdp for stream in self.streams: stream.update(local_sdp, remote_sdp, stream.index) else: self._fail_proposal(originator='remote', error='SDP negotiation failed: %s' % notification.data.error) return elif notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected' and notification.data.sub_state == 'normal': notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=200, reason=sip_status_messages[200], ack_received='unknown')) received_invitation_state = True elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) on_hold_streams = set(stream for stream in self.streams if stream.hold_supported and stream.on_hold_by_remote) if on_hold_streams != prev_on_hold_streams: hold_supported_streams = (stream for stream in self.streams if stream.hold_supported) notification_center.post_notification('SIPSessionDidChangeHoldState', self, NotificationData(originator='remote', on_hold=bool(on_hold_streams), partial=bool(on_hold_streams) and any(not stream.on_hold_by_remote for stream in hold_supported_streams))) for stream in streams: # TODO: check if port is 0 in local_sdp. In that case PJSIP disabled the stream because # negotiation failed. If there are more streams, however, the negotiation is considered successful as a # whole, so while we built a normal SDP, PJSIP modified it and sent it to the other side. That's kind of # OK, but we cannot really start the stream. -Saul stream.start(local_sdp, remote_sdp, stream.index) with api.timeout(self.media_stream_timeout): wait_count = len(streams) while wait_count > 0 or self._channel: notification = self._channel.wait() if notification.name == 'MediaStreamDidStart': wait_count -= 1 else: unhandled_notifications.append(notification) except api.TimeoutError: self._fail_proposal(originator='remote', error='media stream timed-out while starting') except MediaStreamDidNotInitializeError as e: self._fail_proposal(originator='remote', error='media stream did not initialize: {.data.reason}'.format(e)) except MediaStreamDidFailError as e: self._fail_proposal(originator='remote', error='media stream failed: {.data.reason}'.format(e)) except InvitationDisconnectedError as e: self._fail_proposal(originator='remote', error='session ended') notification = Notification('SIPInvitationChangedState', e.invitation, e.data) notification.center = notification_center self.handle_notification(notification) except SIPCoreError as e: self._fail_proposal(originator='remote', error='SIP core error: %s' % str(e)) else: proposed_streams = self.proposed_streams self.proposed_streams = None self.streams = self.streams + streams self.greenlet = None self.state = 'connected' notification_center.post_notification('SIPSessionProposalAccepted', self, NotificationData(originator='remote', accepted_streams=streams, proposed_streams=proposed_streams)) notification_center.post_notification('SIPSessionDidRenegotiateStreams', self, NotificationData(originator='remote', added_streams=streams, removed_streams=[])) for notification in unhandled_notifications: self.handle_notification(notification) if self._hold_in_progress: self._send_hold() finally: self.greenlet = None @transition_state('received_proposal', 'rejecting_proposal') @run_in_green_thread def reject_proposal(self, code=488, reason=None): self.greenlet = api.getcurrent() notification_center = NotificationCenter() try: self._invitation.send_response(code, reason) with api.timeout(1, None): while True: notification = self._channel.wait() if notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected' and notification.data.sub_state == 'normal': notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=code, reason=sip_status_messages[code], ack_received='unknown')) break except SIPCoreError as e: self._fail_proposal(originator='remote', error='SIP core error: %s' % str(e)) else: proposed_streams = self.proposed_streams self.proposed_streams = None self.greenlet = None self.state = 'connected' notification_center.post_notification('SIPSessionProposalRejected', self, NotificationData(originator='remote', code=code, reason=sip_status_messages[code], proposed_streams=proposed_streams)) if self._hold_in_progress: self._send_hold() finally: self.greenlet = None def add_stream(self, stream): self.add_streams([stream]) @transition_state('connected', 'sending_proposal') @run_in_green_thread def add_streams(self, streams): streams = list(set(streams).difference(self.streams)) if not streams: self.state = 'connected' return self.greenlet = api.getcurrent() notification_center = NotificationCenter() settings = SIPSimpleSettings() unhandled_notifications = [] self.proposed_streams = streams for stream in self.proposed_streams: notification_center.add_observer(self, sender=stream) stream.initialize(self, direction='outgoing') try: wait_count = len(self.proposed_streams) while wait_count > 0: notification = self._channel.wait() if notification.name == 'MediaStreamDidInitialize': wait_count -= 1 elif notification.name == 'SIPInvitationChangedState': # This is actually the only reason for which this notification could be received if notification.data.state == 'connected' and notification.data.sub_state == 'received_proposal': self._fail_proposal(originator='local', error='received stream proposal') self.handle_notification(notification) return local_sdp = SDPSession.new(self._invitation.sdp.active_local) local_sdp.version += 1 for stream in self.proposed_streams: # Try to reuse a disabled media stream to avoid an ever-growing SDP try: index = next(index for index, media in enumerate(local_sdp.media) if media.port == 0) reuse_media = True except StopIteration: index = len(local_sdp.media) reuse_media = False stream.index = index media = stream.get_local_media(remote_sdp=None, index=index) if reuse_media: local_sdp.media[index] = media else: local_sdp.media.append(media) self._invitation.send_reinvite(sdp=local_sdp) notification_center.post_notification('SIPSessionNewProposal', sender=self, data=NotificationData(originator='local', proposed_streams=self.proposed_streams[:])) received_invitation_state = False received_sdp_update = False try: with api.timeout(settings.sip.invite_timeout): while not received_invitation_state or not received_sdp_update: notification = self._channel.wait() if notification.name == 'SIPInvitationGotSDPUpdate': received_sdp_update = True if notification.data.succeeded: local_sdp = notification.data.local_sdp remote_sdp = notification.data.remote_sdp for s in self.streams: s.update(local_sdp, remote_sdp, s.index) else: self._fail_proposal(originator='local', error='SDP negotiation failed: %s' % notification.data.error) return elif notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected' and notification.data.sub_state == 'normal': received_invitation_state = True notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='INVITE', code=notification.data.code, reason=notification.data.reason)) if notification.data.code >= 300: proposed_streams = self.proposed_streams for stream in proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self.proposed_streams = None self.greenlet = None self.state = 'connected' notification_center.post_notification('SIPSessionProposalRejected', self, NotificationData(originator='local', code=notification.data.code, reason=notification.data.reason, proposed_streams=proposed_streams)) return elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) except api.TimeoutError: self.cancel_proposal() return accepted_streams = [] for stream in self.proposed_streams: try: remote_media = remote_sdp.media[stream.index] except IndexError: self._fail_proposal(originator='local', error='SDP media missing in answer') return else: if remote_media.port: stream.start(local_sdp, remote_sdp, stream.index) accepted_streams.append(stream) else: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() with api.timeout(self.media_stream_timeout): wait_count = len(accepted_streams) while wait_count > 0: notification = self._channel.wait() if notification.name == 'MediaStreamDidStart': wait_count -= 1 except api.TimeoutError: self._fail_proposal(originator='local', error='media stream timed-out while starting') except MediaStreamDidNotInitializeError as e: self._fail_proposal(originator='local', error='media stream did not initialize: {.data.reason}'.format(e)) except MediaStreamDidFailError as e: self._fail_proposal(originator='local', error='media stream failed: {.data.reason}'.format(e)) except InvitationDisconnectedError as e: self._fail_proposal(originator='local', error='session ended') notification = Notification('SIPInvitationChangedState', e.invitation, e.data) notification.center = notification_center self.handle_notification(notification) except SIPCoreError as e: self._fail_proposal(originator='local', error='SIP core error: %s' % str(e)) else: self.greenlet = None self.state = 'connected' self.streams += accepted_streams proposed_streams = self.proposed_streams self.proposed_streams = None any_stream_ice = any(getattr(stream, 'ice_active', False) for stream in accepted_streams) if any_stream_ice: self._reinvite_after_ice() notification_center.post_notification('SIPSessionProposalAccepted', self, NotificationData(originator='local', accepted_streams=accepted_streams, proposed_streams=proposed_streams)) notification_center.post_notification('SIPSessionDidRenegotiateStreams', self, NotificationData(originator='local', added_streams=accepted_streams, removed_streams=[])) for notification in unhandled_notifications: self.handle_notification(notification) if self._hold_in_progress: self._send_hold() finally: self.greenlet = None def remove_stream(self, stream): self.remove_streams([stream]) @transition_state('connected', 'sending_proposal') @run_in_green_thread def remove_streams(self, streams): streams = list(set(streams).intersection(self.streams)) if not streams: self.state = 'connected' return self.greenlet = api.getcurrent() notification_center = NotificationCenter() unhandled_notifications = [] try: local_sdp = SDPSession.new(self._invitation.sdp.active_local) local_sdp.version += 1 for stream in streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() self.streams.remove(stream) media = local_sdp.media[stream.index] media.port = 0 media.attributes = [] media.bandwidth_info = [] self._invitation.send_reinvite(sdp=local_sdp) received_invitation_state = False received_sdp_update = False with api.timeout(self.short_reinvite_timeout): while not received_invitation_state or not received_sdp_update: notification = self._channel.wait() if notification.name == 'SIPInvitationGotSDPUpdate': received_sdp_update = True if notification.data.succeeded: local_sdp = notification.data.local_sdp remote_sdp = notification.data.remote_sdp for s in self.streams: s.update(local_sdp, remote_sdp, s.index) elif notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected' and notification.data.sub_state == 'normal': received_invitation_state = True notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='INVITE', code=notification.data.code, reason=notification.data.reason)) if not (200 <= notification.data.code < 300): break elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) except InvitationDisconnectedError as e: for stream in streams: stream.end() self.greenlet = None notification = Notification('SIPInvitationChangedState', e.invitation, e.data) notification.center = notification_center self.handle_notification(notification) except (api.TimeoutError, MediaStreamDidFailError, SIPCoreError): for stream in streams: stream.end() self.end() else: for stream in streams: stream.end() self.greenlet = None self.state = 'connected' notification_center.post_notification('SIPSessionDidRenegotiateStreams', self, NotificationData(originator='local', added_streams=[], removed_streams=streams)) for notification in unhandled_notifications: self.handle_notification(notification) if self._hold_in_progress: self._send_hold() finally: self.greenlet = None @transition_state('sending_proposal', 'cancelling_proposal') @run_in_green_thread def cancel_proposal(self): if self.greenlet is not None: api.kill(self.greenlet, api.GreenletExit()) self.greenlet = api.getcurrent() notification_center = NotificationCenter() try: self._invitation.cancel_reinvite() while True: try: notification = self._channel.wait() except MediaStreamDidFailError: continue if notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected' and notification.data.sub_state == 'normal': notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=notification.data.code, reason=notification.data.reason)) if notification.data.code == 487: proposed_streams = self.proposed_streams or [] for stream in proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self.proposed_streams = None self.state = 'connected' notification_center.post_notification('SIPSessionProposalRejected', self, NotificationData(originator='local', code=notification.data.code, reason=notification.data.reason, proposed_streams=proposed_streams)) elif notification.data.code == 200: self.end() elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) break except SIPCoreError as e: notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', code=0, reason=None, failure_reason='SIP core error: %s' % str(e), redirect_identities=None)) proposed_streams = self.proposed_streams or [] for stream in proposed_streams: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self.proposed_streams = None self.greenlet = None self.state = 'connected' notification_center.post_notification('SIPSessionProposalRejected', self, NotificationData(originator='local', code=0, reason='SIP core error: %s' % str(e), proposed_streams=proposed_streams)) except InvitationDisconnectedError as e: for stream in self.proposed_streams or []: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self.proposed_streams = None self.greenlet = None notification = Notification('SIPInvitationChangedState', e.invitation, e.data) notification.center = notification_center self.handle_notification(notification) else: for stream in self.proposed_streams or []: notification_center.remove_observer(self, sender=stream) stream.deactivate() stream.end() self.proposed_streams = None self.greenlet = None self.state = 'connected' finally: self.greenlet = None if self._hold_in_progress: self._send_hold() @run_in_green_thread def hold(self): if self.on_hold or self._hold_in_progress: return self._hold_in_progress = True streams = (self.streams or []) + (self.proposed_streams or []) if not streams: return for stream in streams: stream.hold() if self.state == 'connected': self._send_hold() @run_in_green_thread def unhold(self): if not self.on_hold and not self._hold_in_progress: return self._hold_in_progress = False streams = (self.streams or []) + (self.proposed_streams or []) if not streams: return for stream in streams: stream.unhold() if self.state == 'connected': self._send_unhold() @run_in_green_thread def end(self): if self.state in (None, 'terminating', 'terminated'): return if self.greenlet is not None: api.kill(self.greenlet, api.GreenletExit()) self.greenlet = None notification_center = NotificationCenter() if self._invitation is None: # The invitation was not yet constructed self.state = 'terminated' notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator='local', code=487, reason='Session Cancelled', failure_reason='user request', redirect_identities=None)) return elif self._invitation.state is None: # The invitation was built but never sent streams = (self.streams or []) + (self.proposed_streams or []) for stream in streams[:]: try: notification_center.remove_observer(self, sender=stream) except KeyError: streams.remove(stream) else: stream.deactivate() stream.end() self.state = 'terminated' notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator='local', code=487, reason='Session Cancelled', failure_reason='user request', redirect_identities=None)) return invitation_state = self._invitation.state if invitation_state in ('disconnecting', 'disconnected'): return self.greenlet = api.getcurrent() self.state = 'terminating' if invitation_state == 'connected': notification_center.post_notification('SIPSessionWillEnd', self, NotificationData(originator='local')) streams = (self.streams or []) + (self.proposed_streams or []) for stream in streams[:]: try: notification_center.remove_observer(self, sender=stream) except KeyError: streams.remove(stream) else: stream.deactivate() cancelling = invitation_state != 'connected' and self.direction == 'outgoing' try: self._invitation.end(timeout=1) while True: try: notification = self._channel.wait() except MediaStreamDidFailError: continue if notification.name == 'SIPInvitationChangedState' and notification.data.state == 'disconnected': if notification.data.disconnect_reason in ('internal error', 'missing ACK'): pass elif notification.data.disconnect_reason == 'timeout': notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local' if self.direction=='outgoing' else 'remote', method='INVITE', code=408, reason='Timeout')) elif cancelling: notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='INVITE', code=notification.data.code, reason=notification.data.reason)) elif hasattr(notification.data, 'method'): notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method=notification.data.method, code=200, reason=sip_status_messages[200])) elif notification.data.disconnect_reason == 'user request': notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='BYE', code=notification.data.code, reason=notification.data.reason)) break except SIPCoreError as e: if cancelling: notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator='local', code=0, reason=None, failure_reason='SIP core error: %s' % str(e), redirect_identities=None)) else: self.end_time = ISOTimestamp.now() notification_center.post_notification('SIPSessionDidEnd', self, NotificationData(originator='local', end_reason='SIP core error: %s' % str(e))) except InvitationDisconnectedError as e: # As it weird as it may sound, PJSIP accepts a BYE even without receiving a final response to the INVITE if e.data.prev_state == 'connected': if e.data.originator == 'remote': notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator=e.data.originator, method=e.data.method, code=200, reason=sip_status_messages[200])) self.end_time = ISOTimestamp.now() notification_center.post_notification('SIPSessionDidEnd', self, NotificationData(originator=e.data.originator, end_reason=e.data.disconnect_reason)) elif getattr(e.data, 'method', None) == 'BYE' and e.data.originator == 'remote': notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator=e.data.originator, method=e.data.method, code=200, reason=sip_status_messages[200])) notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator=e.data.originator, code=0, reason=None, failure_reason=e.data.disconnect_reason, redirect_identities=None)) else: if e.data.originator == 'remote': code = e.data.code reason = e.data.reason elif e.data.disconnect_reason == 'timeout': code = 408 reason = 'timeout' else: code = 0 reason = None if e.data.originator == 'remote' and code // 100 == 3: redirect_identities = e.data.headers.get('Contact', []) else: redirect_identities = None notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='INVITE', code=code, reason=reason)) notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator=e.data.originator, code=code, reason=reason, failure_reason=e.data.disconnect_reason, redirect_identities=redirect_identities)) else: if cancelling: notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator='local', code=487, reason='Session Cancelled', failure_reason='user request', redirect_identities=None)) else: self.end_time = ISOTimestamp.now() notification_center.post_notification('SIPSessionDidEnd', self, NotificationData(originator='local', end_reason='user request')) finally: for stream in streams: stream.end() notification_center.remove_observer(self, sender=self._invitation) self.greenlet = None self.state = 'terminated' @check_state(['connected']) @check_transfer_state(None, None) @run_in_twisted_thread def transfer(self, target_uri, replaced_session=None): notification_center = NotificationCenter() notification_center.post_notification('SIPSessionTransferNewOutgoing', self, NotificationData(transfer_destination=target_uri)) try: self._invitation.transfer(target_uri, replaced_session.dialog_id if replaced_session is not None else None) except SIPCoreError as e: notification_center.post_notification('SIPSessionTransferDidFail', sender=self, data=NotificationData(code=500, reason=str(e))) @check_state(['connected', 'received_proposal', 'sending_proposal', 'accepting_proposal', 'rejecting_proposal', 'cancelling_proposal']) @check_transfer_state('incoming', 'starting') def accept_transfer(self): notification_center = NotificationCenter() notification_center.post_notification('SIPSessionTransferDidStart', sender=self) @check_state(['connected', 'received_proposal', 'sending_proposal', 'accepting_proposal', 'rejecting_proposal', 'cancelling_proposal']) @check_transfer_state('incoming', 'starting') def reject_transfer(self, code=603, reason=None): notification_center = NotificationCenter() notification_center.post_notification('SIPSessionTransferDidFail', self, NotificationData(code=code, reason=reason or sip_status_messages[code])) @property def dialog_id(self): return self._invitation.dialog_id if self._invitation is not None else None @property def local_identity(self): if self._invitation is not None and self._invitation.local_identity is not None: return self._invitation.local_identity else: return self._local_identity @property def peer_address(self): return self._invitation.peer_address if self._invitation is not None else None @property def remote_identity(self): if self._invitation is not None and self._invitation.remote_identity is not None: return self._invitation.remote_identity else: return self._remote_identity @property def remote_user_agent(self): return self._invitation.remote_user_agent if self._invitation is not None else None def _cancel_hold(self): notification_center = NotificationCenter() try: self._invitation.cancel_reinvite() while True: try: notification = self._channel.wait() except MediaStreamDidFailError: continue if notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected' and notification.data.sub_state == 'normal': notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=notification.data.code, reason=notification.data.reason)) if notification.data.code == 200: self.end() return False elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) break except SIPCoreError as e: notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', code=0, reason=None, failure_reason='SIP core error: %s' % str(e), redirect_identities=None)) except InvitationDisconnectedError as e: self.greenlet = None notification = Notification('SIPInvitationChangedState', e.invitation, e.data) notification.center = notification_center self.handle_notification(notification) return False return True def _send_hold(self): self.state = 'sending_proposal' self.greenlet = api.getcurrent() notification_center = NotificationCenter() unhandled_notifications = [] try: local_sdp = SDPSession.new(self._invitation.sdp.active_local) local_sdp.version += 1 for stream in self.streams: local_sdp.media[stream.index] = stream.get_local_media(remote_sdp=None, index=stream.index) self._invitation.send_reinvite(sdp=local_sdp) received_invitation_state = False received_sdp_update = False with api.timeout(self.short_reinvite_timeout): while not received_invitation_state or not received_sdp_update: notification = self._channel.wait() if notification.name == 'SIPInvitationGotSDPUpdate': received_sdp_update = True if notification.data.succeeded: local_sdp = notification.data.local_sdp remote_sdp = notification.data.remote_sdp for stream in self.streams: stream.update(local_sdp, remote_sdp, stream.index) elif notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected' and notification.data.sub_state == 'normal': received_invitation_state = True notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='INVITE', code=notification.data.code, reason=notification.data.reason)) elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) except InvitationDisconnectedError as e: self.greenlet = None notification = Notification('SIPInvitationChangedState', e.invitation, e.data) notification.center = notification_center self.handle_notification(notification) return except api.TimeoutError: if not self._cancel_hold(): return except SIPCoreError: pass self.greenlet = None self.on_hold = True self.state = 'connected' hold_supported_streams = (stream for stream in self.streams if stream.hold_supported) notification_center.post_notification('SIPSessionDidChangeHoldState', self, NotificationData(originator='local', on_hold=True, partial=any(not stream.on_hold_by_local for stream in hold_supported_streams))) for notification in unhandled_notifications: self.handle_notification(notification) if self._hold_in_progress: self._hold_in_progress = False else: for stream in self.streams: stream.unhold() self._send_unhold() def _send_unhold(self): self.state = 'sending_proposal' self.greenlet = api.getcurrent() notification_center = NotificationCenter() unhandled_notifications = [] try: local_sdp = SDPSession.new(self._invitation.sdp.active_local) local_sdp.version += 1 for stream in self.streams: local_sdp.media[stream.index] = stream.get_local_media(remote_sdp=None, index=stream.index) self._invitation.send_reinvite(sdp=local_sdp) received_invitation_state = False received_sdp_update = False with api.timeout(self.short_reinvite_timeout): while not received_invitation_state or not received_sdp_update: notification = self._channel.wait() if notification.name == 'SIPInvitationGotSDPUpdate': received_sdp_update = True if notification.data.succeeded: local_sdp = notification.data.local_sdp remote_sdp = notification.data.remote_sdp for stream in self.streams: stream.update(local_sdp, remote_sdp, stream.index) elif notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected' and notification.data.sub_state == 'normal': received_invitation_state = True notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='INVITE', code=notification.data.code, reason=notification.data.reason)) elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) except InvitationDisconnectedError as e: self.greenlet = None notification = Notification('SIPInvitationChangedState', e.invitation, e.data) notification.center = notification_center self.handle_notification(notification) return except api.TimeoutError: if not self._cancel_hold(): return except SIPCoreError: pass self.greenlet = None self.on_hold = False self.state = 'connected' notification_center.post_notification('SIPSessionDidChangeHoldState', self, NotificationData(originator='local', on_hold=False, partial=False)) for notification in unhandled_notifications: self.handle_notification(notification) if self._hold_in_progress: for stream in self.streams: stream.hold() self._send_hold() def _fail(self, originator, code, reason, error, reason_header=None): notification_center = NotificationCenter() prev_inv_state = self._invitation.state self.state = 'terminating' if prev_inv_state not in (None, 'incoming', 'outgoing', 'early', 'connecting'): notification_center.post_notification('SIPSessionWillEnd', self, NotificationData(originator=originator)) if self._invitation.state not in (None, 'disconnecting', 'disconnected'): try: if self._invitation.direction == 'incoming' and self._invitation.state in ('incoming', 'early'): if 400<=code<=699 and reason is not None: self._invitation.send_response(code, extra_headers=[reason_header] if reason_header is not None else []) else: self._invitation.end(extra_headers=[reason_header] if reason_header is not None else []) with api.timeout(1): while True: notification = self._channel.wait() if notification.name == 'SIPInvitationChangedState' and notification.data.state == 'disconnected': if prev_inv_state in ('connecting', 'connected'): if notification.data.disconnect_reason in ('timeout', 'missing ACK'): sip_code = 200 sip_reason = 'OK' originator = 'local' elif hasattr(notification.data, 'method'): sip_code = 200 sip_reason = 'OK' originator = 'remote' else: sip_code = notification.data.code sip_reason = notification.data.reason originator = 'local' notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator=originator, method='BYE', code=sip_code, reason=sip_reason)) elif self._invitation.direction == 'incoming' and prev_inv_state in ('incoming', 'early'): ack_received = notification.data.disconnect_reason != 'missing ACK' notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=code, reason=reason, ack_received=ack_received)) elif self._invitation.direction == 'outgoing' and prev_inv_state in ('outgoing', 'early'): notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='INVITE', code=487, reason='Session Cancelled')) break except SIPCoreError: pass except api.TimeoutError: if prev_inv_state in ('connecting', 'connected'): notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='local', method='BYE', code=408, reason=sip_status_messages[408])) notification_center.remove_observer(self, sender=self._invitation) self.state = 'terminated' notification_center.post_notification('SIPSessionDidFail', self, NotificationData(originator=originator, code=code, reason=reason, failure_reason=error, redirect_identities=None)) self.greenlet = None def _fail_proposal(self, originator, error): notification_center = NotificationCenter() for stream in self.proposed_streams: try: notification_center.remove_observer(self, sender=stream) except KeyError: # _fail_proposal can be called from reject_proposal, which means the stream will # not have been initialized or the session registered as an observer for it. pass else: stream.deactivate() stream.end() if originator == 'remote' and self._invitation.sub_state == 'received_proposal': try: self._invitation.send_response(488 if self.proposed_streams else 500) except SIPCoreError: pass else: notification_center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=500, reason=sip_status_messages[500], ack_received='unknown')) notification_center.post_notification('SIPSessionHadProposalFailure', self, NotificationData(originator=originator, failure_reason=error, proposed_streams=self.proposed_streams[:])) self.state = 'connected' self.proposed_streams = None self.greenlet = None @run_in_green_thread def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_SIPInvitationChangedState(self, notification): if self.state == 'terminated': return if notification.data.originator == 'remote' and notification.data.state not in ('disconnecting', 'disconnected'): contact_header = notification.data.headers.get('Contact', None) if contact_header and 'isfocus' in contact_header[0].parameters: self.remote_focus = True if self.greenlet is not None: if notification.data.state == 'disconnected' and notification.data.prev_state != 'disconnecting': self._channel.send_exception(InvitationDisconnectedError(notification.sender, notification.data)) else: self._channel.send(notification) else: self.greenlet = api.getcurrent() unhandled_notifications = [] try: if notification.data.state == 'connected' and notification.data.sub_state == 'received_proposal': self.state = 'received_proposal' try: proposed_remote_sdp = self._invitation.sdp.proposed_remote active_remote_sdp = self._invitation.sdp.active_remote if len(proposed_remote_sdp.media) < len(active_remote_sdp.media): engine = Engine() self._invitation.send_response(488, extra_headers=[WarningHeader(399, engine.user_agent, 'Streams cannot be deleted from the SDP')]) self.state = 'connected' notification.center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=488, reason=sip_status_messages[488], ack_received='unknown')) return for stream in self.streams: if not stream.validate_update(proposed_remote_sdp, stream.index): engine = Engine() self._invitation.send_response(488, extra_headers=[WarningHeader(399, engine.user_agent, 'Failed to update media stream index %d' % stream.index)]) self.state = 'connected' notification.center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=488, reason=sip_status_messages[488], ack_received='unknown')) return added_media_indexes = set() removed_media_indexes = set() reused_media_indexes = set() for index, media_stream in enumerate(proposed_remote_sdp.media): if index >= len(active_remote_sdp.media): added_media_indexes.add(index) elif media_stream.port == 0 and active_remote_sdp.media[index].port > 0: removed_media_indexes.add(index) elif media_stream.port > 0 and active_remote_sdp.media[index].port == 0: reused_media_indexes.add(index) elif media_stream.media != active_remote_sdp.media[index].media: added_media_indexes.add(index) removed_media_indexes.add(index) if added_media_indexes | reused_media_indexes and removed_media_indexes: engine = Engine() self._invitation.send_response(488, extra_headers=[WarningHeader(399, engine.user_agent, 'Both removing AND adding a media stream is currently not supported')]) self.state = 'connected' notification.center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=488, reason=sip_status_messages[488], ack_received='unknown')) return elif added_media_indexes | reused_media_indexes: self.proposed_streams = [] for index in added_media_indexes | reused_media_indexes: media_stream = proposed_remote_sdp.media[index] if media_stream.port != 0: for stream_type in MediaStreamRegistry: try: stream = stream_type.new_from_sdp(self, proposed_remote_sdp, index) except UnknownStreamError: continue except InvalidStreamError as e: log.error("Invalid stream: {}".format(e)) break except Exception as e: log.exception("Exception occurred while setting up stream from SDP: {}".format(e)) break else: stream.index = index self.proposed_streams.append(stream) break if self.proposed_streams: self._invitation.send_response(100) notification.center.post_notification('SIPSessionNewProposal', sender=self, data=NotificationData(originator='remote', proposed_streams=self.proposed_streams[:])) else: self._invitation.send_response(488) self.state = 'connected' notification.center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=488, reason=sip_status_messages[488], ack_received='unknown')) return else: local_sdp = SDPSession.new(self._invitation.sdp.active_local) local_sdp.version += 1 removed_streams = [stream for stream in self.streams if stream.index in removed_media_indexes] prev_on_hold_streams = set(stream for stream in self.streams if stream.hold_supported and stream.on_hold_by_remote) for stream in removed_streams: notification.center.remove_observer(self, sender=stream) stream.deactivate() media = local_sdp.media[stream.index] media.port = 0 media.attributes = [] media.bandwidth_info = [] for stream in self.streams: local_sdp.media[stream.index] = stream.get_local_media(remote_sdp=proposed_remote_sdp, index=stream.index) try: self._invitation.send_response(200, sdp=local_sdp) except PJSIPError: for stream in removed_streams: self.streams.remove(stream) stream.end() if removed_streams: self.end() return else: try: self._invitation.send_response(488) except PJSIPError: self.end() return else: for stream in removed_streams: self.streams.remove(stream) stream.end() notification.center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=200, reason=sip_status_messages[200], ack_received='unknown')) received_invitation_state = False received_sdp_update = False while not received_sdp_update or not received_invitation_state or self._channel: notification = self._channel.wait() if notification.name == 'SIPInvitationGotSDPUpdate': received_sdp_update = True if notification.data.succeeded: local_sdp = notification.data.local_sdp remote_sdp = notification.data.remote_sdp for stream in self.streams: stream.update(local_sdp, remote_sdp, stream.index) elif notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected' and notification.data.sub_state == 'normal': received_invitation_state = True elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) else: unhandled_notifications.append(notification) else: unhandled_notifications.append(notification) on_hold_streams = set(stream for stream in self.streams if stream.hold_supported and stream.on_hold_by_remote) if on_hold_streams != prev_on_hold_streams: hold_supported_streams = (stream for stream in self.streams if stream.hold_supported) notification.center.post_notification('SIPSessionDidChangeHoldState', self, NotificationData(originator='remote', on_hold=bool(on_hold_streams), partial=bool(on_hold_streams) and any(not stream.on_hold_by_remote for stream in hold_supported_streams))) if removed_media_indexes: notification.center.post_notification('SIPSessionDidRenegotiateStreams', self, NotificationData(originator='remote', added_streams=[], removed_streams=removed_streams)) except InvitationDisconnectedError as e: self.greenlet = None self.state = 'connected' notification = Notification('SIPInvitationChangedState', e.invitation, e.data) notification.center = NotificationCenter() self.handle_notification(notification) except SIPCoreError: self.end() else: self.state = 'connected' elif notification.data.state == 'connected' and notification.data.sub_state == 'received_proposal_request': self.state = 'received_proposal_request' try: # An empty proposal was received, generate an offer self._invitation.send_response(100) local_sdp = SDPSession.new(self._invitation.sdp.active_local) local_sdp.version += 1 connection_address = host.outgoing_ip_for(self._invitation.peer_address.ip) if local_sdp.connection is not None: local_sdp.connection.address = connection_address for index, stream in enumerate(self.streams): stream.reset(index) media = stream.get_local_media(remote_sdp=None, index=index) if media.connection is not None: media.connection.address = connection_address local_sdp.media[stream.index] = media self._invitation.send_response(200, sdp=local_sdp) notification.center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=200, reason=sip_status_messages[200], ack_received='unknown')) received_invitation_state = False received_sdp_update = False while not received_sdp_update or not received_invitation_state or self._channel: notification = self._channel.wait() if notification.name == 'SIPInvitationGotSDPUpdate': received_sdp_update = True if notification.data.succeeded: local_sdp = notification.data.local_sdp remote_sdp = notification.data.remote_sdp for stream in self.streams: stream.update(local_sdp, remote_sdp, stream.index) elif notification.name == 'SIPInvitationChangedState': if notification.data.state == 'connected' and notification.data.sub_state == 'normal': received_invitation_state = True elif notification.data.state == 'disconnected': raise InvitationDisconnectedError(notification.sender, notification.data) else: unhandled_notifications.append(notification) else: unhandled_notifications.append(notification) except InvitationDisconnectedError as e: self.greenlet = None self.state = 'connected' notification = Notification('SIPInvitationChangedState', e.invitation, e.data) notification.center = NotificationCenter() self.handle_notification(notification) except SIPCoreError: raise # FIXME else: self.state = 'connected' elif notification.data.prev_state == notification.data.state == 'connected' and notification.data.prev_sub_state == 'received_proposal' and notification.data.sub_state == 'normal': if notification.data.originator == 'local' and notification.data.code == 487: proposed_streams = self.proposed_streams self.proposed_streams = None self.state = 'connected' notification.center.post_notification('SIPSessionProposalRejected', self, NotificationData(originator='remote', code=notification.data.code, reason=notification.data.reason, proposed_streams=proposed_streams)) if self._hold_in_progress: self._send_hold() elif notification.data.state == 'disconnected': if self.state == 'incoming': self.state = 'terminated' if notification.data.originator == 'remote': notification.center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator='remote', method='INVITE', code=487, reason='Session Cancelled', ack_received='unknown')) notification.center.post_notification('SIPSessionDidFail', self, NotificationData(originator='remote', code=487, reason='Session Cancelled', failure_reason=notification.data.disconnect_reason, redirect_identities=None)) else: # There must have been an error involved notification.center.post_notification('SIPSessionDidFail', self, NotificationData(originator='local', code=0, reason=None, failure_reason=notification.data.disconnect_reason, redirect_identities=None)) else: self.state = 'terminated' notification.center.post_notification('SIPSessionWillEnd', self, NotificationData(originator=notification.data.originator)) for stream in self.streams: notification.center.remove_observer(self, sender=stream) stream.deactivate() stream.end() if notification.data.originator == 'remote': if hasattr(notification.data, 'method'): notification.center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator=notification.data.originator, method=notification.data.method, code=200, reason=sip_status_messages[200])) else: notification.center.post_notification('SIPSessionDidProcessTransaction', self, NotificationData(originator=notification.data.originator, method='INVITE', code=notification.data.code, reason=notification.data.reason)) self.end_time = ISOTimestamp.now() notification.center.post_notification('SIPSessionDidEnd', self, NotificationData(originator=notification.data.originator, end_reason=notification.data.disconnect_reason)) notification.center.remove_observer(self, sender=self._invitation) finally: self.greenlet = None for notification in unhandled_notifications: self.handle_notification(notification) def _NH_SIPInvitationGotSDPUpdate(self, notification): if self.greenlet is not None: self._channel.send(notification) def _NH_MediaStreamDidInitialize(self, notification): if self.greenlet is not None: self._channel.send(notification) def _NH_RTPStreamDidEnableEncryption(self, notification): if notification.sender.type != 'audio': return audio_stream = notification.sender if audio_stream.encryption.type == 'ZRTP': # start ZRTP on the video stream, if applicable try: video_stream = next(stream for stream in self.streams or [] if stream.type=='video') except StopIteration: return if video_stream.encryption.type == 'ZRTP' and not video_stream.encryption.active: video_stream.encryption.zrtp._enable(audio_stream) def _NH_MediaStreamDidStart(self, notification): stream = notification.sender if stream.type == 'audio' and stream.encryption.type == 'ZRTP': stream.encryption.zrtp._enable() elif stream.type == 'video' and stream.encryption.type == 'ZRTP': # start ZRTP on the video stream, if applicable try: audio_stream = next(stream for stream in self.streams or [] if stream.type=='audio') except StopIteration: pass else: if audio_stream.encryption.type == 'ZRTP' and audio_stream.encryption.active: stream.encryption.zrtp._enable(audio_stream) if self.greenlet is not None: self._channel.send(notification) def _NH_MediaStreamDidNotInitialize(self, notification): if self.greenlet is not None and self.state not in ('terminating', 'terminated'): self._channel.send_exception(MediaStreamDidNotInitializeError(notification.sender, notification.data)) def _NH_MediaStreamDidFail(self, notification): if self.greenlet is not None: if self.state not in ('terminating', 'terminated'): self._channel.send_exception(MediaStreamDidFailError(notification.sender, notification.data)) else: stream = notification.sender if self.streams == [stream]: self.end() else: try: self.remove_stream(stream) except IllegalStateError: self.end() @implementer(IObserver) class SessionManager(object, metaclass=Singleton): def __init__(self): self.sessions = [] self.state = None self._channel = coros.queue() def start(self): self.state = 'starting' notification_center = NotificationCenter() notification_center.post_notification('SIPSessionManagerWillStart', sender=self) notification_center.add_observer(self, 'SIPInvitationChangedState') notification_center.add_observer(self, 'SIPSessionNewIncoming') notification_center.add_observer(self, 'SIPSessionNewOutgoing') notification_center.add_observer(self, 'SIPSessionDidFail') notification_center.add_observer(self, 'SIPSessionDidEnd') self.state = 'started' notification_center.post_notification('SIPSessionManagerDidStart', sender=self) def stop(self): self.state = 'stopping' notification_center = NotificationCenter() notification_center.post_notification('SIPSessionManagerWillEnd', sender=self) for session in self.sessions: session.end() while self.sessions: self._channel.wait() notification_center.remove_observer(self, 'SIPInvitationChangedState') notification_center.remove_observer(self, 'SIPSessionNewIncoming') notification_center.remove_observer(self, 'SIPSessionNewOutgoing') notification_center.remove_observer(self, 'SIPSessionDidFail') notification_center.remove_observer(self, 'SIPSessionDidEnd') self.state = 'stopped' notification_center.post_notification('SIPSessionManagerDidEnd', sender=self) @run_in_twisted_thread def handle_notification(self, notification): if notification.name == 'SIPInvitationChangedState' and notification.data.state == 'incoming': account_manager = AccountManager() account = account_manager.find_account(notification.data.request_uri) if account is None: notification.sender.send_response(404) return notification.sender.send_response(100) session = Session(account) session.init_incoming(notification.sender, notification.data) elif notification.name in ('SIPSessionNewIncoming', 'SIPSessionNewOutgoing'): self.sessions.append(notification.sender) elif notification.name in ('SIPSessionDidFail', 'SIPSessionDidEnd'): self.sessions.remove(notification.sender) if self.state == 'stopping': self._channel.send(notification) diff --git a/sipsimple/streams/rtp/audio.py b/sipsimple/streams/rtp/audio.py index aafc129c..98b4c119 100644 --- a/sipsimple/streams/rtp/audio.py +++ b/sipsimple/streams/rtp/audio.py @@ -1,236 +1,239 @@ __all__ = ['AudioStream'] from application.notification import NotificationCenter, NotificationData from zope.interface import implementer from sipsimple.audio import AudioBridge, AudioDevice, IAudioPort, WaveRecorder from sipsimple.configuration.settings import SIPSimpleSettings from sipsimple.core import AudioTransport, PJSIPError, SIPCoreError from sipsimple.streams.rtp import RTPStream @implementer(IAudioPort) class AudioStream(RTPStream): type = 'audio' priority = 1 def __init__(self): super(AudioStream, self).__init__() from sipsimple.application import SIPApplication self.mixer = SIPApplication.voice_audio_mixer self.bridge = AudioBridge(self.mixer) self.device = AudioDevice(self.mixer) self._audio_rec = None self.bridge.add(self.device) @property def muted(self): return self.__dict__.get('muted', False) @muted.setter def muted(self, value): if not isinstance(value, bool): raise ValueError("illegal value for muted property: %r" % (value,)) if value == self.muted: return old_producer_slot = self.producer_slot self.__dict__['muted'] = value notification_center = NotificationCenter() data = NotificationData(consumer_slot_changed=False, producer_slot_changed=True, old_producer_slot=old_producer_slot, new_producer_slot=self.producer_slot) notification_center.post_notification('AudioPortDidChangeSlots', sender=self, data=data) @property def consumer_slot(self): return self._transport.slot if self._transport else None @property def producer_slot(self): return self._transport.slot if self._transport and not self.muted else None @property def recorder(self): return self._audio_rec def start(self, local_sdp, remote_sdp, stream_index): with self._lock: if self.state != "INITIALIZED": raise RuntimeError("AudioStream.start() may only be called in the INITIALIZED state") settings = SIPSimpleSettings() self._transport.start(local_sdp, remote_sdp, stream_index, timeout=settings.rtp.timeout) self._save_remote_sdp_rtp_info(remote_sdp, stream_index) self._check_hold(self._transport.direction, True) if self._try_ice and self._ice_state == "NULL": self.state = 'WAIT_ICE' else: self.state = 'ESTABLISHED' self.notification_center.post_notification('MediaStreamDidStart', sender=self) def validate_update(self, remote_sdp, stream_index): with self._lock: # TODO: implement return True def update(self, local_sdp, remote_sdp, stream_index): with self._lock: connection = remote_sdp.media[stream_index].connection or remote_sdp.connection if not self._rtp_transport.ice_active and (connection.address != self._remote_rtp_address_sdp or self._remote_rtp_port_sdp != remote_sdp.media[stream_index].port): settings = SIPSimpleSettings() if self._audio_rec is not None: self.bridge.remove(self._audio_rec) old_consumer_slot = self.consumer_slot old_producer_slot = self.producer_slot self.notification_center.remove_observer(self, sender=self._transport) self._transport.stop() + available_codecs = self.session.account.rtp.audio_codec_list or settings.rtp.audio_codec_list + codecs = list(c.encode() for c in available_codecs) try: - self._transport = AudioTransport(self.mixer, self._rtp_transport, remote_sdp, stream_index, codecs=list(self.session.account.rtp.audio_codec_list or settings.rtp.audio_codec_list)) + self._transport = AudioTransport(self.mixer, self._rtp_transport, remote_sdp, stream_index, codecs=codecs) except SIPCoreError as e: self.state = "ENDED" self._failure_reason = e.args[0] self.notification_center.post_notification('MediaStreamDidFail', sender=self, data=NotificationData(context='update', reason=self._failure_reason)) return self.notification_center.add_observer(self, sender=self._transport) self._transport.start(local_sdp, remote_sdp, stream_index, timeout=settings.rtp.timeout) self.notification_center.post_notification('AudioPortDidChangeSlots', sender=self, data=NotificationData(consumer_slot_changed=True, producer_slot_changed=True, old_consumer_slot=old_consumer_slot, new_consumer_slot=self.consumer_slot, old_producer_slot=old_producer_slot, new_producer_slot=self.producer_slot)) if connection.address == '0.0.0.0' and remote_sdp.media[stream_index].direction == 'sendrecv': self._transport.update_direction('recvonly') self._check_hold(self._transport.direction, False) self.notification_center.post_notification('RTPStreamDidChangeRTPParameters', sender=self) else: new_direction = local_sdp.media[stream_index].direction self._transport.update_direction(new_direction) self._check_hold(new_direction, False) self._save_remote_sdp_rtp_info(remote_sdp, stream_index) self._transport.update_sdp(local_sdp, remote_sdp, stream_index) self._hold_request = None def deactivate(self): with self._lock: self.bridge.stop() def end(self): with self._lock: if self.state == "ENDED" or self._done: return self._done = True if not self._initialized: self.state = "ENDED" self.notification_center.post_notification('MediaStreamDidNotInitialize', sender=self, data=NotificationData(reason='Interrupted')) return self.notification_center.post_notification('MediaStreamWillEnd', sender=self) if self._transport is not None: if self._audio_rec is not None: self._stop_recording() self.notification_center.remove_observer(self, sender=self._transport) self.notification_center.remove_observer(self, sender=self._rtp_transport) self._transport.stop() self._transport = None self._rtp_transport = None self.state = "ENDED" self.notification_center.post_notification('MediaStreamDidEnd', sender=self, data=NotificationData(error=self._failure_reason)) self.session = None def reset(self, stream_index): with self._lock: if self.direction == "inactive" and not self.on_hold_by_local: new_direction = "sendrecv" self._transport.update_direction(new_direction) self._check_hold(new_direction, False) # TODO: do a full reset, re-creating the AudioTransport, so that a new offer # would contain all codecs and ICE would be renegotiated -Saul def send_dtmf(self, digit): with self._lock: if self.state != "ESTABLISHED": raise RuntimeError("AudioStream.send_dtmf() cannot be used in %s state" % self.state) try: self._transport.send_dtmf(digit) except PJSIPError as e: if not e.args[0].endswith("(PJ_ETOOMANY)"): raise def start_recording(self, filename): with self._lock: if self.state == "ENDED": raise RuntimeError("AudioStream.start_recording() may not be called in the ENDED state") if self._audio_rec is not None: raise RuntimeError("Already recording audio to a file") self._audio_rec = WaveRecorder(self.mixer, filename) if self.state == "ESTABLISHED": self._check_recording() def stop_recording(self): with self._lock: if self._audio_rec is None: raise RuntimeError("Not recording any audio") self._stop_recording() def _NH_RTPAudioStreamGotDTMF(self, notification): notification.center.post_notification('AudioStreamGotDTMF', sender=self, data=NotificationData(digit=notification.data.digit)) def _NH_RTPAudioTransportDidTimeout(self, notification): notification.center.post_notification('RTPStreamDidTimeout', sender=self) # Private methods # def _create_transport(self, rtp_transport, remote_sdp=None, stream_index=None): settings = SIPSimpleSettings() - codecs = list(self.session.account.rtp.audio_codec_list or settings.rtp.audio_codec_list) + available_codecs = self.session.account.rtp.audio_codec_list or settings.rtp.audio_codec_list + codecs = list(c.encode() for c in available_codecs) return AudioTransport(self.mixer, rtp_transport, remote_sdp=remote_sdp, sdp_index=stream_index or 0, codecs=codecs) def _check_hold(self, direction, is_initial): was_on_hold_by_local = self.on_hold_by_local was_on_hold_by_remote = self.on_hold_by_remote was_inactive = self.direction == "inactive" self.direction = direction inactive = self.direction == "inactive" self.on_hold_by_local = was_on_hold_by_local if inactive else direction == "sendonly" self.on_hold_by_remote = "send" not in direction if (is_initial or was_on_hold_by_local or was_inactive) and not inactive and not self.on_hold_by_local and self._hold_request != 'hold': self._resume() if not was_on_hold_by_local and self.on_hold_by_local: self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="local", on_hold=True)) if was_on_hold_by_local and not self.on_hold_by_local: self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="local", on_hold=False)) if not was_on_hold_by_remote and self.on_hold_by_remote: self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="remote", on_hold=True)) if was_on_hold_by_remote and not self.on_hold_by_remote: self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="remote", on_hold=False)) if self._audio_rec is not None: self._check_recording() def _check_recording(self): if not self._audio_rec.is_active: self.notification_center.post_notification('AudioStreamWillStartRecording', sender=self, data=NotificationData(filename=self._audio_rec.filename)) try: self._audio_rec.start() except SIPCoreError as e: self._audio_rec = None self.notification_center.post_notification('AudioStreamDidStopRecording', sender=self, data=NotificationData(filename=self._audio_rec.filename, reason=e.args[0])) return self.notification_center.post_notification('AudioStreamDidStartRecording', sender=self, data=NotificationData(filename=self._audio_rec.filename)) if not self.on_hold: self.bridge.add(self._audio_rec) elif self._audio_rec in self.bridge: self.bridge.remove(self._audio_rec) def _stop_recording(self): self.notification_center.post_notification('AudioStreamWillStopRecording', sender=self, data=NotificationData(filename=self._audio_rec.filename)) try: if self._audio_rec.is_active: self._audio_rec.stop() finally: self.notification_center.post_notification('AudioStreamDidStopRecording', sender=self, data=NotificationData(filename=self._audio_rec.filename)) self._audio_rec = None def _pause(self): self.bridge.remove(self) def _resume(self): self.bridge.add(self)