diff options
Diffstat (limited to 'pjsip/src/pjsua2/endpoint.cpp')
-rw-r--r-- | pjsip/src/pjsua2/endpoint.cpp | 1612 |
1 files changed, 1612 insertions, 0 deletions
diff --git a/pjsip/src/pjsua2/endpoint.cpp b/pjsip/src/pjsua2/endpoint.cpp new file mode 100644 index 00000000..d3ba3719 --- /dev/null +++ b/pjsip/src/pjsua2/endpoint.cpp @@ -0,0 +1,1612 @@ +/* $Id$ */ +/* + * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua2/endpoint.hpp> +#include <pjsua2/account.hpp> +#include <pjsua2/call.hpp> +#include <pjsua2/presence.hpp> +#include <algorithm> +#include "util.hpp" + +using namespace pj; +using namespace std; + +#include <pjsua2/account.hpp> +#include <pjsua2/call.hpp> + +#define THIS_FILE "endpoint.cpp" +#define MAX_STUN_SERVERS 32 +#define TIMER_SIGNATURE 0x600D878A +#define MAX_CODEC_NUM 64 + +struct UserTimer +{ + pj_uint32_t signature; + OnTimerParam prm; + pj_timer_entry entry; +}; + +Endpoint *Endpoint::instance_; + +/////////////////////////////////////////////////////////////////////////////// + +UaConfig::UaConfig() +{ + pjsua_config ua_cfg; + + pjsua_config_default(&ua_cfg); + fromPj(ua_cfg); +} + +void UaConfig::fromPj(const pjsua_config &ua_cfg) +{ + unsigned i; + + this->maxCalls = ua_cfg.max_calls; + this->threadCnt = ua_cfg.thread_cnt; + this->userAgent = pj2Str(ua_cfg.user_agent); + + for (i=0; i<ua_cfg.nameserver_count; ++i) { + this->nameserver.push_back(pj2Str(ua_cfg.nameserver[i])); + } + + for (i=0; i<ua_cfg.stun_srv_cnt; ++i) { + this->stunServer.push_back(pj2Str(ua_cfg.stun_srv[i])); + } + + this->stunIgnoreFailure = PJ2BOOL(ua_cfg.stun_ignore_failure); + this->natTypeInSdp = ua_cfg.nat_type_in_sdp; + this->mwiUnsolicitedEnabled = PJ2BOOL(ua_cfg.enable_unsolicited_mwi); +} + +pjsua_config UaConfig::toPj() const +{ + unsigned i; + pjsua_config pua_cfg; + + pjsua_config_default(&pua_cfg); + + pua_cfg.max_calls = this->maxCalls; + pua_cfg.thread_cnt = this->threadCnt; + pua_cfg.user_agent = str2Pj(this->userAgent); + + for (i=0; i<this->nameserver.size() && i<PJ_ARRAY_SIZE(pua_cfg.nameserver); + ++i) + { + pua_cfg.nameserver[i] = str2Pj(this->nameserver[i]); + } + pua_cfg.nameserver_count = i; + + for (i=0; i<this->stunServer.size() && i<PJ_ARRAY_SIZE(pua_cfg.stun_srv); + ++i) + { + pua_cfg.stun_srv[i] = str2Pj(this->stunServer[i]); + } + pua_cfg.stun_srv_cnt = i; + + pua_cfg.nat_type_in_sdp = this->natTypeInSdp; + pua_cfg.enable_unsolicited_mwi = this->mwiUnsolicitedEnabled; + + return pua_cfg; +} + +void UaConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("UaConfig"); + + NODE_READ_UNSIGNED( this_node, maxCalls); + NODE_READ_UNSIGNED( this_node, threadCnt); + NODE_READ_BOOL ( this_node, mainThreadOnly); + NODE_READ_STRINGV ( this_node, nameserver); + NODE_READ_STRING ( this_node, userAgent); + NODE_READ_STRINGV ( this_node, stunServer); + NODE_READ_BOOL ( this_node, stunIgnoreFailure); + NODE_READ_INT ( this_node, natTypeInSdp); + NODE_READ_BOOL ( this_node, mwiUnsolicitedEnabled); +} + +void UaConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("UaConfig"); + + NODE_WRITE_UNSIGNED( this_node, maxCalls); + NODE_WRITE_UNSIGNED( this_node, threadCnt); + NODE_WRITE_BOOL ( this_node, mainThreadOnly); + NODE_WRITE_STRINGV ( this_node, nameserver); + NODE_WRITE_STRING ( this_node, userAgent); + NODE_WRITE_STRINGV ( this_node, stunServer); + NODE_WRITE_BOOL ( this_node, stunIgnoreFailure); + NODE_WRITE_INT ( this_node, natTypeInSdp); + NODE_WRITE_BOOL ( this_node, mwiUnsolicitedEnabled); +} + +/////////////////////////////////////////////////////////////////////////////// + +LogConfig::LogConfig() +{ + pjsua_logging_config lc; + + pjsua_logging_config_default(&lc); + fromPj(lc); +} + +void LogConfig::fromPj(const pjsua_logging_config &lc) +{ + this->msgLogging = lc.msg_logging; + this->level = lc.level; + this->consoleLevel = lc.console_level; + this->decor = lc.decor; + this->filename = pj2Str(lc.log_filename); + this->fileFlags = lc.log_file_flags; + this->writer = NULL; +} + +pjsua_logging_config LogConfig::toPj() const +{ + pjsua_logging_config lc; + + pjsua_logging_config_default(&lc); + + lc.msg_logging = this->msgLogging; + lc.level = this->level; + lc.console_level = this->consoleLevel; + lc.decor = this->decor; + lc.log_file_flags = this->fileFlags; + lc.log_filename = str2Pj(this->filename); + + return lc; +} + +void LogConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("LogConfig"); + + NODE_READ_UNSIGNED( this_node, msgLogging); + NODE_READ_UNSIGNED( this_node, level); + NODE_READ_UNSIGNED( this_node, consoleLevel); + NODE_READ_UNSIGNED( this_node, decor); + NODE_READ_STRING ( this_node, filename); + NODE_READ_UNSIGNED( this_node, fileFlags); +} + +void LogConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("LogConfig"); + + NODE_WRITE_UNSIGNED( this_node, msgLogging); + NODE_WRITE_UNSIGNED( this_node, level); + NODE_WRITE_UNSIGNED( this_node, consoleLevel); + NODE_WRITE_UNSIGNED( this_node, decor); + NODE_WRITE_STRING ( this_node, filename); + NODE_WRITE_UNSIGNED( this_node, fileFlags); +} + +/////////////////////////////////////////////////////////////////////////////// + +MediaConfig::MediaConfig() +{ + pjsua_media_config mc; + + pjsua_media_config_default(&mc); + fromPj(mc); +} + +void MediaConfig::fromPj(const pjsua_media_config &mc) +{ + this->clockRate = mc.clock_rate; + this->sndClockRate = mc.snd_clock_rate; + this->channelCount = mc.channel_count; + this->audioFramePtime = mc.audio_frame_ptime; + this->maxMediaPorts = mc.max_media_ports; + this->hasIoqueue = PJ2BOOL(mc.has_ioqueue); + this->threadCnt = mc.thread_cnt; + this->quality = mc.quality; + this->ptime = mc.ptime; + this->noVad = PJ2BOOL(mc.no_vad); + this->ilbcMode = mc.ilbc_mode; + this->txDropPct = mc.tx_drop_pct; + this->rxDropPct = mc.rx_drop_pct; + this->ecOptions = mc.ec_options; + this->ecTailLen = mc.ec_tail_len; + this->sndRecLatency = mc.snd_rec_latency; + this->sndPlayLatency = mc.snd_play_latency; + this->jbInit = mc.jb_init; + this->jbMinPre = mc.jb_min_pre; + this->jbMaxPre = mc.jb_max_pre; + this->jbMax = mc.jb_max; + this->sndAutoCloseTime = mc.snd_auto_close_time; + this->vidPreviewEnableNative = PJ2BOOL(mc.vid_preview_enable_native); +} + +pjsua_media_config MediaConfig::toPj() const +{ + pjsua_media_config mcfg; + + pjsua_media_config_default(&mcfg); + + mcfg.clock_rate = this->clockRate; + mcfg.snd_clock_rate = this->sndClockRate; + mcfg.channel_count = this->channelCount; + mcfg.audio_frame_ptime = this->audioFramePtime; + mcfg.max_media_ports = this->maxMediaPorts; + mcfg.has_ioqueue = this->hasIoqueue; + mcfg.thread_cnt = this->threadCnt; + mcfg.quality = this->quality; + mcfg.ptime = this->ptime; + mcfg.no_vad = this->noVad; + mcfg.ilbc_mode = this->ilbcMode; + mcfg.tx_drop_pct = this->txDropPct; + mcfg.rx_drop_pct = this->rxDropPct; + mcfg.ec_options = this->ecOptions; + mcfg.ec_tail_len = this->ecTailLen; + mcfg.snd_rec_latency = this->sndRecLatency; + mcfg.snd_play_latency = this->sndPlayLatency; + mcfg.jb_init = this->jbInit; + mcfg.jb_min_pre = this->jbMinPre; + mcfg.jb_max_pre = this->jbMaxPre; + mcfg.jb_max = this->jbMax; + mcfg.snd_auto_close_time = this->sndAutoCloseTime; + mcfg.vid_preview_enable_native = this->vidPreviewEnableNative; + + return mcfg; +} + +void MediaConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("MediaConfig"); + + NODE_READ_UNSIGNED( this_node, clockRate); + NODE_READ_UNSIGNED( this_node, sndClockRate); + NODE_READ_UNSIGNED( this_node, channelCount); + NODE_READ_UNSIGNED( this_node, audioFramePtime); + NODE_READ_UNSIGNED( this_node, maxMediaPorts); + NODE_READ_BOOL ( this_node, hasIoqueue); + NODE_READ_UNSIGNED( this_node, threadCnt); + NODE_READ_UNSIGNED( this_node, quality); + NODE_READ_UNSIGNED( this_node, ptime); + NODE_READ_BOOL ( this_node, noVad); + NODE_READ_UNSIGNED( this_node, ilbcMode); + NODE_READ_UNSIGNED( this_node, txDropPct); + NODE_READ_UNSIGNED( this_node, rxDropPct); + NODE_READ_UNSIGNED( this_node, ecOptions); + NODE_READ_UNSIGNED( this_node, ecTailLen); + NODE_READ_UNSIGNED( this_node, sndRecLatency); + NODE_READ_UNSIGNED( this_node, sndPlayLatency); + NODE_READ_INT ( this_node, jbInit); + NODE_READ_INT ( this_node, jbMinPre); + NODE_READ_INT ( this_node, jbMaxPre); + NODE_READ_INT ( this_node, jbMax); + NODE_READ_INT ( this_node, sndAutoCloseTime); + NODE_READ_BOOL ( this_node, vidPreviewEnableNative); +} + +void MediaConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("MediaConfig"); + + NODE_WRITE_UNSIGNED( this_node, clockRate); + NODE_WRITE_UNSIGNED( this_node, sndClockRate); + NODE_WRITE_UNSIGNED( this_node, channelCount); + NODE_WRITE_UNSIGNED( this_node, audioFramePtime); + NODE_WRITE_UNSIGNED( this_node, maxMediaPorts); + NODE_WRITE_BOOL ( this_node, hasIoqueue); + NODE_WRITE_UNSIGNED( this_node, threadCnt); + NODE_WRITE_UNSIGNED( this_node, quality); + NODE_WRITE_UNSIGNED( this_node, ptime); + NODE_WRITE_BOOL ( this_node, noVad); + NODE_WRITE_UNSIGNED( this_node, ilbcMode); + NODE_WRITE_UNSIGNED( this_node, txDropPct); + NODE_WRITE_UNSIGNED( this_node, rxDropPct); + NODE_WRITE_UNSIGNED( this_node, ecOptions); + NODE_WRITE_UNSIGNED( this_node, ecTailLen); + NODE_WRITE_UNSIGNED( this_node, sndRecLatency); + NODE_WRITE_UNSIGNED( this_node, sndPlayLatency); + NODE_WRITE_INT ( this_node, jbInit); + NODE_WRITE_INT ( this_node, jbMinPre); + NODE_WRITE_INT ( this_node, jbMaxPre); + NODE_WRITE_INT ( this_node, jbMax); + NODE_WRITE_INT ( this_node, sndAutoCloseTime); + NODE_WRITE_BOOL ( this_node, vidPreviewEnableNative); +} + +/////////////////////////////////////////////////////////////////////////////// + +void EpConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("EpConfig"); + NODE_READ_OBJ( this_node, uaConfig); + NODE_READ_OBJ( this_node, logConfig); + NODE_READ_OBJ( this_node, medConfig); +} + +void EpConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("EpConfig"); + NODE_WRITE_OBJ( this_node, uaConfig); + NODE_WRITE_OBJ( this_node, logConfig); + NODE_WRITE_OBJ( this_node, medConfig); +} + +/////////////////////////////////////////////////////////////////////////////// +/* Class to post log to main thread */ +struct PendingLog : public PendingJob +{ + LogEntry entry; + virtual void execute(bool is_pending) + { + PJ_UNUSED_ARG(is_pending); + Endpoint::instance().utilLogWrite(entry); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +/* + * Endpoint instance + */ +Endpoint::Endpoint() +: writer(NULL), mainThreadOnly(false), mainThread(NULL), pendingJobSize(0) +{ + if (instance_) { + PJSUA2_RAISE_ERROR(PJ_EEXISTS); + } + + instance_ = this; +} + +Endpoint& Endpoint::instance() throw(Error) +{ + if (!instance_) { + PJSUA2_RAISE_ERROR(PJ_ENOTFOUND); + } + return *instance_; +} + +Endpoint::~Endpoint() +{ + while (!pendingJobs.empty()) { + delete pendingJobs.front(); + pendingJobs.pop_front(); + } + + while(mediaList.size() > 0) { + AudioMedia *cur_media = mediaList[0]; + delete cur_media; /* this will remove itself from the list */ + } + + clearCodecInfoList(); + + try { + libDestroy(); + } catch (Error &err) { + // Ignore + PJ_UNUSED_ARG(err); + } + + instance_ = NULL; +} + +void Endpoint::utilAddPendingJob(PendingJob *job) +{ + enum { + MAX_PENDING_JOBS = 1024 + }; + + /* See if we can execute immediately */ + if (!mainThreadOnly || pj_thread_this()==mainThread) { + job->execute(false); + delete job; + return; + } + + if (pendingJobSize > MAX_PENDING_JOBS) { + enum { NUMBER_TO_DISCARD = 5 }; + + pj_enter_critical_section(); + for (unsigned i=0; i<NUMBER_TO_DISCARD; ++i) { + delete pendingJobs.back(); + pendingJobs.pop_back(); + } + + pendingJobSize -= NUMBER_TO_DISCARD; + pj_leave_critical_section(); + + utilLogWrite(1, THIS_FILE, + "*** ERROR: Job queue full!! Jobs discarded!!! ***"); + } + + pj_enter_critical_section(); + pendingJobs.push_back(job); + pendingJobSize++; + pj_leave_critical_section(); +} + +/* Handle log callback */ +void Endpoint::utilLogWrite(LogEntry &entry) +{ + if (mainThreadOnly && pj_thread_this() != mainThread) { + PendingLog *job = new PendingLog; + job->entry = entry; + utilAddPendingJob(job); + } else { + writer->write(entry); + } +} + +/* Run pending jobs only in main thread */ +void Endpoint::performPendingJobs() +{ + if (pj_thread_this() != mainThread) + return; + + if (pendingJobSize == 0) + return; + + for (;;) { + PendingJob *job = NULL; + + pj_enter_critical_section(); + if (pendingJobSize != 0) { + job = pendingJobs.front(); + pendingJobs.pop_front(); + pendingJobSize--; + } + pj_leave_critical_section(); + + if (job) { + job->execute(true); + delete job; + } else + break; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Endpoint static callbacks + */ +void Endpoint::logFunc(int level, const char *data, int len) +{ + Endpoint &ep = Endpoint::instance(); + + if (!ep.writer) + return; + + LogEntry entry; + entry.level = level; + entry.msg = string(data, len); + entry.threadId = (long)pj_thread_this(); + entry.threadName = string(pj_thread_get_name(pj_thread_this())); + + ep.utilLogWrite(entry); +} + +void Endpoint::stun_resolve_cb(const pj_stun_resolve_result *res) +{ + Endpoint &ep = Endpoint::instance(); + + if (!res) + return; + + OnNatCheckStunServersCompleteParam prm; + + prm.userData = res->token; + prm.status = res->status; + if (res->status == PJ_SUCCESS) { + char straddr[PJ_INET6_ADDRSTRLEN+10]; + + prm.name = string(res->name.ptr, res->name.slen); + pj_sockaddr_print(&res->addr, straddr, sizeof(straddr), 3); + prm.addr = straddr; + } + + ep.onNatCheckStunServersComplete(prm); +} + +void Endpoint::on_timer(pj_timer_heap_t *timer_heap, + pj_timer_entry *entry) +{ + PJ_UNUSED_ARG(timer_heap); + + Endpoint &ep = Endpoint::instance(); + UserTimer *ut = (UserTimer*) entry->user_data; + + if (ut->signature != TIMER_SIGNATURE) + return; + + ep.onTimer(ut->prm); +} + +void Endpoint::on_nat_detect(const pj_stun_nat_detect_result *res) +{ + Endpoint &ep = Endpoint::instance(); + + if (!res) + return; + + OnNatDetectionCompleteParam prm; + + prm.status = res->status; + prm.reason = res->status_text; + prm.natType = res->nat_type; + prm.natTypeName = res->nat_type_name; + + ep.onNatDetectionComplete(prm); +} + +void Endpoint::on_transport_state( pjsip_transport *tp, + pjsip_transport_state state, + const pjsip_transport_state_info *info) +{ + Endpoint &ep = Endpoint::instance(); + + OnTransportStateParam prm; + + prm.hnd = (TransportHandle)tp; + prm.state = state; + prm.lastError = info ? info->status : PJ_SUCCESS; + + ep.onTransportState(prm); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Account static callbacks + */ + +Account *Endpoint::lookupAcc(int acc_id, const char *op) +{ + Account *acc = Account::lookup(acc_id); + if (!acc) { + PJ_LOG(1,(THIS_FILE, + "Error: cannot find Account instance for account id %d in " + "%s", acc_id, op)); + } + + return acc; +} + +Call *Endpoint::lookupCall(int call_id, const char *op) +{ + Call *call = Call::lookup(call_id); + if (!call) { + PJ_LOG(1,(THIS_FILE, + "Error: cannot find Call instance for call id %d in " + "%s", call_id, op)); + } + + return call; +} + +void Endpoint::on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, + pjsip_rx_data *rdata) +{ + Account *acc = lookupAcc(acc_id, "on_incoming_call()"); + if (!acc) { + pjsua_call_hangup(call_id, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL); + return; + } + + /* call callback */ + OnIncomingCallParam prm; + prm.callId = call_id; + prm.rdata.fromPj(*rdata); + + acc->onIncomingCall(prm); + + /* disconnect if callback doesn't handle the call */ + pjsua_call_info ci; + + pjsua_call_get_info(call_id, &ci); + if (!pjsua_call_get_user_data(call_id) && + ci.state != PJSIP_INV_STATE_DISCONNECTED) + { + pjsua_call_hangup(call_id, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL); + } +} + +void Endpoint::on_reg_started(pjsua_acc_id acc_id, pj_bool_t renew) +{ + Account *acc = lookupAcc(acc_id, "on_reg_started()"); + if (!acc) { + return; + } + + OnRegStartedParam prm; + prm.renew = PJ2BOOL(renew); + acc->onRegStarted(prm); +} + +void Endpoint::on_reg_state2(pjsua_acc_id acc_id, pjsua_reg_info *info) +{ + Account *acc = lookupAcc(acc_id, "on_reg_state2()"); + if (!acc) { + return; + } + + OnRegStateParam prm; + prm.status = info->cbparam->status; + prm.code = (pjsip_status_code) info->cbparam->code; + prm.reason = pj2Str(info->cbparam->reason); + if (info->cbparam->rdata) + prm.rdata.fromPj(*info->cbparam->rdata); + prm.expiration = info->cbparam->expiration; + + acc->onRegState(prm); +} + +void Endpoint::on_incoming_subscribe(pjsua_acc_id acc_id, + pjsua_srv_pres *srv_pres, + pjsua_buddy_id buddy_id, + const pj_str_t *from, + pjsip_rx_data *rdata, + pjsip_status_code *code, + pj_str_t *reason, + pjsua_msg_data *msg_data) +{ + PJ_UNUSED_ARG(buddy_id); + PJ_UNUSED_ARG(srv_pres); + + Account *acc = lookupAcc(acc_id, "on_incoming_subscribe()"); + if (!acc) { + /* default behavior should apply */ + return; + } + + OnIncomingSubscribeParam prm; + prm.srvPres = srv_pres; + prm.fromUri = pj2Str(*from); + prm.rdata.fromPj(*rdata); + prm.code = *code; + prm.reason = pj2Str(*reason); + prm.txOption.fromPj(*msg_data); + + acc->onIncomingSubscribe(prm); + + *code = prm.code; + acc->tmpReason = prm.reason; + *reason = str2Pj(acc->tmpReason); + prm.txOption.toPj(*msg_data); +} + +void Endpoint::on_pager2(pjsua_call_id call_id, + const pj_str_t *from, + const pj_str_t *to, + const pj_str_t *contact, + const pj_str_t *mime_type, + const pj_str_t *body, + pjsip_rx_data *rdata, + pjsua_acc_id acc_id) +{ + OnInstantMessageParam prm; + prm.fromUri = pj2Str(*from); + prm.toUri = pj2Str(*to); + prm.contactUri = pj2Str(*contact); + prm.contentType = pj2Str(*mime_type); + prm.msgBody = pj2Str(*body); + prm.rdata.fromPj(*rdata); + + if (call_id != PJSUA_INVALID_ID) { + Call *call = lookupCall(call_id, "on_pager2()"); + if (!call) { + /* Ignored */ + return; + } + + call->onInstantMessage(prm); + } else { + Account *acc = lookupAcc(acc_id, "on_pager2()"); + if (!acc) { + /* Ignored */ + return; + } + + acc->onInstantMessage(prm); + } +} + +void Endpoint::on_pager_status2( pjsua_call_id call_id, + const pj_str_t *to, + const pj_str_t *body, + void *user_data, + pjsip_status_code status, + const pj_str_t *reason, + pjsip_tx_data *tdata, + pjsip_rx_data *rdata, + pjsua_acc_id acc_id) +{ + PJ_UNUSED_ARG(tdata); + + OnInstantMessageStatusParam prm; + prm.userData = user_data; + prm.toUri = pj2Str(*to); + prm.msgBody = pj2Str(*body); + prm.code = status; + prm.reason = pj2Str(*reason); + if (rdata) + prm.rdata.fromPj(*rdata); + + if (call_id != PJSUA_INVALID_ID) { + Call *call = lookupCall(call_id, "on_pager_status2()"); + if (!call) { + /* Ignored */ + return; + } + + call->onInstantMessageStatus(prm); + } else { + Account *acc = lookupAcc(acc_id, "on_pager_status2()"); + if (!acc) { + /* Ignored */ + return; + } + + acc->onInstantMessageStatus(prm); + } +} + +void Endpoint::on_typing2( pjsua_call_id call_id, + const pj_str_t *from, + const pj_str_t *to, + const pj_str_t *contact, + pj_bool_t is_typing, + pjsip_rx_data *rdata, + pjsua_acc_id acc_id) +{ + OnTypingIndicationParam prm; + prm.fromUri = pj2Str(*from); + prm.toUri = pj2Str(*to); + prm.contactUri = pj2Str(*contact); + prm.isTyping = is_typing != 0; + prm.rdata.fromPj(*rdata); + + if (call_id != PJSUA_INVALID_ID) { + Call *call = lookupCall(call_id, "on_typing2()"); + if (!call) { + /* Ignored */ + return; + } + + call->onTypingIndication(prm); + } else { + Account *acc = lookupAcc(acc_id, "on_typing2()"); + if (!acc) { + /* Ignored */ + return; + } + + acc->onTypingIndication(prm); + } +} + +void Endpoint::on_mwi_info(pjsua_acc_id acc_id, + pjsua_mwi_info *mwi_info) +{ + OnMwiInfoParam prm; + prm.state = pjsip_evsub_get_state(mwi_info->evsub); + prm.rdata.fromPj(*mwi_info->rdata); + + Account *acc = lookupAcc(acc_id, "on_mwi_info()"); + if (!acc) { + /* Ignored */ + return; + } + + acc->onMwiInfo(prm); +} + +void Endpoint::on_buddy_state(pjsua_buddy_id buddy_id) +{ + Buddy *buddy = (Buddy*)pjsua_buddy_get_user_data(buddy_id); + if (!buddy || !buddy->isValid()) { + /* Ignored */ + return; + } + + buddy->onBuddyState(); +} + +// Call callbacks +void Endpoint::on_call_state(pjsua_call_id call_id, pjsip_event *e) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + OnCallStateParam prm; + prm.e.fromPj(*e); + + call->processStateChange(prm); + /* If the state is DISCONNECTED, call may have already been deleted + * by the application in the callback, so do not access it anymore here. + */ +} + +void Endpoint::on_call_tsx_state(pjsua_call_id call_id, + pjsip_transaction *tsx, + pjsip_event *e) +{ + PJ_UNUSED_ARG(tsx); + + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + OnCallTsxStateParam prm; + prm.e.fromPj(*e); + + call->onCallTsxState(prm); +} + +void Endpoint::on_call_media_state(pjsua_call_id call_id) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + OnCallMediaStateParam prm; + call->processMediaUpdate(prm); +} + +void Endpoint::on_call_sdp_created(pjsua_call_id call_id, + pjmedia_sdp_session *sdp, + pj_pool_t *pool, + const pjmedia_sdp_session *rem_sdp) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + OnCallSdpCreatedParam prm; + string orig_sdp; + + prm.sdp.fromPj(*sdp); + orig_sdp = prm.sdp.wholeSdp; + if (rem_sdp) + prm.remSdp.fromPj(*rem_sdp); + + call->onCallSdpCreated(prm); + + /* Check if application modifies the SDP */ + if (orig_sdp != prm.sdp.wholeSdp) { + pjmedia_sdp_parse(pool, (char*)prm.sdp.wholeSdp.c_str(), + prm.sdp.wholeSdp.size(), &sdp); + } +} + +void Endpoint::on_stream_created(pjsua_call_id call_id, + pjmedia_stream *strm, + unsigned stream_idx, + pjmedia_port **p_port) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + OnStreamCreatedParam prm; + prm.stream = strm; + prm.streamIdx = stream_idx; + prm.pPort = (void *)*p_port; + + call->onStreamCreated(prm); + + if (prm.pPort != (void *)*p_port) + *p_port = (pjmedia_port *)prm.pPort; +} + +void Endpoint::on_stream_destroyed(pjsua_call_id call_id, + pjmedia_stream *strm, + unsigned stream_idx) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + OnStreamDestroyedParam prm; + prm.stream = strm; + prm.streamIdx = stream_idx; + + call->onStreamDestroyed(prm); +} + +struct PendingOnDtmfDigitCallback : public PendingJob +{ + int call_id; + OnDtmfDigitParam prm; + + virtual void execute(bool is_pending) + { + PJ_UNUSED_ARG(is_pending); + + Call *call = Call::lookup(call_id); + if (!call) + return; + + call->onDtmfDigit(prm); + } +}; + +void Endpoint::on_dtmf_digit(pjsua_call_id call_id, int digit) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + PendingOnDtmfDigitCallback *job = new PendingOnDtmfDigitCallback; + job->call_id = call_id; + char buf[10]; + pj_ansi_sprintf(buf, "%c", digit); + job->prm.digit = (string)buf; + + Endpoint::instance().utilAddPendingJob(job); +} + +void Endpoint::on_call_transfer_request2(pjsua_call_id call_id, + const pj_str_t *dst, + pjsip_status_code *code, + pjsua_call_setting *opt) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + OnCallTransferRequestParam prm; + prm.dstUri = pj2Str(*dst); + prm.statusCode = *code; + prm.opt.fromPj(*opt); + + call->onCallTransferRequest(prm); + + *code = prm.statusCode; + *opt = prm.opt.toPj(); +} + +void Endpoint::on_call_transfer_status(pjsua_call_id call_id, + int st_code, + const pj_str_t *st_text, + pj_bool_t final, + pj_bool_t *p_cont) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + OnCallTransferStatusParam prm; + prm.statusCode = (pjsip_status_code)st_code; + prm.reason = pj2Str(*st_text); + prm.finalNotify = PJ2BOOL(final); + prm.cont = PJ2BOOL(*p_cont); + + call->onCallTransferStatus(prm); + + *p_cont = prm.cont; +} + +void Endpoint::on_call_replace_request2(pjsua_call_id call_id, + pjsip_rx_data *rdata, + int *st_code, + pj_str_t *st_text, + pjsua_call_setting *opt) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + OnCallReplaceRequestParam prm; + prm.rdata.fromPj(*rdata); + prm.statusCode = (pjsip_status_code)*st_code; + prm.reason = pj2Str(*st_text); + prm.opt.fromPj(*opt); + + call->onCallReplaceRequest(prm); + + *st_code = prm.statusCode; + *st_text = str2Pj(prm.reason); + *opt = prm.opt.toPj(); +} + +void Endpoint::on_call_replaced(pjsua_call_id old_call_id, + pjsua_call_id new_call_id) +{ + Call *call = Call::lookup(old_call_id); + if (!call) { + return; + } + + OnCallReplacedParam prm; + prm.newCallId = new_call_id; + + call->onCallReplaced(prm); +} + +void Endpoint::on_call_rx_offer(pjsua_call_id call_id, + const pjmedia_sdp_session *offer, + void *reserved, + pjsip_status_code *code, + pjsua_call_setting *opt) +{ + PJ_UNUSED_ARG(reserved); + + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + OnCallRxOfferParam prm; + prm.offer.fromPj(*offer); + prm.statusCode = *code; + prm.opt.fromPj(*opt); + + call->onCallRxOffer(prm); + + *code = prm.statusCode; + *opt = prm.opt.toPj(); +} + +pjsip_redirect_op Endpoint::on_call_redirected(pjsua_call_id call_id, + const pjsip_uri *target, + const pjsip_event *e) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return PJSIP_REDIRECT_STOP; + } + + OnCallRedirectedParam prm; + char uristr[PJSIP_MAX_URL_SIZE]; + int len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, target, uristr, + sizeof(uristr)); + if (len < 1) { + pj_ansi_strcpy(uristr, "--URI too long--"); + } + prm.targetUri = string(uristr); + if (e) + prm.e.fromPj(*e); + else + prm.e.type = PJSIP_EVENT_UNKNOWN; + + return call->onCallRedirected(prm); +} + + +struct PendingOnMediaTransportCallback : public PendingJob +{ + int call_id; + OnCallMediaTransportStateParam prm; + + virtual void execute(bool is_pending) + { + PJ_UNUSED_ARG(is_pending); + + Call *call = Call::lookup(call_id); + if (!call) + return; + + call->onCallMediaTransportState(prm); + } +}; + +pj_status_t +Endpoint::on_call_media_transport_state(pjsua_call_id call_id, + const pjsua_med_tp_state_info *info) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return PJ_SUCCESS; + } + + PendingOnMediaTransportCallback *job = new PendingOnMediaTransportCallback; + + job->call_id = call_id; + job->prm.medIdx = info->med_idx; + job->prm.state = info->state; + job->prm.status = info->status; + job->prm.sipErrorCode = info->sip_err_code; + + Endpoint::instance().utilAddPendingJob(job); + + return PJ_SUCCESS; +} + +struct PendingOnMediaEventCallback : public PendingJob +{ + int call_id; + OnCallMediaEventParam prm; + + virtual void execute(bool is_pending) + { + Call *call = Call::lookup(call_id); + if (!call) + return; + + if (is_pending) { + /* Can't do this anymore, pointer is invalid */ + prm.ev.pjMediaEvent = NULL; + } + + call->onCallMediaEvent(prm); + } +}; + +void Endpoint::on_call_media_event(pjsua_call_id call_id, + unsigned med_idx, + pjmedia_event *event) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return; + } + + PendingOnMediaEventCallback *job = new PendingOnMediaEventCallback; + + job->call_id = call_id; + job->prm.medIdx = med_idx; + job->prm.ev.fromPj(*event); + + Endpoint::instance().utilAddPendingJob(job); +} + +pjmedia_transport* +Endpoint::on_create_media_transport(pjsua_call_id call_id, + unsigned media_idx, + pjmedia_transport *base_tp, + unsigned flags) +{ + Call *call = Call::lookup(call_id); + if (!call) { + return base_tp; + } + + OnCreateMediaTransportParam prm; + prm.mediaIdx = media_idx; + prm.mediaTp = base_tp; + prm.flags = flags; + + call->onCreateMediaTransport(prm); + + return (pjmedia_transport *)prm.mediaTp; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Endpoint library operations + */ +Version Endpoint::libVersion() const +{ + Version ver; + ver.major = PJ_VERSION_NUM_MAJOR; + ver.minor = PJ_VERSION_NUM_MINOR; + ver.rev = PJ_VERSION_NUM_REV; + ver.suffix = PJ_VERSION_NUM_EXTRA; + ver.full = pj_get_version(); + ver.numeric = PJ_VERSION_NUM; + return ver; +} + +void Endpoint::libCreate() throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_create() ); + mainThread = pj_thread_this(); +} + +pjsua_state Endpoint::libGetState() const +{ + return pjsua_get_state(); +} + +void Endpoint::libInit(const EpConfig &prmEpConfig) throw(Error) +{ + pjsua_config ua_cfg; + pjsua_logging_config log_cfg; + pjsua_media_config med_cfg; + + ua_cfg = prmEpConfig.uaConfig.toPj(); + log_cfg = prmEpConfig.logConfig.toPj(); + med_cfg = prmEpConfig.medConfig.toPj(); + + /* Setup log callback */ + if (prmEpConfig.logConfig.writer) { + this->writer = prmEpConfig.logConfig.writer; + log_cfg.cb = &Endpoint::logFunc; + } + mainThreadOnly = prmEpConfig.uaConfig.mainThreadOnly; + + /* Setup UA callbacks */ + pj_bzero(&ua_cfg.cb, sizeof(ua_cfg.cb)); + ua_cfg.cb.on_nat_detect = &Endpoint::on_nat_detect; + ua_cfg.cb.on_transport_state = &Endpoint::on_transport_state; + + ua_cfg.cb.on_incoming_call = &Endpoint::on_incoming_call; + ua_cfg.cb.on_reg_started = &Endpoint::on_reg_started; + ua_cfg.cb.on_reg_state2 = &Endpoint::on_reg_state2; + ua_cfg.cb.on_incoming_subscribe = &Endpoint::on_incoming_subscribe; + ua_cfg.cb.on_pager2 = &Endpoint::on_pager2; + ua_cfg.cb.on_pager_status2 = &Endpoint::on_pager_status2; + ua_cfg.cb.on_typing2 = &Endpoint::on_typing2; + ua_cfg.cb.on_mwi_info = &Endpoint::on_mwi_info; + ua_cfg.cb.on_buddy_state = &Endpoint::on_buddy_state; + + /* Call callbacks */ + ua_cfg.cb.on_call_state = &Endpoint::on_call_state; + ua_cfg.cb.on_call_tsx_state = &Endpoint::on_call_tsx_state; + ua_cfg.cb.on_call_media_state = &Endpoint::on_call_media_state; + ua_cfg.cb.on_call_sdp_created = &Endpoint::on_call_sdp_created; + ua_cfg.cb.on_stream_created = &Endpoint::on_stream_created; + ua_cfg.cb.on_stream_destroyed = &Endpoint::on_stream_destroyed; + ua_cfg.cb.on_dtmf_digit = &Endpoint::on_dtmf_digit; + ua_cfg.cb.on_call_transfer_request2 = &Endpoint::on_call_transfer_request2; + ua_cfg.cb.on_call_transfer_status = &Endpoint::on_call_transfer_status; + ua_cfg.cb.on_call_replace_request2 = &Endpoint::on_call_replace_request2; + ua_cfg.cb.on_call_replaced = &Endpoint::on_call_replaced; + ua_cfg.cb.on_call_rx_offer = &Endpoint::on_call_rx_offer; + ua_cfg.cb.on_call_redirected = &Endpoint::on_call_redirected; + ua_cfg.cb.on_call_media_transport_state = + &Endpoint::on_call_media_transport_state; + ua_cfg.cb.on_call_media_event = &Endpoint::on_call_media_event; + ua_cfg.cb.on_create_media_transport = &Endpoint::on_create_media_transport; + + /* Init! */ + PJSUA2_CHECK_EXPR( pjsua_init(&ua_cfg, &log_cfg, &med_cfg) ); +} + +void Endpoint::libStart() throw(Error) +{ + PJSUA2_CHECK_EXPR(pjsua_start()); +} + +void Endpoint::libRegisterWorkerThread(const string &name) throw(Error) +{ + PJSUA2_CHECK_EXPR(pjsua_register_worker_thread(name.c_str())); +} + +void Endpoint::libStopWorkerThreads() +{ + pjsua_stop_worker_threads(); +} + +int Endpoint::libHandleEvents(unsigned msec_timeout) +{ + performPendingJobs(); + return pjsua_handle_events(msec_timeout); +} + +void Endpoint::libDestroy(unsigned flags) throw(Error) +{ + pj_status_t status; + + status = pjsua_destroy2(flags); + + delete this->writer; + this->writer = NULL; + + if (pj_log_get_log_func() == &Endpoint::logFunc) { + pj_log_set_log_func(NULL); + } + + PJSUA2_CHECK_RAISE_ERROR(status); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Endpoint Utilities + */ +string Endpoint::utilStrError(pj_status_t prmErr) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(prmErr, errmsg, sizeof(errmsg)); + return errmsg; +} + +static void ept_log_write(int level, const char *sender, + const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(sender, level, format, arg ); + va_end(arg); +} + +void Endpoint::utilLogWrite(int prmLevel, + const string &prmSender, + const string &prmMsg) +{ + ept_log_write(prmLevel, prmSender.c_str(), "%s", prmMsg.c_str()); +} + +pj_status_t Endpoint::utilVerifySipUri(const string &prmUri) +{ + return pjsua_verify_sip_url(prmUri.c_str()); +} + +pj_status_t Endpoint::utilVerifyUri(const string &prmUri) +{ + return pjsua_verify_url(prmUri.c_str()); +} + +Token Endpoint::utilTimerSchedule(unsigned prmMsecDelay, + Token prmUserData) throw (Error) +{ + UserTimer *ut; + pj_time_val delay; + pj_status_t status; + + ut = new UserTimer; + ut->signature = TIMER_SIGNATURE; + ut->prm.msecDelay = prmMsecDelay; + ut->prm.userData = prmUserData; + pj_timer_entry_init(&ut->entry, 1, ut, &Endpoint::on_timer); + + delay.sec = 0; + delay.msec = prmMsecDelay; + pj_time_val_normalize(&delay); + + status = pjsua_schedule_timer(&ut->entry, &delay); + if (status != PJ_SUCCESS) { + delete ut; + PJSUA2_CHECK_RAISE_ERROR(status); + } + + return (Token)ut; +} + +void Endpoint::utilTimerCancel(Token prmTimerToken) +{ + UserTimer *ut = (UserTimer*)(void*)prmTimerToken; + + if (ut->signature != TIMER_SIGNATURE) { + PJ_LOG(1,(THIS_FILE, + "Invalid timer token in Endpoint::utilTimerCancel()")); + return; + } + + ut->entry.id = 0; + ut->signature = 0xFFFFFFFE; + pjsua_cancel_timer(&ut->entry); + + delete ut; +} + +IntVector Endpoint::utilSslGetAvailableCiphers() throw (Error) +{ +#if PJ_HAS_SSL_SOCK + pj_ssl_cipher ciphers[64]; + unsigned count = PJ_ARRAY_SIZE(ciphers); + + PJSUA2_CHECK_EXPR( pj_ssl_cipher_get_availables(ciphers, &count) ); + + return IntVector(ciphers, ciphers + count); +#else + return IntVector(); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Endpoint NAT operations + */ +void Endpoint::natDetectType(void) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_detect_nat_type() ); +} + +pj_stun_nat_type Endpoint::natGetType() throw(Error) +{ + pj_stun_nat_type type; + + PJSUA2_CHECK_EXPR( pjsua_get_nat_type(&type) ); + + return type; +} + +void Endpoint::natCheckStunServers(const StringVector &servers, + bool wait, + Token token) throw(Error) +{ + pj_str_t srv[MAX_STUN_SERVERS]; + unsigned i, count = 0; + + for (i=0; i<servers.size() && i<MAX_STUN_SERVERS; ++i) { + srv[count].ptr = (char*)servers[i].c_str(); + srv[count].slen = servers[i].size(); + ++count; + } + + PJSUA2_CHECK_EXPR(pjsua_resolve_stun_servers(count, srv, wait, token, + &Endpoint::stun_resolve_cb) ); +} + +void Endpoint::natCancelCheckStunServers(Token token, + bool notify_cb) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_cancel_stun_resolution(token, notify_cb) ); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Transport API + */ +TransportId Endpoint::transportCreate(pjsip_transport_type_e type, + const TransportConfig &cfg) throw(Error) +{ + pjsua_transport_config tcfg; + pjsua_transport_id tid; + + tcfg = cfg.toPj(); + PJSUA2_CHECK_EXPR( pjsua_transport_create(type, + &tcfg, &tid) ); + + return tid; +} + +IntVector Endpoint::transportEnum() throw(Error) +{ + pjsua_transport_id tids[32]; + unsigned count = PJ_ARRAY_SIZE(tids); + + PJSUA2_CHECK_EXPR( pjsua_enum_transports(tids, &count) ); + + return IntVector(tids, tids+count); +} + +TransportInfo Endpoint::transportGetInfo(TransportId id) throw(Error) +{ + pjsua_transport_info pj_tinfo; + TransportInfo tinfo; + + PJSUA2_CHECK_EXPR( pjsua_transport_get_info(id, &pj_tinfo) ); + tinfo.fromPj(pj_tinfo); + + return tinfo; +} + +void Endpoint::transportSetEnable(TransportId id, bool enabled) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_transport_set_enable(id, enabled) ); +} + +void Endpoint::transportClose(TransportId id) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_transport_close(id, PJ_FALSE) ); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Call operations + */ + +void Endpoint::hangupAllCalls(void) +{ + pjsua_call_hangup_all(); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Media API + */ +unsigned Endpoint::mediaMaxPorts() const +{ + return pjsua_conf_get_max_ports(); +} + +unsigned Endpoint::mediaActivePorts() const +{ + return pjsua_conf_get_active_ports(); +} + +const AudioMediaVector &Endpoint::mediaEnumPorts() const throw(Error) +{ + return mediaList; +} + +void Endpoint::mediaAdd(AudioMedia &media) +{ + if (mediaExists(media)) + return; + + mediaList.push_back(&media); +} + +void Endpoint::mediaRemove(AudioMedia &media) +{ + AudioMediaVector::iterator it = std::find(mediaList.begin(), + mediaList.end(), + &media); + + if (it != mediaList.end()) + mediaList.erase(it); + +} + +bool Endpoint::mediaExists(const AudioMedia &media) const +{ + AudioMediaVector::const_iterator it = std::find(mediaList.begin(), + mediaList.end(), + &media); + + return (it != mediaList.end()); +} + +AudDevManager &Endpoint::audDevManager() +{ + return audioDevMgr; +} + +/* + * Codec operations. + */ +const CodecInfoVector &Endpoint::codecEnum() throw(Error) +{ + pjsua_codec_info pj_codec[MAX_CODEC_NUM]; + unsigned count = 0; + + PJSUA2_CHECK_EXPR( pjsua_enum_codecs(pj_codec, &count) ); + + pj_enter_critical_section(); + clearCodecInfoList(); + for (unsigned i=0;(i<count && i<MAX_CODEC_NUM);++i) { + CodecInfo *codec_info = new CodecInfo; + + codec_info->fromPj(pj_codec[i]); + codecInfoList.push_back(codec_info); + } + pj_leave_critical_section(); + return codecInfoList; +} + +void Endpoint::codecSetPriority(const string &codec_id, + pj_uint8_t priority) throw(Error) +{ + pj_str_t codec_str = str2Pj(codec_id); + PJSUA2_CHECK_EXPR( pjsua_codec_set_priority(&codec_str, priority) ); +} + +CodecParam Endpoint::codecGetParam(const string &codec_id) const throw(Error) +{ + pjmedia_codec_param *pj_param = NULL; + pj_str_t codec_str = str2Pj(codec_id); + + PJSUA2_CHECK_EXPR( pjsua_codec_get_param(&codec_str, pj_param) ); + + return pj_param; +} + +void Endpoint::codecSetParam(const string &codec_id, + const CodecParam param) throw(Error) +{ + pj_str_t codec_str = str2Pj(codec_id); + pjmedia_codec_param *pj_param = (pjmedia_codec_param*)param; + + PJSUA2_CHECK_EXPR( pjsua_codec_set_param(&codec_str, pj_param) ); +} + +void Endpoint::clearCodecInfoList() +{ + for (unsigned i=0;i<codecInfoList.size();++i) { + delete codecInfoList[i]; + } + codecInfoList.clear(); +} |