diff --git a/sipsimple/core/_core.invitation.pxi b/sipsimple/core/_core.invitation.pxi index 8a5496df..e5d3a589 100644 --- a/sipsimple/core/_core.invitation.pxi +++ b/sipsimple/core/_core.invitation.pxi @@ -1,1856 +1,1856 @@ 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.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.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).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) 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.decode() self.direction = "outgoing" self.credentials = FrozenCredentials.new(credentials) if credentials is not None else None 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.encode()) to_header_parameters = to_header.parameters.copy() to_header_parameters.pop("tag", None) to_header.parameters = {} to_header_str = PJSTR(to_header.body.encode()) contact_str = PJSTR(str(self.local_contact_header.body).encode()) self.request_uri = FrozenSIPURI.new(request_uri) struri = str(request_uri) request_uri_str = PJSTR(struri.encode()) 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 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, contact_str) pj_strdup2_with_null(self._dialog.pool, &contact_str_pj, contact_str.encode()) 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 = b"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(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).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_bytes(event.body.tsx_state.tsx.method.name), rdata_dict) + timer = TransferResponseCallbackTimer(_pj_str_to_str(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.sdp.pxi b/sipsimple/core/_core.sdp.pxi index ec992bf0..4a9fbca5 100644 --- a/sipsimple/core/_core.sdp.pxi +++ b/sipsimple/core/_core.sdp.pxi @@ -1,1229 +1,1229 @@ 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(object 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 _pj_buf_len_to_str(cbuf, buf_len).decode() 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, object address not None, object id=None, object version=None, object user not None=b"-", object net_type not None=b"IN", object address_type not None=b"IP4", object name not None=b" ", 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, object 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, object address not None): _str_to_pj_str(address, &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, object user not None): _str_to_pj_str(user, &self._sdp_session.origin.user) self._user = user property net_type: def __get__(self): return self._net_type def __set__(self, object net_type not None): _str_to_pj_str(net_type, &self._sdp_session.origin.net_type) self._net_type = net_type property address_type: def __get__(self): return self._address_type def __set__(self, object address_type not None): _str_to_pj_str(address_type, &self._sdp_session.origin.addr_type) self._address_type = address_type property name: def __get__(self): return self._name def __set__(self, object name not None): _str_to_pj_str(name, &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, object address not None, object id=None, object version=None, object user not None=b"-", object net_type not None=b"IN", object address_type not None=b"IP4", object name not None=b" ", 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) 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) self.net_type = net_type _str_to_pj_str(net_type, &self._sdp_session.origin.net_type) self.address_type = address_type _str_to_pj_str(address_type, &self._sdp_session.origin.addr_type) self.name = name _str_to_pj_str(name, &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, object 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 (b"sendrecv", b"sendonly", b"recvonly", b"inactive"): return attribute.name return b"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, object media not None, int port, object 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, object media not None): _str_to_pj_str(media, &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, object transport not None): _str_to_pj_str(transport, &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]) 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 (b"audio", b"video"): rtp_mappings = self.rtp_mappings.copy() rtpmap_lines = '\n'.join([attr.value.decode() for attr in attributes if attr.name==b'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, object media not None, int port, object 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) self.port = port self._sdp_media.desc.port = port self.transport = transport _str_to_pj_str(transport, &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]) 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 (b"audio", b"video"): rtp_mappings = self.rtp_mappings.copy() rtpmap_lines = '\n'.join([attr.value.decode() for attr in attributes if attr.name==b'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, object address not None, object net_type not None=b"IN", object address_type not None=b"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, object address not None): _str_to_pj_str(address, &self._sdp_connection.addr) self._address = address property net_type: def __get__(self): return self._net_type def __set__(self, object net_type not None): _str_to_pj_str(net_type, &self._sdp_connection.net_type) self._net_type = net_type property address_type: def __get__(self): return self._address_type def __set__(self, object address_type not None): _str_to_pj_str(address_type, &self._sdp_connection.addr_type) self._address_type = address_type cdef class FrozenSDPConnection(BaseSDPConnection): def __init__(self, object address not None, object net_type not None=b"IN", object address_type not None=b"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) 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, object name not None, object 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, object name not None): #print('SDP attribute name %s %s' % (name, type(name))) _str_to_pj_str(name, &self._sdp_attribute.name) self._name = name property value: def __get__(self): return self._value def __set__(self, object value not None): #print('SDP attribute value %s %s' % (value, type(value))) _str_to_pj_str(value, &self._sdp_attribute.value) self._value = value cdef class FrozenSDPAttribute(BaseSDPAttribute): def __init__(self, object name not None, object 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) 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] 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] 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, object 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, object modifier not None): _str_to_pj_str(modifier, &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, object modifier not None, object value not None): if not self.initialized: _str_to_pj_str(modifier, &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_bytes(pj_session.origin.addr), pj_session.origin.id, pj_session.origin.version, _pj_str_to_bytes(pj_session.origin.user), _pj_str_to_bytes(pj_session.origin.net_type), _pj_str_to_bytes(pj_session.origin.addr_type), _pj_str_to_bytes(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_bytes(pj_session.origin.addr), pj_session.origin.id, pj_session.origin.version, _pj_str_to_bytes(pj_session.origin.user), _pj_str_to_bytes(pj_session.origin.net_type), _pj_str_to_bytes(pj_session.origin.addr_type), _pj_str_to_bytes(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_bytes(pj_media.desc.media), pj_media.desc.port, _pj_str_to_bytes(pj_media.desc.transport), pj_media.desc.port_count, [_pj_str_to_bytes(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_bytes(pj_media.desc.media), pj_media.desc.port, _pj_str_to_bytes(pj_media.desc.transport), pj_media.desc.port_count, frozenlist([_pj_str_to_bytes(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_bytes(pj_conn.addr), _pj_str_to_bytes(pj_conn.net_type), _pj_str_to_bytes(pj_conn.addr_type)) cdef FrozenSDPConnection FrozenSDPConnection_create(pjmedia_sdp_conn *pj_conn): return FrozenSDPConnection(_pj_str_to_bytes(pj_conn.addr), _pj_str_to_bytes(pj_conn.net_type), _pj_str_to_bytes(pj_conn.addr_type)) cdef SDPAttribute SDPAttribute_create(pjmedia_sdp_attr *pj_attr): return SDPAttribute(_pj_str_to_bytes(pj_attr.name), _pj_str_to_bytes(pj_attr.value)) cdef FrozenSDPAttribute FrozenSDPAttribute_create(pjmedia_sdp_attr *pj_attr): return FrozenSDPAttribute(_pj_str_to_bytes(pj_attr.name), _pj_str_to_bytes(pj_attr.value)) cdef SDPBandwidthInfo SDPBandwidthInfo_create(pjmedia_sdp_bandw *pj_bandw): return SDPBandwidthInfo(_pj_str_to_bytes(pj_bandw.modifier), int(pj_bandw.value)) cdef FrozenSDPBandwidthInfo FrozenSDPBandwidthInfo_create(pjmedia_sdp_bandw *pj_bandw): return FrozenSDPBandwidthInfo(_pj_str_to_bytes(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)