diff --git a/daemon/src/call.h b/daemon/src/call.h index 13da8e69a3b9a548c282b2fd288ae2fe37e34af7..234244532e51f67826b47aa1629f7dfef1a38a20 100644 --- a/daemon/src/call.h +++ b/daemon/src/call.h @@ -253,6 +253,19 @@ class Call : public Recordable { */ virtual void refuse() = 0; + /** + * Transfer a call to specified URI + * @param to The recipient of the call + */ + virtual void transfer(const std::string &to) = 0; + + /** + * Attended transfer + * @param The target call id + * @return True on success + */ + virtual bool attendedTransfer(const std::string& to) = 0; + private: bool validTransition(CallState newState); diff --git a/daemon/src/iax/iaxcall.cpp b/daemon/src/iax/iaxcall.cpp index f47b8d56f9af88ef283e93c09fedbaf5e02a149f..56484d7026aff6466d0717c699731f4df562ae7a 100644 --- a/daemon/src/iax/iaxcall.cpp +++ b/daemon/src/iax/iaxcall.cpp @@ -166,3 +166,18 @@ IAXCall::refuse() link_->removeIaxCall(getCallId()); } + +void +IAXCall::transfer(const std::string& to) +{ + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + char callto[to.length() + 1]; + strcpy(callto, to.c_str()); + iax_transfer(session, callto); +} + +bool +IAXCall::attendedTransfer(const std::string& /*targetID*/) +{ + return false; // TODO +} diff --git a/daemon/src/iax/iaxcall.h b/daemon/src/iax/iaxcall.h index f369c27966a2d4d76ccb85ed24bffa04dcdce7c3..75bdb9b7cbdcbb060f6d54f50afd240cd4d1f8e4 100644 --- a/daemon/src/iax/iaxcall.h +++ b/daemon/src/iax/iaxcall.h @@ -86,6 +86,10 @@ class IAXCall : public Call { void refuse(); + void transfer(const std::string& to); + + bool attendedTransfer(const std::string& to); + private: NON_COPYABLE(IAXCall); diff --git a/daemon/src/iax/iaxvoiplink.cpp b/daemon/src/iax/iaxvoiplink.cpp index 4ee5d1ba92e17ca2e62a1a0a150f0183338c1b25..373b2f06c94338810d2d805d09c97115c888f1aa 100644 --- a/daemon/src/iax/iaxvoiplink.cpp +++ b/daemon/src/iax/iaxvoiplink.cpp @@ -371,30 +371,6 @@ IAXVoIPLink::offhold(const std::string& id) Manager::instance().startAudioDriverStream(); } -void -IAXVoIPLink::transfer(const std::string& id, const std::string& to) -{ - char callto[to.length() + 1]; - strcpy(callto, to.c_str()); - - { - std::lock_guard<std::mutex> lock(iaxCallMapMutex_); - auto call = getIAXCall(id); - if (!call) - return; - { - std::lock_guard<std::mutex> lock(mutexIAX); - iax_transfer(call->session, callto); - } - } -} - -bool -IAXVoIPLink::attendedTransfer(const std::string& /*transferID*/, const std::string& /*targetID*/) -{ - return false; // TODO -} - void IAXVoIPLink::carryingDTMFdigits(const std::string& id, char code) { diff --git a/daemon/src/iax/iaxvoiplink.h b/daemon/src/iax/iaxvoiplink.h index 5a070c71e14f23c6801e9f82e9118a7d5d7810d1..7848e32552a6c0a7a93cc72a4552d097d84ed035 100644 --- a/daemon/src/iax/iaxvoiplink.h +++ b/daemon/src/iax/iaxvoiplink.h @@ -149,21 +149,6 @@ class IAXVoIPLink : public VoIPLink { */ virtual void offhold(const std::string& id); - /** - * Transfer a call - * @param id The ID of the call - * @param to The recipient of the transfer - */ - virtual void transfer(const std::string& id, const std::string& to); - - /** - * Perform attended transfer - * @param Transfered call ID - * @param Target call ID - * @return true on success - */ - virtual bool attendedTransfer(const std::string& transferID, const std::string& targetID); - /** * Send DTMF * @param id The ID of the call diff --git a/daemon/src/managerimpl.cpp b/daemon/src/managerimpl.cpp index 17aa38b82ce58055d8ee54e8d324a79b0be849aa..7db23b04b8655cf0fe0fdca56f8e4e3fa4b939c0 100644 --- a/daemon/src/managerimpl.cpp +++ b/daemon/src/managerimpl.cpp @@ -615,10 +615,10 @@ bool ManagerImpl::transferCall(const std::string& callId, const std::string& to) } else if (not isConference(getCurrentCallId())) unsetCurrentCall(); - std::string accountID(getAccountFromCall(callId)); - if (accountID.empty()) + if (auto call = getCallFromCallID(callId)) + call->transfer(to); + else return false; - getAccountLink(accountID)->transfer(callId, to); // remove waiting call in case we make transfer without even answer removeWaitingCall(callId); @@ -638,10 +638,9 @@ void ManagerImpl::transferSucceeded() bool ManagerImpl::attendedTransfer(const std::string& transferID, const std::string& targetID) { - std::string accountid(getAccountFromCall(transferID)); - if (accountid.empty()) - return false; - return getAccountLink(accountid)->attendedTransfer(transferID, targetID); + if (auto call = getCallFromCallID(transferID)) + return call->attendedTransfer(targetID); + return false; } //THREAD=Main : Call:Incoming diff --git a/daemon/src/sip/sipcall.cpp b/daemon/src/sip/sipcall.cpp index ae8a9692732746c00b86ee1cf3e11e0ddba05e21..685aa3606fe682592b697e042141489d34dfcd7a 100644 --- a/daemon/src/sip/sipcall.cpp +++ b/daemon/src/sip/sipcall.cpp @@ -51,6 +51,10 @@ getSettings() static const int INITIAL_SIZE = 16384; static const int INCREMENT_SIZE = INITIAL_SIZE; +/** A map to retreive SFLphone internal call id + * Given a SIP call ID (usefull for transaction sucha as transfer)*/ +static std::map<std::string, std::string> transferCallID; + static void updateSDPFromSTUN(SIPCall &call, SIPAccount &account, const SipTransport &transport) { @@ -349,3 +353,173 @@ SIPCall::refuse() siplink.removeSipCall(getCallId()); } + +static void +transfer_client_cb(pjsip_evsub *sub, pjsip_event *event) +{ + auto mod_ua_id = SIPVoIPLink::instance().getMod()->id; + + switch (pjsip_evsub_get_state(sub)) { + case PJSIP_EVSUB_STATE_ACCEPTED: + if (!event) + return; + + pj_assert(event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG); + break; + + case PJSIP_EVSUB_STATE_TERMINATED: + pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL); + break; + + case PJSIP_EVSUB_STATE_ACTIVE: { + if (!event) + return; + + pjsip_rx_data* r_data = event->body.rx_msg.rdata; + + if (!r_data) + return; + + std::string request(pjsip_rx_data_get_info(r_data)); + + pjsip_status_line status_line = { 500, *pjsip_get_status_text(500) }; + + if (!r_data->msg_info.msg) + return; + + if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD and + request.find("NOTIFY") != std::string::npos) { + pjsip_msg_body *body = r_data->msg_info.msg->body; + + if (!body) + return; + + if (pj_stricmp2(&body->content_type.type, "message") or + pj_stricmp2(&body->content_type.subtype, "sipfrag")) + return; + + if (pjsip_parse_status_line((char*) body->data, body->len, &status_line) != PJ_SUCCESS) + return; + } + + if (!r_data->msg_info.cid) + return; + + auto call = static_cast<SIPCall *>(pjsip_evsub_get_mod_data(sub, mod_ua_id)); + if (!call) + return; + + if (status_line.code / 100 == 2) { + pjsip_tx_data *tdata; + + if (!call->inv) + return; + + if (pjsip_inv_end_session(call->inv, PJSIP_SC_GONE, NULL, &tdata) == PJ_SUCCESS) + pjsip_inv_send_msg(call->inv, tdata); + + Manager::instance().hangupCall(call->getCallId()); + pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL); + } + + break; + } + + default: + break; + } +} + +bool +SIPCall::transferCommon(pj_str_t *dst) +{ + if (!inv) + return false; + + pjsip_evsub_user xfer_cb; + pj_bzero(&xfer_cb, sizeof(xfer_cb)); + xfer_cb.on_evsub_state = &transfer_client_cb; + + pjsip_evsub *sub; + + if (pjsip_xfer_create_uac(inv->dlg, &xfer_cb, &sub) != PJ_SUCCESS) + return false; + + auto& siplink = SIPVoIPLink::instance(); + + /* Associate this voiplink of call with the client subscription + * We can not just associate call with the client subscription + * because after this function, we can no find the cooresponding + * voiplink from the call any more. But the voiplink is useful! + */ + pjsip_evsub_set_mod_data(sub, siplink.getMod()->id, this); + + /* + * Create REFER request. + */ + pjsip_tx_data *tdata; + + if (pjsip_xfer_initiate(sub, dst, &tdata) != PJ_SUCCESS) + return false; + + // Put SIP call id in map in order to retrieve call during transfer callback + std::string callidtransfer(inv->dlg->call_id->id.ptr, inv->dlg->call_id->id.slen); + transferCallID[callidtransfer] = getCallId(); + + /* Send. */ + if (pjsip_xfer_send_request(sub, tdata) != PJ_SUCCESS) + return false; + + return true; +} + +void +SIPCall::transfer(const std::string& to) +{ + stopRecording(); + + std::string account_id(getAccountId()); + SIPAccount *account = Manager::instance().getSipAccount(account_id); + + if (account == NULL) + throw VoipLinkException("Could not find account"); + + std::string toUri; + pj_str_t dst = { 0, 0 }; + + toUri = account->getToUri(to); + pj_cstr(&dst, toUri.c_str()); + DEBUG("Transferring to %.*s", dst.slen, dst.ptr); + + if (!transferCommon(&dst)) + throw VoipLinkException("Couldn't transfer"); +} + +bool +SIPCall::attendedTransfer(const std::string& /*to*/) +{ + if (!inv or !inv->dlg) + throw VoipLinkException("Couldn't get invite dialog"); + + pjsip_dialog *target_dlg = inv->dlg; + pjsip_uri *uri = (pjsip_uri*) pjsip_uri_get_uri(target_dlg->remote.info->uri); + + char str_dest_buf[PJSIP_MAX_URL_SIZE * 2] = { '<' }; + pj_str_t dst = { str_dest_buf, 1 }; + + dst.slen += pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, str_dest_buf + 1, sizeof(str_dest_buf) - 1); + dst.slen += pj_ansi_snprintf(str_dest_buf + dst.slen, + sizeof(str_dest_buf) - dst.slen, + "?" + "Replaces=%.*s" + "%%3Bto-tag%%3D%.*s" + "%%3Bfrom-tag%%3D%.*s>", + (int)target_dlg->call_id->id.slen, + target_dlg->call_id->id.ptr, + (int)target_dlg->remote.info->tag.slen, + target_dlg->remote.info->tag.ptr, + (int)target_dlg->local.info->tag.slen, + target_dlg->local.info->tag.ptr); + + return transferCommon(&dst); +} diff --git a/daemon/src/sip/sipcall.h b/daemon/src/sip/sipcall.h index d4332fcdd57465164b2bbb4de3a2d09a6741229f..af4a689ec1868e5e49fab4c2b491a5910b5d9c5c 100644 --- a/daemon/src/sip/sipcall.h +++ b/daemon/src/sip/sipcall.h @@ -121,6 +121,10 @@ class SIPCall : public Call { void refuse(); + void transfer(const std::string& to); + + bool attendedTransfer(const std::string& to); + private: // override of Call::createHistoryEntry @@ -129,6 +133,11 @@ class SIPCall : public Call { NON_COPYABLE(SIPCall); + /** + * Transfer method used for both type of transfer + */ + bool transferCommon(pj_str_t *dst); + /** * Audio Rtp Session factory */ diff --git a/daemon/src/sip/sipvoiplink.cpp b/daemon/src/sip/sipvoiplink.cpp index e8d0b62d3ba9d8ee4c6cefbb7ddfa24a072da09b..f79a892185f080352cd87c4611fe04e67a7c3c37 100644 --- a/daemon/src/sip/sipvoiplink.cpp +++ b/daemon/src/sip/sipvoiplink.cpp @@ -91,10 +91,6 @@ SIPVoIPLink *SIPVoIPLink::instance_ = nullptr; /** Environment variable used to set pjsip's logging level */ #define SIPLOGLEVEL "SIPLOGLEVEL" -/** A map to retreive SFLphone internal call id - * Given a SIP call ID (usefull for transaction sucha as transfer)*/ -static std::map<std::string, std::string> transferCallID; - /**************** EXTERN VARIABLES AND FUNCTIONS (callbacks) **************************/ /** @@ -117,8 +113,6 @@ static void outgoing_request_forked_cb(pjsip_inv_session *inv, pjsip_event *e); static void transaction_state_changed_cb(pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e); static void registration_cb(pjsip_regc_cbparam *param); -static void transfer_client_cb(pjsip_evsub *sub, pjsip_event *event); - /** * Helper function to process refer function on call transfer */ @@ -1210,109 +1204,6 @@ SIPVoIPLink::tryGetSIPCall(const std::string& id) return call; } -bool -SIPVoIPLink::transferCommon(SIPCall *call, pj_str_t *dst) -{ - if (!call or !call->inv) - return false; - - pjsip_evsub_user xfer_cb; - pj_bzero(&xfer_cb, sizeof(xfer_cb)); - xfer_cb.on_evsub_state = &transfer_client_cb; - - pjsip_evsub *sub; - - if (pjsip_xfer_create_uac(call->inv->dlg, &xfer_cb, &sub) != PJ_SUCCESS) - return false; - - /* Associate this voiplink of call with the client subscription - * We can not just associate call with the client subscription - * because after this function, we can no find the cooresponding - * voiplink from the call any more. But the voiplink is useful! - */ - pjsip_evsub_set_mod_data(sub, mod_ua_.id, this); - - /* - * Create REFER request. - */ - pjsip_tx_data *tdata; - - if (pjsip_xfer_initiate(sub, dst, &tdata) != PJ_SUCCESS) - return false; - - // Put SIP call id in map in order to retrieve call during transfer callback - std::string callidtransfer(call->inv->dlg->call_id->id.ptr, call->inv->dlg->call_id->id.slen); - transferCallID[callidtransfer] = call->getCallId(); - - /* Send. */ - if (pjsip_xfer_send_request(sub, tdata) != PJ_SUCCESS) - return false; - - return true; -} - -void -SIPVoIPLink::transfer(const std::string& id, const std::string& to) -{ - auto call = getSipCall(id); - if (!call) - return; - - call->stopRecording(); - - std::string account_id(call->getAccountId()); - SIPAccount *account = Manager::instance().getSipAccount(account_id); - - if (account == NULL) - throw VoipLinkException("Could not find account"); - - std::string toUri; - pj_str_t dst = { 0, 0 }; - - toUri = account->getToUri(to); - pj_cstr(&dst, toUri.c_str()); - DEBUG("Transferring to %.*s", dst.slen, dst.ptr); - - if (!transferCommon(call.get(), &dst)) - throw VoipLinkException("Couldn't transfer"); -} - -bool SIPVoIPLink::attendedTransfer(const std::string& id, const std::string& /*to*/) -{ - auto toCall = getSipCall(id); - if (!toCall) - return false; - - if (!toCall->inv or !toCall->inv->dlg) - throw VoipLinkException("Couldn't get invite dialog"); - - pjsip_dialog *target_dlg = toCall->inv->dlg; - pjsip_uri *uri = (pjsip_uri*) pjsip_uri_get_uri(target_dlg->remote.info->uri); - - char str_dest_buf[PJSIP_MAX_URL_SIZE * 2] = { '<' }; - pj_str_t dst = { str_dest_buf, 1 }; - - dst.slen += pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, str_dest_buf + 1, sizeof(str_dest_buf) - 1); - dst.slen += pj_ansi_snprintf(str_dest_buf + dst.slen, - sizeof(str_dest_buf) - dst.slen, - "?" - "Replaces=%.*s" - "%%3Bto-tag%%3D%.*s" - "%%3Bfrom-tag%%3D%.*s>", - (int)target_dlg->call_id->id.slen, - target_dlg->call_id->id.ptr, - (int)target_dlg->remote.info->tag.slen, - target_dlg->remote.info->tag.ptr, - (int)target_dlg->local.info->tag.slen, - target_dlg->local.info->tag.ptr); - - auto call = getSipCall(id); - if (!call) - return false; - - return transferCommon(call.get(), &dst); -} - static void sendSIPInfo(const SIPCall &call, const char *const body, const char *const subtype) { @@ -2173,83 +2064,6 @@ onCallTransfered(pjsip_inv_session *inv, pjsip_rx_data *rdata) } } -static void -transfer_client_cb(pjsip_evsub *sub, pjsip_event *event) -{ - switch (pjsip_evsub_get_state(sub)) { - case PJSIP_EVSUB_STATE_ACCEPTED: - if (!event) - return; - - pj_assert(event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG); - break; - - case PJSIP_EVSUB_STATE_TERMINATED: - pjsip_evsub_set_mod_data(sub, mod_ua_.id, NULL); - break; - - case PJSIP_EVSUB_STATE_ACTIVE: { - SIPVoIPLink *link = static_cast<SIPVoIPLink *>(pjsip_evsub_get_mod_data(sub, mod_ua_.id)); - - if (!link or !event) - return; - - pjsip_rx_data* r_data = event->body.rx_msg.rdata; - - if (!r_data) - return; - - std::string request(pjsip_rx_data_get_info(r_data)); - - pjsip_status_line status_line = { 500, *pjsip_get_status_text(500) }; - - if (!r_data->msg_info.msg) - return; - - if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD and - request.find("NOTIFY") != std::string::npos) { - pjsip_msg_body *body = r_data->msg_info.msg->body; - - if (!body) - return; - - if (pj_stricmp2(&body->content_type.type, "message") or - pj_stricmp2(&body->content_type.subtype, "sipfrag")) - return; - - if (pjsip_parse_status_line((char*) body->data, body->len, &status_line) != PJ_SUCCESS) - return; - } - - if (!r_data->msg_info.cid) - return; - - std::string transferID(r_data->msg_info.cid->id.ptr, r_data->msg_info.cid->id.slen); - auto call = SIPVoIPLink::instance().getSipCall(transferCallID[transferID]); - if (!call) - return; - - if (status_line.code / 100 == 2) { - pjsip_tx_data *tdata; - - if (!call->inv) - return; - - if (pjsip_inv_end_session(call->inv, PJSIP_SC_GONE, NULL, &tdata) == PJ_SUCCESS) - pjsip_inv_send_msg(call->inv, tdata); - - Manager::instance().hangupCall(call->getCallId()); - pjsip_evsub_set_mod_data(sub, mod_ua_.id, NULL); - } - - break; - } - - default: - break; - } -} - static void setCallMediaLocal(SIPCall* call, const pj_sockaddr& localIP) { diff --git a/daemon/src/sip/sipvoiplink.h b/daemon/src/sip/sipvoiplink.h index 34f70a5ca36624263777c4586041f6b6e0e71465..b797ed54b70d1fd957b297d80097b505f5017398 100644 --- a/daemon/src/sip/sipvoiplink.h +++ b/daemon/src/sip/sipvoiplink.h @@ -179,26 +179,6 @@ class SIPVoIPLink : public VoIPLink { */ virtual void offhold(const std::string& id); - /** - * Transfer method used for both type of transfer - */ - bool transferCommon(SIPCall *call, pj_str_t *dst); - - /** - * Transfer the call - * @param id The call identifier - * @param to The recipient of the transfer - */ - virtual void transfer(const std::string& id, const std::string& to); - - /** - * Attended transfer - * @param The transfered call id - * @param The target call id - * @return True on success - */ - virtual bool attendedTransfer(const std::string&, const std::string&); - /** * Send DTMF refering to account configuration * @param id The call identifier diff --git a/daemon/src/voiplink.h b/daemon/src/voiplink.h index 18598a93d765760b3e8e034bc8041e9fd019a799..3751c79a83793733558d1d6889cabace08ef3665 100644 --- a/daemon/src/voiplink.h +++ b/daemon/src/voiplink.h @@ -113,21 +113,6 @@ class VoIPLink { */ virtual void offhold(const std::string &id) = 0; - /** - * Transfer a call to specified URI - * @param id The call identifier - * @param to The recipient of the call - */ - virtual void transfer(const std::string &id, const std::string &to) = 0; - - /** - * Attended transfer - * @param The transfered call id - * @param The target call id - * @return True on success - */ - virtual bool attendedTransfer(const std::string&, const std::string&) = 0; - /** * Send DTMF * @param id The call identifier