From 9bcf44d21b3a7588a267e98573396dbf2deceb12 Mon Sep 17 00:00:00 2001 From: pescuma Date: Sun, 17 Jan 2010 22:16:02 +0000 Subject: sip: start of client plugins code git-svn-id: http://pescuma.googlecode.com/svn/trunk/Miranda@209 c086bb3d-8645-0410-b8da-73a8550f86e7 --- Protocols/SIP/SIPClient.cpp | 701 ++++++++++++++++++++++++++++++++++++++++++++ Protocols/SIP/SIPClient.h | 87 ++++++ Protocols/SIP/SIPProto.cpp | 247 ++++------------ Protocols/SIP/SIPProto.h | 39 +-- Protocols/SIP/commons.h | 39 +++ Protocols/SIP/m_sip.h | 98 +++++++ Protocols/SIP/sip.cpp | 112 ++++++- Protocols/SIP/sip.vcproj | 16 + Protocols/SIP/strutils.h | 200 +++++++++++++ 9 files changed, 1304 insertions(+), 235 deletions(-) create mode 100644 Protocols/SIP/SIPClient.cpp create mode 100644 Protocols/SIP/SIPClient.h create mode 100644 Protocols/SIP/m_sip.h create mode 100644 Protocols/SIP/strutils.h (limited to 'Protocols/SIP') diff --git a/Protocols/SIP/SIPClient.cpp b/Protocols/SIP/SIPClient.cpp new file mode 100644 index 0000000..efd64ee --- /dev/null +++ b/Protocols/SIP/SIPClient.cpp @@ -0,0 +1,701 @@ +/* +Copyright (C) 2009 Ricardo Pescuma Domenecci + +This is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this file; see the file license.txt. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +*/ + +#include "commons.h" + +static int sttCompareClients(const SIPClient *p1, const SIPClient *p2) +{ + return p1 < p2; +} +OBJLIST clients(1, &sttCompareClients); + + +SIPClient::SIPClient(SIP_REGISTRATION *reg) +{ + hasToDestroy = false; + udp.transport_id = -1; + udp.acc_id = -1; + udp.port = 0; + tcp.transport_id = -1; + tcp.acc_id = -1; + tcp.port = 0; + tls.transport_id = -1; + tls.acc_id = -1; + tls.port = 0; + + hNetlibUser = reg->hNetlib; + lstrcpynA(name, reg->name, MAX_REGS(name)); + lstrcpyn(username, reg->username, MAX_REGS(username)); + + callback = reg->callback; + callback_param = reg->callback_param; + + InitializeCriticalSection(&cs); +} + + +SIPClient::~SIPClient() +{ + DeleteCriticalSection(&cs); +} + + +static void CALLBACK ProcessEvents(void *param) +{ + for(int i = 0; i < clients.getCount(); ++i) + { + SIPClient *proto = &clients[i]; + + EnterCriticalSection(&proto->cs); + + std::vector events(proto->events); + proto->events.clear(); + + LeaveCriticalSection(&proto->cs); + + for(unsigned int i = 0; i < events.size(); ++i) + { + SIPEvent &ev = events[i]; + switch(ev.type) + { + case SIPEvent::incoming_call: + proto->on_incoming_call(ev.call_id); + break; + case SIPEvent::call_state: + proto->on_call_state(ev.call_id, ev.call_info); + break; + case SIPEvent::call_media_state: + proto->on_call_media_state(ev.call_id); + break; + } + mir_free(ev.from); + mir_free(ev.text); + mir_free(ev.mime); + delete ev.messageData; + } + } +} + + +static void static_on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) +{ + pjsua_call_info info; + pj_status_t status = pjsua_call_get_info(call_id, &info); + if (status != PJ_SUCCESS) + return; + + SIPClient *proto = (SIPClient *) pjsua_acc_get_user_data(acc_id); + if (proto == NULL) + return; + + SIPEvent ev; + memset(&ev, 0, sizeof(ev)); + ev.type = SIPEvent::incoming_call; + ev.call_id = call_id; + ev.call_info = info; + + EnterCriticalSection(&proto->cs); + proto->events.push_back(ev); + LeaveCriticalSection(&proto->cs); + + CallFunctionAsync(&ProcessEvents, NULL); +} + + +static void static_on_call_state(pjsua_call_id call_id, pjsip_event *e) +{ + pjsua_call_info info; + pj_status_t status = pjsua_call_get_info(call_id, &info); + if (status != PJ_SUCCESS) + return; + + SIPClient *proto = (SIPClient *) pjsua_acc_get_user_data(info.acc_id); + if (proto == NULL) + return; + + SIPEvent ev; + memset(&ev, 0, sizeof(ev)); + ev.type = SIPEvent::call_state; + ev.call_id = call_id; + ev.call_info = info; + + EnterCriticalSection(&proto->cs); + proto->events.push_back(ev); + LeaveCriticalSection(&proto->cs); + + CallFunctionAsync(&ProcessEvents, NULL); +} + + +static void static_on_call_media_state(pjsua_call_id call_id) +{ + pjsua_call_info info; + pj_status_t status = pjsua_call_get_info(call_id, &info); + if (status != PJ_SUCCESS) + return; + + SIPClient *proto = (SIPClient *) pjsua_acc_get_user_data(info.acc_id); + if (proto == NULL) + return; + + if (!proto->on_call_media_state_sync(call_id, info)) + return; + + SIPEvent ev; + memset(&ev, 0, sizeof(ev)); + ev.type = SIPEvent::call_media_state; + ev.call_id = call_id; + ev.call_info = info; + + EnterCriticalSection(&proto->cs); + proto->events.push_back(ev); + LeaveCriticalSection(&proto->cs); + + CallFunctionAsync(&ProcessEvents, NULL); +} + + +static void static_on_log(int level, const char *data, int len) +{ + char tmp[1024]; + mir_snprintf(tmp, MAX_REGS(tmp), "Level %d : %*s", level, len, data); + OutputDebugStringA(tmp); +} + + +#define TransportName(_T_) SipToTchar(pj_cstr(pjsip_transport_get_type_name(_T_))) + +void SIPClient::RegisterTransport(pjsip_transport_type_e type, int port, ta *ta) +{ + ta->transport_id = -1; + ta->acc_id = -1; + ta->port = 0; + + if (port < 0) + return; + + pjsua_transport_config cfg; + pjsua_transport_config_default(&cfg); + cfg.port = port; + + pj_status_t status = pjsua_transport_create(type, &cfg, &ta->transport_id); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error creating %s transport"), (const TCHAR *) TransportName(type)); + return; + } + + pjsua_transport_info info; + status = pjsua_transport_get_info(ta->transport_id, &info); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error getting %s info"), (const TCHAR *) TransportName(type)); + pjsua_transport_close(ta->transport_id, PJ_TRUE); + ta->transport_id = -1; + return; + } + + status = pjsua_acc_add_local(ta->transport_id, PJ_TRUE, &ta->acc_id); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error adding %s account"), (const TCHAR *) TransportName(type)); + pjsua_transport_close(ta->transport_id, PJ_TRUE); + ta->transport_id = -1; + return; + } + + lstrcpyn(host, SipToTchar(info.local_name.host), MAX_REGS(host)); + ta->port = info.local_name.port; +} + + +int SIPClient::Connect(int udp_port, int tcp_port, int tls_port) +{ + Trace(_T("Connecting...")); + + { + pj_status_t status = pjsua_create(); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error creating pjsua")); + return 1; + } + hasToDestroy = true; + } + + { + pjsua_config cfg; + pjsua_config_default(&cfg); +#ifndef _DEBUG + cfg.use_srtp = PJMEDIA_SRTP_OPTIONAL; +#endif + cfg.cb.on_incoming_call = &static_on_incoming_call; + cfg.cb.on_call_media_state = &static_on_call_media_state; + cfg.cb.on_call_state = &static_on_call_state; + + pjsua_logging_config log_cfg; + pjsua_logging_config_default(&log_cfg); + log_cfg.cb = &static_on_log; + + pjsua_media_config media_cfg; + pjsua_media_config_default(&media_cfg); + media_cfg.enable_ice = PJ_TRUE; + + pj_status_t status = pjsua_init(&cfg, &log_cfg, &media_cfg); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error initializing pjsua")); + return 1; + } + } + + { + RegisterTransport(PJSIP_TRANSPORT_UDP, udp_port, &udp); + RegisterTransport(PJSIP_TRANSPORT_TCP, tcp_port, &tcp); + RegisterTransport(PJSIP_TRANSPORT_TLS, tls_port, &tls); + + if (udp.port <= 0 && tcp.port <= 0 && tls.port <= 0) + return 1; + } + + { + pj_status_t status = pjsua_start(); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error starting pjsua")); + return 1; + } + } + + return 0; +} + + +#define MESSAGE_TYPE_TRACE 0 +#define MESSAGE_TYPE_INFO 1 +#define MESSAGE_TYPE_ERROR 2 + + +void SIPClient::Trace(TCHAR *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + ShowMessage(MESSAGE_TYPE_TRACE, fmt, args); + + va_end(args); +} + + +void SIPClient::Info(TCHAR *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + ShowMessage(MESSAGE_TYPE_INFO, TranslateTS(fmt), args); + + va_end(args); +} + + +void SIPClient::Error(TCHAR *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + ShowMessage(MESSAGE_TYPE_ERROR, TranslateTS(fmt), args); + + va_end(args); +} + + +void SIPClient::Error(pj_status_t status, TCHAR *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + TCHAR buff[1024]; + mir_vsntprintf(buff, MAX_REGS(buff), TranslateTS(fmt), args); + + va_end(args); + + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + + Error(_T("%s: %s"), buff, Utf8ToTchar(errmsg)); +} + + +void SIPClient::ShowMessage(int type, TCHAR *fmt, va_list args) +{ + TCHAR buff[1024]; + if (type == MESSAGE_TYPE_TRACE) + lstrcpy(buff, _T("SIP: TRACE: ")); + else if (type == MESSAGE_TYPE_INFO) + lstrcpy(buff, _T("SIP: INFO : ")); + else + lstrcpy(buff, _T("SIP: ERROR: ")); + + mir_vsntprintf(&buff[12], MAX_REGS(buff)-12, fmt, args); + +#ifdef _DEBUG + OutputDebugString(buff); + OutputDebugString(_T("\n")); +#endif + + if (hNetlibUser) + CallService(MS_NETLIB_LOG, (WPARAM) hNetlibUser, (LPARAM) TcharToChar(buff).get()); +} + + +void SIPClient::Disconnect() +{ + Trace(_T("Disconnecting...")); + + EnterCriticalSection(&cs); + events.clear(); + LeaveCriticalSection(&cs); + +#define CLOSE(_TA_) \ + if (_TA_.acc_id >= 0) \ + { \ + pjsua_acc_del(_TA_.acc_id); \ + _TA_.acc_id = -1; \ + } \ + if (_TA_.transport_id >= 0) \ + { \ + pjsua_transport_close(_TA_.transport_id, PJ_TRUE); \ + _TA_.transport_id = -1; \ + } + + CLOSE(udp) + CLOSE(tcp) + CLOSE(tls) + + if (hasToDestroy) + { + pjsua_destroy(); + hasToDestroy = false; + } +} + + +void SIPClient::NotifyCall(pjsua_call_id call_id, int state, const TCHAR *name, const TCHAR *uri) +{ + Trace(_T("NotifyCall %d -> %d"), call_id, state); + + if (callback != NULL) + callback(callback_param, (int) call_id, state, name, uri); +} + + +void SIPClient::on_incoming_call(pjsua_call_id call_id) +{ + Trace(_T("on_incoming_call: %d"), call_id); + + pjsua_call_info info; + pj_status_t status = pjsua_call_get_info(call_id, &info); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error obtaining call info")); + return; + } + + SipToTchar remote_info(info.remote_info); + SipToTchar remote_contact(info.remote_contact); + + TCHAR name[256]; + CleanupURI(name, MAX_REGS(name), remote_info); + + NotifyCall(call_id, VOICE_STATE_RINGING, name, remote_contact); +} + + +void SIPClient::on_call_state(pjsua_call_id call_id, const pjsua_call_info &info) +{ + Trace(_T("on_call_state: %d"), call_id); + Trace(_T("Call info: %d / last: %d %s"), info.state, info.last_status, info.last_status_text); + + switch(info.state) + { + case PJSIP_INV_STATE_NULL: + case PJSIP_INV_STATE_DISCONNECTED: + { + NotifyCall(call_id, VOICE_STATE_ENDED); + break; + } + case PJSIP_INV_STATE_CONFIRMED: + { + NotifyCall(call_id, VOICE_STATE_TALKING); + break; + } + } +} + + +bool SIPClient::on_call_media_state_sync(pjsua_call_id call_id, const pjsua_call_info &info) +{ + Trace(_T("on_call_media_state_sync: %d"), call_id); + + // Connect ports appropriately when media status is ACTIVE or REMOTE HOLD, + // otherwise we should NOT connect the ports. + if (info.media_status == PJSUA_CALL_MEDIA_ACTIVE || info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) + { + ConfigureDevices(); + + pjsua_conf_connect(info.conf_slot, 0); + pjsua_conf_connect(0, info.conf_slot); + } + + return true; +} + + +void SIPClient::on_call_media_state(pjsua_call_id call_id) +{ + Trace(_T("on_call_media_state: %d"), call_id); + + pjsua_call_info info; + pj_status_t status = pjsua_call_get_info(call_id, &info); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error obtaining call info")); + return; + } + + if (info.media_status == PJSUA_CALL_MEDIA_ACTIVE) + { + NotifyCall(call_id, VOICE_STATE_TALKING); + } + else if (info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) + { + NotifyCall(call_id, VOICE_STATE_ON_HOLD); + } +} + + +static int GetDevice(bool out, int defaultVal) +{ + DBVARIANT dbv; + if (DBGetContactSettingString(NULL, "VoiceService", out ? "Output" : "Input", &dbv)) + return defaultVal; + + int ret = defaultVal; + + unsigned count = pjmedia_aud_dev_count(); + for (unsigned i = 0; i < count; ++i) + { + pjmedia_aud_dev_info info; + + pj_status_t status = pjmedia_aud_dev_get_info(i, &info); + if (status != PJ_SUCCESS) + continue; + + if (!out && info.input_count < 1) + continue; + if (out && info.output_count < 1) + continue; + + if (strcmp(dbv.pszVal, info.name) == 0) + { + ret = i; + break; + } + } + + DBFreeVariant(&dbv); + return ret; +} + + +void SIPClient::ConfigureDevices() +{ + int input, output; + pjsua_get_snd_dev(&input, &output); + + int expectedInput = GetDevice(false, input); + int expectedOutput = GetDevice(true, output); + + if (input != expectedInput || output != expectedOutput) + pjsua_set_snd_dev(input, output); + + if (DBGetContactSettingByte(NULL, "VoiceService", "EchoCancelation", TRUE)) + pjsua_set_ec(PJSUA_DEFAULT_EC_TAIL_LEN, 0); + else + pjsua_set_ec(0, 0); +} + + +void SIPClient::CleanupURI(TCHAR *out, int outSize, const TCHAR *url) +{ + if (url[0] == _T('"')) + { + const TCHAR *other = _tcsstr(&url[1], _T("\" <")); + if (other != NULL) + url = other + 2; + } + + lstrcpyn(out, url, outSize); + + RemoveLtGt(out); + + TCHAR *info = _tcschr(out, _T(';')); + if (info != NULL) + *info = 0; + + RemoveLtGt(out); + + info = _tcschr(out, _T(';')); + if (info != NULL) + *info = 0; + + if (_tcsnicmp(_T("sip:"), out, 4) == 0) + lstrcpyn(out, &out[4], outSize); +} + + +void SIPClient::BuildURI(TCHAR *out, int outSize, const TCHAR *user, const TCHAR *host, int port, int protocol) +{ + if (protocol == PJSIP_TRANSPORT_UDP) + mir_sntprintf(out, outSize, _T(""), user, host, port); + else + mir_sntprintf(out, outSize, _T(""), user, host, + (const TCHAR *) TransportName((pjsip_transport_type_e) protocol)); +} + +pjsua_call_id SIPClient::Call(const TCHAR *username, const TCHAR *host, int port, int protocol) +{ + pjsua_acc_id acc_id; + switch(protocol) + { + case PJSIP_TRANSPORT_UDP: acc_id = udp.acc_id; break; + case PJSIP_TRANSPORT_TCP: acc_id = tcp.acc_id; break; + case PJSIP_TRANSPORT_TLS: acc_id = tls.acc_id; break; + default: return -1; + } + if (acc_id < 0) + return -1; + + TCHAR uri[1024]; + BuildURI(uri, MAX_REGS(uri), username, host, port, protocol); + + pjsua_call_id call_id; + pj_str_t ret; + pj_status_t status = pjsua_call_make_call(acc_id, pj_cstr(&ret, TcharToSip(uri)), 0, NULL, NULL, &call_id); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error making call")); + return -1; + } + + NotifyCall(call_id, VOICE_STATE_CALLING, username, uri); + + return call_id; +} + +int SIPClient::DropCall(pjsua_call_id call_id) +{ + if (call_id < 0 || call_id >= (int) pjsua_call_get_max_count()) + return 2; + + pj_status_t status = pjsua_call_hangup(call_id, 0, NULL, NULL); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error hanging up call")); + return -1; + } + + return 0; +} + +int SIPClient::HoldCall(pjsua_call_id call_id) +{ + if (call_id < 0 || call_id >= (int) pjsua_call_get_max_count()) + return 2; + + pj_status_t status = pjsua_call_set_hold(call_id, NULL); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error holding call")); + return -1; + } + + return 0; +} + +int SIPClient::AnswerCall(pjsua_call_id call_id) +{ + if (call_id < 0 || call_id >= (int) pjsua_call_get_max_count()) + return 2; + + pjsua_call_info info; + pj_status_t status = pjsua_call_get_info(call_id, &info); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error obtaining call info")); + return -1; + } + + if (info.state == PJSIP_INV_STATE_INCOMING) + { + pj_status_t status = pjsua_call_answer(call_id, 200, NULL, NULL); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error answering call")); + return -1; + } + } + else if (info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) + { + pj_status_t status = pjsua_call_reinvite(call_id, PJ_TRUE, NULL); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error un-holding call")); + return -1; + } + } + else + { + // Wrong state + return 3; + } + + return 0; +} + +int SIPClient::SendDTMF(pjsua_call_id call_id, TCHAR dtmf) +{ + if (call_id < 0 || call_id >= (int) pjsua_call_get_max_count()) + return 2; + + if (dtmf >= _T('a') && dtmf <= _T('d')) + dtmf += _T('A') - _T('a'); + + if (!IsValidDTMF(dtmf)) + return 3; + + TCHAR tmp[2]; + tmp[0] = dtmf; + tmp[1] = 0; + + pj_str_t ret; + pjsua_call_dial_dtmf(call_id, pj_cstr(&ret, TcharToSip(tmp))); + + return 0; +} diff --git a/Protocols/SIP/SIPClient.h b/Protocols/SIP/SIPClient.h new file mode 100644 index 0000000..275d032 --- /dev/null +++ b/Protocols/SIP/SIPClient.h @@ -0,0 +1,87 @@ +/* +Copyright (C) 2009 Ricardo Pescuma Domenecci + +This is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this file; see the file license.txt. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +*/ + +#ifndef __SIPCLIENT_H__ +#define __SIPCLIENT_H__ + + +class SIPClient +{ +private: + HANDLE hNetlibUser; + bool hasToDestroy; + +public: + struct ta { + pjsua_transport_id transport_id; + pjsua_acc_id acc_id; + int port; + }; + + ta udp; + ta tcp; + ta tls; + + SIPClientCallback callback; + void *callback_param; + + char name[512]; + TCHAR username[16]; + TCHAR host[256]; + + CRITICAL_SECTION cs; + std::vector events; + + SIPClient(SIP_REGISTRATION *reg); + virtual ~SIPClient(); + + void on_incoming_call(pjsua_call_id call_id); + void on_call_state(pjsua_call_id call_id, const pjsua_call_info &info); + bool on_call_media_state_sync(pjsua_call_id call_id, const pjsua_call_info &info); + void on_call_media_state(pjsua_call_id call_id); + + pjsua_call_id Call(const TCHAR *username, const TCHAR *host, int port, int protocol); + int DropCall(pjsua_call_id call_id); + int HoldCall(pjsua_call_id call_id); + int AnswerCall(pjsua_call_id call_id); + int SendDTMF(pjsua_call_id call_id, TCHAR dtmf); + + int Connect(int udp_port, int tcp_port, int tls_port); + void Disconnect(); + +private: + void Trace(TCHAR *fmt, ...); + void Info(TCHAR *fmt, ...); + void Error(TCHAR *fmt, ...); + void Error(pj_status_t status, TCHAR *fmt, ...); + void ShowMessage(int type, TCHAR *fmt, va_list args); + + void RegisterTransport(pjsip_transport_type_e type, int port, ta *ta); + + void ConfigureDevices(); + void BuildURI(TCHAR *out, int outSize, const TCHAR *user, const TCHAR *host, int port, int protocol); + void CleanupURI(TCHAR *out, int outSize, const TCHAR *url); + + void NotifyCall(pjsua_call_id call_id, int state, const TCHAR *name = NULL, const TCHAR *uri = NULL); +}; + + + + +#endif // __SIPCLIENT_H__ diff --git a/Protocols/SIP/SIPProto.cpp b/Protocols/SIP/SIPProto.cpp index 7d5e84d..9a1c949 100644 --- a/Protocols/SIP/SIPProto.cpp +++ b/Protocols/SIP/SIPProto.cpp @@ -26,140 +26,12 @@ static INT_PTR CALLBACK DlgProcAccMgrUI(HWND hwndDlg, UINT msg, WPARAM wParam, L static INT_PTR CALLBACK DlgOptions(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); -#define TcharToSip TcharToUtf8 - -class SipToTchar -{ -public: - SipToTchar(const pj_str_t &str) : tchar(NULL) - { - if (str.ptr == NULL) - return; - - int size = MultiByteToWideChar(CP_UTF8, 0, str.ptr, str.slen, NULL, 0); - if (size < 0) - throw _T("Could not convert string to WCHAR"); - size++; - - WCHAR *tmp = (WCHAR *) mir_alloc(size * sizeof(WCHAR)); - if (tmp == NULL) - throw _T("mir_alloc returned NULL"); - - MultiByteToWideChar(CP_UTF8, 0, str.ptr, str.slen, tmp, size); - tmp[size-1] = 0; - -#ifdef UNICODE - - tchar = tmp; - -#else - - size = WideCharToMultiByte(CP_ACP, 0, tmp, -1, NULL, 0, NULL, NULL); - if (size <= 0) - { - mir_free(tmp); - throw _T("Could not convert string to ACP"); - } - - tchar = (TCHAR *) mir_alloc(size * sizeof(char)); - if (tchar == NULL) - { - mir_free(tmp); - throw _T("mir_alloc returned NULL"); - } - - WideCharToMultiByte(CP_ACP, 0, tmp, -1, tchar, size, NULL, NULL); - - mir_free(tmp); - -#endif - } - - ~SipToTchar() - { - if (tchar != NULL) - mir_free(tchar); - } - - TCHAR *detach() - { - TCHAR *ret = tchar; - tchar = NULL; - return ret; - } - - TCHAR * get() const - { - return tchar; - } - - operator TCHAR *() const - { - return tchar; - } - - const TCHAR & operator[](int pos) const - { - return tchar[pos]; - } - -private: - TCHAR *tchar; -}; - - -static pj_str_t pj_str(const char *str) -{ - pj_str_t ret; - pj_cstr(&ret, str); - return ret; -} - - -static char * mir_pjstrdup(const pj_str_t *from) -{ - char *ret = (char *) mir_alloc(from->slen + 1); - strncpy(ret, from->ptr, from->slen); - ret[from->slen] = 0; - return ret; -} - - -static int FirstGtZero(int n1, int n2) -{ - if (n1 > 0) - return n1; - return n2; -} - - -static TCHAR *CleanupSip(TCHAR *str) -{ - if (_tcsnicmp(_T("sip:"), str, 4) == 0) - return &str[4]; - else - return str; -} - - -static const TCHAR *CleanupSip(const TCHAR *str) -{ - if (_tcsnicmp(_T("sip:"), str, 4) == 0) - return &str[4]; - else - return str; -} - - - - - SIPProto::SIPProto(const char *aProtoName, const TCHAR *aUserName) { - InitializeCriticalSection(&cs); - hasToDestroy = false; - transport_id = -1; + udp_transport_id = -1; + tcp_transport_id = -1; + tls_transport_id = -1; acc_id = -1; hNetlibUser = 0; hCallStateEvent = 0; @@ -169,6 +41,18 @@ SIPProto::SIPProto(const char *aProtoName, const TCHAR *aUserName) memset(awayMessages, 0, sizeof(awayMessages)); + { + pj_status_t status = pjsua_create(); + if (status != PJ_SUCCESS) + { + Error(status, _T("Error creating pjsua")); + throw "Error creating pjsua"; + } + hasToDestroy = true; + } + + InitializeCriticalSection(&cs); + m_tszUserName = mir_tstrdup(aUserName); m_szProtoName = mir_strdup(aProtoName); m_szModuleName = mir_strdup(aProtoName); @@ -624,23 +508,15 @@ int SIPProto::Connect() BroadcastStatus(ID_STATUS_CONNECTING); - { - pj_status_t status = pjsua_create(); - if (status != PJ_SUCCESS) - { - Error(status, _T("Error creating pjsua")); - return -1; - } - hasToDestroy = true; - } - { scoped_mir_free stun; scoped_mir_free dns; pjsua_config cfg; pjsua_config_default(&cfg); - //cfg.use_srtp = PJMEDIA_SRTP_OPTIONAL; +#ifndef DEBUG + cfg.use_srtp = PJMEDIA_SRTP_OPTIONAL; +#endif cfg.cb.on_incoming_call = &static_on_incoming_call; cfg.cb.on_call_media_state = &static_on_call_media_state; cfg.cb.on_call_state = &static_on_call_state; @@ -681,7 +557,11 @@ int SIPProto::Connect() pjsua_logging_config_default(&log_cfg); log_cfg.cb = &static_on_log; - pj_status_t status = pjsua_init(&cfg, &log_cfg, NULL); + pjsua_media_config media_cfg; + pjsua_media_config_default(&media_cfg); + media_cfg.enable_ice = PJ_TRUE; + + pj_status_t status = pjsua_init(&cfg, &log_cfg, &media_cfg); if (status != PJ_SUCCESS) { Error(status, _T("Error initializing pjsua")); @@ -693,13 +573,21 @@ int SIPProto::Connect() { pjsua_transport_config cfg; pjsua_transport_config_default(&cfg); - pj_status_t status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, &transport_id); + pj_status_t status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, &udp_transport_id); if (status != PJ_SUCCESS) { - Error(status, _T("Error creating transport")); + Error(status, _T("Error creating UDP transport")); Disconnect(); return 2; } + + status = pjsua_transport_create(PJSIP_TRANSPORT_TCP, &cfg, &tcp_transport_id); + if (status != PJ_SUCCESS) + Error(status, _T("Error creating TCP transport")); + + status = pjsua_transport_create(PJSIP_TRANSPORT_TLS, &cfg, &udp_transport_id); + if (status != PJ_SUCCESS) + Error(status, _T("Error creating TLS transport")); } { @@ -720,8 +608,9 @@ int SIPProto::Connect() pjsua_acc_config cfg; pjsua_acc_config_default(&cfg); cfg.user_data = this; - cfg.transport_id = transport_id; - //cfg.use_srtp = PJMEDIA_SRTP_OPTIONAL; +#ifndef DEBUG + cfg.use_srtp = PJMEDIA_SRTP_OPTIONAL; +#endif BuildURI(tmp, MAX_REGS(tmp), opts.username, opts.domain); TcharToSip id(tmp); @@ -1018,17 +907,16 @@ void SIPProto::Disconnect() acc_id = -1; } - if (transport_id >= 0) - { - pjsua_transport_close(transport_id, PJ_FALSE); - transport_id = -1; +#define CLOSE_TRANSPORT(_T_) \ + if (_T_ >= 0) \ + { \ + pjsua_transport_close(_T_, PJ_FALSE); \ + _T_ = -1; \ } - if (hasToDestroy) - { - pjsua_destroy(); - hasToDestroy = false; - } + CLOSE_TRANSPORT(udp_transport_id) + CLOSE_TRANSPORT(tcp_transport_id) + CLOSE_TRANSPORT(tls_transport_id) for(HANDLE hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); hContact != NULL; @@ -1108,6 +996,13 @@ int __cdecl SIPProto::OnOptionsInit(WPARAM wParam, LPARAM lParam) int __cdecl SIPProto::OnPreShutdown(WPARAM wParam, LPARAM lParam) { + if (hasToDestroy) + { + pjsua_destroy(); + hasToDestroy = false; + } + + return 0; } @@ -1209,16 +1104,6 @@ void SIPProto::on_reg_state() } } -static void RemoveLtGt(TCHAR *str) -{ - int len = lstrlen(str); - if (str[0] == _T('<') && str[len-1] == _T('>')) - { - str[len-1] = 0; - memmove(str, &str[1], len * sizeof(TCHAR)); - } -} - void SIPProto::on_incoming_call(pjsua_call_id call_id) { Trace(_T("on_incoming_call: %d"), call_id); @@ -1382,23 +1267,6 @@ int __cdecl SIPProto::VoiceCaps(WPARAM wParam,LPARAM lParam) } -static void CleanupNumber(TCHAR *out, int outSize, const TCHAR *number) -{ - int pos = 0; - int len = lstrlen(number); - for(int i = 0; i < len && pos < outSize - 1; ++i) - { - TCHAR c = number[i]; - - if (i == 0 && c == _T('+')) - out[pos++] = c; - else if (c >= _T('0') && c <= _T('9')) - out[pos++] = c; - } - out[pos] = 0; -} - - void SIPProto::CleanupURI(TCHAR *out, int outSize, const TCHAR *url) { if (url[0] == _T('"')) @@ -1613,19 +1481,6 @@ int __cdecl SIPProto::VoiceHoldCall(WPARAM wParam, LPARAM lParam) } -static bool IsValidDTMF(TCHAR c) -{ - if (c >= _T('A') && c <= _T('D')) - return true; - if (c >= _T('0') && c <= _T('9')) - return true; - if (c == _T('#') || c == _T('*')) - return true; - - return false; -} - - int __cdecl SIPProto::VoiceSendDTMF(WPARAM wParam, LPARAM lParam) { char *id = (char *) wParam; diff --git a/Protocols/SIP/SIPProto.h b/Protocols/SIP/SIPProto.h index 5c449de..5d3797f 100644 --- a/Protocols/SIP/SIPProto.h +++ b/Protocols/SIP/SIPProto.h @@ -27,38 +27,6 @@ typedef INT_PTR (__cdecl SIPProto::*SIPServiceFunc)(WPARAM, LPARAM); typedef INT_PTR (__cdecl SIPProto::*SIPServiceFuncParam)(WPARAM, LPARAM, LPARAM); typedef int (__cdecl SIPProto::*SIPEventFunc)(WPARAM, LPARAM); -struct MessageData -{ - HANDLE hContact; - LONG messageID; - pjsip_status_code status; -}; - -struct SIPEvent -{ - enum { - reg_state, - incoming_call, - call_state, - call_media_state, - incoming_subscribe, - buddy_state, - pager, - pager_status, - typing - - } type; - - pjsua_call_id call_id; - pjsua_call_info call_info; - pjsua_buddy_id buddy_id; - char *from; - char *text; - char *mime; - bool isTyping; - MessageData *messageData; - pjsua_srv_pres *srv_pres; -}; class SIPProto : public PROTO_INTERFACE { @@ -66,7 +34,9 @@ private: HANDLE hNetlibUser; HANDLE hCallStateEvent; bool hasToDestroy; - pjsua_transport_id transport_id; + pjsua_transport_id udp_transport_id; + pjsua_transport_id tcp_transport_id; + pjsua_transport_id tls_transport_id; pjsua_acc_id acc_id; LONG messageID; LONG awayMessageID; @@ -228,9 +198,6 @@ private: void __cdecl GetAwayMsgThread(void* arg); TCHAR *GetAwayMsg(int status); INT_PTR __cdecl GetMyAwayMsg(WPARAM wParam, LPARAM lParam); - - // Static callbacks - static void CALLBACK DisconnectProto(void *param); }; diff --git a/Protocols/SIP/commons.h b/Protocols/SIP/commons.h index 47b4cfd..1fb4108 100644 --- a/Protocols/SIP/commons.h +++ b/Protocols/SIP/commons.h @@ -80,7 +80,46 @@ Boston, MA 02111-1307, USA. #include "../../plugins/voiceservice/m_voiceservice.h" #include "resource.h" +#include "strutils.h" + + +struct MessageData +{ + HANDLE hContact; + LONG messageID; + pjsip_status_code status; +}; + +struct SIPEvent +{ + enum { + reg_state, + incoming_call, + call_state, + call_media_state, + incoming_subscribe, + buddy_state, + pager, + pager_status, + typing + + } type; + + pjsua_call_id call_id; + pjsua_call_info call_info; + pjsua_buddy_id buddy_id; + char *from; + char *text; + char *mime; + bool isTyping; + MessageData *messageData; + pjsua_srv_pres *srv_pres; +}; + + +#include "m_sip.h" #include "SIPProto.h" +#include "SIPClient.h" #define MODULE_NAME "SIP" diff --git a/Protocols/SIP/m_sip.h b/Protocols/SIP/m_sip.h new file mode 100644 index 0000000..22b9e03 --- /dev/null +++ b/Protocols/SIP/m_sip.h @@ -0,0 +1,98 @@ +/* +Copyright (C) 2010 Ricardo Pescuma Domenecci + +This is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this file; see the file license.txt. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +*/ + + +#ifndef __M_SIP_H__ +# define __M_SIP_H__ + + +// state is a VOICE_STATE_* +typedef void (*SIPClientCallback)(void *param, int callId, int state, const TCHAR *name, const TCHAR *uri); + +struct SIP_REGISTRATION +{ + int cbSize; + const char *name; // Internal name of client + const TCHAR *username; + int udp_port; // UDP port to be used: 0 means random, -1 means don't want UDP + int tcp_port; // UDP port to be used: 0 means TCP, -1 means don't want TCP + int tls_port; // UDP port to be used: 0 means TLS, -1 means don't want TLS + + HANDLE hNetlib; // To be used for logs. Can be 0 + + SIPClientCallback callback; + void *callback_param; +}; + + +struct SIP_CLIENT +{ + void *data; // Do not touch + const TCHAR *username; + const TCHAR *host; + const int udp_port; + const int tcp_port; + const int tls_port; + + // @param protocol 1 UDP, 2 TCP, 3 TLS + // @return callId or <0 on error + int (*Call)(SIP_CLIENT *sip, const TCHAR *username, const TCHAR *host, int port, int protocol); + + // @return 0 on success + int (*DropCall)(SIP_CLIENT *sip, int callId); + + // @return 0 on success + int (*HoldCall)(SIP_CLIENT *sip, int callId); + + // @return 0 on success + int (*AnswerCall)(SIP_CLIENT *sip, int callId); + + // @return 0 on success + int (*SendDTMF)(SIP_CLIENT *sip, int callId, TCHAR dtmf); +}; + + +/* +Register a SIP client, allowing it to make calls + +wParam = SIP_REGISTRATION * +lParam = 0 +return SIP_CLIENT * or NULL on error +*/ +#define MS_SIP_REGISTER "SIP/Client/Register" + + +/* +Unregister a SIP client and free the internal structures. + +wParam = SIP_CLIENT * +lParam = 0 +return 0 on success +*/ +#define MS_SIP_UNREGISTER "SIP/Client/Unregister" + + + + + + + + + +#endif // __M_SIP_H__ diff --git a/Protocols/SIP/sip.cpp b/Protocols/SIP/sip.cpp index cb3dc36..4bfaeff 100644 --- a/Protocols/SIP/sip.cpp +++ b/Protocols/SIP/sip.cpp @@ -57,6 +57,9 @@ std::vector hHooks; int ModulesLoaded(WPARAM wParam, LPARAM lParam); int PreShutdown(WPARAM wParam, LPARAM lParam); +static INT_PTR ClientRegister(WPARAM wParam, LPARAM lParam); +static INT_PTR ClientUnregister(WPARAM wParam, LPARAM lParam); + static int sttCompareProtocols(const SIPProto *p1, const SIPProto *p2) { @@ -106,9 +109,16 @@ static SIPProto *SIPProtoInit(const char* pszProtoName, const TCHAR* tszUserName return NULL; } - SIPProto *proto = new SIPProto(pszProtoName, tszUserName); - instances.insert(proto); - return proto; + try + { + SIPProto *proto = new SIPProto(pszProtoName, tszUserName); + instances.insert(proto); + return proto; + } + catch(const char *) + { + return NULL; + } } @@ -192,6 +202,9 @@ extern "C" int __declspec(dllexport) Load(PLUGINLINK *link) pd.type = PROTOTYPE_PROTOCOL; CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM) &pd); + CreateServiceFunction(MS_SIP_REGISTER, ClientRegister); + CreateServiceFunction(MS_SIP_UNREGISTER, ClientUnregister); + return 0; } @@ -248,3 +261,96 @@ int PreShutdown(WPARAM wParam, LPARAM lParam) return 0; } + + +static int ClientCall(SIP_CLIENT *sip, const TCHAR *username, const TCHAR *host, int port, int protocol) +{ + if (sip == NULL || sip->data == NULL) + return -1; + + SIPClient *cli = (SIPClient *) sip->data; + return (int) cli->Call(username, host, port, protocol); +} + +static int ClientDropCall(SIP_CLIENT *sip, int callId) +{ + if (sip == NULL || sip->data == NULL) + return -1; + + SIPClient *cli = (SIPClient *) sip->data; + return (int) cli->DropCall((pjsua_call_id) callId); +} + +static int ClientHoldCall(SIP_CLIENT *sip, int callId) +{ + if (sip == NULL || sip->data == NULL) + return -1; + + SIPClient *cli = (SIPClient *) sip->data; + return (int) cli->HoldCall((pjsua_call_id) callId); +} + +static int ClientAnswerCall(SIP_CLIENT *sip, int callId) +{ + if (sip == NULL || sip->data == NULL) + return -1; + + SIPClient *cli = (SIPClient *) sip->data; + return (int) cli->AnswerCall((pjsua_call_id) callId); +} + +static int ClientSendDTMF(SIP_CLIENT *sip, int callId, TCHAR dtmf) +{ + if (sip == NULL || sip->data == NULL) + return -1; + + SIPClient *cli = (SIPClient *) sip->data; + return (int) cli->SendDTMF((pjsua_call_id) callId, dtmf); +} + +static INT_PTR ClientRegister(WPARAM wParam, LPARAM lParam) +{ + SIP_REGISTRATION *reg = (SIP_REGISTRATION *) wParam; + if (reg == NULL || reg->cbSize < sizeof(SIP_REGISTRATION)) + return NULL; + + if (reg->name[0] == 0 || reg->username[0] == 0) + return NULL; + + SIPClient *cli = new SIPClient(reg); + if (cli->Connect(reg->udp_port, reg->tcp_port, reg->tls_port) != 0) + { + cli->Disconnect(); + delete cli; + return NULL; + } + + SIP_CLIENT *ret = (SIP_CLIENT *) mir_alloc0(sizeof(SIP_CLIENT) + sizeof(SIPClient *)); + ret->data = cli; + * (TCHAR **) & ret->username = cli->username; + * (TCHAR **) & ret->host = cli->host; + * (int *) & ret->udp_port = cli->udp.port; + * (int *) & ret->tcp_port = cli->tcp.port; + * (int *) & ret->tls_port = cli->tls.port; + ret->Call = &ClientCall; + ret->DropCall = &ClientDropCall; + ret->HoldCall = &ClientHoldCall; + ret->AnswerCall = &ClientAnswerCall; + ret->SendDTMF = &ClientSendDTMF; + + return (INT_PTR) ret; +} + +static INT_PTR ClientUnregister(WPARAM wParam, LPARAM lParam) +{ + SIP_CLIENT *sc = (SIP_CLIENT *) wParam; + if (sc == NULL || sc->data == NULL) + return 1; + + SIPClient * cli = (SIPClient *) sc->data; + cli->Disconnect(); + delete cli; + mir_free(sc); + + return 0; +} diff --git a/Protocols/SIP/sip.vcproj b/Protocols/SIP/sip.vcproj index 1d867aa..ddb8396 100644 --- a/Protocols/SIP/sip.vcproj +++ b/Protocols/SIP/sip.vcproj @@ -424,6 +424,10 @@ RelativePath="commons.h" > + + @@ -444,10 +448,18 @@ RelativePath="resource.h" > + + + + + + diff --git a/Protocols/SIP/strutils.h b/Protocols/SIP/strutils.h new file mode 100644 index 0000000..b534ac2 --- /dev/null +++ b/Protocols/SIP/strutils.h @@ -0,0 +1,200 @@ +/* +Copyright (C) 2009 Ricardo Pescuma Domenecci + +This is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this file; see the file license.txt. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +*/ + +#pragma once +#ifndef __STRUTILS_H__ +#define __STRUTILS_H__ + + +#define TcharToSip TcharToUtf8 + +class SipToTchar +{ +public: + SipToTchar(const pj_str_t &str) : tchar(NULL) + { + if (str.ptr == NULL) + return; + + int size = MultiByteToWideChar(CP_UTF8, 0, str.ptr, str.slen, NULL, 0); + if (size < 0) + throw _T("Could not convert string to WCHAR"); + size++; + + WCHAR *tmp = (WCHAR *) mir_alloc(size * sizeof(WCHAR)); + if (tmp == NULL) + throw _T("mir_alloc returned NULL"); + + MultiByteToWideChar(CP_UTF8, 0, str.ptr, str.slen, tmp, size); + tmp[size-1] = 0; + +#ifdef UNICODE + + tchar = tmp; + +#else + + size = WideCharToMultiByte(CP_ACP, 0, tmp, -1, NULL, 0, NULL, NULL); + if (size <= 0) + { + mir_free(tmp); + throw _T("Could not convert string to ACP"); + } + + tchar = (TCHAR *) mir_alloc(size * sizeof(char)); + if (tchar == NULL) + { + mir_free(tmp); + throw _T("mir_alloc returned NULL"); + } + + WideCharToMultiByte(CP_ACP, 0, tmp, -1, tchar, size, NULL, NULL); + + mir_free(tmp); + +#endif + } + + ~SipToTchar() + { + if (tchar != NULL) + mir_free(tchar); + } + + TCHAR *detach() + { + TCHAR *ret = tchar; + tchar = NULL; + return ret; + } + + TCHAR * get() const + { + return tchar; + } + + operator TCHAR *() const + { + return tchar; + } + + const TCHAR & operator[](int pos) const + { + return tchar[pos]; + } + +private: + TCHAR *tchar; +}; + + +static pj_str_t pj_str(const char *str) +{ + pj_str_t ret; + pj_cstr(&ret, str); + return ret; +} + + +static char * mir_pjstrdup(const pj_str_t *from) +{ + char *ret = (char *) mir_alloc(from->slen + 1); + strncpy(ret, from->ptr, from->slen); + ret[from->slen] = 0; + return ret; +} + + +static int FirstGtZero(int n1, int n2) +{ + if (n1 > 0) + return n1; + return n2; +} + + +static TCHAR *CleanupSip(TCHAR *str) +{ + if (_tcsnicmp(_T("sip:"), str, 4) == 0) + return &str[4]; + else + return str; +} + + +static const TCHAR *CleanupSip(const TCHAR *str) +{ + if (_tcsnicmp(_T("sip:"), str, 4) == 0) + return &str[4]; + else + return str; +} + + +static void CleanupNumber(TCHAR *out, int outSize, const TCHAR *number) +{ + int pos = 0; + int len = lstrlen(number); + for(int i = 0; i < len && pos < outSize - 1; ++i) + { + TCHAR c = number[i]; + + if (i == 0 && c == _T('+')) + out[pos++] = c; + else if (c >= _T('0') && c <= _T('9')) + out[pos++] = c; + } + out[pos] = 0; +} + + +static void RemoveLtGt(TCHAR *str) +{ + int len = lstrlen(str); + if (str[0] == _T('<') && str[len-1] == _T('>')) + { + str[len-1] = 0; + memmove(str, &str[1], len * sizeof(TCHAR)); + } +} + + +static bool IsValidDTMF(TCHAR c) +{ + if (c >= _T('A') && c <= _T('D')) + return true; + if (c >= _T('0') && c <= _T('9')) + return true; + if (c == _T('#') || c == _T('*')) + return true; + + return false; +} + + +static const pj_str_t pj_cstr(const char *s) +{ + pj_str_t str; + str.ptr = (char*)s; + str.slen = s ? (pj_ssize_t)strlen(s) : 0; + return str; +} + + +#endif // __STRUTILS_H__ -- cgit v1.2.3