diff --git a/sipsimple/core/_core.lib.pxi b/sipsimple/core/_core.lib.pxi index 0c2632d5..fa336155 100644 --- a/sipsimple/core/_core.lib.pxi +++ b/sipsimple/core/_core.lib.pxi @@ -1,551 +1,552 @@ import sys # classes cdef class PJLIB: def __cinit__(self): cdef int status status = pj_init() if status != 0: raise PJSIPError("Could not initialize PJLIB", status) self._init_done = 1 status = pjlib_util_init() if status != 0: raise PJSIPError("Could not initialize PJLIB-UTIL", status) status = pjnath_init() if status != 0: raise PJSIPError("Could not initialize PJNATH", status) def __dealloc__(self): if self._init_done: with nogil: pj_shutdown() cdef class PJCachingPool: def __cinit__(self): pj_caching_pool_init(&self._obj, &pj_pool_factory_default_policy, 0) self._init_done = 1 def __dealloc__(self): if self._init_done: pj_caching_pool_destroy(&self._obj) cdef class PJSIPEndpoint: def __cinit__(self, PJCachingPool caching_pool, ip_address, udp_port, tcp_port, tls_port, tls_verify_server, tls_ca_file, tls_cert_file, tls_privkey_file, int tls_timeout): cdef pj_dns_resolver *resolver cdef pjsip_tpmgr *tpmgr cdef int status if ip_address is not None and not _is_valid_ip(pj_AF_INET(), ip_address.encode()): raise ValueError("Not a valid IPv4 address: %s" % ip_address) self._local_ip_used = ip_address status = pjsip_endpt_create(&caching_pool._obj.factory, "core", &self._obj) if status != 0: raise PJSIPError("Could not initialize PJSIP endpoint", status) self._pool = pjsip_endpt_create_pool(self._obj, "PJSIPEndpoint", 4096, 4096) if self._pool == NULL: raise SIPCoreError("Could not allocate memory pool") status = pjsip_tsx_layer_init_module(self._obj) if status != 0: raise PJSIPError("Could not initialize transaction layer module", status) status = pjsip_ua_init_module(self._obj, NULL) # TODO: handle forking if status != 0: raise PJSIPError("Could not initialize common dialog layer module", status) status = pjsip_evsub_init_module(self._obj) if status != 0: raise PJSIPError("Could not initialize event subscription module", status) status = pjsip_100rel_init_module(self._obj) if status != 0: raise PJSIPError("Could not initialize 100rel module", status) status = pjsip_replaces_init_module(self._obj) if status != 0: raise PJSIPError("Could not initialize replaces module", status) status = pjsip_inv_usage_init(self._obj, &_inv_cb) if status != 0: raise PJSIPError("Could not initialize invitation module", status) status = pjsip_endpt_create_resolver(self._obj, &resolver) if status != 0: raise PJSIPError("Could not create fake DNS resolver for endpoint", status) status = pjsip_endpt_set_resolver(self._obj, resolver) if status != 0: raise PJSIPError("Could not set fake DNS resolver on endpoint", status) tpmgr = pjsip_endpt_get_tpmgr(self._obj) if tpmgr == NULL: raise SIPCoreError("Could not get the transport manager") status = pjsip_tpmgr_set_state_cb(tpmgr, _transport_state_cb) if status != 0: raise PJSIPError("Could not set transport state callback", status) if udp_port is not None: self._start_udp_transport(udp_port) if tcp_port is not None: self._start_tcp_transport(tcp_port) self._tls_verify_server = int(tls_verify_server) if tls_ca_file is not None: self._tls_ca_file = PJSTR(tls_ca_file.encode(sys.getfilesystemencoding())) if tls_cert_file is not None: self._tls_cert_file = PJSTR(tls_cert_file.encode(sys.getfilesystemencoding())) if tls_privkey_file is not None: self._tls_privkey_file = PJSTR(tls_privkey_file.encode(sys.getfilesystemencoding())) if tls_timeout < 0: raise ValueError("Invalid TLS timeout value: %d" % tls_timeout) self._tls_timeout = tls_timeout if tls_port is not None: self._start_tls_transport(tls_port) cdef int _make_local_addr(self, pj_sockaddr_in *local_addr, object ip_address, int port) except -1: cdef pj_str_t local_ip_pj cdef pj_str_t *local_ip_p = NULL cdef int status if not (0 <= port <= 65535): raise SIPCoreError("Invalid port: %d" % port) if ip_address is not None and ip_address is not "0.0.0.0": local_ip_p = &local_ip_pj _str_to_pj_str(ip_address.encode(), local_ip_p) status = pj_sockaddr_in_init(local_addr, local_ip_p, port) if status != 0: raise PJSIPError("Could not create local address", status) return 0 cdef int _start_udp_transport(self, int port) except -1: cdef pj_sockaddr_in local_addr self._make_local_addr(&local_addr, self._local_ip_used, port) status = pjsip_udp_transport_start(self._obj, &local_addr, NULL, 1, &self._udp_transport) if status != 0: raise PJSIPError("Could not create UDP transport", status) return 0 cdef int _stop_udp_transport(self) except -1: pjsip_transport_shutdown(self._udp_transport) self._udp_transport = NULL return 0 cdef int _start_tcp_transport(self, int port) except -1: cdef pj_sockaddr_in local_addr self._make_local_addr(&local_addr, self._local_ip_used, port) status = pjsip_tcp_transport_start2(self._obj, &local_addr, NULL, 1, &self._tcp_transport) if status != 0: raise PJSIPError("Could not create TCP transport", status) return 0 cdef int _stop_tcp_transport(self) except -1: self._tcp_transport.destroy(self._tcp_transport) self._tcp_transport = NULL return 0 cdef int _start_tls_transport(self, port) except -1: cdef pj_sockaddr_in local_addr cdef pjsip_tls_setting tls_setting self._make_local_addr(&local_addr, self._local_ip_used, port) pjsip_tls_setting_default(&tls_setting) # The following value needs to be reasonably low, as TLS negotiation hogs the PJSIP polling loop tls_setting.timeout.sec = self._tls_timeout / 1000 tls_setting.timeout.msec = self._tls_timeout % 1000 if self._tls_ca_file is not None: tls_setting.ca_list_file = self._tls_ca_file.pj_str if self._tls_cert_file is not None: tls_setting.cert_file = self._tls_cert_file.pj_str if self._tls_privkey_file is not None: tls_setting.privkey_file = self._tls_privkey_file.pj_str tls_setting.method = PJSIP_SSLV23_METHOD tls_setting.verify_server = self._tls_verify_server status = pjsip_tls_transport_start(self._obj, &tls_setting, &local_addr, NULL, 1, &self._tls_transport) if status in (PJSIP_TLS_EUNKNOWN, PJSIP_TLS_EINVMETHOD, PJSIP_TLS_ECACERT, PJSIP_TLS_ECERTFILE, PJSIP_TLS_EKEYFILE, PJSIP_TLS_ECIPHER, PJSIP_TLS_ECTX): raise PJSIPTLSError("Could not create TLS transport", status) elif status != 0: raise PJSIPError("Could not create TLS transport", status) return 0 cdef int _stop_tls_transport(self) except -1: self._tls_transport.destroy(self._tls_transport) self._tls_transport = NULL return 0 cdef int _set_dns_nameservers(self, list servers) except -1: cdef int num_servers = len(servers) cdef pj_str_t *pj_servers cdef int status cdef pj_dns_resolver *resolver if num_servers == 0: return 0 resolver = pjsip_endpt_get_resolver(self._obj) if resolver == NULL: raise SIPCoreError("Could not get DNS resolver on endpoint") pj_servers = malloc(sizeof(pj_str_t)*num_servers) if pj_servers == NULL: raise MemoryError() for i, ns in enumerate(servers): _str_to_pj_str(ns.encode(), &pj_servers[i]) status = pj_dns_resolver_set_ns(resolver, num_servers, pj_servers, NULL) free(pj_servers) if status != 0: raise PJSIPError("Could not set nameservers on DNS resolver", status) return 0 def __dealloc__(self): cdef pjsip_tpmgr *tpmgr tpmgr = pjsip_endpt_get_tpmgr(self._obj) if tpmgr != NULL: pjsip_tpmgr_set_state_cb(tpmgr, NULL) if self._udp_transport != NULL: self._stop_udp_transport() if self._tcp_transport != NULL: self._stop_tcp_transport() if self._tls_transport != NULL: self._stop_tls_transport() if self._pool != NULL: pjsip_endpt_release_pool(self._obj, self._pool) if self._obj != NULL: with nogil: pjsip_endpt_destroy(self._obj) cdef class PJMEDIAEndpoint: def __cinit__(self, PJCachingPool caching_pool): cdef int status status = pjmedia_endpt_create(&caching_pool._obj.factory, NULL, 1, &self._obj) if status != 0: raise PJSIPError("Could not create PJMEDIA endpoint", status) self._pool = pjmedia_endpt_create_pool(self._obj, "PJMEDIAEndpoint", 4096, 4096) if self._pool == NULL: raise SIPCoreError("Could not allocate memory pool") self._audio_subsystem_init(caching_pool) self._video_subsystem_init(caching_pool) def __dealloc__(self): self._audio_subsystem_shutdown() self._video_subsystem_shutdown() if self._pool != NULL: pj_pool_release(self._pool) if self._obj != NULL: with nogil: pjmedia_endpt_destroy(self._obj) cdef void _audio_subsystem_init(self, PJCachingPool caching_pool): cdef int status cdef pjmedia_audio_codec_config audio_codec_cfg pjmedia_audio_codec_config_default(&audio_codec_cfg) audio_codec_cfg.speex.option = PJMEDIA_SPEEX_NO_NB audio_codec_cfg.ilbc.mode = 30 status = pjmedia_codec_register_audio_codecs(self._obj, &audio_codec_cfg) if status != 0: raise PJSIPError("Could not initialize audio codecs", status) self._has_audio_codecs = 1 cdef void _audio_subsystem_shutdown(self): pass cdef void _video_subsystem_init(self, PJCachingPool caching_pool): cdef int status status = pjmedia_video_format_mgr_create(self._pool, 64, 0, NULL) if status != 0: raise PJSIPError("Could not initialize video format manager", status) status = pjmedia_converter_mgr_create(self._pool, NULL) if status != 0: raise PJSIPError("Could not initialize converter manager", status) status = pjmedia_event_mgr_create(self._pool, 0, NULL) if status != 0: raise PJSIPError("Could not initialize event manager", status) status = pjmedia_vid_codec_mgr_create(self._pool, NULL) if status != 0: raise PJSIPError("Could not initialize video codec manager", status) status = pjmedia_codec_ffmpeg_vid_init(NULL, &caching_pool._obj.factory) if status != 0: raise PJSIPError("Could not initialize ffmpeg video codecs", status) self._has_ffmpeg_video = 1 status = pjmedia_codec_vpx_init(NULL, &caching_pool._obj.factory) if status != 0: raise PJSIPError("Could not initialize vpx video codecs", status) self._has_vpx = 1 status = pjmedia_vid_dev_subsys_init(&caching_pool._obj.factory) if status != 0: raise PJSIPError("Could not initialize video subsystem", status) self._has_video = 1 cdef void _video_subsystem_shutdown(self): if self._has_video: pjmedia_vid_dev_subsys_shutdown() if self._has_ffmpeg_video: pjmedia_codec_ffmpeg_vid_deinit() if self._has_vpx: pjmedia_codec_vpx_deinit() if pjmedia_vid_codec_mgr_instance() != NULL: pjmedia_vid_codec_mgr_destroy(NULL) if pjmedia_event_mgr_instance() != NULL: pjmedia_event_mgr_destroy(NULL) if pjmedia_converter_mgr_instance() != NULL: pjmedia_converter_mgr_destroy(NULL) if pjmedia_video_format_mgr_instance() != NULL: pjmedia_video_format_mgr_destroy(NULL) cdef list _get_codecs(self): cdef unsigned int count = PJMEDIA_CODEC_MGR_MAX_CODECS cdef pjmedia_codec_info info[PJMEDIA_CODEC_MGR_MAX_CODECS] cdef unsigned int prio[PJMEDIA_CODEC_MGR_MAX_CODECS] cdef int i cdef list retval cdef int status status = pjmedia_codec_mgr_enum_codecs(pjmedia_endpt_get_codec_mgr(self._obj), &count, info, prio) if status != 0: raise PJSIPError("Could not get available codecs", status) retval = list() for i from 0 <= i < count: retval.append((prio[i], _pj_str_to_bytes(info[i].encoding_name), info[i].channel_cnt, info[i].clock_rate)) return retval cdef list _get_all_codecs(self): cdef list codecs cdef tuple codec_data codecs = self._get_codecs() return list(set([codec_data[1] for codec_data in codecs])) cdef list _get_current_codecs(self): cdef list codecs cdef tuple codec_data cdef list retval codecs = [codec_data for codec_data in self._get_codecs() if codec_data[0] > 0] codecs.sort(reverse=True) retval = list(set([codec_data[1] for codec_data in codecs])) return retval cdef int _set_codecs(self, list req_codecs) except -1: cdef object new_codecs cdef object all_codecs cdef object codec_set cdef list codecs cdef tuple codec_data cdef object codec cdef int sample_rate cdef int channel_count cdef object codec_name cdef int prio cdef list codec_prio cdef pj_str_t codec_pj new_codecs = set(req_codecs) if len(new_codecs) != len(req_codecs): raise ValueError("Requested codec list contains doubles") all_codecs = set(self._get_all_codecs()) codec_set = new_codecs.difference(all_codecs) if len(codec_set) > 0: raise SIPCoreError("Unknown codec(s): %s" % ", ".join(codec_set)) # reverse the codec data tuples so that we can easily sort on sample rate # to make sure that bigger sample rates get higher priority codecs = [list(reversed(codec_data)) for codec_data in self._get_codecs()] codecs.sort(reverse=True) codec_prio = list() for codec in req_codecs: for sample_rate, channel_count, codec_name, prio in codecs: if codec == codec_name and channel_count == 1: codec_prio.append("%s/%d/%d" % (codec_name.decode(), sample_rate, channel_count)) for prio, codec in enumerate(reversed(codec_prio)): _str_to_pj_str(codec.encode(), &codec_pj) status = pjmedia_codec_mgr_set_codec_priority(pjmedia_endpt_get_codec_mgr(self._obj), &codec_pj, prio + 1) if status != 0: raise PJSIPError("Could not set codec priority", status) for sample_rate, channel_count, codec_name, prio in codecs: if codec_name not in req_codecs or channel_count > 1: codec = "%s/%d/%d" % (codec_name.decode(), sample_rate, channel_count) _str_to_pj_str(codec.encode(), &codec_pj) status = pjmedia_codec_mgr_set_codec_priority(pjmedia_endpt_get_codec_mgr(self._obj), &codec_pj, 0) if status != 0: raise PJSIPError("Could not set codec priority", status) return 0 cdef list _get_video_codecs(self): cdef unsigned int count = PJMEDIA_VID_CODEC_MGR_MAX_CODECS cdef pjmedia_vid_codec_info info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef unsigned int prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef int i cdef list retval cdef int status status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio) if status != 0: raise PJSIPError("Could not get available video codecs", status) retval = list() for i from 0 <= i < count: if info[i].packings & PJMEDIA_VID_PACKING_PACKETS: retval.append((prio[i], _pj_str_to_bytes(info[i].encoding_name), info[i].pt)) return retval cdef list _get_all_video_codecs(self): cdef list codecs cdef tuple codec_data codecs = self._get_video_codecs() return list(set([codec_data[1] for codec_data in codecs])) cdef list _get_current_video_codecs(self): cdef list codecs cdef tuple codec_data cdef list retval codecs = [codec_data for codec_data in self._get_video_codecs() if codec_data[0] > 0] codecs.sort(reverse=True) retval = list(set([codec_data[1] for codec_data in codecs])) return retval cdef int _set_video_codecs(self, list req_codecs) except -1: cdef object new_codecs cdef object codec_set cdef list codecs cdef tuple codec_data cdef object codec cdef int payload_type cdef object codec_name cdef int prio cdef list codec_prio cdef pj_str_t codec_pj new_codecs = set(req_codecs) if len(new_codecs) != len(req_codecs): raise ValueError("Requested video codec list contains doubles") codec_set = new_codecs.difference(set(self._get_all_video_codecs())) if len(codec_set) > 0: raise SIPCoreError("Unknown video codec(s): %s" % ", ".join(codec_set)) codecs = self._get_video_codecs() codec_prio = list() for codec in req_codecs: for prio, codec_name, payload_type in codecs: if codec == codec_name: codec_prio.append("%s/%d" % (codec_name.decode(), payload_type)) for prio, codec in enumerate(reversed(codec_prio)): _str_to_pj_str(codec.encode(), &codec_pj) status = pjmedia_vid_codec_mgr_set_codec_priority(NULL, &codec_pj, prio + 1) if status != 0: raise PJSIPError("Could not set video codec priority", status) for prio, codec_name, payload_type in codecs: if codec_name not in req_codecs: codec = "%s/%d" % (codec_name.decode(), payload_type) _str_to_pj_str(codec.encode(), &codec_pj) status = pjmedia_vid_codec_mgr_set_codec_priority(NULL, &codec_pj, 0) if status != 0: raise PJSIPError("Could not set video codec priority", status) return 0 cdef void _set_h264_options(self, object profile, int level): global h264_profiles_map, h264_profile_level_id, h264_packetization_mode cdef unsigned int count = PJMEDIA_VID_CODEC_MGR_MAX_CODECS cdef pjmedia_vid_codec_info info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef pjmedia_vid_codec_param vparam cdef unsigned int prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef int i cdef int status cdef PJSTR h264_profile_level_id_value cdef PJSTR h264_packetization_mode_value = PJSTR(b"1") # TODO; make it configurable? try: profile_n = h264_profiles_map[profile] except KeyError: raise ValueError("invalid profile specified: %s" % profile) h264_profile_level_id_value = PJSTR(b"%xe0%x" % (profile_n, level)) # use common subset (e0) status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio) if status != 0: raise PJSIPError("Could not get available video codecs", status) for i from 0 <= i < count: if not (info[i].packings & PJMEDIA_VID_PACKING_PACKETS): continue if _pj_str_to_bytes(info[i].encoding_name) != b'H264': continue status = pjmedia_vid_codec_mgr_get_default_param(NULL, &info[i], &vparam) if status != 0: continue # 2 format parameters are currently defined for H264: profile-level-id and packetization-mode vparam.dec_fmtp.param[0].name = h264_profile_level_id.pj_str vparam.dec_fmtp.param[0].val = h264_profile_level_id_value.pj_str vparam.dec_fmtp.param[1].name = h264_packetization_mode.pj_str vparam.dec_fmtp.param[1].val = h264_packetization_mode_value.pj_str vparam.dec_fmtp.cnt = 2 status = pjmedia_vid_codec_mgr_set_default_param(NULL, &info[i], &vparam) if status != 0: raise PJSIPError("Could not set H264 options", status) cdef void _set_video_options(self, tuple max_resolution, int max_framerate, float max_bitrate): cdef unsigned int count = PJMEDIA_VID_CODEC_MGR_MAX_CODECS cdef pjmedia_vid_codec_info info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef pjmedia_vid_codec_param vparam cdef unsigned int prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] cdef int i cdef int status max_width, max_height = max_resolution status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio) if status != 0: raise PJSIPError("Could not get available video codecs", status) for i from 0 <= i < count: if not (info[i].packings & PJMEDIA_VID_PACKING_PACKETS): continue status = pjmedia_vid_codec_mgr_get_default_param(NULL, &info[i], &vparam) if status != 0: continue # Max resolution vparam.enc_fmt.det.vid.size.w = max_width vparam.enc_fmt.det.vid.size.h = max_height vparam.dec_fmt.det.vid.size.w = max_width vparam.dec_fmt.det.vid.size.h = max_height # Max framerate vparam.enc_fmt.det.vid.fps.num = max_framerate vparam.enc_fmt.det.vid.fps.denum = 1 vparam.dec_fmt.det.vid.fps.num = 10 vparam.dec_fmt.det.vid.fps.denum = 1 # Average and max bitrate (set to 0 for 'unlimited') vparam.enc_fmt.det.vid.avg_bps = int(max_bitrate * 1e6) vparam.enc_fmt.det.vid.max_bps = int(max_bitrate * 1e6) vparam.dec_fmt.det.vid.avg_bps = 0 vparam.dec_fmt.det.vid.max_bps = 0 status = pjmedia_vid_codec_mgr_set_default_param(NULL, &info[i], &vparam) if status != 0: raise PJSIPError("Could not set video options", status) cdef void _transport_state_cb(pjsip_transport *tp, pjsip_transport_state state, pjsip_transport_state_info_ptr_const info) with gil: cdef PJSIPUA ua cdef str local_address cdef str remote_address cdef char buf[PJ_INET6_ADDRSTRLEN] cdef dict event_dict try: ua = _get_ua() except: return if pj_sockaddr_has_addr(&tp.local_addr): pj_sockaddr_print(&tp.local_addr, buf, 512, 0) local_address = '%s:%d' % (_buf_to_str(buf), pj_sockaddr_get_port(&tp.local_addr)) else: local_address = None + transport = tp.type_name.decode().lower() remote_address = '%s:%d' % (_pj_str_to_str(tp.remote_name.host), tp.remote_name.port) - event_dict = dict(transport=tp.type_name, local_address=local_address, remote_address=remote_address) + event_dict = dict(transport=transport, local_address=local_address, remote_address=remote_address) if state == PJSIP_TP_STATE_CONNECTED: _add_event("SIPEngineTransportDidConnect", event_dict) else: reason = _pj_status_to_str(info.status) event_dict['reason'] = reason _add_event("SIPEngineTransportDidDisconnect", event_dict) # globals cdef PJSTR h264_profile_level_id = PJSTR(b"profile-level-id") cdef PJSTR h264_packetization_mode = PJSTR(b"packetization-mode") cdef dict h264_profiles_map = dict(baseline=66, main=77, high=100) diff --git a/sipsimple/core/_core.request.pxi b/sipsimple/core/_core.request.pxi index f6e7597b..2704b827 100644 --- a/sipsimple/core/_core.request.pxi +++ b/sipsimple/core/_core.request.pxi @@ -1,505 +1,505 @@ from datetime import datetime, timedelta cdef class EndpointAddress: def __init__(self, ip, port): self.ip = ip self.port = port def __repr__(self): return "%s(%r, %r)" % (self.__class__.__name__, self.ip, self.port) def __str__(self): - return "%s:%d" % (self.ip, self.port) + return "%s:%d" % (self.ip.decode(), self.port) cdef class Request: expire_warning_time = 30 # properties property method: def __get__(self): return self._method.str property call_id: def __get__(self): return self._call_id.str property content_type: def __get__(self): if self._content_type is None: return None else: return "/".join([self._content_type.str, self._content_subtype.str]) property body: def __get__(self): if self._body is None: return None else: return self._body.str property expires_in: def __get__(self): cdef object dt self._get_ua() if self.state != "EXPIRING" or self._expire_time is None: return 0 else: dt = self._expire_time - datetime.now() return max(0, dt.seconds) # public methods def __cinit__(self, *args, **kwargs): self.state = "INIT" self.peer_address = None pj_timer_entry_init(&self._timer, 0, self, _Request_cb_timer) self._timer_active = 0 def __init__(self, method, SIPURI request_uri not None, FromHeader from_header not None, ToHeader to_header not None, RouteHeader route_header not None, Credentials credentials=None, ContactHeader contact_header=None, call_id=None, cseq=None, object extra_headers=None, content_type=None, body=None): cdef pjsip_method method_pj cdef PJSTR from_header_str cdef PJSTR to_header_str cdef PJSTR request_uri_str cdef PJSTR contact_header_str cdef pj_str_t *contact_header_pj = NULL cdef pj_str_t *call_id_pj = NULL cdef object content_type_spl cdef pjsip_hdr *hdr cdef pjsip_contact_hdr *contact_hdr cdef pjsip_cid_hdr *cid_hdr cdef pjsip_cseq_hdr *cseq_hdr cdef int status cdef dict event_dict cdef PJSIPUA ua = _get_ua() if self._tsx != NULL or self.state != "INIT": raise SIPCoreError("Request.__init__() was already called") if cseq is not None and cseq < 0: raise ValueError("cseq argument cannot be negative") if extra_headers is not None: header_names = set([header.name for header in extra_headers]) if "Route" in header_names: raise ValueError("Route should be specified with route_header argument, not extra_headers") if "Content-Type" in header_names: raise ValueError("Content-Type should be specified with content_type argument, not extra_headers") else: header_names = () if content_type is not None and body is None: raise ValueError("Cannot specify a content_type without a body") if content_type is None and body is not None: raise ValueError("Cannot specify a body without a content_type") self._method = PJSTR(method.encode()) pjsip_method_init_np(&method_pj, &self._method.pj_str) if credentials is not None: self.credentials = FrozenCredentials.new(credentials) from_header_str = PJSTR(from_header.body.encode()) self.to_header = FrozenToHeader.new(to_header) to_header_str = PJSTR(to_header.body.encode()) struri = str(request_uri) self.request_uri = FrozenSIPURI.new(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 if contact_header is not None: self.contact_header = FrozenContactHeader.new(contact_header) contact_parameters = contact_header.parameters.copy() contact_parameters.pop("q", None) contact_parameters.pop("expires", None) contact_header.parameters = {} contact_header_str = PJSTR(contact_header.body.encode()) contact_header_pj = &contact_header_str.pj_str if call_id is not None: self._call_id = PJSTR(call_id) call_id_pj = &self._call_id.pj_str if cseq is None: self.cseq = -1 else: self.cseq = cseq if extra_headers is None: self.extra_headers = frozenlist() else: self.extra_headers = frozenlist([header.frozen_type.new(header) for header in extra_headers]) if body is not None: content_type_spl = content_type.split("/", 1) self._content_type = PJSTR(content_type_spl[0].encode()) self._content_subtype = PJSTR(content_type_spl[1].encode()) self._body = PJSTR(body) status = pjsip_endpt_create_request(ua._pjsip_endpoint._obj, &method_pj, &request_uri_str.pj_str, &from_header_str.pj_str, &to_header_str.pj_str, contact_header_pj, call_id_pj, self.cseq, NULL, &self._tdata) if status != 0: raise PJSIPError("Could not create request", status) if body is not None: self._tdata.msg.body = pjsip_msg_body_create(self._tdata.pool, &self._content_type.pj_str, &self._content_subtype.pj_str, &self._body.pj_str) status = _BaseRouteHeader_to_pjsip_route_hdr(self.route_header, &self._route_header, self._tdata.pool) pjsip_msg_add_hdr(self._tdata.msg, &self._route_header) hdr = ( &self._tdata.msg.hdr).next while hdr != &self._tdata.msg.hdr: hdr_name = _pj_str_to_str(hdr.name) if hdr_name in header_names: raise ValueError("Cannot override %s header value in extra_headers" % _pj_str_to_bytes(hdr.name)) if hdr.type == PJSIP_H_CONTACT: contact_hdr = hdr _dict_to_pjsip_param(contact_parameters, &contact_hdr.other_param, self._tdata.pool) elif hdr.type == PJSIP_H_CALL_ID: cid_hdr = hdr self._call_id = PJSTR(_pj_str_to_bytes(cid_hdr.id)) elif hdr.type == PJSIP_H_CSEQ: cseq_hdr = hdr self.cseq = cseq_hdr.cseq elif hdr.type == PJSIP_H_FROM: self.from_header = FrozenFromHeader_create( hdr) else: pass hdr = ( hdr).next _add_headers_to_tdata(self._tdata, self.extra_headers) #event_dict = dict(obj=self) #_pjsip_msg_to_dict(self._tdata.msg, event_dict) #print('Request dict %s' % event_dict) if self.credentials is not None: status = pjsip_auth_clt_init(&self._auth, ua._pjsip_endpoint._obj, self._tdata.pool, 0) if status != 0: raise PJSIPError("Could not init authentication credentials", status) status = pjsip_auth_clt_set_credentials(&self._auth, 1, self.credentials.get_cred_info()) if status != 0: raise PJSIPError("Could not set authentication credentials", status) self._need_auth = 1 else: self._need_auth = 0 status = pjsip_tsx_create_uac(&ua._module, self._tdata, &self._tsx) if status != 0: raise PJSIPError("Could not create transaction for request", status) self._tsx.mod_data[ua._module.id] = self def __dealloc__(self): cdef PJSIPUA ua = self._get_ua() if self._tsx != NULL: self._tsx.mod_data[ua._module.id] = NULL if self._tsx.state < PJSIP_TSX_STATE_COMPLETED: pjsip_tsx_terminate(self._tsx, 500) self._tsx = NULL if self._tdata != NULL: pjsip_tx_data_dec_ref(self._tdata) self._tdata = NULL if self._timer_active: pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._timer) self._timer_active = 0 def send(self, timeout=None): cdef pj_time_val timeout_pj cdef int status cdef PJSIPUA ua = self._get_ua() if self.state != "INIT": raise SIPCoreError('This method may only be called in the "INIT" state, current state is "%s"' % self.state) if timeout is not None: if timeout <= 0: raise ValueError("Timeout value cannot be negative") timeout_pj.sec = int(timeout) timeout_pj.msec = (timeout * 1000) % 1000 self._timeout = timeout status = pjsip_tsx_send_msg(self._tsx, self._tdata) if status != 0: raise PJSIPError("Could not send request", status) pjsip_tx_data_add_ref(self._tdata) if timeout: status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._timer, &timeout_pj) if status == 0: self._timer_active = 1 self.state = "IN_PROGRESS" def end(self): cdef PJSIPUA ua = self._get_ua() if self.state == "IN_PROGRESS": pjsip_tsx_terminate(self._tsx, 408) elif self.state == "EXPIRING": pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._timer) self._timer_active = 0 self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) # private methods cdef PJSIPUA _get_ua(self): cdef PJSIPUA ua try: ua = _get_ua() except SIPCoreError: self._tsx = NULL self._tdata = NULL self._timer_active = 0 self.state = "TERMINATED" return None else: return ua cdef int _cb_tsx_state(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: cdef pjsip_tx_data *tdata_auth cdef pjsip_transaction *tsx_auth cdef pjsip_cseq_hdr *cseq cdef dict event_dict cdef int expires = -1 cdef SIPURI contact_uri cdef dict contact_params cdef pj_time_val timeout_pj cdef int status if rdata != NULL: self.to_header = FrozenToHeader_create(rdata.msg_info.to_hdr) if self.peer_address is None: self.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) else: self.peer_address.ip = rdata.pkt_info.src_name self.peer_address.port = rdata.pkt_info.src_port if self._tsx.state == PJSIP_TSX_STATE_PROCEEDING: if rdata == NULL: return 0 event_dict = dict(obj=self) _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) _add_event("SIPRequestGotProvisionalResponse", event_dict) elif self._tsx.state == PJSIP_TSX_STATE_COMPLETED: if self._timer_active: pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._timer) self._timer_active = 0 if self._need_auth and self._tsx.status_code in [401, 407]: self._need_auth = 0 status = pjsip_auth_clt_reinit_req(&self._auth, rdata, self._tdata, &tdata_auth) if status != 0: _add_event("SIPRequestDidFail", dict(obj=self, code=0, reason="Could not add auth data to request %s" % _pj_status_to_str(status))) self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) return 0 cseq = pjsip_msg_find_hdr(tdata_auth.msg, PJSIP_H_CSEQ, NULL) if cseq != NULL: cseq.cseq += 1 self.cseq = cseq.cseq status = pjsip_tsx_create_uac(&ua._module, tdata_auth, &tsx_auth) if status != 0: pjsip_tx_data_dec_ref(tdata_auth) _add_event("SIPRequestDidFail", dict(obj=self, code=0, reason="Could not create transaction for request with auth %s" % _pj_status_to_str(status))) self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) return 0 self._tsx.mod_data[ua._module.id] = NULL self._tsx = tsx_auth self._tsx.mod_data[ua._module.id] = self status = pjsip_tsx_send_msg(self._tsx, tdata_auth) if status != 0: pjsip_tx_data_dec_ref(tdata_auth) _add_event("SIPRequestDidFail", dict(obj=self, code=0, reason="Could not send request with auth %s" % _pj_status_to_str(status))) self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) return 0 elif self._timeout is not None: timeout_pj.sec = int(self._timeout) timeout_pj.msec = (self._timeout * 1000) % 1000 status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._timer, &timeout_pj) if status == 0: self._timer_active = 1 else: event_dict = dict(obj=self) if rdata != NULL: # This shouldn't happen, but safety fist! _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) if self._tsx.status_code / 100 == 2: if rdata != NULL: if "Expires" in event_dict["headers"]: expires = event_dict["headers"]["Expires"] elif self.contact_header is not None: for contact_header in event_dict["headers"].get("Contact", []): if contact_header.uri == self.contact_header.uri and contact_header.expires is not None: expires = contact_header.expires if expires == -1: expires = 0 for header in self.extra_headers: if header.name == "Expires": try: expires = int(header.body) except ValueError: pass break event_dict["expires"] = expires self._expire_time = datetime.now() + timedelta(seconds=expires) _add_event("SIPRequestDidSucceed", event_dict) else: expires = 0 _add_event("SIPRequestDidFail", event_dict) if expires == 0: self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) else: timeout_pj.sec = max(1, expires - self.expire_warning_time, expires/2) timeout_pj.msec = 0 status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._timer, &timeout_pj) if status == 0: self._timer_active = 1 self.state = "EXPIRING" self._expire_rest = max(1, expires - timeout_pj.sec) else: self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) elif self._tsx.state == PJSIP_TSX_STATE_TERMINATED: if self.state == "IN_PROGRESS": if self._timer_active: pjsip_endpt_cancel_timer(ua._pjsip_endpoint._obj, &self._timer) self._timer_active = 0 _add_event("SIPRequestDidFail", dict(obj=self, code=self._tsx.status_code, reason=_pj_str_to_bytes(self._tsx.status_text))) self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) self._tsx.mod_data[ua._module.id] = NULL self._tsx = NULL else: pass cdef int _cb_timer(self, PJSIPUA ua) except -1: cdef pj_time_val expires cdef int status if self.state == "IN_PROGRESS": pjsip_tsx_terminate(self._tsx, 408) elif self.state == "EXPIRING": if self._expire_rest > 0: _add_event("SIPRequestWillExpire", dict(obj=self, expires=self._expire_rest)) expires.sec = self._expire_rest expires.msec = 0 self._expire_rest = 0 status = pjsip_endpt_schedule_timer(ua._pjsip_endpoint._obj, &self._timer, &expires) if status == 0: self._timer_active = 1 else: self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) else: self.state = "TERMINATED" _add_event("SIPRequestDidEnd", dict(obj=self)) return 0 cdef class IncomingRequest: def __cinit__(self, *args, **kwargs): self.peer_address = None def __dealloc__(self): cdef PJSIPUA ua try: ua = _get_ua() except SIPCoreError: return if self._tsx != NULL: pjsip_tsx_terminate(self._tsx, 500) self._tsx = NULL if self._tdata != NULL: pjsip_tx_data_dec_ref(self._tdata) self._tdata = NULL def answer(self, int code, str reason=None, object extra_headers=None): cdef bytes reason_bytes cdef dict event_dict cdef int status cdef PJSIPUA ua = _get_ua() if self.state != "incoming": raise SIPCoreInvalidStateError('Can only answer an incoming request in the "incoming" state, ' 'object is currently in the "%s" state' % self.state) if code < 200 or code >= 700: raise ValueError("Invalid SIP final response code: %d" % code) self._tdata.msg.line.status.code = code if reason is None: self._tdata.msg.line.status.reason = pjsip_get_status_text(code)[0] else: pj_strdup2_with_null(self._tdata.pool, &self._tdata.msg.line.status.reason, reason.encode()) if extra_headers is not None: _add_headers_to_tdata(self._tdata, extra_headers) event_dict = dict(obj=self) _pjsip_msg_to_dict(self._tdata.msg, event_dict) status = pjsip_tsx_send_msg(self._tsx, self._tdata) if status != 0: raise PJSIPError("Could not send response", status) self.state = "answered" self._tdata = NULL self._tsx = NULL _add_event("SIPIncomingRequestSentResponse", event_dict) cdef int init(self, PJSIPUA ua, pjsip_rx_data *rdata) except -1: cdef dict event_dict cdef int status status = pjsip_endpt_create_response(ua._pjsip_endpoint._obj, rdata, 500, NULL, &self._tdata) if status != 0: raise PJSIPError("Could not create response", status) status = pjsip_tsx_create_uas(&ua._module, rdata, &self._tsx) if status != 0: pjsip_tx_data_dec_ref(self._tdata) self._tdata = NULL raise PJSIPError("Could not create transaction for incoming request", status) pjsip_tsx_recv_msg(self._tsx, rdata) self.state = "incoming" self.peer_address = EndpointAddress(rdata.pkt_info.src_name, rdata.pkt_info.src_port) event_dict = dict(obj=self) _pjsip_msg_to_dict(rdata.msg_info.msg, event_dict) _add_event("SIPIncomingRequestGotRequest", event_dict) # callback functions cdef void _Request_cb_tsx_state(pjsip_transaction *tsx, pjsip_event *event) with gil: cdef PJSIPUA ua cdef void *req_ptr cdef Request req cdef pjsip_rx_data *rdata = NULL try: ua = _get_ua() except: return try: req_ptr = tsx.mod_data[ua._module.id] if req_ptr != NULL: req = req_ptr if event.type == PJSIP_EVENT_RX_MSG: rdata = event.body.rx_msg.rdata elif event.type == PJSIP_EVENT_TSX_STATE and event.body.tsx_state.type == PJSIP_EVENT_RX_MSG: rdata = event.body.tsx_state.src.rdata req._cb_tsx_state(ua, rdata) except: ua._handle_exception(1) cdef void _Request_cb_timer(pj_timer_heap_t *timer_heap, pj_timer_entry *entry) with gil: cdef PJSIPUA ua cdef Request req try: ua = _get_ua() except: return try: if entry.user_data != NULL: req = entry.user_data req._timer_active = 0 req._cb_timer(ua) except: ua._handle_exception(1)