/*-----------------------------------------------------------------------------------------------*/ /* signaling.cpp */ /* copyright (c) innovaphone 2020 */ /* */ /*-----------------------------------------------------------------------------------------------*/ #include #include "sdk/common/os/iomux.h" #include "sdk/common/interface/time.h" #include "sdk/common/interface/random.h" #include "sdk/common/interface/task.h" #include "sdk/common/interface/stask.h" #include "sdk/common/interface/socket.h" #include "sdk/common/interface/webserver_plugin.h" #include "sdk/common/interface/json_api.h" #include "sdk/common/interface/appwebsocket_client.h" #include "sdk/common/interface/dns.h" #include "sdk/common/interface/media.h" #include "sdk/common/interface/media_tones.h" #include "sdk/common/interface/audio.h" #include "sdk/common/interface/ringer.h" #include "sdk/common/interface/url_handler.h" #include "sdk/common/interface/remote_services.h" #include "sdk/common/lib/appservice.h" #include "sdk/common/lib/appwebsocket.h" #include "sdk/common/lib/app_updates.h" #include "sdk/common/interface/guuid.h" #include "sdk/common/interface/file.h" #include "sdk/common/lib/tasks_file.h" #include "sdk/common/interface/local_storage.h" #include "common/ilib/json.h" #include "common/ilib/uri.h" #include "common/ilib/str.h" #include "sdk/common/interface/http_client.h" #include "signaling_api.h" #include "signaling.h" #define TIMEOUT_MIN 1000 #define TIMEOUT_MAX 30000 #define DELETE_OBJ(obj) { if (obj) { delete obj; obj = 0; } } enum { presenceSubscriptionCallId = 1, diversionCallId = 2, firstCallId = 3 }; static const char * CoderName(int type); static int CoderPt(int type); static int CoderType(const char * name); static const char * CandidateName(int type); static int CandidateType(const char * name); static const char * ChannelsCmdName(unsigned type); static int ChannelsCmdType(const char * name); static char charPool[] = { // 64 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 }; SignalingMediaConfig::SignalingMediaConfig( ISocketProvider * udpSocketProvider, ISocketProvider * tcpSocketProvider, ISocketProvider * tlsSocketProvider, class ISocketContext * socketContext, word minPort, word maxPort, word minVideoPort, word maxVideoPort, const char * stunServers, const char * turnServers, const char * turnUsername, const char * turnPassword, enum MediaType media, bool stunSlow, bool turnOnly, bool iceNoHost, bool recordRtpStream, int dropMediaTx, int dropMediaRx, const char * hostPbx, bool noVpnAddresses, const char * recordingUrl, bool isRecordingByDefaultOn, bool recordExternalOnly) { this->udpSocketProvider = udpSocketProvider; this->tcpSocketProvider = tcpSocketProvider; this->tlsSocketProvider = tlsSocketProvider; this->socketContext = socketContext; this->minPort = minPort; this->maxPort = maxPort; this->minVideoPort = minVideoPort; this->maxVideoPort = maxVideoPort; this->stunServers = _strdup(stunServers); this->turnServers = _strdup(turnServers); this->turnUsername = _strdup(turnUsername); this->turnPassword = _strdup(turnPassword); this->media = media; this->stunSlow = stunSlow; this->turnOnly = turnOnly; this->iceNoHost = iceNoHost; this->dropMediaTx = dropMediaTx; this->dropMediaRx = dropMediaRx; this->hostPbx = _strdup(hostPbx); this->noVpnAddresses = noVpnAddresses; this->recordingUrl = _strdup(recordingUrl); this->isRecordingByDefaultOn = isRecordingByDefaultOn; this->recordExternalOnly = recordExternalOnly; this->recordRtpStream = recordRtpStream; } /*-----------------------------------------------------------------------------------------------*/ /* class SignalingService */ /*-----------------------------------------------------------------------------------------------*/ class ISignalingService * ISignalingService::CreateSignaling(class IIoMux * const iomux, class ISocketProvider * localSocketProvider, class ISocketProvider * tcpSocketProvider, class ISocketProvider * tlsSocketProvider, IWebserverPluginProvider * const webserverPluginProvider, IAppWebsocketClientProvider * appWebsocketClientProvider, IMediaProvider * mediaProvider, SignalingMediaConfig * mediaConfig, IAudioIo * audioIo, IRingerIo * ringerIo, IVideoIo * videoIo, IRemoteServices * remoteServices, AppServiceArgs * args) { return new SignalingService(iomux, localSocketProvider, tcpSocketProvider, tlsSocketProvider, webserverPluginProvider, appWebsocketClientProvider, mediaProvider, mediaConfig, audioIo, ringerIo, videoIo, remoteServices, args); } SignalingService::SignalingService(class IIoMux * const iomux, class ISocketProvider * localSocketProvider, class ISocketProvider * tcpSocketProvider, class ISocketProvider * tlsSocketProvider, class IWebserverPluginProvider * const webserverPluginProvider, class IAppWebsocketClientProvider * appWebsocketClientProvider, IMediaProvider * mediaProvider, SignalingMediaConfig * mediaConfig, IAudioIo * audioIo, IRingerIo * ringerIo, IVideoIo * videoIo, IRemoteServices * remoteServices, AppServiceArgs * args) : AppService(iomux, localSocketProvider, args) { this->iomux = iomux; this->localSocketProvider = localSocketProvider; this->tcpSocketProvider = tcpSocketProvider; this->tlsSocketProvider = tlsSocketProvider; this->webserverPluginProvider = webserverPluginProvider; this->appWebsocketClientProvider = appWebsocketClientProvider; this->mediaProvider = mediaProvider; this->mediaConfig = mediaConfig; this->audioIo = audioIo; this->ringerIo = ringerIo; this->videoIo = videoIo; this->remoteServices = remoteServices; } SignalingService::~SignalingService() { } void SignalingService::IAppStart(AppInstanceArgs * args) { AppStart(args); } class AppInstance * SignalingService::CreateInstance(AppInstanceArgs * args) { debug->printf("%x:SignalingService::CreateInstance()", this ); return new Signaling(iomux, 0, this, args); } class ISignaling * SignalingService::ICreateInstance(class USignaling * user, AppInstanceArgs * args) { debug->printf("%x:SignalingService::ICreateInstance()", this ); return new Signaling(iomux, user, this, args); } void SignalingService::AppServiceApps(istd::list * appList) { } /*-----------------------------------------------------------------------------------------------*/ /* class Signaling */ /*-----------------------------------------------------------------------------------------------*/ ISignaling::ISignaling(class USignaling * user, class AppService * appService, AppInstanceArgs * args) : AppInstance(appService, args) { } Signaling::Signaling(IIoMux * const iomux, class USignaling * user, class SignalingService * service, AppInstanceArgs * args) : ISignaling(user, service, args), AppUpdates(iomux), SignalingRcc(this) { debug->printf("%x:Signaling::Signaling user is: %s", this, user ? "true" : "false "); this->user = user; this->service = service; this->log = this; if (args->webserver) { webserverPlugin = service->webserverPluginProvider->CreateWebserverPlugin(iomux, service->localSocketProvider, this, args->webserver, args->webserverPath, this); webserverPlugin->HttpListen(0, 0, 0, 0, _BUILD_STRING_); webserverPlugin->WebsocketListen(); } else webserverPlugin = NULL; pwd = _strdup(args->appPassword); stopping = false; client = 0; registration = 0; rtptp4 = 0; rtptp6 = 0; videoLicense = false; recordingLicense = false; startVideo = false; callWaiting = false; onhookTransfer = false; clir = false; uiRunning = 0; autoHangup = AUTOHANGUP_OFF; hookDeviceState = 0; defaultAudioDeviceId = 0; changedAudioDeviceId = 0; videoDeviceId = 0; ringerDeviceId = 0; ringtoneId = 0; testAudioDeviceId = 0; testRingerDeviceId = 0; testVideoDeviceId = 0; localIpAddr = 0; recordingUrlSet = false; lastStartCallDest = 0; class IVideoIo* videoIo = GetVideoIo(); if (videoIo) { videoIo->SetUSelectDeviceIoUser(this); videoIo->QueryDevices(NULL); videoIo->SubscribeApplications(); } class IRingerIo* ringerIo = GetRingerIo(); if (ringerIo) { ringerIo->SetUSelectDeviceIoUser(this); ringerIo->QueryDevices(NULL); ringerIo->QueryRingtones(NULL); } class IAudioIo* audioIo = GetAudioIo(); if (audioIo) { audioIo->SetUSelectDeviceIoUser(this); audioIo->AddUHookDeviceUser(this); audioIo->QueryDevices(NULL); } service->signaling = this; service->mediaConfig->recordingUrl = ILocalStorage::GetItem("recordingUrl"); if (service->mediaConfig->recordingUrl && (strlen(service->mediaConfig->recordingUrl) <= 1)) { free(service->mediaConfig->recordingUrl); service->mediaConfig->recordingUrl = NULL; } this->workingPath = nullptr; this->url = NULL; this->sec = NULL; this->phys = NULL; this->app = NULL; this->usr = NULL; this->userPwd = NULL; this->domain = NULL; this->appdomain = NULL; this->localInit = false; this->log->Log(LOG_SIGNALING, "%x:App instance started %s", this, service->GetAppServiceId()); } Signaling::~Signaling() { this->log->Log(LOG_SIGNALING, "Signaling(%p)::~Signaling", this); if (service->mediaConfig->recordingUrl) { free(service->mediaConfig->recordingUrl); service->mediaConfig->recordingUrl = NULL; } if (webserverPlugin) delete webserverPlugin; free(pwd); if(defaultAudioDeviceId) free(defaultAudioDeviceId); if(videoDeviceId) free(videoDeviceId); if(ringerDeviceId) free(ringerDeviceId); if(ringtoneId) free(ringtoneId); if(testAudioDeviceId) free(testAudioDeviceId); if(testRingerDeviceId) free(testRingerDeviceId); if(testVideoDeviceId) free(testVideoDeviceId); if(localIpAddr) free(localIpAddr); if(lastStartCallDest) delete lastStartCallDest; if(this->url) free(this->url); if(this->sec) free(this->sec); if(this->phys) free(this->phys); if(this->app) free(this->app); if(this->usr) free(this->usr); if(this->userPwd) free(this->userPwd); if(this->domain) free(this->domain); if(this->appdomain) free(this->appdomain); } void Signaling::SetSignalingUser(class USignaling* user) { debug->printf("%x:Signaling::SetSignalingUser user:%x", this, user); this->user = user; } void Signaling::RegisterToAltPbx() { log->Log(LOG_SIGNALING, "%x:Signaling::RegisterToAltPbx", this); if(client) client->RegisterToAltPbx(); } void Signaling::RegisterToRedirectUrl(const char* url) { log->Log(LOG_SIGNALING, "%x:Signaling::RegisterToRedirectUrl url=%s", this, url ? url : ""); if (client) client->RegisterToRedirectUrl(url); } void Signaling::SetClient(const char * url, const char * sec, const char * phys, const char * app, const char * usr, const char * pwd, const char * domain, const char * appdomain, unsigned timeout, bool localInit) { log->Log(LOG_SIGNALING, "%x:Signaling::SetClient app: %s", this, app ? app : ""); if (!client) client = new SignalingClient(this); client->Update(url, sec, phys, app, usr, pwd, domain, appdomain, timeout, localInit); } void Signaling::CloseClient() { log->Log(LOG_SIGNALING, "%x:Signaling::CloseClient client=%p registration=%p", this, client, registration); if (client) { client->Close(); if (registration) registration->Update(NULL, NULL, 0); RegistrationState(false); } } int Signaling::NumActiveCalls() { if (registration && registration->calls) return registration->calls->get_count(); return 0; } void Signaling::SetRegistration(const char * hw, const char * phys, unsigned timeout) { log->Log(LOG_SIGNALING, "%x:Signaling::SetRegistration hw: %s", this, hw ? hw : ""); if (!registration) registration = new SignalingRegistration(this); registration->Update(hw, phys, timeout); } void Signaling::RegistrationCheck() { if (client) client->RegistrationCheck(); } void Signaling::LookupInfo(const char* src, class json_io& msg, word base) { class SignalingCall * call = FindCallByRemoteNum(src); if(call) call->LookupInfo(msg, base); } void Signaling::StartRinging(const char* uuid) { class SignalingCall* call = FindCallByConfId(uuid); if (call) call->StartRinging(); } void Signaling::SaveCredentials(const char* url, const char* sec, const char* phys, const char* app, const char* usr, const char* pwd, const char* domain, const char* appdomain, unsigned timeout) { debug->printf("%x:Signaling::SaveCredentials user=%x", this, user); if (this->user) this->user->SaveCredentials(url, sec, phys, app, usr, pwd, domain, appdomain, timeout); if (this->url) free(this->url); this->url = _strdup(url); if (this->sec) free(this->sec); this->sec = _strdup(sec); if (this->phys) free(this->phys); this->phys = _strdup(phys); if (this->app) free(this->app); this->app = _strdup(app); if (this->usr) free(this->usr); this->usr = _strdup(usr); if (this->userPwd) free(this->userPwd); this->userPwd = _strdup(pwd); if (this->domain) free(this->domain); this->domain = _strdup(domain); if (this->appdomain) free(this->appdomain); this->appdomain = _strdup(appdomain); } void Signaling::SaveRegistration(const char* hw, const char* phys, unsigned timeout) { debug->printf("%x:Signaling::SaveRegistration user=%x", this, user); if (this->user) this->user->SaveRegistration(hw, phys, timeout); } void Signaling::IncomingCall() { log->Log(LOG_SIGNALING, "%x:Signaling::IncomingCall close test devices audio=%s, ringer=%s, video=%s", this, testAudioDeviceId, testRingerDeviceId, testVideoDeviceId); class IAudioIo* audioIo = GetAudioIo(); if (audioIo && testAudioDeviceId) { audioIo->StopDevice((void*)this, testAudioDeviceId); audioIo->LoopTest(false); audioIo->StopDualTones(); free(testAudioDeviceId); testAudioDeviceId = NULL; } class IRingerIo* ringerIo = GetRingerIo(); if (ringerIo && testRingerDeviceId) { ringerIo->RingtoneStop(testRingerDeviceId); free(testRingerDeviceId); testRingerDeviceId = NULL; } class IVideoIo* videoIo = GetVideoIo(); if (videoIo && testVideoDeviceId) { videoIo->StopDevice((void*)this, testVideoDeviceId); free(testVideoDeviceId); testVideoDeviceId = NULL; } } void Signaling::VideoDeviceList() { if (registration && registration->registered) { if (videoDeviceId) { if (service->videoIo->GetDeviceIo(registration->GetHwId()) && !strcmp(videoDeviceId, service->videoIo->GetDeviceIo(registration->GetHwId()))) return; free(videoDeviceId); } videoDeviceId = _strdup(service->videoIo->GetDeviceIo(registration->GetHwId())); log->Log(LOG_SIGNALING, "%x:Signaling::VideoDeviceList hw=%s videoDeviceId=%s", this, registration->GetHwId() ? registration->GetHwId() : "-", videoDeviceId ? videoDeviceId : "-"); } } void Signaling::SelectVideoDevice(const char* app, const char* deviceId) { log->Log(LOG_SIGNALING, "Signaling::SelectVideoDevice app=%s deviceId=%s", app ? app : "-", deviceId ? deviceId : "-"); if (registration && registration->GetHwId() && app && !strcmp(registration->GetHwId(), app) && deviceId && (videoDeviceId == NULL || strcmp(deviceId, videoDeviceId))) { if (videoDeviceId) free(videoDeviceId); videoDeviceId = _strdup(deviceId); } } void Signaling::ChangeVideoDevice(const char* app, const char* deviceId) { log->Log(LOG_SIGNALING, "Signaling::ChangeVideoDevice app=%s deviceId=%s", app ? app : "-", deviceId ? deviceId : "-"); if (registration && registration->calls && registration->calls->get_count() > 0) { SignalingCall* call = nullptr; while ((call = (class SignalingCall*)registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { call->ChangeVideoDevice(deviceId); } } } void Signaling::RingtoneList() { if (registration) { if (ringtoneId) { if (service->ringerIo->GetRingtone(registration->GetHwId()) && !strcmp(ringtoneId, service->ringerIo->GetRingtone(registration->GetHwId()))) return; free(ringtoneId); } ringtoneId = _strdup(service->ringerIo->GetRingtone(registration->GetHwId())); log->Log(LOG_SIGNALING, "%x:Signaling::RingerDeviceList hw=%s ringtoneId=%s", this, registration->GetHwId() ? registration->GetHwId() : "-", ringtoneId ? ringtoneId : "-"); } } void Signaling::RingerDeviceList() { if (registration && registration->registered) { if (ringerDeviceId) { if (service->ringerIo->GetDeviceIo(registration->GetHwId()) && !strcmp(ringerDeviceId, service->ringerIo->GetDeviceIo(registration->GetHwId()))) return; free(ringerDeviceId); } ringerDeviceId = _strdup(service->ringerIo->GetDeviceIo(registration->GetHwId())); log->Log(LOG_SIGNALING, "%x:Signaling::RingerDeviceList hw=%s ringerDeviceId=%s", this, registration->GetHwId() ? registration->GetHwId() : "-", ringerDeviceId ? ringerDeviceId : "-"); } } void Signaling::SelectRingerDevice(class IRingerIo* ringerIo, const char* app, const char* deviceId) { log->Log(LOG_SIGNALING, "Signaling::SelectRingerDevice app=%s deviceId=%s audioDeviceId=%s", app ? app : "-", deviceId ? deviceId : "-", ringerDeviceId ? ringerDeviceId : "-"); if (registration && registration->GetHwId() && app && !strcmp(registration->GetHwId(), app) && deviceId && (ringerDeviceId == NULL || strcmp(deviceId, ringerDeviceId))) { if (ringerDeviceId) free(ringerDeviceId); ringerDeviceId = _strdup(deviceId); } } void Signaling::AudioDeviceList() { if (registration && registration->registered) { class IAudioIo* a = GetAudioIo(); if (defaultAudioDeviceId) { if (service->audioIo->GetDeviceIo(registration->GetHwId()) && !strcmp(defaultAudioDeviceId, service->audioIo->GetDeviceIo(registration->GetHwId()))) return; free(defaultAudioDeviceId); } defaultAudioDeviceId = _strdup(service->audioIo->GetDeviceIo(registration->GetHwId())); log->Log(LOG_SIGNALING, "%x:Signaling::AudioDeviceList hw=%s audioDeviceId=%s", this, registration->GetHwId() ? registration->GetHwId() : "-", defaultAudioDeviceId ? defaultAudioDeviceId : "-"); if (defaultAudioDeviceId && (a->GetDeviceIoType(defaultAudioDeviceId) == (int)AudioDeviceType::HeadsetDevice)) { a->StartHookDevice(defaultAudioDeviceId); a->SendHookKey(defaultAudioDeviceId, KEY_ONHOOK, 0, 0, 0); } } } void Signaling::SelectAudioDevice(class IAudioIo * audioIo, const char * app, const char * deviceId, int type) { class IAudioIo* a = GetAudioIo(); log->Log(LOG_SIGNALING, "Signaling::SelectAudioDevice reg=%s app=%s deviceId=%s type=%d audioDeviceId=%s type=%d", registration ? "ok" : "nok", app ? app : "-", deviceId ? deviceId : "-", type, defaultAudioDeviceId ? defaultAudioDeviceId : "-", defaultAudioDeviceId ? a->GetDeviceIoType(defaultAudioDeviceId) : 0); if (registration && registration->GetHwId() && app && !strcmp(registration->GetHwId(), app) && deviceId && (defaultAudioDeviceId == NULL || strcmp(deviceId, defaultAudioDeviceId))) { if (defaultAudioDeviceId) { if(a->GetDeviceIoType(defaultAudioDeviceId) == (int)AudioDeviceType::HeadsetDevice) a->StopHookDevice(defaultAudioDeviceId); free(defaultAudioDeviceId); } defaultAudioDeviceId = _strdup(deviceId); if (type == (int)AudioDeviceType::HeadsetDevice) { a->StartHookDevice(deviceId); a->SendHookKey(deviceId, KEY_ONHOOK, 0, 0, 0); } } } void Signaling::ChangeAudioDevice(class IAudioIo * audioIo, const char * app, const char * deviceId, int type) { log->Log(LOG_SIGNALING, "Signaling::ChangeAudioDevice reg=%s app=%s deviceId=%s audioDeviceId=%s type=%d", registration ? "ok" : "nok", app ? app : "-", deviceId ? deviceId : "-", defaultAudioDeviceId ? defaultAudioDeviceId : "-", type); const char * checkDeviceId = changedAudioDeviceId ? changedAudioDeviceId : defaultAudioDeviceId; if (deviceId && checkDeviceId && strcmp(deviceId, checkDeviceId) && registration && registration->calls && registration->calls->get_count() > 0) { audioIo->StartHookDevice(deviceId); SignalingCall* call = nullptr; while ((call = (class SignalingCall*)registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { call->ChangeAudioDevice(deviceId); } if (changedAudioDeviceId) free(changedAudioDeviceId); changedAudioDeviceId = _strdup(deviceId); } } void Signaling::StartHookDevice(const char* deviceId, byte key, byte callId, const char* number, const char* name) { class IAudioIo* audioIo = GetAudioIo(); log->Log(LOG_SIGNALING, "Signaling::StartHookDevice deviceId=%s", deviceId ? deviceId : "-"); audioIo->StartHookDevice(deviceId); audioIo->SendHookKey(deviceId, key, callId, number, name); } void Signaling::SendHookKey(const char* deviceId, byte key, byte callId, const char* number, const char* name) { class IAudioIo* a = GetAudioIo(); if (a->DeviceCapabilities(deviceId) & AUDIO_CAPABILITY_HOOK_DEVICE) a->SendHookKey(deviceId, key, callId, number, name); } void Signaling::SetNoVpnAddresses(bool noVpnAddresses) { if(service->mediaConfig->noVpnAddresses != noVpnAddresses) log->Log(LOG_SIGNALING, "%x:Signaling::SetNoVpnAddresses noVpnAddresses: %d", this, noVpnAddresses); service->mediaConfig->noVpnAddresses = noVpnAddresses; } void Signaling::SetRecordingParameters(const char* url, bool pbx, bool forceUrl, bool isRecordingByDefaultOn, bool forceDefaultOn, bool recordExternalOnly, bool forceExternalOnly) { log->Log(LOG_SIGNALING, "%x:Signaling::SetRecordingParameters url: %s pbx: %d force: %d recordingUrlSet: %d isRecordingByDefaultOn: %d forceDefaultOn: %d recordExternalOnly: %d forceExternalOnly: %d" , this, url ? url : "-", pbx, forceUrl, recordingUrlSet, isRecordingByDefaultOn, forceDefaultOn, recordExternalOnly, forceExternalOnly); // force == true means, url comes from the PBX and it will store in LocalStorage outside signaling if (forceUrl) { if (service->mediaConfig->recordingUrl) free(service->mediaConfig->recordingUrl); service->mediaConfig->recordingUrl = _strdup(url); } if (forceDefaultOn) service->mediaConfig->isRecordingByDefaultOn = isRecordingByDefaultOn; if (forceExternalOnly) service->mediaConfig->recordExternalOnly = recordExternalOnly; // PBX setting, force false. Apply if Url not set over Softphone2 Configuration // Also stored outside signaling else if (pbx) { if (service->mediaConfig->recordingUrl == nullptr && url) service->mediaConfig->recordingUrl = _strdup(url); const char * isRecordingByDefaultOnString = ILocalStorage::GetItem("isRecordingByDefaultOn"); if(!isRecordingByDefaultOnString) service->mediaConfig->isRecordingByDefaultOn = isRecordingByDefaultOn; else if(!strcmp(isRecordingByDefaultOnString, "true")) service->mediaConfig->isRecordingByDefaultOn = true; else service->mediaConfig->isRecordingByDefaultOn = false; const char * recordExternalOnlyString = ILocalStorage::GetItem("recordExternalOnly"); if(!recordExternalOnlyString) service->mediaConfig->recordExternalOnly = recordExternalOnly; else if(!strcmp(recordExternalOnlyString, "true")) service->mediaConfig->recordExternalOnly = true; else service->mediaConfig->recordExternalOnly = false; } // Softphone2 Config, not possible if pbx-force is set // Apply even the PBX setting is set without force // url may be null if Softphone2 config value was removed else if (url || recordingUrlSet) { if (service->mediaConfig->recordingUrl) free(service->mediaConfig->recordingUrl); service->mediaConfig->recordingUrl = _strdup(url); service->mediaConfig->isRecordingByDefaultOn = isRecordingByDefaultOn; service->mediaConfig->recordExternalOnly = recordExternalOnly; recordingUrlSet = url != nullptr; ILocalStorage::SetItem("recordingUrl", url ? url : ""); ILocalStorage::SetItem("isRecordingByDefaultOn", isRecordingByDefaultOn ? "true" : "false"); ILocalStorage::SetItem("recordExternalOnly", recordExternalOnly ? "true" : "false"); } else if (url == nullptr) ILocalStorage::SetItem("recordingUrl", ""); } void Signaling::HookDeviceMsg(IAudioIo* audioIo, const char* deviceId, const char* msg) { log->Log(LOG_SIGNALING, "%x:Signaling::HookDeviceMsg deviceId: %s msg: %s", this, deviceId, msg); if (registration && msg) { if (!strcmp(msg, "HookDeviceAcquired")) { hookDeviceState = HOOK_DEVICE_ACQUIRE; SignalingCall* call = nullptr; while ((call = (class SignalingCall*)registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { call->HookDeviceAcquired(); } } else if (!strcmp(msg, "HookDeviceDenied")) { hookDeviceState = HOOK_DEVICE_DENIED; SignalingCall* call = nullptr; while ((call = (class SignalingCall*)registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { call->HookDeviceDenied(); } } else if (!strcmp(msg, "HookDeviceNotFound")) { hookDeviceState = HOOK_DEVICE_NOT_FOUND; SignalingCall* call = nullptr; while ((call = (class SignalingCall*)registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { call->HookDeviceNotFound(); } } } } void Signaling::HookKeyReceived(IAudioIo* audioIo, const char* deviceId, byte key) { log->Log(LOG_SIGNALING, "%x:Signaling::HookKeyReceived deviceId: %s key: %d activeCall:%d activeCallState:%x Sessions():%d callWating:%d onhookTransfer:%d", this, deviceId, key, registration && registration->activePhoneCall ? 1 : 0, registration && registration->activePhoneCall ? registration->activePhoneCall->state : 0, !sessions.empty(), callWaiting, onhookTransfer); switch(key) { case KEY_EHS_TALK: if (registration) { if (!registration->activePhoneCall) { if (!sessions.empty()) { class SignalingMessage offhook("HookKeyReceived", 0, alloca(1000), alloca(100)); offhook.add_string(offhook.base, "device", deviceId); offhook.add_string(offhook.base, "key", "OFFHOOK"); class SignalingSessionHelper* helper = sessions.front(); while (helper) { offhook.Send(helper->session); helper = helper->goNext(); } } } else if (((registration->activePhoneCall->state & 0xF) == SIG_CALL_STATE_ALERT) || registration->activePhoneCall->state & SIG_CALL_STATE_CALL_WAITING) { // accept inbound call if (registration->signaling->videoLicense) { registration->activePhoneCall->cameraAllowed = registration->signaling->startVideo; } registration->activePhoneCall->ConnectCall(GetAudioIo(), GetVideoIo()); } else { SignalingCall* queuedCall = nullptr; SignalingCall* call = nullptr; while ((call = (class SignalingCall*)registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { log->Log(LOG_SIGNALING, "%x:Signaling::HookKeyReceived KEY_EHS_TALK call:%d callState:%x", this, call->call, call->state); if (call == registration->activePhoneCall) continue; else if (call->state & SIG_CALL_STATE_CALL_WAITING) { queuedCall = call; } } if (queuedCall) { if (registration->activePhoneCall && (registration->activePhoneCall != queuedCall)) { if ((registration->activePhoneCall->state & 0xF) == SIG_CALL_STATE_CONN) registration->activePhoneCall->Hold(); // put this on HOLD else if (registration->activePhoneCall->state & SIG_CALL_STATE_HOLD) {} // already on HOLD else if (registration->activePhoneCall->state & SIG_CALL_STATE_HELD) {} // already on HOLD else registration->activePhoneCall->ClearCall(); // abort this call } queuedCall->ConnectCall(GetAudioIo(), GetVideoIo()); } else { registration->activePhoneCall->ClearCall(); } } } break; case KEY_OFFHOOK: if (registration) { if (!registration->activePhoneCall) { if (!sessions.empty()) { class SignalingMessage offhook("HookKeyReceived", 0, alloca(1000), alloca(100)); offhook.add_string(offhook.base, "device", deviceId); offhook.add_string(offhook.base, "key", "OFFHOOK"); class SignalingSessionHelper* helper = sessions.front(); while (helper) { offhook.Send(helper->session); helper = helper->goNext(); } } } else if (((registration->activePhoneCall->state & 0xF) == SIG_CALL_STATE_ALERT) || registration->activePhoneCall->state & SIG_CALL_STATE_CALL_WAITING) { // accept inbound call if (registration->signaling->videoLicense) { registration->activePhoneCall->cameraAllowed = registration->signaling->startVideo; } registration->activePhoneCall->ConnectCall(GetAudioIo(), GetVideoIo()); } else { SignalingCall* call = nullptr; while ((call = (class SignalingCall*)registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { log->Log(LOG_SIGNALING, "%x:Signaling::HookKeyReceived KEY_OFFHOOK call:%d callState:%x", this, call->call, call->state); if (call == registration->activePhoneCall) continue; else if (call->state & SIG_CALL_STATE_CALL_WAITING) { if (registration->activePhoneCall) { if ((registration->activePhoneCall->state & 0xF) == SIG_CALL_STATE_CONN) registration->activePhoneCall->Hold(); // put this on HOLD else if (registration->activePhoneCall->state & SIG_CALL_STATE_HOLD) {} // already on HOLD else if (registration->activePhoneCall->state & SIG_CALL_STATE_HELD) {} // already on HOLD else registration->activePhoneCall->ClearCall(); // abort this call } call->ConnectCall(GetAudioIo(), GetVideoIo()); } } } } break; case KEY_ONHOOK: if (registration && registration->activePhoneCall) { bool initiateTransfer = false; class SignalingCall * heldCall = nullptr; if (onhookTransfer) { SignalingCall* call = nullptr; while ((call = (class SignalingCall*)registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { if (call == registration->activePhoneCall) continue; else if ((call->state & SIG_CALL_STATE_HOLD) && !heldCall) { heldCall = call; initiateTransfer = true; } else initiateTransfer = false; } } if (initiateTransfer && heldCall) { //TEST if registration->activePhoneCall is in Ringback registration->activePhoneCall->StartTransfer(heldCall); } else { registration->activePhoneCall->ClearCall(); } } break; case KEY_MIC: if (registration && registration->activePhoneCall) { registration->activePhoneCall->muted = !registration->activePhoneCall->muted; class IAudioIo* audioIo = registration->signaling->GetAudioIo(); if (audioIo) audioIo->Mute(registration->activePhoneCall->muted, false); registration->activePhoneCall->Mute(); } break; case KEY_DISC: if (registration && registration->activePhoneCall) { registration->activePhoneCall->ClearCall(); } break; case KEY_EHS_REDIAL: if (registration && lastStartCallDest) { class SignalingCall * call = new SignalingCall(registration, CallTypeVoice, 0, lastStartCallDest->num, lastStartCallDest->sip, lastStartCallDest->dn, true); if (registration->signaling->videoLicense) { call->cameraAllowed = registration->signaling->startVideo; } class IAudioIo * audioIo = GetAudioIo(); class IVideoIo * videoIo = GetVideoIo(); if (audioIo && call) call->StartCall(audioIo, videoIo, 0, 0); else debug->printf("%x:Signaling::EHS_REDIAL Oooops There is either no audioIo or no call object ", this); } break; case KEY_EHS_FLASH: if (registration && registration->activePhoneCall) { // toggle between active call and held call SignalingCall* heldCall = nullptr; SignalingCall* call = nullptr; while ((call = (class SignalingCall*)registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { if (call == registration->activePhoneCall) continue; else if ((call->state & SIG_CALL_STATE_HOLD) && !heldCall) { heldCall = call; } } if (!(registration->activePhoneCall->state & SIG_CALL_STATE_HOLD)) { registration->activePhoneCall->ChangeState(registration->activePhoneCall->state | SIG_CALL_STATE_HOLD); registration->activePhoneCall->Hold(); if (heldCall) { heldCall->ChangeState(heldCall->state & ~SIG_CALL_STATE_HOLD); heldCall->Retrieve(); } } else if (registration->activePhoneCall->state & SIG_CALL_STATE_HOLD) { registration->activePhoneCall->ChangeState(registration->activePhoneCall->state & ~SIG_CALL_STATE_HOLD); registration->activePhoneCall->Retrieve(); } } break; } } static unsigned NormalizeE164(const char *number, char * buffer, unsigned length) { const char * n; unsigned i = 0; if (number) { n = number;; while (*n != '\0') { if ((*n >= 0) && (*n != ' ') && (*n != '(') && (*n != ')') && (*n != '-') && (*n != '/') && (*n != '.') && (*n != ':') && (*n != ';') && (*n != '\r') && (*n != '\n')) { if (i+1 < length) { buffer[i] = *n; i++; } } n++; } } buffer[i] = '\0'; return i; } static bool IsE164(const char * string) { const char * s; s = string; while ((*s != '\0') && (((*s >= '0') && (*s <= '9')) || (*s == '*') || (*s == '#') || ((*s == '+') && (s == string)) || ((*s == ',') && (s != string)))) { s++; } return (*s == '\0'); } const char * Signaling::AppendSipDomain(const char * sip, char * buffer, unsigned length) { if (sip && !strchr(sip, '@') && client && client->domain) { _snprintf(buffer, length, "%s@%s", sip, client->domain); return buffer; } else { return sip; } } void Signaling::PhoneStartRinging(const char* uuid) { log->Log(LOG_SIGNALING, "%x:Signaling::PhoneStartRinging uuid: %s", this, uuid); class SignalingCall* call = FindCallByConfId(uuid); if (call) { if (((call->state & 0xF) == SIG_CALL_STATE_ALERT) || call->state & SIG_CALL_STATE_CALL_WAITING) call->StartRinging(); } else debug->printf("%x:Signaling::PhoneStartRinging Oooops could not find a call object for uuid %s ", this, uuid); } void Signaling::PhoneCheckIncomingCall(const char* uuid) { log->Log(LOG_SIGNALING, "%x:Signaling::PhoneCheckIncomingCall uuid: %s", this, uuid); class SignalingCall* call = FindCallByConfId(uuid); if (!call && registration) { class SignalingRegMessage reg("CheckCall", registration->src, alloca(1000)); reg.add_string(reg.base, "confID", uuid); reg.Send(registration); } } void Signaling::PhoneStartCallAction(const char * uuid, const char * number, const char * name, bool video, bool sendingComplete) { log->Log(LOG_SIGNALING, "%x:Signaling::PhoneStartCallAction uuid: %s number: %s name: %s video: %d", this, uuid, number, name, video); class SignalingCall *call = FindCallByConfId(uuid); if (call) { char normalizedNumber[1024]; NormalizeE164(number, normalizedNumber, sizeof(normalizedNumber)); if (IsE164(normalizedNumber)) { if (!call->remote.num) { char * dtmf = strchr(normalizedNumber, ','); if (dtmf) { *dtmf = '\0'; call->SendDTMF(dtmf + 1, false); } char prefixedNumber[1024]; call->remote.SetNum(registration->AddExternalLinePrefix(normalizedNumber, prefixedNumber, sizeof(prefixedNumber))); } } else { if (number != call->remote.sip) { call->remote.SetSip(number); } } if (name != call->remote.dn) { call->remote.SetDn(name); } call->sendingComplete = sendingComplete; call->DoStartCall(); } else { debug->printf("%x:Signaling::PhoneStartCallAction Oooops could not find a call object for uuid %s ", this, uuid); if (user) { user->ReportCallEnded(uuid, CALL_ENDED_REASON_FAILED); } } } void Signaling::PhoneAnswerCallAction(const char * uuid) { log->Log(LOG_SIGNALING, "%x:Signaling::PhoneAnswerCallAction uuid: %s", this, uuid); class SignalingCall *call = FindCallByConfId(uuid); if (call) { if (registration->signaling->videoLicense) { call->cameraAllowed = registration->signaling->startVideo; } call->ConnectCall(GetAudioIo(), GetVideoIo()); } else { debug->printf("%x:Signaling::PhoneAnswerCallAction Oooops could not find a call object for uuid %s ", this, uuid); if (user) { user->ReportCallEnded(uuid, CALL_ENDED_REASON_FAILED); } } } void Signaling::PhoneEndCallAction(const char *uuid, int callError) { log->Log(LOG_SIGNALING, "%x:Signaling::PhoneEndCallAction uuid: %s error: %d", this, uuid, callError); class SignalingCall *call = FindCallByConfId(uuid); if (call) call->ClearCall(); else debug->printf("%x:Signaling::PhoneEndCallAction Oooops could not find a call object for uuid %s ", this, uuid); } void Signaling::PhoneDialNumber(const char * number, const char * name, StartCallVideoMode videoMode, bool sendingComplete) { log->Log(LOG_SIGNALING, "%x:Signaling::PhoneDialNumber number: %s name: %s video_mode: %d", this, number, name, videoMode); if (registration) { char prefixedNumber[1024]; class SignalingCall * call = new SignalingCall(registration, CallTypeVoice, 0, registration->AddExternalLinePrefix(number, prefixedNumber, sizeof(prefixedNumber)), name, 0, sendingComplete); if (registration->signaling->videoLicense) { call->cameraAllowed = registration->signaling->startVideo || (videoMode == START_CALL_VIDEO_MODE_ON); } class IAudioIo * audioIo = GetAudioIo(); class IVideoIo * videoIo = GetVideoIo(); if (audioIo && call) call->StartCall(audioIo, videoIo, 0, 0); else debug->printf("%x:Signaling::PhoneDialNumber Oooops There is either no audioIo or no call object ", this); } else { debug->printf("%x:Signaling::PhoneDialNumber Oooops no registration", this); } } void Signaling::PhoneHoldCallAction(const char * uuid) { log->Log(LOG_SIGNALING, "%x:Signaling::PhoneHoldCallAction uuid: %s", this, uuid); class SignalingCall *call = FindCallByConfId(uuid); if (call) { call->ChangeState(call->state | SIG_CALL_STATE_HOLD); call->Hold(); } else { debug->printf("%x:Signaling::PhoneHoldCallAction Oooops could not find a call object for uuid %s ", this, uuid); if (user) { user->ReportCallEnded(uuid, CALL_ENDED_REASON_FAILED); } } } void Signaling::PhoneRetrieveCallAction(const char * uuid) { log->Log(LOG_SIGNALING, "%x:Signaling::PhoneRetrieveCallAction uuid: %s", this, uuid); class SignalingCall *call = FindCallByConfId(uuid); if (call) { call->ChangeState(call->state & ~SIG_CALL_STATE_HOLD); call->Retrieve(); } else { debug->printf("%x:Signaling::PhoneRetrieveCallAction Oooops could not find a call object for uuid %s ", this, uuid); if (user) { user->ReportCallEnded(uuid, CALL_ENDED_REASON_FAILED); } } } void Signaling::PhoneSetMutedCallAction(const char * uuid, bool muted) { log->Log(LOG_SIGNALING, "%x:Signaling::PhoneSetMutedCallAction uuid: %s muted is: %s", this, uuid, muted ? "true" : "false"); class SignalingCall *call = FindCallByConfId(uuid); if(call && registration){ class IAudioIo* audioIo = registration->signaling->GetAudioIo(); call->muted = muted; if(audioIo) audioIo->Mute(muted, false); call->Mute(); } else { debug->printf("%x:Signaling::PhoneSetMutedCallAction Oooops could not find a call object for uuid %s ", this, uuid); if (user) { user->ReportCallEnded(uuid, CALL_ENDED_REASON_FAILED); } } } void Signaling::PhoneAudioRouteChanged(const char * uuid, const char * deviceId) { log->Log(LOG_SIGNALING, "%x:Signaling::PhoneAudioRouteChanged uuid: %s new deviceId: %s ", this, uuid, deviceId); class IAudioIo* audioIo = GetAudioIo(); if (audioIo && registration && registration->GetHwId() && deviceId) audioIo->ChangeDevice(registration->GetHwId(), deviceId); class SignalingCall *call = FindCallByConfId(uuid); if(call && registration){ call->AudioRouteChanged(); } else { debug->printf("%x:Signaling::PhoneAudioRouteChanged Oooops could not find a call object for uuid %s ", this, uuid); if (user) { user->ReportCallEnded(uuid, CALL_ENDED_REASON_FAILED); } } } void Signaling::PhonePlayDtmfCallAction(const char * uuid, const char * digits) { log->Log(LOG_SIGNALING, "%x:Signaling::PhonePlayDtmfCallAction uuid: %s digits: %s", this, uuid, digits); class SignalingCall *call = FindCallByConfId(uuid); if (call) { call->SendDTMF(digits, false); } else { debug->printf("%x:Signaling::PhonePlayDtmfCallAction Oooops could not find a call object for uuid %s ", this, uuid); if (user) { user->ReportCallEnded(uuid, CALL_ENDED_REASON_FAILED); } } } SignalingCall * Signaling::FindCallByConfId(const char * uuid) { if (registration && uuid) { SignalingCall *call = nullptr; while ((call = (class SignalingCall *) registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { if (call->confId && !strcmp(uuid, call->confId)) { return call; } } } return NULL; } SignalingCall* Signaling::FindCallByRemoteNum(const char* num) { if (registration && num) { SignalingCall* call = nullptr; while ((call = (class SignalingCall*)registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { if (call->remote.num && !strcmp(call->remote.num, num)) { return call; } } } return NULL; } IRTPTP * Signaling::SetRTPTP4(URTPTP * user, const char * sip, unsigned timeout) { if (!rtptp4) rtptp4 = new SignalingRTPTP(this); rtptp4 = rtptp4->Update(user, sip, timeout); return rtptp4; } IRTPTP * Signaling::SetRTPTP6(URTPTP * user, const char * sip, unsigned timeout) { if (!rtptp6) rtptp6 = new SignalingRTPTP(this); rtptp6 = rtptp6->Update(user, sip, timeout); return rtptp6; } void Signaling::WebserverPluginWebsocketListenResult(IWebserverPlugin * plugin, const char * path, const char * registeredPathForRequest, const char * host) { new SignalingSession(this); } void Signaling::WebserverPluginClose(IWebserverPlugin * plugin, wsp_close_reason_t reason, bool lastUser) { delete webserverPlugin; webserverPlugin = NULL; if (stopping) Stop(); } void Signaling::Stop() { log->Log(LOG_SIGNALING, "%x:Signaling::Stop client:%p registration:%p", this, client, registration); if (!stopping) { stopping = true; user->SignalingClosing(this); } if (client) { client->Close(); client = NULL; } if (registration) { registration->Update(NULL, NULL, 0); registration = NULL; } if (rtptp4) { rtptp4->Update(NULL, NULL, 0); rtptp4 = NULL; } if (rtptp6) { rtptp6->Update(NULL, NULL, 0); rtptp6 = NULL; } if (webserverPlugin) webserverPlugin->Close(); else this->appService->AppStopped(this); } void Signaling::AppInstanceShutdownTimeout() { } class IAudioIo* Signaling::GetAudioIo() { if (user && user->UseRemoteServices()) return service->remoteServices->GetAudioIo(); else return service->audioIo; } class IRingerIo* Signaling::GetRingerIo() { if (user && user->UseRemoteServices()) return service->remoteServices->GetRingerIo(); else return service->ringerIo; } class IVideoIo* Signaling::GetVideoIo() { if (user && user->UseRemoteServices()) return service->remoteServices->GetVideoIo(); else return service->videoIo; } class IMediaProvider* Signaling::GetMediaProvider() { if (user && user->UseRemoteServices()) return service->remoteServices->GetMediaProvider(); else return service->mediaProvider; }; void Signaling::AddClientMonitor(class SignalingClientMonitor * monitor) { clientMonitors.push_back(monitor); if (client->websocket) monitor->ClientState(true); } void Signaling::RemoveClientMonitor(class SignalingClientMonitor* monitor) { monitor->remove(); } void Signaling::ClientState(bool up, bool localInit) { log->Log(LOG_SIGNALING, "%x:Signaling::ClientState up: %s sessions: %d", this, up ? "true" : "false", Sessions()); if (Sessions()) { StartUpdate(new SignalingUpdate(SignalingClientInfo, up ? "ClientUp" : "ClientDown", 0, filters)); } for (class SignalingClientMonitor * m = clientMonitors.front(); m; m = m->goNext()) { m->ClientState(up); } if (user) user->ClientState(up, localInit); this->localInit = localInit; } void Signaling::RccSessionOpened() { if (user) user->UserSession(true); uiRunning++; } void Signaling::RccSessionClosed() { if (user) user->UserSession(false); if (uiRunning) uiRunning--; } void Signaling::AddSignalingSession(class SignalingSession* session) { if (!sessions.empty()) { class SignalingSessionHelper* helper = sessions.front(); while (helper) { if (helper->session == session) return; helper = helper->goNext(); } } sessions.push_back(new SignalingSessionHelper(session)); } void Signaling::RemoveSignalingSession(class SignalingSession* session) { if (!sessions.empty()) { class SignalingSessionHelper* helper = sessions.front(); while (helper) { if (helper->session == session) { helper->remove(); delete helper; return; } helper = helper->goNext(); } } } void Signaling::AddRegistrationMonitor(class SignalingRegistrationMonitor * monitor) { registrationMonitors.push_back(monitor); if (registration && registration->clientUp && (registration->registered || registration->err)) monitor->RegistrationState(true); } void Signaling::RemoveRegistrationMonitor(class SignalingRegistrationMonitor * monitor) { monitor->remove(); } void Signaling::MonitorSync(class SignalingRegistrationMonitor * monitor) { log->Log(LOG_SIGNALING, "%x:Signaling::MonitorSync", this); if (registration) { SignalingCall *call = nullptr; while ((call = (class SignalingCall *) registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { class SignalingCallMonitor* m = monitor->CallAdded(call); if (m) m->MonitorSync(); } } } void Signaling::RegistrationState(bool up) { log->Log(LOG_SIGNALING, "%x:Signaling::RegistrationState reg: %x up: %s user : %s ", this, registration, up ? "true" : "false", user ? "true" : "false"); for (class SignalingRegistrationMonitor * m = registrationMonitors.front(); m; m = m->goNext()) { m->RegistrationState(up); } if (registration) { SignalingCall *call = nullptr; while ((call = (class SignalingCall *) registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { call->RegistrationState(up); } } if (user) user->RegistrationState(up); if (client) client->RegistrationState(up); // Default devices may not be still initialized if the registration with the PBX took longer if (up) { AudioDeviceList(); VideoDeviceList(); RingerDeviceList(); } } void Signaling::CallAdded(class SignalingCall * call, class SignalingRegistrationMonitor * exclude) { for (class SignalingRegistrationMonitor * m = registrationMonitors.front(); m; m = m->goNext()) { if (m != exclude && call->callType == m->callType) m->CallAdded(call); } } void Signaling::UnknownCallMessage(const char * type, class json_io & msg, word base) { for (class SignalingRegistrationMonitor * m = registrationMonitors.front(); m; m = m->goNext()) { m->UnknownCallMessage(type, msg, base); } } void Signaling::RTPTPState(class IRTPTP * rtptp, bool up) { if (Sessions()) { if (rtptp == rtptp4) { StartUpdate(new SignalingUpdate(SignalingRTPTP4Info, up ? "RTPTP4Up" : "RTPTP4Down", 0, filters)); } else { StartUpdate(new SignalingUpdate(SignalingRTPTP6Info, up ? "RTPTP6Up" : "RTPTP6Down", 0, filters)); } } } void SignalingDialingLocation::zeroize() { prefixIntl = prefixNtl = prefixSubs = country = area = subscriber = 0; maxIntern = 0; tones = MEDIA_TONES_EUROPE_PBX; } void SignalingDialingLocation::cleanup() { if (prefixIntl) free(prefixIntl); if (prefixNtl) free(prefixNtl); if (prefixSubs) free(prefixSubs); if (country) free(country); if (area) free(area); if (subscriber) free(subscriber); zeroize(); } void SignalingDialingLocation::read(class json_io & msg, word base) { cleanup(); prefixIntl = _strdup(msg.get_string(base, "prefixIntl")); prefixNtl = _strdup(msg.get_string(base, "prefixNtl")); prefixSubs = _strdup(msg.get_string(base, "prefixSubs")); country = _strdup(msg.get_string(base, "country")); area = _strdup(msg.get_string(base, "area")); subscriber = _strdup(msg.get_string(base, "subscriber")); maxIntern = msg.get_unsigned(base, "maxIntern"); tones = msg.get_unsigned(base, "tones"); } void SignalingDialingLocation::write(class SignalingRCCMessage * msg) { msg->add_string(msg->base, "prefixIntl", prefixIntl); msg->add_string(msg->base, "prefixNtl", prefixNtl); msg->add_string(msg->base, "prefixSubs", prefixSubs); msg->add_string(msg->base, "country", country); msg->add_string(msg->base, "area", area); msg->add_string(msg->base, "subscriber", subscriber); msg->add_unsigned(msg->base, "maxIntern", maxIntern, msg->tmp); msg->add_unsigned(msg->base, "tones", tones, msg->tmp); } /*-----------------------------------------------------------------------------------------------*/ /* class SignalingSession */ /*-----------------------------------------------------------------------------------------------*/ SignalingSession::SignalingSession(class Signaling * signaling) : AppUpdatesSession(signaling, signaling->webserverPlugin, signaling, signaling) { signaling->log->Log(LOG_SIGNALING, "%x:SignalingSession()", this); this->signaling = signaling; currentSrc = 0; if (false) ChannelsCmdName(0); } void SignalingSession::AppWebsocketClosed() { signaling->log->Log(LOG_SIGNALING, "%x:SignalingSession::AppWebsocketClosed()", this); signaling->RemoveSignalingSession(this); delete this; } void SignalingSession::ResponseSent() { } bool SignalingSession::AppWebsocketConnectComplete(class json_io & msg, word info) { signaling->log->Log(LOG_SIGNALING, "%x:SignalingSession::AppWebsocketConnectComplete()", this); signaling->AddSignalingSession(this); return true; } void SignalingSession::AppWebsocketMessage(class json_io & msg, word base, const char * mt, const char * src) { signaling->log->Log(LOG_SIGNALING, "%x:SignalingSession::AppWebsocketMessage(%s)", this, mt); if (currentSrc) free(currentSrc); currentSrc = _strdup(src); if (!strcmp(mt, "SetClient")) { ((class ISignaling *)signaling)->SetClient(msg.get_string(base, "url"), msg.get_string(base, "sec"), msg.get_string(base, "phys"), msg.get_string(base, "sip"), msg.get_string(base, "usr"), msg.get_string(base, "pwd"), msg.get_string(base, "domain"), msg.get_string(base, "appdomain"), msg.get_unsigned(base, "timeout"), false); class SignalingMessage result("SetClientResult", src, alloca(1000), alloca(100)); if (signaling->client) signaling->client->Sync(result, base); result.Send(this); AppWebsocketMessageComplete(); new SignalingUpdatesFilter(SignalingClientInfo, signaling->filters, this, src, sip); } else if (!strcmp(mt, "ConnInfoNotAvailable")) { class SignalingMessage result("ConnInfoNotAvailableResult", src, alloca(1000), alloca(100)); if (signaling->client) signaling->client->Sync(result, base); result.Send(this); AppWebsocketMessageComplete(); } else if (!strcmp(mt, "SetRegistration")) { // Signaling already registered and a new SetRegistration arrives for a different device if (signaling->registration && strcmp(msg.get_string(base, "hw"), signaling->registration->GetHwId()) && signaling->registration->registered) { class SignalingMessage result("SetRegistrationResult", src, alloca(1000), alloca(100)); word handle = result.add_object(result.base, "sync"); result.add_string(handle, "mt", "RegistrationBusy"); result.Send(this); } else { ((class ISignaling *)signaling)->SetRegistration(msg.get_string(base, "hw"), msg.get_string(base, "phys"), msg.get_unsigned(base, "timeout")); class SignalingMessage result("SetRegistrationResult", src, alloca(1000), alloca(100)); if (signaling->registration) signaling->registration->Sync(result, base); result.Send(this); } AppWebsocketMessageComplete(); new SignalingUpdatesFilter(SignalingRegistrationInfo, signaling->filters, this, src, sip); } else if (!strcmp(mt, "SetRTPTP4")) { ((class ISignaling *)signaling)->SetRTPTP4(signaling->rtptp4 ? signaling->rtptp4->user : 0, msg.get_string(base, "sip"), msg.get_unsigned(base, "timeout")); class SignalingMessage result("SetRTPTP4Result", src, alloca(1000), alloca(100)); if (signaling->rtptp4) signaling->rtptp4->Sync(result, base); result.Send(this); AppWebsocketMessageComplete(); new SignalingUpdatesFilter(SignalingRTPTP4Info, signaling->filters, this, src, sip); } else if (!strcmp(mt, "SetRTPTP6")) { ((class ISignaling *)signaling)->SetRTPTP6(signaling->rtptp6 ? signaling->rtptp6->user : 0, msg.get_string(base, "sip"), msg.get_unsigned(base, "timeout")); class SignalingMessage result("SetRTPTP6Result", src, alloca(1000), alloca(100)); if (signaling->rtptp6) signaling->rtptp6->Sync(result, base); result.Send(this); AppWebsocketMessageComplete(); new SignalingUpdatesFilter(SignalingRTPTP6Info, signaling->filters, this, src, sip); } else if (!strcmp(mt, "TestAudioDevice")) { class IAudioIo* audioIo = signaling->GetAudioIo(); if (audioIo) { if (signaling->testAudioDeviceId) { audioIo->StopDevice((void*)this, signaling->testAudioDeviceId); audioIo->LoopTest(false); audioIo->StopDualTones(); free(signaling->testAudioDeviceId); signaling->testAudioDeviceId = NULL; } if (msg.get_string(base, "id")) { signaling->testAudioDeviceId = _strdup(msg.get_string(base, "id")); audioIo->StartDevice((void*)this, (void*)this, signaling->testAudioDeviceId, AudioDeviceMode::AudioModeCommunication); audioIo->LoopTest(true); const char* digits = "#"; struct AudioIoDualTone tone; MediaTones::PrepareDtmf(digits[0], &tone); audioIo->StartDualTones(AUDIO_IO_DUAL_TONE_BOTH | AUDIO_IO_DUAL_TONE_PEER, 1, &tone); } } AppWebsocketMessageComplete(); } else if (!strcmp(mt, "TestRingerDevice")) { class IRingerIo* ringerIo = signaling->GetRingerIo(); if (ringerIo && signaling->registration) { if (signaling->testRingerDeviceId) { ringerIo->RingtoneStop(signaling->testRingerDeviceId); free(signaling->testRingerDeviceId); signaling->testRingerDeviceId = NULL; } if (msg.get_string(base, "id")) { signaling->testRingerDeviceId = _strdup(msg.get_string(base, "id")); ringerIo->RingtoneStart(signaling->testRingerDeviceId, 0, ringerIo->GetRingtone(signaling->registration->GetHwId())); } } AppWebsocketMessageComplete(); } else if (!strcmp(mt, "TestVideoDevice")) { class IVideoIo* videoIo = signaling->GetVideoIo(); if (videoIo) { if (signaling->testVideoDeviceId) { videoIo->StopDevice((void*)this, signaling->testVideoDeviceId); free(signaling->testVideoDeviceId); signaling->testVideoDeviceId = NULL; } if (msg.get_string(base, "id")) { signaling->testVideoDeviceId = _strdup(msg.get_string(base, "id")); videoIo->StartVideoDevice((void*)this, (void*)this, signaling->testVideoDeviceId, 1280, 720, 0); } } AppWebsocketMessageComplete(); } else if (!strcmp(mt, "WindowTitle")) { if (signaling->user) signaling->user->SoftphoneAppWindowTitle(msg.get_string(base, "title")); AppWebsocketMessageComplete(); } else { debug->printf("%x:SignalingSession::AppWebsocketMessage unknown message %s", this, mt); AppWebsocketMessageComplete(); } } SignalingMessage::SignalingMessage(const char * mt, const char * src, void * sb, void * tmp) : JsonIo((char *)sb) { this->sb = (char *)sb; this->tmp = (char *)tmp; base = add_object(0xffff, 0); add_string(base, "mt", mt); add_string(base, "src", src); } void SignalingMessage::Send(class SignalingSession * session) { session->SendResponse(*this, sb); } /*-----------------------------------------------------------------------------------------------*/ /* SignalingUpdate */ /*-----------------------------------------------------------------------------------------------*/ SignalingUpdate::SignalingUpdate(enum SignalingUpdateType type, const char * mt, const char * info, class AppUpdatesFilters & filters, const char * sip) : AppUpdate(filters, sip) { this->type = type; this->mt = mt; this->info = _strdup(info); debug->printf("%x(%u):SignalingUpdate(mt=%s)", this, type, mt); } SignalingUpdate::~SignalingUpdate() { free(info); } SignalingUpdatesFilter::SignalingUpdatesFilter(enum SignalingUpdateType type, AppUpdatesFilters & filters, class AppUpdatesSession * session, const char * src, const char * sip) : AppUpdatesFilter(filters, session, src, sip) { this->type = type; this->src = _strdup(src); } SignalingUpdatesFilter::~SignalingUpdatesFilter() { if (this->src) free(this->src); }; bool SignalingUpdatesFilter::Test(SignalingUpdate * update) { return update->type==type; } void SignalingUpdatesFilter::Send(SignalingUpdate * update) { char sb[16000]; class json_io send(sb); word base = send.add_object(0xffff, 0); if (GetSrc()) send.add_string(base, "src", GetSrc()); send.add_string(base, "mt", update->mt); send.add_json(base, "info", update->info); SendUpdate(send, sb); } /*-----------------------------------------------------------------------------------------------*/ /* class SignalingClient */ /*-----------------------------------------------------------------------------------------------*/ SignalingClient::SignalingClient(class Signaling * signaling) : retry(signaling->service->iomux, this), tryAltPbx(signaling->service->iomux, this) { signaling->log->Log(LOG_SIGNALING, "%x:SignalingClient()", this); this->signaling = signaling; signaling->client = this; websocket = 0; usePrimaryUrl = true; url = 0; sec = 0; redirectUrl = 0; curPbx = 0; phys = 0; app = 0; usr = 0; pwd = 0; domain = 0; appdomain = 0; timeout = TIMEOUT_MIN; up = false; localInit = false; clearRegistration = false; updateCredentials = false; fastConnect = false; } SignalingClient::~SignalingClient() { if (signaling && signaling->client == this) signaling->client = 0; free(url); if (sec) free(sec); if (curPbx) free(curPbx); if (phys) free(phys); free(app); free(usr); free(pwd); free(domain); if (appdomain) free(appdomain); } void SignalingClient::Close() { if (websocket) { websocket->Close(); DeleteCalls(); if (signaling && signaling->client == this) signaling->client = 0; signaling = 0; retry.Cancel(); tryAltPbx.Cancel(); } else delete this; } void SignalingClient::Update(const char * url, const char * sec, const char * phys, const char * app, const char * usr, const char * pwd, const char * domain, const char * appdomain, unsigned timeout, bool localInit) { uri_dissector uriDiss; if (url && uriDiss.dissect_uri((char *)url) && (uriDiss.ip || uriDiss.fqdn)) { SignalingMediaConfig* c = signaling->service->mediaConfig; const char *host = (uriDiss.ip) ? uriDiss.ip : uriDiss.fqdn; const char *hostEnd = (uriDiss.port) ? uriDiss.e_port : ((uriDiss.ip) ? uriDiss.e_ip : uriDiss.e_fqdn); if (c->hostPbx) free(c->hostPbx); c->hostPbx = (char *)(malloc(hostEnd - host + 1)); memcpy(c->hostPbx, host, hostEnd - host); c->hostPbx[hostEnd - host] = '\0'; } if (url && app && usr && pwd) { if (timeout) this->timeout = timeout; if (!websocket) { this->url = _strdup(url); this->sec = sec && sec[0] ? _strdup(sec) : NULL; if (domain && phys && phys[0]) { if (strstr(phys, domain)) { this->phys = _strdup(phys); } else { this->phys = (char*)malloc(strlen(domain)+1+strlen(phys)+1); _sprintf(this->phys, "%s@%s", domain, phys); } } else this->phys = NULL; this->app = _strdup(app); this->usr = _strdup(usr); this->pwd = _strdup(pwd); this->domain = _strdup(domain); this->appdomain = appdomain && appdomain[0] ? _strdup(appdomain) : NULL; this->clientTimeout = this->timeout; this->localInit = localInit; if (signaling) { websocket = signaling->service->appWebsocketClientProvider->Create(this, signaling->service); websocket->Connect(url, pwd, usr, "-", app, "-", "-"); if (curPbx) free(curPbx); curPbx = _strdup(this->url); if (!localInit) updateCredentials = true; } } else if (strcmp(this->url, url) || strcmp(this->app, app) || strcmp(this->usr, usr) || strcmp(this->pwd, pwd)) { if (signaling) signaling->ClientState(false, localInit); websocket->Close(); DeleteCalls(); if (signaling) { class SignalingClient * s = new SignalingClient(signaling); s->Update(url, sec, phys, app, usr, pwd, domain, appdomain, this->timeout, localInit); signaling->client = s; signaling = 0; // Clear registration if for instance a users connects to a different PBX // Clear when the app connects to the new PBX. Otherwise myApps could crash if a call is present, registration pointer is used. s->ClearRegistration(); } } else { bool updateSecPhysCredentials = false; if (!this->sec && sec && sec[0]) { this->sec = _strdup(sec); updateSecPhysCredentials = true; } else if (this->sec && (!sec || !sec[0])) { if (this->sec) free(this->sec); this->sec = NULL; updateSecPhysCredentials = true; } if (!this->phys && domain && phys && phys[0]) { if (strstr(phys, domain)) { this->phys = _strdup(phys); } else { this->phys = (char*)malloc(strlen(domain) + 1 + strlen(phys) + 1); _sprintf(this->phys, "%s@%s", domain, phys); } updateSecPhysCredentials = true; } else if (this->phys && (!phys || !phys[0])) { if (this->phys) free(this->phys); this->phys = NULL; updateSecPhysCredentials = true; } if(updateSecPhysCredentials) signaling->SaveCredentials(this->url, this->sec, this->phys, this->app, this->usr, this->pwd, this->domain, this->appdomain, this->clientTimeout); } } else { if (signaling && up) signaling->ClientState(false, localInit); if (websocket) { websocket->Close(); DeleteCalls(); if(signaling && signaling->client == this) signaling->client = 0; signaling = 0; } else { delete this; } } } void SignalingClient::RegisterToAltPbx() { if (signaling) signaling->log->Log(LOG_SIGNALING, "%x:SignalingClient::RegisterToAltPbx() localInit:%d", this, localInit); localInit = false; updateCredentials = false; if (usePrimaryUrl && sec) usePrimaryUrl = false; else if (!usePrimaryUrl && url) usePrimaryUrl = true; websocket->Close(); } void SignalingClient::RegisterToRedirectUrl(const char* url) { if (signaling) signaling->log->Log(LOG_SIGNALING, "%x:SignalingClient::RegisterToRedirectUrl()", this); localInit = false; if (this->redirectUrl) free(this->redirectUrl); this->redirectUrl = (url && url[0]) ? _strdup(url) : NULL; updateCredentials = false; websocket->Close(); } void SignalingClient::RegistrationState(bool up) { if(up) tryAltPbx.Cancel(); } void SignalingClient::Sync(class json_io & msg, word base) { if(signaling) signaling->log->Log(LOG_SIGNALING, "%x:SignalingClient::Sync() up: %d", this, up); if (up) { word handle = msg.add_object(base, "sync"); msg.add_string(handle, "mt", "ClientUp"); } } void SignalingClient::AppWebsocketClientConnectComplete(class IAppWebsocketClient * appWebsocketClient) { if(signaling) signaling->log->Log(LOG_SIGNALING, "%x:SignalingClient::AppWebsocketClientConnectComplete websocket: %x pbx: %s clearRegistration: %d", this, websocket, curPbx ? curPbx : "", clearRegistration); up = true; timeout = TIMEOUT_MIN; websocket->MessageComplete(); if (signaling) { if (clearRegistration) { if (signaling->registration) signaling->registration->Update(NULL, NULL, 0); clearRegistration = false; } signaling->ClientState(true, localInit); if(updateCredentials) signaling->SaveCredentials(this->url, this->sec, this->phys, this->app, this->usr, this->pwd, this->domain, this->appdomain, this->clientTimeout); } } void SignalingClient::AppWebsocketClientMessage(class IAppWebsocketClient * appWebsocketClient, class json_io & msg, word base, const char * mt, const char * src) { if (signaling) { if (signaling->log->LogFlagSet(LOG_SIGNALING)) { char buffer[WS_MAX_DATA_SIZE]; memset(buffer, 0x00, WS_MAX_DATA_SIZE); msg.encode(base, buffer); signaling->log->Log(LOG_SIGNALING, "%x:SignalingClient::AppWebsocketClientMessage(mt=%s,src=%s) websocket=%x buffer=%s", this, mt, src, websocket, buffer); } if (src) { for (class SignalingClientMonitor* m = signaling->clientMonitors.front(); m; m = m->goNext()) { if (!strcmp(m->src, src)) m->ClientMessage(msg, base, mt, src); } } } websocket->MessageComplete(); } void SignalingClient::AppWebsocketClientSendResult(class IAppWebsocketClient * appWebsocketClient) { if(signaling) signaling->log->Log(LOG_SIGNALING, "%x:SignalingClient::AppWebsocketClientSendResult", this); } void SignalingClient::DeleteCalls() { if(signaling) signaling->log->Log(LOG_SIGNALING, "%x:SignalingClient::DeleteCalls reg=%p", this, signaling->registration); if (signaling && signaling->registration) { SignalingCall * call = nullptr; while ((call = (class SignalingCall *)signaling->registration->calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { call->ClearCall(); } if (signaling->registration->presenceCall) { SignalingSigMessage msg(presenceSubscriptionCallId, "rel", alloca(1000), alloca(100)); msg.Send(signaling->registration); signaling->registration->presenceCall = false; } } } void SignalingClient::AppWebsocketClientClosed(class IAppWebsocketClient * appWebsocketClient) { if(signaling) signaling->log->Log(LOG_SIGNALING, "%x:SignalingClient::AppWebsocketClientClosed timeout: %d websocket: %x signaling: %x fastConnect: %d", this, timeout, websocket, signaling, fastConnect); delete websocket; websocket = 0; up = false; if (signaling) { DeleteCalls(); signaling->ClientState(false, localInit); if (fastConnect) { fastConnect = false; retry.Start(500); } else retry.Start(timeout); if(tryAltPbx.IsRunning() == false) tryAltPbx.Start(TIMEOUT_MAX); } else { delete this; } } void SignalingClient::TimerOnTimeout(ITimer * timer) { if (timer == &retry) { if (!signaling || websocket) return; websocket = signaling->service->appWebsocketClientProvider->Create(this, signaling->service); if (redirectUrl) { websocket->Connect(redirectUrl, pwd, usr, "-", app, "-", "-"); if (curPbx) free(curPbx); curPbx = _strdup(redirectUrl); free(redirectUrl); redirectUrl = NULL; } else if (usePrimaryUrl && url) { websocket->Connect(url, pwd, usr, "-", app, "-", "-"); if (curPbx) free(curPbx); curPbx = _strdup(url); } else if (!usePrimaryUrl && sec) { websocket->Connect(sec, pwd, usr, "-", app, "-", "-"); if (curPbx) free(curPbx); curPbx = _strdup(sec); } if (timeout < TIMEOUT_MAX) timeout *= 2; } else if (timer == &tryAltPbx) { if (usePrimaryUrl && sec) usePrimaryUrl = false; else if (!usePrimaryUrl && url) usePrimaryUrl = true; if (websocket) fastConnect = true; else retry.Start(500); tryAltPbx.Start(TIMEOUT_MAX); } } void SignalingClient::RegistrationCheck() { timeout = TIMEOUT_MIN; if (retry.IsRunning()) { retry.Cancel(); retry.Start(1); } } /*-----------------------------------------------------------------------------------------------*/ SignalingClientMonitor::SignalingClientMonitor(class Signaling * signaling, const char * src) { this->signaling = signaling; this->src = _strdup(src); } void SignalingClientMonitor::MessageSend(class json_io & msg, word base, char * buffer) { msg.add_string(base, "api", "PbxSignal"); msg.add_string(base, "src", src); if (signaling->log->LogFlagSet(LOG_SIGNALING)) { word len = msg.encode(); signaling->log->Log(LOG_SIGNALING, "%x:SignalingClientMonitor::MessageSend len:%d buffer: %s", this, len, buffer); } // The websocket connection must be connected before data is sent. Otherwiese ASSERT in websocketclient.cpp due to wrong state. if (signaling->client && signaling->client->IsClientUp()) signaling->client->websocket->MessageSend(msg, buffer); } /*-----------------------------------------------------------------------------------------------*/ /* class SignalingRegistration */ /*-----------------------------------------------------------------------------------------------*/ SignalingRegistration::SignalingRegistration(class Signaling * signaling) : SignalingClientMonitor(signaling, "registration"), retry(signaling->service->iomux, this) { signaling->log->Log(LOG_SIGNALING, "%x:SignalingRegistration() signaling:%x", this, signaling); this->signaling = signaling; signaling->registration = this; hw = 0; phys = 0; timeout = TIMEOUT_MIN; clientUp = false; err = 0; registered = false; calls = 0; activePhoneCall = 0; presenceCall = false; dnd = false; diversionSession = NULL; } SignalingRegistration::~SignalingRegistration() { signaling->log->Log(LOG_SIGNALING, "%x:~SignalingRegistration()", this); free(hw); if (phys) free(phys); if (err) free(err); signaling->RemoveClientMonitor(this); signaling->RegistrationDeleted(this); } void SignalingRegistration::ClientState(bool up) { signaling->log->Log(LOG_SIGNALING, "%x:SignalingRegistration::ClientState(%u) registered:%u", this, up, registered); timeout = TIMEOUT_MIN; retry.Cancel(); this->registered = false; if (up) { Register(); if (this->err) { free(this->err); this->err = 0; } } else { if (signaling->Sessions()) signaling->StartUpdate(new SignalingUpdate(SignalingRegistrationInfo, "RegistrationDown", 0, signaling->filters)); signaling->RegistrationState(false); } this->clientUp = up; } word find_fty(class json_io & msg, word base, const char * name, unsigned index = 0) { word fty = msg.get_array(base, "fty"); if (fty != JSON_ID_NONE) { word object = msg.get_object(fty, 0); for (unsigned idx = 0; object != JSON_ID_NONE; idx++) { const char * type = msg.get_string(object, "type"); if (type && !strcmp(type, name) && idx == index) return object; object = msg.get_object(fty, object); } } return JSON_ID_NONE; } void SignalingRegistration::Register() { class SignalingRegMessage reg("Register", src, alloca(1000)); reg.add_string(reg.base, "hw", hw); if (phys) reg.add_string(reg.base, "gk", phys); char versionId[256]; _sprintf(versionId, "%s %s [%s]", _VERSION_STR_, _RELEASE_STATE_, _BUILD_STRING_); reg.add_string(reg.base, "versionId", versionId); reg.add_string(reg.base, "productId", _PRODUCT_NAME_); reg.Send(this); } void SignalingRegistration::ClientMessage(class json_io & msg, word base, const char * mt, const char * src) { signaling->log->Log(LOG_SIGNALING, "%x:SignalingRegistration::ClientMessage(mt: %s)", this, mt); if (!strcmp(mt, "RegisterResult")) { const char * err = msg.get_string(base, "err"); if (err) { retry.Start(timeout); if (this->err) free(this->err); this->err = _strdup(err); if (this->err && !strcmp(this->err, "standingby")) { signaling->RegisterToAltPbx(); } else if (this->err && !strcmp(this->err, "wrong pbx")) { signaling->RegisterToRedirectUrl(msg.get_string(base, "url")); if (this->phys) free(this->phys); this->phys = _strdup(msg.get_string(base, "alt-gk")); } else if (this->err && !strcmp(this->err, "hw unknown")) { } } else { if (signaling->localIpAddr) free(signaling->localIpAddr); signaling->localIpAddr = _strdup(msg.get_string(base, "addr")); signaling->videoLicense = msg.get_bool(base, "video"); signaling->recordingLicense = msg.get_bool(base, "recording"); dialingLocation.read(msg, base); signaling->SaveRegistration(hw, phys, hwTimeout); registered = true; if (this->err) { free(this->err); this->err = 0; } class SignalingMediaConfig* c = signaling->service->mediaConfig; if (c->stunServers) free(c->stunServers); c->stunServers = _strdup(msg.get_string(base, "stun")); if (c->turnServers) free(c->turnServers); c->turnServers = _strdup(msg.get_string(base, "turn")); if (c->turnUsername) free(c->turnUsername); c->turnUsername = _strdup(msg.get_string(base, "turnUsr")); if (c->turnPassword) free(c->turnPassword); c->turnPassword = _strdup(msg.get_string(base, "turnPwd")); c->stunSlow = msg.get_bool(base, "stunSlow"); if (!presenceCall) { presenceCall = true; SignalingSigMessage msg(presenceSubscriptionCallId, "setup", alloca(1000), alloca(100)); word ftys = msg.add_array(msg.sig, "fty"); word fty = msg.add_object(ftys, 0); msg.add_string(fty, "type", "presence_subscribe"); msg.add_int(msg.sig, "channel", 0, msg.tmp); word cd = msg.add_object(msg.sig, "cd"); char * currentUser = ILocalStorage::GetItem("currentUser"); msg.add_string(cd, "sip", currentUser); msg.Send(this); if (currentUser) free(currentUser); } } if (signaling->Sessions()) signaling->StartUpdate(new SignalingUpdate(SignalingRegistrationInfo, err ? "RegistrationDown" : "RegistrationUp", 0, signaling->filters)); signaling->RegistrationState(err == 0); } else if (!strcmp(mt, "Unregister")) { signaling->RegisterToAltPbx(); } else if (!strcmp(mt, "Signaling")) { int call = msg.get_int(base, "call"); word sig = msg.get_object(base, "sig"); const char * type = msg.get_string(sig, "type"); if (type) { if (call == presenceSubscriptionCallId) { if (!strcmp(type, "facility")) { word fty = find_fty(msg, sig, "presence_notify"); if (fty != JSON_ID_NONE) { const char * contact = msg.get_string(fty, "contact"); const char * activity = msg.get_string(fty, "activity"); if (contact && !strcmp(contact, "tel:")) { if (activity && !strcmp(activity, "dnd")) { signaling->log->Log(LOG_SIGNALING, "%x:SignalingRegistration::ClientMessage presence_notify dnd", this); dnd = true; } else { signaling->log->Log(LOG_SIGNALING, "%x:SignalingRegistration::ClientMessage presence_notify", this); dnd = false; } if (signaling->user) signaling->user->SetDnd(dnd); } } } else if (!strcmp(type, "rel")) { signaling->log->Log(LOG_SIGNALING, "%x:SignalingRegistration::Presence subscribe call was removed", this); } } if (call == diversionCallId) { word diversion_interrogate_result = find_fty(msg, sig, "diversion_interrogate_result"); word diversion_deactivate_result = find_fty(msg, sig, "diversion_deactivate_result"); word diversion_activate_result = find_fty(msg, sig, "diversion_activate_result"); if (diversion_interrogate_result != JSON_ID_NONE && diversionSession) { class SignalingRCCMessage * diversionInterrogateResult = new class SignalingRCCMessage("DiversionInterrogateResult", 0, diversionSession, alloca(1000), alloca(100)); for (unsigned index = 0; true; index++) { word fty = find_fty(msg, sig, "diversion_interrogate_result", index); if (fty == JSON_ID_NONE) break; word list = msg.get_array(fty, "list"); if (list == JSON_ID_NONE) continue; const char * procedure = 0, *num = 0, *sip = 0; for (word object = msg.get_object(list, 0); object != JSON_ID_NONE; object = msg.get_object(fty, object)) { procedure = msg.get_string(object, "procedure"); word diverted_to = msg.get_object(object, "diverted_to"); if (diverted_to != JSON_ID_NONE) { num = msg.get_string(diverted_to, "num"); sip = msg.get_string(diverted_to, "sip"); } } if (procedure && (num || sip)) { word obj = diversionInterrogateResult->add_object(diversionInterrogateResult->base, procedure); if (num) diversionInterrogateResult->add_string(obj, "num", num); if (sip) diversionInterrogateResult->add_string(obj, "sip", sip); } } diversionInterrogateResult->Send(); } else if (diversion_deactivate_result != JSON_ID_NONE && diversionSession) { class SignalingRCCMessage * diversionDeactivateResult = new class SignalingRCCMessage("DiversionDeactivateResult", 0, diversionSession, alloca(1000), alloca(100)); diversionDeactivateResult->Send(); } else if (diversion_activate_result != JSON_ID_NONE && diversionSession) { class SignalingRCCMessage * diversionActivateResult = new class SignalingRCCMessage("DiversionActivateResult", 0, diversionSession, alloca(1000), alloca(100)); diversionActivateResult->Send(); } if (strcmp(type, "rel")) { // release signaling connection SignalingSigMessage msg(call, "rel", alloca(1000), alloca(100)); msg.Send(this); } return; } if (!strcmp(type, "setup") && msg.get_int(sig, "channel") == -1) { while(SignalingCall * signalingCall = FindCallByState(0)) { signalingCall->AbortTone(); signalingCall->Delete(); } } class btree * b = calls->btree_find((void *)(intp)call); if (!b) { if (!strcmp(type, "setup")) { // new inbound call word fty_video_setup = find_fty(msg, sig, "video_setup"); if (fty_video_setup != JSON_ID_NONE) { // keep this call id in mind as pass-through signaling const char * mode = msg.get_string(fty_video_setup, "mode"); inboundVideoCalls.push_back(new SignalingInboundVideoCall(call, mode)); // pass-through to frontend for handling signaling->UnknownCallMessage(type, msg, base); } else if (msg.get_int(sig, "channel") == 0) { ControlCall(msg, base, mt, src); } else if((calls && calls->get_count() > 0) && !signaling->callWaiting) { bool relCall = true; word innoRemoteControl = find_fty(msg, sig, "innovaphone_remote_control"); if (innoRemoteControl != JSON_ID_NONE) { const char* control = msg.get_string(innoRemoteControl, "control"); if (control && !strcmp(control, "CONNECT")) relCall = false; } if (relCall) { class SignalingSigMessage send(call, "rel", alloca(1000), alloca(100)); word cau = send.add_object(send.sig, "cau"); send.add_int(cau, "num", Q931_CAUSE_UserBusy, send.tmp); send.Send(this); } } else { // Close Test Devices if opened! signaling->IncomingCall(); class SignalingCall* c = new SignalingCall(this, CallTypeVoice, call, 0, 0, 0, false); signaling->CallAdded(c, 0); b = c; } } else { class SignalingInboundVideoCall * inboundVideoCall = inboundVideoCalls.front(); while (inboundVideoCall) { if (inboundVideoCall->id == call) break; inboundVideoCall = inboundVideoCall->goNext(); } if (inboundVideoCall) { if (!strcmp(type, "rel")) { inboundVideoCall->remove(); delete inboundVideoCall; } } signaling->UnknownCallMessage(type, msg, base); return; } } if (b) { // "call":-1 is being disconnected and waits for MediaCloseComplete // A ControlCall then arrives with "call":-1 and channel == 0 // Call should be processed separately, nothing to do with the audio call if (!strcmp(type, "setup") && msg.get_int(sig, "channel") == 0) { ControlCall(msg, base, mt, src); } else { ((class SignalingCall *)b)->Message(type, msg, sig); if (((class SignalingCall*)b)->clearCall) ((class SignalingCall*)b)->ClearCall(); } } } } else if (!strcmp(mt, "CheckCallResult")) { const char * uuid = msg.get_string(base, "confID"); bool ok = msg.get_bool(base, "ok"); if (!ok && signaling->user) { signaling->user->ReportCallEnded(uuid, CALL_ENDED_REASON_ANSWERED_ELSEWHERE); } } } void SignalingRegistration::ControlCall(class json_io& msg, word base, const char* mt, const char* src) { int call = msg.get_int(base, "call"); bool sendRel = true; signaling->log->Log(LOG_SIGNALING, "%x:SignalingRegistration::ControlCall reg=%x (hw=%s) this->hw=%s", this, signaling->registration, hw ? hw : "-", this->hw ? this->hw : "-"); word sig = msg.get_object(base, "sig"); if (sig != 0xffff) { word ftys = msg.get_array(sig, "fty"); if (ftys != 0xffff) { word fty = msg.get_object(ftys, 0); while (fty) { if (const char* type = msg.get_string(fty, "type")) { signaling->log->Log(LOG_SIGNALING, "%x:SignalingRegistration::ControlCall type=%s", this, type); if (!strcmp(type, "mwi_activate")) { // show MWI in UI } if (!strcmp(type, "mwi_deactivate")) { // hide MWI in UI } if (!strcmp(type, "diverting_leg4")) { const char * reason = msg.get_string(fty, "reason"); if (diversionSession) { // notify frontend so it will start another interrogation class SignalingRccSession * session = diversionSession; class SignalingRCCMessage * diversionsChanged = new class SignalingRCCMessage("DiversionsChanged", 0, session, alloca(1000), alloca(100)); diversionsChanged->add_string(diversionsChanged->base, "procedure", reason); diversionsChanged->Send(); } } } fty = msg.get_object(ftys, fty); } } } if (sendRel) { class SignalingSigMessage send(call, "rel", alloca(1000), alloca(100)); send.Send(this); } } void SignalingRegistration::Update(const char * hw, const char* phys, unsigned timeout) { signaling->log->Log(LOG_SIGNALING, "%x:SignalingRegistration::Update reg=%x (hw=%s) this->hw=%s timeout=%d", this, signaling->registration, hw ? hw : "-", this->hw ? this->hw : "-", timeout); if (hw) { if (timeout) this->timeout = timeout; if (this->phys) free((this->phys)); if (signaling->client && signaling->client->domain && phys && phys[0]) { if (strstr(phys, signaling->client->domain)) { this->phys = _strdup(phys); } else { this->phys = (char*)malloc(strlen(signaling->client->domain) + 1 + strlen(phys) + 1); _sprintf(this->phys, "%s@%s", signaling->client->domain, phys); } } else this->phys = NULL; if (!this->hw) { this->hw = _strdup(hw); this->hwTimeout = this->timeout; signaling->AddClientMonitor(this); } else if (strcmp(this->hw, hw)) { UnRegister(); class SignalingRegistration* r = new SignalingRegistration(signaling); r->Update(hw, phys, this->timeout); signaling->registration = r; // Update registration pointer in actual calls if (this->calls) { class btree* b = this->calls->btree_find_next_right(0); while (b) { class SignalingCall* c = (class SignalingCall*)b; debug->printf("%x:SignalingRegistration::Update call:%x", this, c); c->ResetRegistration(r); b = this->calls->btree_find_next_right((void*)(intp)c->call); } r->calls = this->calls; this->calls = 0; } if (this->activePhoneCall) { r->activePhoneCall = this->activePhoneCall; this->activePhoneCall = 0; } delete this; } else if(registered) signaling->SaveRegistration(hw, phys, timeout); } else { delete this; } } void SignalingRegistration::Sync(class json_io & msg, word base) { if (registered) { word handle = msg.add_object(base, "sync"); msg.add_string(handle, "mt", "RegistrationUp"); } } void SignalingRegistration::UnRegister() { signaling->log->Log(LOG_SIGNALING, "%x:SignalingRegistration::UnRegister()", this); if (registered) { class SignalingRegMessage reg("UnRegister", src, alloca(1000)); reg.Send(this); } } void SignalingRegistration::TimerOnTimeout(ITimer * timer) { Register(); if (timeout < TIMEOUT_MAX) timeout *= 2; } SignalingCall * SignalingRegistration::FindCallByState(word state) { SignalingCall *call = nullptr; while ((call = (class SignalingCall *)calls->btree_find_next_right(call ? (void*)(intp)call->call : 0))) { if (call->state == state) { return call; } } return NULL; } void SignalingRegistration::StateChanged(SignalingCall* call) { static int inStateChanged = 0; if (inStateChanged == call->call) debug->printf("DEBUG keep from recursion on call %i", call->call); if (inStateChanged == call->call) return; // keep from recursive calling else inStateChanged = call->call; signaling->log->Log(LOG_SIGNALING, "SignalingRegistration::StateChanged() call=%i state=%x", call->call, call->state); class SignalingCall* calling = 0, * connected = 0, * holding = 0, * alerting = 0, * waiting = 0; class btree* b = this->calls->btree_find_next_right(0); while (b) { class SignalingCall* c = (class SignalingCall*)b; if (c->state && (c->state & 0xFF) <= SIG_CALL_STATE_ALERT) calling = c; if (c->state == SIG_CALL_STATE_CONN) connected = c; if (c->state == (SIG_CALL_STATE_CONN | SIG_CALL_STATE_OUTG)) connected = c; if (c->state == (SIG_CALL_STATE_ALERT | SIG_CALL_STATE_OUTG)) alerting = c; if (c->state == (SIG_CALL_STATE_ALERT | SIG_CALL_STATE_OUTG | SIG_CALL_STATE_CALL_WAITING) && !waiting) waiting = c; if (c->state & SIG_CALL_STATE_HOLD) holding = c; b = this->calls->btree_find_next_right((void*)(intp)c->call); } signaling->log->Log(LOG_SIGNALING, "DEBUG calling=%i connected=%i holding=%i alerting=%i waiting=%i dnd=%i", calling ? calling->call : 0, connected ? connected->call : 0, holding ? holding->call : 0, alerting ? alerting->call : 0, waiting ? waiting->call : 0, dnd); if (connected) activePhoneCall = connected; else if (calling) activePhoneCall = calling; else if (alerting) activePhoneCall = alerting; else if (holding) activePhoneCall = holding; else activePhoneCall = 0; if (calling || connected) { if (alerting) { debug->printf("DEBUG Turn alerting call %i into waiting call", alerting->call); alerting->callWaiting = true; alerting->SendAlert(); alerting->StopRinging(); } } else if (alerting) { if (!dnd && signaling->user) signaling->user->RequestStartRinging(alerting->GetConfId()); } else if (holding) { // nothing to do } else if (waiting) { debug->printf("DEBUG Turn waiting call %i into alerting call", waiting->call); waiting->callWaiting = false; waiting->SendAlert(); if (!dnd && signaling->user) signaling->user->RequestStartRinging(waiting->GetConfId()); } inStateChanged = 0; } const char * SignalingRegistration::AddExternalLinePrefix(const char * number, char * buffer, unsigned length) { if(!number) return number; // dialingLocation: "000", "00", "0" // dialingLocation: "900", "90", "9" const char * prefixIntl = dialingLocation.prefixIntl ? dialingLocation.prefixIntl : ""; const char * prefixNtl = dialingLocation.prefixNtl ? dialingLocation.prefixNtl : ""; const char * prefixSubs = dialingLocation.prefixSubs ? dialingLocation.prefixSubs : ""; unsigned maxIntern = dialingLocation.maxIntern ? dialingLocation.maxIntern : 6; if (!strncmp(number, "+", strlen("+")) ) { // fully qualified international number return number; } if (strlen(prefixIntl) > 0 && !strncmp(number, prefixIntl, strlen(prefixIntl)) ) { // international number with external line prefix return number; } if (strlen(prefixIntl) > 0 && strlen(prefixSubs) > 0 && !strncmp(prefixIntl, prefixSubs, strlen(prefixSubs))) { prefixIntl = prefixIntl + strlen(prefixSubs); } if (strlen(prefixNtl) > 0 && strlen(prefixSubs) > 0 && !strncmp(prefixNtl, prefixSubs, strlen(prefixSubs))) { prefixNtl = prefixNtl + strlen(prefixSubs); } if (strlen(prefixIntl) > 0 && !strncmp(number, prefixIntl, strlen(prefixIntl)) ) { // international number without external line prefix // national number with external line prefix _snprintf(buffer,length,"%s%s",prefixSubs, number); return buffer; } else if (strlen(prefixNtl) > 0 && !strncmp(number, prefixNtl, strlen(prefixNtl)) ) { // national number without external line prefix // subscriber number with external line prefix _snprintf(buffer,length,"%s%s",prefixSubs, number); return buffer; } else if (strlen(number) > maxIntern) { // subscriber number without external line prefix _snprintf(buffer,length,"%s%s",prefixSubs, number); return buffer; } return number; } unsigned SignalingRegistration::ExternalLinePrefixOffset(const char * number) { if( (dialingLocation.prefixSubs && strlen(dialingLocation.prefixSubs) > 0 && number && !strncmp(number, dialingLocation.prefixSubs, strlen(dialingLocation.prefixSubs)) && (strchr(number, ',') ? strchr(number, ',') - number : strlen(number)) >= strlen(dialingLocation.prefixSubs) + 7) || ( number && dialingLocation.prefixNtl && !strncmp(number, dialingLocation.prefixNtl, strlen(dialingLocation.prefixNtl)) ) || ( number && dialingLocation.prefixIntl && !strncmp(number, dialingLocation.prefixIntl, strlen(dialingLocation.prefixIntl)) ) ) { return dialingLocation.prefixSubs ? strlen(dialingLocation.prefixSubs) : 0; } return 0; } bool SignalingRegistration::IsExternalCall(const char * number) { if(!number) return false; // dialingLocation: "000", "00", "0" // dialingLocation: "900", "90", "9" const char * prefixIntl = dialingLocation.prefixIntl ? dialingLocation.prefixIntl : ""; const char * prefixNtl = dialingLocation.prefixNtl ? dialingLocation.prefixNtl : ""; const char * prefixSubs = dialingLocation.prefixSubs ? dialingLocation.prefixSubs : ""; unsigned maxIntern = dialingLocation.maxIntern ? dialingLocation.maxIntern : 6; if (!strncmp(number, "+", strlen("+")) ) { // fully qualified international number return true; } if (strlen(prefixIntl) > 0 && !strncmp(number, prefixIntl, strlen(prefixIntl)) ) { // international number with external line prefix return true; } if (strlen(prefixIntl) > 0 && strlen(prefixSubs) > 0 && !strncmp(prefixIntl, prefixSubs, strlen(prefixSubs))) { prefixIntl = prefixIntl + strlen(prefixSubs); } if (strlen(prefixNtl) > 0 && strlen(prefixSubs) > 0 && !strncmp(prefixNtl, prefixSubs, strlen(prefixSubs))) { prefixNtl = prefixNtl + strlen(prefixSubs); } if (strlen(prefixIntl) > 0 && !strncmp(number, prefixIntl, strlen(prefixIntl)) ) { // international number without external line prefix // national number with external line prefix return true; } else if (strlen(prefixNtl) > 0 && !strncmp(number, prefixNtl, strlen(prefixNtl)) ) { // national number without external line prefix // subscriber number with external line prefix return true; } else if (strlen(number) > maxIntern) { // subscriber number without external line prefix return true; } return false; } SignalingRegMessage::SignalingRegMessage(const char * mt, const char * src, void * sb) : JsonIo((char *)sb) { this->sb = (char *)sb; base = add_object(0xffff, 0); add_string(base, "mt", mt); add_string(base, "src", src); } void SignalingRegMessage::Send(class SignalingRegistration * registration) { registration->MessageSend(*this, base, sb); } /*-----------------------------------------------------------------------------------------------*/ static istd::list assignmentsList; SignalingCall::SignalingCall(class SignalingRegistration * registration, enum CallType callType, int call, const char * num, const char * sip, const char * dn, bool sendingComplete) : remote(num, sip, dn), diverting(0, 0, 0), originalCalled(0, 0, 0), transfering(0, 0, 0), toneEndTimer(registration->signaling->service->iomux, this), autoConnectTimer(registration->signaling->service->iomux, this), mediaInfoInterval(registration->signaling->service->iomux, this), queuedDTMFTimer(registration->signaling->service->iomux, this) { this->registration = registration; this->audioIo = registration && registration->signaling ? registration->signaling->GetAudioIo() : NULL; this->videoIo = registration && registration->signaling ? registration->signaling->GetVideoIo() : NULL; this->ringerIo = registration && registration->signaling ? registration->signaling->GetRingerIo() : NULL; this->mediaProvider = registration && registration->signaling ? registration->signaling->GetMediaProvider() : NULL; this->audioCoder = CODER_UNDEFINED; this->videoCoder = CODER_UNDEFINED; this->videoCoderBeforeHold = CODER_UNDEFINED; this->videoDeviceIdBeforeHold = NULL; this->callType = callType; this->sendingComplete = sendingComplete; if (!call) { call = firstCallId; for (class btree * b = registration->calls->btree_find((void *)(intp)call); b; call++, b = registration->calls->btree_find((void *)(intp)call)); } this->call = call; registration->calls = registration->calls->btree_put(this); state = 0; cause = 0; media = 0; // for AUDIO media2 = 0; // for VIDEO localAudioConfig = 0; localVideoConfig = 0; remoteAudioConfig = 0; remoteVideoConfig = 0; remoteMediaCmd = MEDIA_CMD_REQUEST; localDTMF = DEFAULT_PAYLOAD_TYPE_DTMF; remoteDTMF = 0; connPostponed = false; alertPostponedOnHookDevice = false; connPostponedOnHookDevice = false; updateHookDevice = false; connReceived = false; selectByConn = false; cameraAllowed = registration->signaling->startVideo; videoDisabled = false; sendVideoProposal = false; callWaiting = false; initializing = 0; isMediaInitialized = false; connecting = 0; audioIoChannel = 0; videoIoChannel = 0; media2Closing = 0; mediaClosing = 0; videoIoChannelClosing = 0; rtptp = 0; fty_ct_initiate.zerioze(); ctIdentify = 0; ctInitiate = 0; deleting = false; audioDeviceId = 0; videoDeviceId = 0; ringerDeviceId = 0; ringertoneIdent = 0; conferenceGuid = 0; conferenceType = 0; multiVideoSupport = false; remoteVideoPriority = false; announce = false; muted = false; hookState = 0; tx.jitter = tx.loss = tx.roundTrip = 0; rx.jitter = rx.loss = rx.roundTrip = 0; ct_setup_id = 0; confId = (char *)malloc(2 * sizeof(this->conf) + 1); confId[0] = 0; numAudioPackets = numVideoPackets = 0; lastNumAudioPackets = lastNumVideoPackets = 0; audioDataOff = videoDataOff = false; this->allocatedTones = 16; this->tones = (struct AudioIoDualTone*)(malloc(this->allocatedTones * sizeof(struct AudioIoDualTone))); this->duration = 0; this->audioMediaConnected = false; this->clearCall = false; this->recordingInfo.initialize(); this->noAudioIceCandidates = false; this->noVideoIceCandidates = false; if (assignmentsList.empty()) { assignmentsList.push_back(new class CodecAssignments(G711_A, DEFAULT_PAYLOAD_TYPE_G711_A)); assignmentsList.push_back(new class CodecAssignments(G711_U, DEFAULT_PAYLOAD_TYPE_G711_U)); assignmentsList.push_back(new class CodecAssignments(G722, DEFAULT_PAYLOAD_TYPE_G722)); assignmentsList.push_back(new class CodecAssignments(OPUS_NB, DEFAULT_PAYLOAD_TYPE_OPUS)); assignmentsList.push_back(new class CodecAssignments(OPUS_WB, DEFAULT_PAYLOAD_TYPE_OPUS)); assignmentsList.push_back(new class CodecAssignments(G729, DEFAULT_PAYLOAD_TYPE_G729)); assignmentsList.push_back(new class CodecAssignments(G729A, DEFAULT_PAYLOAD_TYPE_G729)); assignmentsList.push_back(new class CodecAssignments(G729B, DEFAULT_PAYLOAD_TYPE_G729)); assignmentsList.push_back(new class CodecAssignments(G729AB, DEFAULT_PAYLOAD_TYPE_G729)); assignmentsList.push_back(new class CodecAssignments(VP8, DEFAULT_PAYLOAD_TYPE_VP8)); assignmentsList.push_back(new class CodecAssignments(VP9, DEFAULT_PAYLOAD_TYPE_VP9)); assignmentsList.push_back(new class CodecAssignments(AV1, DEFAULT_PAYLOAD_TYPE_AV1)); assignmentsList.push_back(new class CodecAssignments(H264, DEFAULT_PAYLOAD_TYPE_H264)); } debug->printf("%x/%i:SignalingCall()", this, call); } SignalingCall::~SignalingCall() { debug->printf("%x/%i:~SignalingCall()", this, call); if (ctInitiate) ctInitiate->ctIdentify = 0; if (ctIdentify) ctIdentify->ctInitiate = 0; if (conferenceGuid) free(conferenceGuid); if (audioDeviceId) free(audioDeviceId); if (videoDeviceId) free(videoDeviceId); if (ringerDeviceId) free(ringerDeviceId); if (ringertoneIdent) free(ringertoneIdent); if (tones) free(tones); if (confId) free(confId); toneEndTimer.Cancel(); autoConnectTimer.Cancel(); mediaInfoInterval.Cancel(); queuedDTMFTimer.Cancel(); while (dtmfs.front()) delete dtmfs.front(); conferenceGuid = 0; recordingInfo.cleanup(); } void SignalingCall::RccSessionOpened() { debug->printf("%x/%i:SignalingCall::RccSessionOpened() reg=%x sig=%x cameraAllowed=%d startVideo=%d calls=%d", this, call, registration, registration ? registration->signaling : 0, cameraAllowed, registration && registration->signaling ? registration->signaling->startVideo : 0, registration && registration->calls ? registration->calls->get_count() : 0); } void SignalingCall::RccSessionClosed() { debug->printf("%x/%i:SignalingCall::RccSessionClosed() active calls=%d", this, call, registration->calls->get_count()); if (registration) { if ((registration->calls->get_count() < 2) || (!registration->signaling->uiRunning)) { if (videoIo && videoDeviceId) { videoIo->StopDevice((void*)this, videoDeviceId); free(videoDeviceId); videoDeviceId = NULL; videoIo->UnshareAllApplications((void*)this); } } if (registration->calls->get_count() < 2) { StopRinging(); } } } void SignalingCall::HookDeviceAcquired() { debug->printf("%x/%i:SignalingCall::HookDeviceAcquired media=%p postponed=%d,%d updateHookDevice=%d state=%x", this, call, media, alertPostponedOnHookDevice, connPostponedOnHookDevice, updateHookDevice, state); if (updateHookDevice) { if (audioDeviceId) { registration->signaling->SendHookKey(audioDeviceId, KEY_OFFHOOK, call, remote.num ? remote.num : remote.sip, remote.dn); if(hookState != KEY_OFFHOOK) registration->signaling->SendHookKey(audioDeviceId, hookState, call, remote.num ? remote.num : remote.sip, remote.dn); } updateHookDevice = false; } if (alertPostponedOnHookDevice) { if (media) MediaInitialized(); alertPostponedOnHookDevice = false; } if (connPostponedOnHookDevice) { SendConn(); connPostponedOnHookDevice = false; } } void SignalingCall::HookDeviceDenied() { debug->printf("%x/%i:SignalingCall::HookDeviceDenied postponed=%d,%d", this, call, alertPostponedOnHookDevice, connPostponedOnHookDevice); if(audioDeviceId) free(audioDeviceId); audioDeviceId = 0; if (alertPostponedOnHookDevice) { if (media) MediaInitialized(); alertPostponedOnHookDevice = false; } if (isMediaInitialized) { char confIdString[2 * sizeof(conf) + 1]; str::from_hexmem(conf.b, sizeof(conf), confIdString); char cfg[1024]; class json_io send(cfg); word sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confIdString); send.add_string(sendBase, "errorText", "Access denied to hook device"); MonitorXmit("hook_device_denied", send, sendBase); } if (connPostponedOnHookDevice) { SendRel(); connPostponedOnHookDevice = false; } } void SignalingCall::HookDeviceNotFound() { debug->printf("%x/%i:SignalingCall::HookDeviceNotFound postponed=%d,%d", this, call, alertPostponedOnHookDevice, connPostponedOnHookDevice); if (audioDeviceId) free(audioDeviceId); audioDeviceId = 0; if (alertPostponedOnHookDevice) { if (media) MediaInitialized(); alertPostponedOnHookDevice = false; } if (isMediaInitialized) { char confIdString[2 * sizeof(conf) + 1]; str::from_hexmem(conf.b, sizeof(conf), confIdString); char cfg[1024]; class json_io send(cfg); word sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confIdString); send.add_string(sendBase, "errorText", "Hook device not found"); MonitorXmit("hook_device_not_found", send, sendBase); } if (connPostponedOnHookDevice) { SendRel(); connPostponedOnHookDevice = false; } } void SignalingCall::LookupInfo(class json_io& msg, word base) { const char* lookupDn = msg.get_string(base, "dn"); debug->printf("%x/%i:SignalingCall::LookupInfo dn:%s", this, call, lookupDn ? lookupDn : ""); if (lookupDn) { remote.SetDn(lookupDn); if (registration && registration->signaling->user) { unsigned offset = registration->ExternalLinePrefixOffset(remote.num); char remoteSip[1024]; registration->signaling->user->ReportNameIdentification(confId, remote.num + offset, registration->signaling->AppendSipDomain(remote.sip, remoteSip, sizeof(remoteSip)), lookupDn, this->diverting.dn ? this->diverting.dn : this->diverting.sip ? this->diverting.sip : this->diverting.num ? this->diverting.num : NULL, this->originalCalled.dn ? this->originalCalled.dn : this->originalCalled.sip ? this->originalCalled.sip : this->originalCalled.num ? this->originalCalled.num : NULL, cameraAllowed, true, true, true, true); } MonitorXmit("contact", msg, base); } } void SignalingCall::StartCall(class IAudioIo * audioIo, class IVideoIo * videoIo, class URTPTP * rtptp, class SignalingRegistrationMonitor * exclude) { IGuuid::GenerateBinaryUUID(&conf); str::from_hexmem(conf.b, sizeof(conf), this->confId); registration->signaling->log->Log(LOG_SIGNALING, "%x/%i:SignalingCall::StartCall() cameraAllowed=%u confId=%s", this, call, cameraAllowed, this->confId); if (registration->signaling->lastStartCallDest) delete registration->signaling->lastStartCallDest; registration->signaling->lastStartCallDest = new VoipEndpoint(remote.num, remote.sip, remote.dn); this->audioIo = audioIo; this->videoIo = videoIo; this->rtptp = rtptp; this->exclude = exclude; if (registration->clientUp && (registration->registered || registration->err)) { this->recordingInfo.isExternal = registration->IsExternalCall(remote.num); this->recordingInfo.isRecordingByDefaultOn = registration->signaling->service->mediaConfig->isRecordingByDefaultOn; if (registration->signaling->user) { RequestStartCall(); } else { DoStartCall(); } } } void SignalingCall::RegistrationState(bool up) { if (state == 0) { if (registration->signaling->user) { RequestStartCall(); } else { DoStartCall(); } } } void SignalingCall::RequestStartCall() { if (registration->signaling->user) { unsigned offset = registration->ExternalLinePrefixOffset(remote.num); if (remote.sip) { char remoteSip[1024]; const char * sipWithDomain = registration->signaling->AppendSipDomain(remote.sip, remoteSip, sizeof(remoteSip)); if (sipWithDomain != remote.sip) { remote.SetSip(sipWithDomain); } } registration->signaling->user->RequestStartCall(confId, remote.num ? remote.num + offset : remote.sip, remote.dn, videoIo && cameraAllowed, sendingComplete); } else { DoStartCall(); } } const char* SignalingCall::GetConfId() { return (const char *)this->confId; } void SignalingCall::DoStartCall() { // Other calls to Hold if connected class btree* b = registration->calls->btree_find_next_right(0); while (b) { class SignalingCall* c = (class SignalingCall*)b; if (c != this && ((c->state == SIG_CALL_STATE_CONN) || (c->state == (SIG_CALL_STATE_CONN | SIG_CALL_STATE_OUTG)))) c->Hold(); b = registration->calls->btree_find_next_right((void*)(intp)c->call); } MediaInitialize(MediaType::AUDIO); if (audioIo) { audioIoChannel = audioIo->CreateChannel(this); if (audioDeviceId) free(audioDeviceId); if (registration->signaling->changedAudioDeviceId) audioDeviceId = _strdup(registration->signaling->changedAudioDeviceId); else audioDeviceId = _strdup(audioIo->GetDeviceIo(registration->GetHwId())); if (audioDeviceId) { audioIo->StartDevice((void*)this, (void*)this, audioDeviceId, AudioDeviceMode::AudioModeCommunication); if (audioIo->DeviceCapabilities(audioDeviceId) & AUDIO_CAPABILITY_HOOK_DEVICE_SUPPORTS_LOCKING) { registration->signaling->hookDeviceState = HOOK_DEVICE_WAITING; audioIo->AcquireHookDevice(audioDeviceId); } else registration->signaling->hookDeviceState = HOOK_DEVICE_ACQUIRE; } else { // No audio device available, what to do? registration->signaling->log->Log(LOG_SIGNALING, "%x/%i:SignalingCall::StartCall() no audio device available", this, call); return; } } else registration->signaling->hookDeviceState = HOOK_DEVICE_ACQUIRE; if (videoIo && cameraAllowed) { videoIoChannel = videoIo->CreateChannel(this); if (videoDeviceId) free(videoDeviceId); videoDeviceId = _strdup(videoIo->GetDeviceIo(registration->GetHwId())); if (videoDeviceId) videoIo->StartVideoDevice((void*)this, (void*)this, videoDeviceId, 1280, 720, 0); MediaInitialize(MediaType::VIDEO); } registration->signaling->CallAdded(this, exclude); if (registration->signaling->hookDeviceState == HOOK_DEVICE_WAITING) alertPostponedOnHookDevice = true; } void SignalingCall::ClearCall(word cause) { debug->printf("%x/%i:SignalingCall::ClearCall() state:%x hookState:%d", this, call, state, hookState); if (registration) { if (audioIo) MediaTones::StopTone(audioIo); if (audioDeviceId) { if (hookState == KEY_HOLD) registration->signaling->SendHookKey(audioDeviceId, KEY_RETRIEVE, call, remote.num ? remote.num : remote.sip, remote.dn); if (hookState != KEY_ONHOOK) registration->signaling->SendHookKey(audioDeviceId, KEY_ONHOOK, call, remote.num ? remote.num : remote.sip, remote.dn); hookState = KEY_ONHOOK; } if (registration->calls->get_count() < 2) { if (audioIo && audioDeviceId) { audioIo->StopDevice((void *) this, audioDeviceId); free(audioDeviceId); audioDeviceId = NULL; registration->signaling->hookDeviceState = 0; } if (videoIo && videoDeviceId) { videoIo->StopDevice((void *)this, videoDeviceId); free(videoDeviceId); videoDeviceId = NULL; videoIo->UnshareAllApplications((void*)this); } StopRinging(); } if (registration->signaling->user) { registration->signaling->user->ReportCallEnded(confId, (cause == Q931_CAUSE_NonSelectedUserClearing) ? CALL_ENDED_REASON_ANSWERED_ELSEWHERE : CALL_ENDED_REASON_REMOTE_ENDED); } } this->cause = cause; SendRel(); } void SignalingCall::ConnectCall(class IAudioIo * audioIo, class IVideoIo * videoIo) { debug->printf("%x/%i:SignalingCall::ConnectCall() cameraAllowed=%u remoteVideoConfig=%p coder=%d IoChannels=%p,%p", this, call, cameraAllowed, remoteVideoConfig, remoteVideoConfig && remoteVideoConfig->codecList.front() ? remoteVideoConfig->codecList.front()->coder : 0, audioIoChannel, videoIoChannel); selectByConn = false; StopRinging(); if (audioIo) { if (!audioIoChannel) audioIoChannel = audioIo->CreateChannel(this); if (audioDeviceId) free(audioDeviceId); if (registration->signaling->changedAudioDeviceId) audioDeviceId = _strdup(registration->signaling->changedAudioDeviceId); else audioDeviceId = _strdup(audioIo->GetDeviceIo(registration->GetHwId())); if (audioDeviceId) { audioIo->StartDevice((void*)this, (void*)this, audioDeviceId, AudioDeviceMode::AudioModeCommunication); if (audioIo->DeviceCapabilities(audioDeviceId) & AUDIO_CAPABILITY_HOOK_DEVICE_SUPPORTS_LOCKING) { registration->signaling->hookDeviceState = HOOK_DEVICE_WAITING; audioIo->AcquireHookDevice(audioDeviceId); } else registration->signaling->hookDeviceState = HOOK_DEVICE_ACQUIRE; } else { debug->printf("%x/%i:SignalingCall::ConnectCall() no audio device available", this, call); ClearCall(); } } // We got a PROPOSAL with Video, (remoteVideoConfig && remoteVideoConfig->codecList.front()) || // nothing (empty setup) received so far, (remoteMediaCmd == MEDIA_CMD_REQUEST), then we add Video to our Proposal if Video is active if (videoIo && !videoDisabled && ((cameraAllowed && (remoteMediaCmd == MEDIA_CMD_REQUEST)) || (remoteVideoConfig && remoteVideoConfig->codecList.front()))) { // Webcam must only be started if cameraAllowed if (cameraAllowed) { if (videoDeviceId) free(videoDeviceId); videoDeviceId = _strdup(videoIo->GetDeviceIo(registration->GetHwId())); if (videoDeviceId) videoIo->StartVideoDevice((void*)this, (void*)this, videoDeviceId, 1280, 720, 0); } if (!videoIoChannel) videoIoChannel = videoIo->CreateChannel(this); if (!media2) { MediaInitialize(MediaType::VIDEO); } } if (initializing) connPostponed = true; else if (registration->signaling->hookDeviceState == HOOK_DEVICE_ACQUIRE) SendConn(); else if (registration->signaling->hookDeviceState == HOOK_DEVICE_WAITING) connPostponedOnHookDevice = true; } void SignalingCall::TimerOnTimeout(ITimer* iTimer) { //debug->printf("%x:SignalingCall::TimerOnTimeout state=%x", this, state); if (iTimer == &autoConnectTimer) { if (state == (SIG_CALL_STATE_ALERT | SIG_CALL_STATE_OUTG)) { // auto-answer announce call with audio-only and mute microphone if (audioIo) { ConnectCall(audioIo, 0); if (announce) { muted = true; audioIo->Mute(true, false); } } } return; } else if (iTimer == &mediaInfoInterval) { class SignalingSigMessage send(call, "facility", alloca(10000), alloca(1000)); word ftys = send.add_array(send.sig, "fty"); word fty = send.add_object(ftys, 0); send.add_string(fty, "type", "media_info"); word rxBase = send.add_object(fty, "receive"); send.add_string(rxBase, "audio", CoderName(audioCoder)); send.add_bool(rxBase, "srtp", true); send.add_unsigned(rxBase, "loss", rx.loss, send.tmp); send.add_unsigned(rxBase, "jitter", rx.jitter, send.tmp); send.add_unsigned(rxBase, "roundTrip", rx.roundTrip, send.tmp); send.add_string(rxBase, "video", CoderName(videoCoder)); word txBase = send.add_object(fty, "transmit"); send.add_string(txBase, "audio", CoderName(audioCoder)); send.add_bool(txBase, "srtp", true); send.add_unsigned(txBase, "loss", rx.loss, send.tmp); send.add_unsigned(txBase, "jitter", rx.jitter, send.tmp); send.add_unsigned(txBase, "roundTrip", rx.roundTrip, send.tmp); send.add_string(txBase, "video", CoderName(videoCoder)); send.Send(registration); mediaInfoInterval.Start(5000); if (!videoDataOff && numVideoPackets && (lastNumVideoPackets == numVideoPackets)) { char cfg[1024]; class json_io send(cfg); debug->printf("%x/%i:SignalingCall::TimerOnTimeout no video packets received", this, call); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", "video"); MonitorXmit("media_data_off", send, sendBase); videoDataOff = true; } lastNumVideoPackets = numVideoPackets; if (!audioDataOff && numAudioPackets && (lastNumAudioPackets == numAudioPackets)) { char cfg[1024]; class json_io send(cfg); debug->printf("%x/%i:SignalingCall::TimerOnTimeout no audio packets received", this, call); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", "audio"); MonitorXmit("media_data_off", send, sendBase); audioDataOff = true; } lastNumAudioPackets = numAudioPackets; return; } else if (iTimer == &queuedDTMFTimer) { if (!dtmfs.empty()) { class QueuedDTMF * dtmfHelper = dtmfs.pop_front(); DTMF(dtmfHelper->dtmf, dtmfHelper->feedback); delete dtmfHelper; if (!dtmfs.empty()) queuedDTMFTimer.Start(500); } return; } else if (iTimer == &toneEndTimer) { if (registration && registration->signaling->user) { registration->signaling->user->ReportCallEnded(confId, (this->cause == Q931_CAUSE_NonSelectedUserClearing) ? CALL_ENDED_REASON_ANSWERED_ELSEWHERE : CALL_ENDED_REASON_REMOTE_ENDED); } } // toneEndTimer AbortTone(); Delete(); } void SignalingCall::ChangeAudioDevice(const char* deviceId) { debug->printf("%x:SignalingCall::ChangeAudioDevice %s -> %s", this, audioDeviceId ? audioDeviceId : "-", deviceId ? deviceId : "-"); if (audioIo && audioDeviceId && deviceId && strcmp(audioDeviceId, deviceId)) { // StopDevice stops the audio channel and should not be called! // audioIo->StopDevice((void*)this, audioDeviceId); free(audioDeviceId); audioDeviceId = _strdup(deviceId); audioIo->StartDevice((void*)this, (void*)this, audioDeviceId, AudioDeviceMode::AudioModeCommunication); if (audioIo->DeviceCapabilities(audioDeviceId) & AUDIO_CAPABILITY_HOOK_DEVICE_SUPPORTS_LOCKING) { registration->signaling->hookDeviceState = HOOK_DEVICE_WAITING; updateHookDevice = true; audioIo->AcquireHookDevice(audioDeviceId); } else { registration->signaling->hookDeviceState = HOOK_DEVICE_ACQUIRE; registration->signaling->SendHookKey(audioDeviceId, KEY_OFFHOOK, call, remote.num ? remote.num : remote.sip, remote.dn); if (hookState != KEY_OFFHOOK) registration->signaling->SendHookKey(audioDeviceId, hookState, call, remote.num ? remote.num : remote.sip, remote.dn); } } } void SignalingCall::ChangeVideoDevice(const char* deviceId) { debug->printf("%x:SignalingCall::ChangeVideoDevice %s -> %s", this, videoDeviceId ? videoDeviceId : "-", deviceId ? deviceId : "-"); if (videoIo && videoDeviceId && deviceId && strcmp(videoDeviceId, deviceId)) { videoIo->StopDevice((void*)this, videoDeviceId); free(videoDeviceId); videoDeviceId = _strdup(deviceId); videoIo->StartVideoDevice((void*)this, (void*)this, videoDeviceId, 1280, 720, 0); } } void SignalingCall::ChangeState(word state) { debug->printf("%x/%i:SignalingCall::ChangeState %x->%x", this, this->call, this->state, state); if (this->state != state) { this->state = state; registration->StateChanged(this); } if ((state == SIG_CALL_STATE_CONN) || (state == (SIG_CALL_STATE_CONN | SIG_CALL_STATE_OUTG))) mediaInfoInterval.Start(5000); else mediaInfoInterval.Cancel(); } void SignalingCall::AbortTone() { debug->printf("%x:SignalingCall::AbortTone", this); if (audioIo) MediaTones::StopTone(audioIo); bool stopDevice = true; if (registration && registration->calls && registration->calls->get_count() > 1) stopDevice = false; if (audioIo && audioDeviceId && stopDevice) { audioIo->StopDevice((void*)this, audioDeviceId); free(audioDeviceId); audioDeviceId = NULL; } if (toneEndTimer.IsRunning()) { if (registration->signaling->user) { registration->signaling->user->ReportCallEnded(confId, (this->cause == Q931_CAUSE_NonSelectedUserClearing) ? CALL_ENDED_REASON_ANSWERED_ELSEWHERE : CALL_ENDED_REASON_REMOTE_ENDED); } toneEndTimer.Cancel(); } } void SignalingCall::Rc(unsigned rc) { debug->printf("%x/%i:SignalingCall::Rc() uuid %s rc: %d reg=%x", this, call, confId, rc, registration); switch (rc) { case 1: SendConn(); break; case FTY_REMOTE_CONTROL_REC_ON: if (!recordingInfo.recordingError && (!recordingInfo.isTrunkRecording || recordingInfo.isRecordingByDefaultOn) ) media->RecordingOn(true); break; case FTY_REMOTE_CONTROL_REC_OFF: if (!recordingInfo.recordingError && (!recordingInfo.isTrunkRecording || recordingInfo.isRecordingByDefaultOn) ) media->RecordingOn(false); break; case FTY_REMOTE_CONTROL_3PTY_ON: ChangeState(state & ~SIG_CALL_STATE_HOLD); Retrieve(); break; case FTY_REMOTE_CONTROL_MIC_ON: if(audioIo){ muted = false; audioIo->Mute(false,false); } break; case FTY_REMOTE_CONTROL_MIC_OFF: if(audioIo){ muted = true; audioIo->Mute(true,false); } break; case FTY_REMOTE_CONTROL_START_CAMERA: debug->printf("%x/%i:SignalingCall::Rc(FTY_REMOTE_CONTROL_START_CAMERA)", this, call); if (videoIo) { if (videoDeviceId) free(videoDeviceId); videoDeviceId = _strdup(videoIo->GetDeviceIo(registration->GetHwId())); if (videoDeviceId) videoIo->StartVideoDevice((void*)this, (void*)this, videoDeviceId, 1280, 720, 0); } break; case FTY_REMOTE_CONTROL_STOP_CAMERA: debug->printf("%x/%i:SignalingCall::Rc(FTY_REMOTE_CONTROL_STOP_CAMERA)", this, call); if (videoIo && videoDeviceId) { videoIo->StopDevice((void*)this, videoDeviceId); free(videoDeviceId); videoDeviceId = NULL; } break; case FTY_REMOTE_CONTROL_ADD_VIDEO: debug->printf("%x/%i:SignalingCall::Rc(FTY_REMOTE_CONTROL_ADD_VIDEO) videoIo=%x videoIoChannel=%x videoCoder=%u media2=%x localVideoConfig=%p initializing=%x connecting=%x", this, call, videoIo, videoIoChannel, videoCoder, media2, localVideoConfig, initializing, connecting); videoDisabled = false; if (registration && registration->signaling && videoIo) { if (!videoIoChannel) videoIoChannel = videoIo->CreateChannel(this); if (!media2) { MediaInitialize(MediaType::VIDEO); if (connecting & (1 << AUDIO)) { connecting &= ~(1 << AUDIO); media->Close(); mediaClosing = media; DELETE_OBJ(localAudioConfig); MediaInitialize(MediaType::AUDIO); } } else if(localVideoConfig) { GenerateIceCredentials(localVideoConfig->ice); media2->RewriteIceCredentials(localVideoConfig->ice->iceUfrag, localVideoConfig->ice->icePwd); if (connecting & (1 << AUDIO)) { connecting &= ~(1 << AUDIO); media->Close(); mediaClosing = media; DELETE_OBJ(localAudioConfig); MediaInitialize(MediaType::AUDIO); } else SendChannels(); } else { debug->printf("%x/%i:SignalingCall::Rc localVideoConfig not set. Video being initialized media2:%x", this, call, media2); } sendVideoProposal = true; } break; case FTY_REMOTE_CONTROL_ALLOW_VIDEO: debug->printf("SignalingCall::Rc(FTY_REMOTE_CONTROL_ALLOW_VIDEO) videoIo=%x videoIoChannel=%x videoCoder=%u media2=%x initializing=%x connecting=%x videoDisabled=%x", videoIo, videoIoChannel, videoCoder, media2, initializing, connecting, videoDisabled); videoDisabled = false; break; case FTY_REMOTE_CONTROL_REM_VIDEO: debug->printf("SignalingCall::Rc(FTY_REMOTE_CONTROL_REM_VIDEO) videoIo=%x videoIoChannel=%x videoCoder=%u media2=%x initializing=%x connecting=%x", videoIo, videoIoChannel, videoCoder, media2, initializing, connecting); videoDisabled = true; if (videoIo && videoDeviceId) { videoIo->StopDevice((void*)this, videoDeviceId); free(videoDeviceId); videoDeviceId = NULL; } if (videoIoChannel) { videoIoChannel->Close(); videoIoChannelClosing = videoIoChannel; videoIoChannel = NULL; } if (media2) { media2->Close(); media2Closing = media2; media2 = NULL; DELETE_OBJ(localVideoConfig); if (initializing & (1 << VIDEO)) { initializing &= ~(1 << VIDEO); MediaInitialized(); } else if (videoCoder) { if (connecting & (1 << AUDIO)) { connecting &= ~(1 << AUDIO); media->Close(); mediaClosing = media; DELETE_OBJ(localAudioConfig); MediaInitialize(MediaType::AUDIO); } else SendChannels(); } } break; case FTY_REMOTE_CONTROL_ADD_APPSHARING: { class SignalingSigMessage send(call, "facility", alloca(10000), alloca(1000)); word ftys = send.add_array(send.sig, "fty"); word fty = send.add_object(ftys, 0); send.add_string(fty, "type", "video_setup"); send.add_string(fty, "mode", "sendonly"); send.add_string(fty, "guid", conferenceGuid); send.add_unsigned(fty, "bitrate", 1024000, send.tmp); send.Send(registration); } break; case FTY_REMOTE_CONTROL_REM_APPSHARING: { class SignalingSigMessage send(call, "facility", alloca(10000), alloca(1000)); word ftys = send.add_array(send.sig, "fty"); word fty = send.add_object(ftys, 0); send.add_string(fty, "type", "video_setup"); send.add_string(fty, "mode", "sendonly"); send.add_string(fty, "guid", conferenceGuid); send.add_unsigned(fty, "bitrate", 0, send.tmp); send.Send(registration); } break; } } void SignalingCall::Info(const char * num, const char * sip) { remote.AddtoNum(num); remote.SetSip(sip); SendInfo(num, sip); } void SignalingCall::SendDTMF(const char* dtmf, bool feedback) { if (!dtmfs.empty() || audioMediaConnected == false) dtmfs.push_back(new class QueuedDTMF(dtmf, feedback)); else DTMF(dtmf, feedback); } void SignalingCall::DTMF(const char * dtmf, bool feedback) { debug->printf("SignalingCall::DTMF (%s) feedback=%d", dtmf, feedback); if (remoteDTMF && media && dtmf) { for (int i = 0; dtmf[i]; i++) { media->RtpDtmf(dtmf[i], remoteDTMF); } } struct AudioIoDualTone* t; unsigned count, digit; unsigned int flags = 0; if (feedback || !remoteDTMF) { flags |= feedback ? AUDIO_IO_DUAL_TONE_BOTH | AUDIO_IO_DUAL_TONE_PEER : AUDIO_IO_DUAL_TONE_PEER; count = 0; for (digit = 0; dtmf[digit] != '\0'; digit++) { if (count == allocatedTones) { t = (struct AudioIoDualTone*)(malloc(2 * allocatedTones * sizeof(struct AudioIoDualTone))); memcpy(t, tones, allocatedTones * sizeof(struct AudioIoDualTone)); free(tones); tones = t; allocatedTones *= 2; } count += MediaTones::PrepareDtmf(dtmf[digit], &tones[count]); } if (count != 0) { audioIo->StartDualTones(flags, count, tones); } } } void SignalingCall::UUI(unsigned pd, const char * uui) { SignalingSigMessage msg(call, "user_info", alloca(10000), alloca(100)); if (pd) msg.add_unsigned(msg.sig, "pd", pd, msg.tmp); msg.add_string(msg.sig, uui, uui); MonitorXmit("user_info", msg, msg.sig); msg.Send(registration); } void SignalingCall::StartRinging() { if (ringerDeviceId) { // ringer already started } else if (ringerIo) { ringerDeviceId = _strdup(ringerIo->GetDeviceIo(registration->GetHwId())); ringertoneIdent = _strdup(ringerIo->GetRingtone(registration->GetHwId())); if (ringerDeviceId && ringertoneIdent) ringerIo->RingtoneStart(ringerDeviceId, 0, ringertoneIdent); } } void SignalingCall::StopRinging() { if (ringerIo && ringerDeviceId) { ringerIo->RingtoneStop(ringerDeviceId); free(ringerDeviceId); ringerDeviceId = 0; if(ringertoneIdent) free(ringertoneIdent); ringertoneIdent = 0; } } void SignalingCall::StartTransfer(class SignalingCall * from) { if (!ctIdentify && !ctInitiate && !from->ctIdentify && !from->ctInitiate) { ctInitiate = from; from->ctIdentify = this; SignalingSigMessage msg(call, "facility", alloca(1000), alloca(100)); word ftys = msg.add_array(msg.sig, "fty"); word fty = msg.add_object(ftys, 0); msg.add_string(fty, "type", "ct_identify"); msg.Send(registration); } } void SignalingCall::StartRedirect(const char * num, const char * sip) { SignalingSigMessage msg(call, "facility", alloca(1000), alloca(100)); word ftys = msg.add_array(msg.sig, "fty"); word fty = msg.add_object(ftys, 0); msg.add_string(fty, "type", "ct_initiate"); word dst = msg.add_object(fty, "dst"); msg.add_string(dst, "num", num); msg.add_string(dst, "sip", sip); msg.Send(registration); } void SignalingCall::StartReroute(const char * num, const char * sip) { SignalingSigMessage msg(call, "facility", alloca(1000), alloca(100)); word ftys = msg.add_array(msg.sig, "fty"); word fty = msg.add_object(ftys, 0); msg.add_string(fty, "type", "diversion_callreroute"); msg.add_string(fty, "reason", "CFU"); word called = msg.add_object(fty, "called"); msg.add_string(called, "num", num); msg.add_string(called, "sip", sip); msg.Send(registration); } void SignalingCall::Hold() { SignalingSigMessage msg(call, "facility", alloca(1000), alloca(100)); MonitorXmit("facility_hold", msg, msg.sig); word ftys = msg.add_array(msg.sig, "fty"); word fty = msg.add_object(ftys, 0); msg.add_string(fty, "type", "remote_hold"); msg.Send(registration); if (registration->signaling->user) { registration->signaling->user->ReportCallHold(confId); } videoCoderBeforeHold = videoCoder; videoDeviceIdBeforeHold = _strdup(videoDeviceId); if (registration) { if (audioDeviceId) { registration->signaling->SendHookKey(audioDeviceId, KEY_HOLD, call, remote.num ? remote.num : remote.sip, remote.dn); hookState = KEY_HOLD; } if (registration->calls->get_count() < 2) { if (videoIo && videoDeviceId) { videoIo->StopDevice((void*)this, videoDeviceId); free(videoDeviceId); videoDeviceId = NULL; videoIo->UnshareAllApplications((void*)this); } } } } void SignalingCall::Retrieve() { SignalingSigMessage msg(call, "facility", alloca(1000), alloca(100)); MonitorXmit("facility_retrieve", msg, msg.sig); word ftys = msg.add_array(msg.sig, "fty"); word fty = msg.add_object(ftys, 0); msg.add_string(fty, "type", "remote_retrieve"); msg.Send(registration); if (registration->signaling->user) { registration->signaling->user->ReportCallRetrieve(confId); } // always re-start audio device on RETRIEVE if (audioIo) { audioIo->Mute(muted, false); if (!audioIoChannel) audioIoChannel = audioIo->CreateChannel(this); if (!audioDeviceId) { if (registration->signaling->changedAudioDeviceId) audioDeviceId = _strdup(registration->signaling->changedAudioDeviceId); else audioDeviceId = _strdup(audioIo->GetDeviceIo(registration->GetHwId())); } if (audioDeviceId) { registration->signaling->SendHookKey(audioDeviceId, KEY_RETRIEVE, call, remote.num ? remote.num : remote.sip, remote.dn); registration->signaling->SendHookKey(audioDeviceId, KEY_OFFHOOK, call, remote.num ? remote.num : remote.sip, remote.dn); hookState = KEY_OFFHOOK; } } // re-start video device only if video was negotiated before HOLD and the webcam was activated, could have been stopped if(videoDeviceIdBeforeHold) { if (videoIo && videoCoderBeforeHold) { if (!videoIoChannel) videoIoChannel = videoIo->CreateChannel(this); videoIo->StartVideoDevice((void*)this, (void*)this, videoDeviceIdBeforeHold, 1280, 720, 0); if (videoDeviceId) free(videoDeviceId); videoDeviceId = videoDeviceIdBeforeHold; } else free(videoDeviceIdBeforeHold); videoDeviceIdBeforeHold = NULL; } } void SignalingCall::Park(const char * parked_to_num, const char * parked_to_sip) { SignalingSigMessage msg(call, "facility", alloca(1000), alloca(100)); MonitorXmit("facility_park", msg, msg.sig); word ftys = msg.add_array(msg.sig, "fty"); word fty = msg.add_object(ftys, 0); msg.add_string(fty, "type", "cp_park"); if (parked_to_num || parked_to_sip) { word parked_to = msg.add_object(fty, "parked_to"); msg.add_string(parked_to, "num", parked_to_num); msg.add_string(parked_to, "sip", parked_to_sip); } msg.Send(registration); } void SignalingCall::Mute() { SignalingSigMessage msg(call, "facility", alloca(1000), alloca(100)); MonitorXmit("facility_mute", msg, msg.sig); } void SignalingCall::AudioRouteChanged() { SignalingSigMessage msg(call, "facility", alloca(1000), alloca(100)); MonitorXmit("facility_audio_route_changed", msg, msg.sig); } bool SignalingCall::FindFty(class json_io& msg, word base, const char * type) { if (base != 0xffff) { for (word last = 0, f = msg.get_object(base, last); f != 0xffff; f = msg.get_object(base, last)) { const char* ftyType = msg.get_string(f, "type"); if (ftyType && !strcmp(ftyType, type)) return true; } } return false; } void SignalingCall::Message(const char * type, class json_io & msg, word base) { if (registration && registration->signaling && registration->signaling->log) registration->signaling->log->Log(LOG_SIGNALING, "%x/%i:SignalingCall::Message(%s) state:%x hookState:%d", this, call, type, state, hookState); word fty = msg.get_array(base, "fty"); word cg = msg.get_object(base, "cg"); if (cg != 0xffff) { const char* flags = msg.get_string(cg, "flags"); bool restricted = flags && strchr(flags, 'R') ? true : false; const char* num = msg.get_string(cg, "num"); remote.SetRestricted(restricted); remote.SetNum(num); remote.SetSip(msg.get_string(cg, "sip")); if (!restricted && !FindFty(msg, fty, "name_identification") && num && registration && registration->signaling->user) { registration->signaling->user->Lookup(num, num, registration->dialingLocation.prefixIntl, registration->dialingLocation.prefixNtl, registration->dialingLocation.prefixSubs, registration->dialingLocation.country, registration->dialingLocation.area, registration->dialingLocation.subscriber); } this->recordingInfo.isExternal = flags && strchr(flags, 'P') ? false : true; this->recordingInfo.isRecordingByDefaultOn = registration->signaling->service->mediaConfig->isRecordingByDefaultOn; } if (fty != 0xffff) { for (word last = 0, f = msg.get_object(fty, last); f != 0xffff; f = msg.get_object(fty, last)) { const char * ftyType = msg.get_string(f, "type"); if (ftyType) { if (!strcmp(ftyType, "name_identification")) { if (registration->signaling->user) { // remote still not set or null if (msg.get_string(f, "name") && (!remote.dn || strcmp(msg.get_string(f, "name"), remote.dn))) { unsigned offset = registration->ExternalLinePrefixOffset(remote.num); char remoteSip[1024]; registration->signaling->user->ReportNameIdentification(confId, remote.num + offset, registration->signaling->AppendSipDomain(remote.sip, remoteSip, sizeof(remoteSip)), msg.get_string(f, "name"), this->diverting.dn ? this->diverting.dn : this->diverting.sip ? this->diverting.sip : this->diverting.num ? this->diverting.num : NULL, this->originalCalled.dn ? this->originalCalled.dn : this->originalCalled.sip ? this->originalCalled.sip : this->originalCalled.num ? this->originalCalled.num : NULL, cameraAllowed, true, true, true, true); } } remote.SetDn(msg.get_string(f, "name")); } else if (!strcmp(ftyType, "diverting_leg1")) { remote.Clear(); // clear all including DN word nominated = msg.get_object(f, "nominated"); if (nominated != 0xffff) { remote.SetNum(msg.get_string(nominated, "num")); remote.SetSip(msg.get_string(nominated, "sip")); } } else if (!strcmp(ftyType, "diverting_leg2")) { word diverting = msg.get_object(f, "diverting"); if (diverting != 0xffff) { this->diverting.SetNum(msg.get_string(diverting, "num")); this->diverting.SetSip(msg.get_string(diverting, "sip")); this->diverting.SetDn(msg.get_string(f, "diverting_name")); } word originalCalled = msg.get_object(f, "original_called"); if (originalCalled != 0xffff) { this->originalCalled.SetNum(msg.get_string(originalCalled, "num")); this->originalCalled.SetSip(msg.get_string(originalCalled, "sip")); this->originalCalled.SetDn(msg.get_string(f, "original_called_name")); } } else if (!strcmp(ftyType, "diverting_leg3")) { remote.Clear(); // clear all including DN word redirection = msg.get_object(f, "redirection"); if (redirection != 0xffff) { remote.SetNum(msg.get_string(redirection, "num")); remote.SetSip(msg.get_string(redirection, "sip")); } } else if (!strcmp(ftyType, "ct_setup")) { word dst = msg.get_object(f, "dst"); if (dst != 0xffff) { this->transfering.SetNum(msg.get_string(dst, "num")); this->transfering.SetSip(msg.get_string(dst, "sip")); } } else if (!strcmp(ftyType, "ct_initiate")) { // {"type":"ct_initiate","id":960051513,"dst":{"num":"70532"}} this->fty_ct_initiate.id = msg.get_unsigned(f, "id"); word dst = msg.get_object(f, "dst"); if (dst != 0xffff) { const char* num = msg.get_string(dst, "num"); this->fty_ct_initiate.num = _strdup(num); this->fty_ct_initiate.sip = _strdup(msg.get_string(dst, "sip")); if (this->remote.dn && num && this->remote.num && !strcmp(num, this->remote.num)) { this->fty_ct_initiate.dn = _strdup(this->remote.dn); } } } else if (!strcmp(ftyType, "ct_complete")) { // {"type":"ct_complete","remote":{"num":"00049703173009670"},"remote_name":""} word remote = msg.get_object(f, "remote"); if (remote != 0xffff) { const char* flags = msg.get_string(remote, "flags"); bool restricted = flags && strchr(flags, 'R') ? true : false; const char* num = msg.get_string(remote, "num"); const char* sip = msg.get_string(remote, "sip"); const char* remoteName = msg.get_string(f, "remote_name"); this->remote.SetRestricted(restricted); this->remote.SetNum(num); this->remote.SetSip(sip); this->remote.SetDn(remoteName); if (!restricted && !FindFty(msg, fty, "name_identification") && num && !sip && !remoteName && registration && registration->signaling->user) { registration->signaling->user->Lookup(num, num, registration->dialingLocation.prefixIntl, registration->dialingLocation.prefixNtl, registration->dialingLocation.prefixSubs, registration->dialingLocation.country, registration->dialingLocation.area, registration->dialingLocation.subscriber); } } } else if (!strcmp(ftyType, "video_conn")) { const char * guid = msg.get_string(f, "guid"); bool passive = msg.get_bool(f, "passive"); if (conferenceGuid) free(conferenceGuid); conferenceGuid = guid ? _strdup(guid) : 0; conferenceType = guid ? (passive ? "3pty" : "central") : 0; } else if (!strcmp(ftyType, "video_capabilities")) { multiVideoSupport = msg.get_bool(f, "multivideo"); } else if (!strcmp(ftyType, "conference_info")) { conferenceInfo.read(msg, f); } else if (!strcmp(ftyType, "im_message")) { chatMessage.read(msg, f); } else if (!strcmp(ftyType, "innovaphone_remote_control")) { const char* control = msg.get_string(f, "control"); if (!strcmp(control, "ANNOUNCE")) { // auto-connect after 2 seconds alerting and auto-mute autoConnectTimer.Start(2000); announce = true; } else if (!strcmp(control, "CONNECT")) { StopRinging(); ChangeState(SIG_CALL_STATE_CONN | SIG_CALL_STATE_OUTG); if (const char* confIdString = msg.get_string(base, "confId")) { str::to_hexmem(confIdString, conf.b, sizeof(conf.b), true); if (this->confId) free(this->confId); this->confId = _strdup(confIdString); } class SignalingSigMessage send(call, "conn", alloca(10000), alloca(1000)); if (localAudioConfig) SendLocalMedia(send, send.sig, send.tmp); MonitorXmit("conn", send, send.sig); send.Send(registration); if (audioDeviceId) { registration->signaling->SendHookKey(audioDeviceId, KEY_OFFHOOK, call, remote.num ? remote.num : remote.sip, remote.dn); hookState = KEY_OFFHOOK; } // Other calls to Hold if connected class btree* b = registration->calls->btree_find_next_right(0); while (b) { class SignalingCall* c = (class SignalingCall*)b; if (c != this && ((c->state == SIG_CALL_STATE_CONN) || (c->state == (SIG_CALL_STATE_CONN | SIG_CALL_STATE_OUTG)))) c->Hold(); b = registration->calls->btree_find_next_right((void*)(intp)c->call); } return; // no default "setup" handling } else if (!strcmp(control, "MIC_OFF")) { if (audioIo) { muted = true; audioIo->Mute(true, false); Mute(); } } else if (!strcmp(control, "MIC_ON")) { if (audioIo) { muted = false; audioIo->Mute(false, false); Mute(); } } else if (!strcmp(control, "DTMF_DIGIT")) { SendDTMF(msg.get_string(f, "digit"), true); } else if (!strcmp(control, "INFO_REMOTE_HOLD")) { // PBX performed remote_hold on behalf of us if (!(state & SIG_CALL_STATE_HOLD)) { ChangeState(state | SIG_CALL_STATE_HOLD); if (registration->signaling->user) { registration->signaling->user->ReportCallHold(confId); } } } else if (!strcmp(control, "INFO_REMOTE_RETRIEVE")) { // PBX performed remote_hold on behalf of us if (state & SIG_CALL_STATE_HOLD) { ChangeState(state & ~SIG_CALL_STATE_HOLD); if (registration->signaling->user) { //registration->signaling->user->ReportCallRetrieve(confId); } } // Other calls to Hold if connected class btree* b = registration->calls->btree_find_next_right(0); while (b) { class SignalingCall* c = (class SignalingCall*)b; if (c != this && ((c->state == SIG_CALL_STATE_CONN) || (c->state == (SIG_CALL_STATE_CONN | SIG_CALL_STATE_OUTG)))) c->Hold(); b = registration->calls->btree_find_next_right((void*)(intp)c->call); } } else if (!strcmp(control, "3PTY_ON")) { ChangeState(state & ~SIG_CALL_STATE_HOLD); Retrieve(); } } } } } if (!strcmp(type, "setup")) { if (toneEndTimer.IsRunning()) AbortTone(); callWaiting = (registration->calls->get_count() > 1); // already have other call(s) if (!callWaiting) { if (registration) { if (audioDeviceId) free(audioDeviceId); audioDeviceId = _strdup(audioIo->GetDeviceIo(registration->GetHwId())); if (audioDeviceId) { if (audioIo->DeviceCapabilities(audioDeviceId) & AUDIO_CAPABILITY_HOOK_DEVICE_SUPPORTS_LOCKING) { registration->signaling->hookDeviceState = HOOK_DEVICE_WAITING; audioIo->AcquireHookDevice(audioDeviceId); } else registration->signaling->hookDeviceState = HOOK_DEVICE_ACQUIRE; } } } if (const char * confIdString = msg.get_string(base, "confId")) { str::to_hexmem(confIdString, conf.b, sizeof(conf.b), true); if (this->confId) free(this->confId); this->confId = _strdup(confIdString); } MediaInitialize(MediaType::AUDIO); if (registration && registration->signaling->user) { unsigned offset = registration->ExternalLinePrefixOffset(remote.num); char remoteSip[1024]; registration->signaling->user->ReportNewIncomingCall( this->confId, remote.num + offset, registration->signaling->AppendSipDomain(remote.sip, remoteSip, sizeof(remoteSip)), remote.dn, this->diverting.dn ? this->diverting.dn : this->diverting.sip ? this->diverting.sip : this->diverting.num ? this->diverting.num : NULL, this->originalCalled.dn ? this->originalCalled.dn : this->originalCalled.sip ? this->originalCalled.sip : this->originalCalled.num ? this->originalCalled.num : NULL, cameraAllowed, true, true, true, true); } ChangeState(SIG_CALL_STATE_SETUP | SIG_CALL_STATE_OUTG); if (!remoteMediaCmd && (msg.get_int(base, "channel") == -1)) { remoteMediaCmd = MEDIA_CMD_REQUEST; // fixing undefined remoteMediaCmd } if (registration->signaling->hookDeviceState == HOOK_DEVICE_WAITING) alertPostponedOnHookDevice = true; } else if (!strcmp(type, "setup_ack")) { ChangeState(SIG_CALL_STATE_SETUP_ACK); } else if (!strcmp(type, "call_proc")) { ChangeState(SIG_CALL_STATE_CALL_PROC); } else if (!strcmp(type, "alert")) { ChangeState(SIG_CALL_STATE_ALERT); if(audioIo) MediaTones::StartTone(MEDIA_TONES_RINGBACK, registration->dialingLocation.tones, audioIo); if (registration->signaling->user) { unsigned offset = registration->ExternalLinePrefixOffset(remote.num); char remoteSip[1024]; registration->signaling->user->ReportOutgoingCallAlerting(confId, remote.dn, registration->signaling->AppendSipDomain(remote.sip, remoteSip, sizeof(remoteSip)), remote.num + offset); } } else if (!strcmp(type, "conn")) { if (connReceived) { debug->printf("%x/%i:SignalingCall::Message ignore additional conn message", this, call); return; } connReceived = true; ChangeState(SIG_CALL_STATE_CONN); if(audioIo) MediaTones::StopTone(audioIo); } else if (!strcmp(type, "disc")) { word cau = msg.get_object(base, "cau"); if (cau != 0xffff) this->cause = msg.get_int(cau, "num"); ChangeState(SIG_CALL_STATE_DISC_RCVD); } else if (!strcmp(type, "rel")) { if (registration) { if (audioDeviceId) { if (hookState == KEY_HOLD) registration->signaling->SendHookKey(audioDeviceId, KEY_RETRIEVE, call, remote.num ? remote.num : remote.sip, remote.dn); if (hookState != KEY_ONHOOK) registration->signaling->SendHookKey(audioDeviceId, KEY_ONHOOK, call, remote.num ? remote.num : remote.sip, remote.dn); hookState = KEY_ONHOOK; } if (registration->calls->get_count() < 2) { if (audioIo && audioDeviceId) { audioIo->StopDevice((void*)this, audioDeviceId); free(audioDeviceId); audioDeviceId = NULL; } if (videoIo && videoDeviceId) { videoIo->StopDevice((void*)this, videoDeviceId); free(videoDeviceId); videoDeviceId = NULL; videoIo->UnshareAllApplications((void*)this); } } StopRinging(); word cau = msg.get_object(base, "cau"); if (cau != 0xffff) this->cause = msg.get_int(cau, "num"); unsigned delay = 0; int autoHangup = registration->signaling->autoHangup; if (autoHangup == AUTOHANGUP_ALLCALLS) delay = 0; else if (this->cause == Q931_CAUSE_NonSelectedUserClearing) delay = 0; // remove this call immediately else if ((state & SIG_CALL_STATE_OUTG) && (state & 0xF) < SIG_CALL_STATE_CONN) delay = 0; // inbound un-answered calls else if ((state & 0xF) == SIG_CALL_STATE_CONN) delay = (autoHangup == AUTOHANGUP_CONNECTEDCALLS) ? 0 : 2500; // disc arrives before rel else if ((state & 0xF) == SIG_CALL_STATE_DISC_RCVD) delay = 2500; else if (this->cause == Q931_CAUSE_NormalCallClearing) delay = 2500; else delay = 10000; // keep failed outbound calls for even longer time and wait for user to hangup if (delay) { MediaTones::StartTone(MEDIA_TONES_BUSY, registration->dialingLocation.tones, audioIo); if (audioDeviceId) free(audioDeviceId); if (registration->signaling->changedAudioDeviceId) audioDeviceId = _strdup(registration->signaling->changedAudioDeviceId); else audioDeviceId = _strdup(audioIo->GetDeviceIo(registration->GetHwId())); if (audioDeviceId) audioIo->StartDevice((void*)this, (void*)this, audioDeviceId, AudioDeviceMode::AudioModeCommunication); } toneEndTimer.Start(delay); // delete call after delay } ChangeState(0); } // RecvRemoteMedia must be processed before the code below is executed in case a PROPOSAL arrives with Video and remoteVideoConfig is then set. if (ChannelsCmdType(msg.get_string(base, "channels_cmd"))) RecvRemoteMedia(msg, base); if (!strcmp(type, "channels") && msg.get_string(base, "channels_cmd") && !strcmp(msg.get_string(base, "channels_cmd"), "PROPOSAL")) { // This should be called after a RETRIEVE - PROPOSAL if (!media) { MediaInitialize(MediaType::AUDIO); } debug->printf("%x/%i:SignalingCall::Message(%s) PROPOSAL video=%d videoDisabled=%d media2=%p remoteVideoConfig=%x", this, call, type, cameraAllowed, videoDisabled, media2, remoteVideoConfig); if (!media2 && (!videoDisabled) && (remoteVideoConfig && remoteVideoConfig->codecList.front())) { // Remote side has proposed video // cameraAllowed tells us if we should start the webcam but we can receive remote video. // Therefore we should allocate the media channel to receive remote video stream if (videoIo && cameraAllowed && !videoDeviceId) { videoDeviceId = _strdup(videoIo->GetDeviceIo(registration->GetHwId())); videoIo->StartVideoDevice((void*)this, (void*)this, videoDeviceId, 1280, 720, 0); } MediaInitialize(MediaType::VIDEO); } // leave state unchanged } else if (!strcmp(type, "channels") && msg.get_string(base, "channels_cmd") && !strcmp(msg.get_string(base, "channels_cmd"), "REQUEST") && (!media2 || (initializing & (1 << VIDEO))) && cameraAllowed && !videoDisabled) { debug->printf("%x/%i:SignalingCall::Message(%s) REQUEST add video", this, call, type); // REQUEST from the PBX // cameraAllowed tells us if we should start the webcam but we can receive remote video. // Therefore we should allocate the media channel to receive remote video stream if (videoIo) { videoDeviceId = _strdup(videoIo->GetDeviceIo(registration->GetHwId())); videoIo->StartVideoDevice((void*)this, (void*)this, videoDeviceId, 1280, 720, 0); } if (!media2) { MediaInitialize(MediaType::VIDEO); } } // Should we initialize Video with setup? Two options: else if (!strcmp(type, "setup") && !media2 && !videoDisabled) { bool initializeVideo = false; // PROPOSAL in setup with Video if (msg.get_string(base, "channels_cmd")) { if (!strcmp(msg.get_string(base, "channels_cmd"), "PROPOSAL") && (remoteVideoConfig && remoteVideoConfig->codecList.front())) initializeVideo = true; } // Empty setup, no channels and auto-video is on else if((msg.get_object(base, "channels") == JSON_ID_NONE) && cameraAllowed) initializeVideo = true; if (initializeVideo) { MediaInitialize(MediaType::VIDEO); } } fty = msg.get_array(base, "fty"); if (fty != 0xffff) { for (word last = 0, f = msg.get_object(fty, last); f != 0xffff; f = msg.get_object(fty, last)) { const char * type = msg.get_string(f, "type"); if (type) { if (!strcmp(type, "ct_identify_result")) { if (ctInitiate) { SignalingSigMessage send(ctInitiate->call, "facility", alloca(1000), alloca(100)); word ftys = send.add_array(send.sig, "fty"); word fty = send.add_object(ftys, 0); send.add_string(fty, "type", "ct_initiate"); send.add_unsigned(fty, "id", msg.get_unsigned(f, "id"), send.tmp); send.Send(ctInitiate->registration); ctInitiate->ctIdentify = 0; ctInitiate = 0; } } else if (!strcmp(type, "call_waiting")) { ChangeState(state | SIG_CALL_STATE_CALL_WAITING); } else if (!strcmp(type, "co_alerting")) { ChangeState(state & ~SIG_CALL_STATE_CALL_WAITING); } else if (!strcmp(type, "hold_notify")) { ChangeState(state | SIG_CALL_STATE_HELD); } else if (!strcmp(type, "retrieve_notify")) { ChangeState(state & ~SIG_CALL_STATE_HELD); } else if (!strcmp(type, "video_priority")) { remoteVideoPriority = msg.get_bool(f, "on"); if(videoIo) videoIo->RemoteVideoPriority((void*)this, (const char*)confId, remoteVideoPriority); } if (!strcmp(type, "innovaphone_recording_state")) { this->recordingInfo.isTrunkRecording = true; SignalingSigMessage message(call, "facility", alloca(1000), alloca(100)); if (msg.get_bool(f, "on")) MonitorXmit("facility_recording_started", message, message.sig); else MonitorXmit("facility_recording_stopped", message, message.sig); } } } } MonitorRecv(type, msg, base); conferenceInfo.cleanup(); chatMessage.cleanup(); } void SignalingCall::GenerateIceCredentials(class IceCandidates* ice) { if (ice) { if (ice->icePwd) free(ice->icePwd); ice->icePwd = GenerateRandomString(22); if (ice->iceUfrag) free(ice->iceUfrag); ice->iceUfrag = GenerateRandomString(4); } } char* SignalingCall::GenerateRandomString(int len) { char* result = (char*)malloc((len + 1) * sizeof(char)); if (result) { for (int i = 0; i < len; i++) { int u = (IRandom::GetRandom() % 64); result[i] = charPool[u]; } result[len] = 0x0; } return result; } void SignalingCall::MediaInitializeComplete(IMedia* const media, class MediaConfig* localMediaConfig) { enum MediaType mediaType = localMediaConfig->type; const char* type = (mediaType == MediaType::AUDIO) ? "AUDIO" : (mediaType == MediaType::VIDEO) ? "VIDEO" : "APPSHARING"; initializing &= ~(1 << mediaType); debug->printf("%x/%i:SignalingCall::MediaInitializeComplete(%p) addr=%s type=%s state=%x connPostponed=%u selectByConn=%u", this, call, localMediaConfig, localMediaConfig->defAddr, type, state, connPostponed, selectByConn); if (localMediaConfig) { debug->printf("SignalingCall(%p)::MediaInitializeComplete (%s) %s:%d", this, (localMediaConfig->type == MediaType::AUDIO ? "AUDIO" : localMediaConfig->type == MediaType::VIDEO ? "VIDEO" : "APPSHARING"), (localMediaConfig->defAddr ? localMediaConfig->defAddr : ""), localMediaConfig->defPort); class Codec * c; for (c = localMediaConfig->codecList.front(); c; c = c->goNext()) { debug->printf("SignalingCall(%p)::MediaInitializeComplete codec (%d) %s:%d pt=%d", this, c->coder, (c->addr ? c->addr : ""), c->port, c->pt); } if (localMediaConfig->ice) { debug->printf("SignalingCall(%p)::MediaInitializeComplete RtcpMux(%d) ICE(%s:%s) Count=%d", this, localMediaConfig->ice->rtcpMux, ((localMediaConfig->ice && localMediaConfig->ice->iceUfrag) ? localMediaConfig->ice->iceUfrag : ""), ((localMediaConfig->ice && localMediaConfig->ice->icePwd) ? localMediaConfig->ice->icePwd : ""), localMediaConfig->ice->count); class IceCandidate * c = localMediaConfig->ice->candidateList.front(); if (c) { for (; c; c = c->goNext()) { if (c->type == 0) debug->printf("SignalingCall(%p)::MediaInitializeComplete candidate(%d) %s:%d,%d (%s) (%d,%d)", this, c->type, c->addr, c->rtpPort, c->rtcpPort, (c->foundation ? c->foundation : ""), c->rtpPriority, c->rtcpPriority); else debug->printf("SignalingCall(%p)::MediaInitializeComplete candidate(%d) %s:%d,%d (%s:%d,%d) (%s) (%d,%d)", this, c->type, c->addr, c->rtpPort, c->rtcpPort, c->relatedAddr, c->relatedRtpPort, c->relatedRtcpPort, (c->foundation ? c->foundation : ""), c->rtpPriority, c->rtcpPriority); } } else if (this->media == media) noAudioIceCandidates = true; else if (this->media2 == media) noVideoIceCandidates = true; } } debug->printf("DEBUG SignalingCall::MediaInitializeComplete() [1] initializing=%x deleting=%x media=%x this->media=%x this->media2=%x state=%d localMediaConfig=%p,%p hookDevice=%d state=%x", initializing, deleting, media, this->media, this->media2, state, this->localAudioConfig, this->localVideoConfig, registration->signaling->hookDeviceState, state); if (mediaType == MediaType::AUDIO) this->localAudioConfig = localMediaConfig; else if (mediaType == MediaType::VIDEO || mediaType == MediaType::VIDEO_APPSHARING) this->localVideoConfig = localMediaConfig; MediaInitialized(); } void SignalingCall::MediaInitialized() { if (!initializing) { isMediaInitialized = true; if (!deleting) { if (registration->signaling->hookDeviceState == HOOK_DEVICE_ACQUIRE) { switch (state) { case 0: SendSetup(); if (audioDeviceId) { registration->signaling->SendHookKey(audioDeviceId, KEY_OFFHOOK, call, remote.num ? remote.num : remote.sip, remote.dn); hookState = KEY_OFFHOOK; } break; case SIG_CALL_STATE_SETUP | SIG_CALL_STATE_OUTG: if (connPostponed) SendConn(); else { SendAlert(); if (audioIo) { // Second incoming alert if (!audioDeviceId) { if (registration->signaling->changedAudioDeviceId) audioDeviceId = _strdup(registration->signaling->changedAudioDeviceId); else audioDeviceId = _strdup(audioIo->GetDeviceIo(registration->GetHwId())); } if (audioDeviceId) { registration->signaling->SendHookKey(audioDeviceId, KEY_INCALL, call, remote.num ? remote.num : remote.sip, remote.dn); hookState = KEY_INCALL; } } } break; case SIG_CALL_STATE_ALERT | SIG_CALL_STATE_OUTG: if (!selectByConn) SendConn(); else selectByConn = false; break; default: if (connPostponed) SendConn(); else SendChannels(); break; } if (noAudioIceCandidates) { char cfg[1024]; class json_io send(cfg); debug->printf("%x/%i:SignalingCall::MediaInitialized missing ice candidates for audio", this, call); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", "RemoteRtp"); send.add_string(sendBase, "kind", "audio"); send.add_string(sendBase, "errorText", "Could not open RTP port for audio"); MonitorXmit("no_ice_candidates", send, sendBase); noAudioIceCandidates = false; } if (noVideoIceCandidates) { char cfg[1024]; class json_io send(cfg); debug->printf("%x/%i:SignalingCall::MediaInitialized missing ice candidates for video", this, call); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", "RemoteRtp"); send.add_string(sendBase, "kind", "video"); send.add_string(sendBase, "errorText", "Could not open RTP port for video"); MonitorXmit("no_ice_candidates", send, sendBase); noVideoIceCandidates = false; } } else if (registration->signaling->hookDeviceState == HOOK_DEVICE_WAITING) { debug->printf("DEBUG SignalingCall::MediaInitializeComplete() waiting on hookDevice ... "); } else { if (registration->signaling->hookDeviceState == HOOK_DEVICE_DENIED) { char cfg[1024]; class json_io send(cfg); word sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "errorText", "Access denied to hook device"); MonitorXmit("hook_device_denied", send, sendBase); } if (registration->signaling->hookDeviceState == HOOK_DEVICE_NOT_FOUND) { char cfg[1024]; class json_io send(cfg); word sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "errorText", "Hook device not found"); MonitorXmit("hook_device_not_found", send, sendBase); } ClearCall(); } } } } void SignalingCall::MediaInitialize(enum MediaType mediaType) { SignalingMediaConfig* c = registration->signaling->service->mediaConfig; if (mediaType == MediaType::AUDIO) { bool recordOn = registration->signaling->recordingLicense && c->recordingUrl && c->isRecordingByDefaultOn && (!c->recordExternalOnly || (c->recordExternalOnly && this->recordingInfo.isExternal)); media = mediaProvider->CreateMedia(registration->signaling->GetIoMux(), this, registration->signaling); media->Initialize( c->udpSocketProvider, c->tcpSocketProvider, c->socketContext, c->minPort, c->maxPort, c->stunServers, c->turnServers, c->turnUsername, c->turnPassword, AUDIO, c->stunSlow, c->turnOnly, c->iceNoHost, c->dropMediaTx, c->dropMediaRx, c->hostPbx, c->noVpnAddresses, &assignmentsList, c->tlsSocketProvider, c->recordingUrl, &conf, recordOn, c->recordRtpStream); initializing |= (1 << AUDIO); } else { media2 = mediaProvider->CreateMedia(registration->signaling->GetIoMux(), this, registration->signaling); media2->Initialize( c->udpSocketProvider, c->tcpSocketProvider, c->socketContext, c->minVideoPort, c->maxVideoPort, c->stunServers, c->turnServers, c->turnUsername, c->turnPassword, VIDEO, c->stunSlow, c->turnOnly, c->iceNoHost, c->dropMediaTx, c->dropMediaRx, c->hostPbx, c->noVpnAddresses, &assignmentsList, c->tlsSocketProvider, c->recordingUrl, &conf); initializing |= (1 << VIDEO); } isMediaInitialized = false; } void SignalingCall::MediaConnectResult(IMedia * const media, const char * error) { enum MediaType mediaType = AUDIO; if (media == this->media) mediaType = AUDIO; if (media == this->media2) mediaType = VIDEO; connecting &= ~(1 << mediaType); debug->printf("%x/%i:SignalingCall::MediaConnectResult(error=%s) mediaType=%u audioCoder=%u videoCoder=%u connecting=%x", this, call, error, mediaType, audioCoder, videoCoder, connecting); if (error) { debug->printf("%x/%i:SignalingCall::MediaConnectResult(error=%s) mediaType=%s", this, call, error ? error : "", (media == this->media) ? "Audio" : "Video"); if (media == this->media) ClearCall(Q931_CAUSE_NetworkOutOfOrder); // Should the call be cleared if problem with Video? // audio failed too and call may be in deleting state else if (!deleting && registration->signaling->user) { unsigned offset = registration->ExternalLinePrefixOffset(remote.num); char remoteSip[1024]; registration->signaling->user->ReportCallConnected(confId, error == nullptr, remote.num + offset, registration->signaling->AppendSipDomain(remote.sip, remoteSip, sizeof(remoteSip)), remote.dn, this->diverting.dn ? this->diverting.dn : this->diverting.sip ? this->diverting.sip : this->diverting.num ? this->diverting.num : NULL, this->originalCalled.dn ? this->originalCalled.dn : this->originalCalled.sip ? this->originalCalled.sip : this->originalCalled.num ? this->originalCalled.num : NULL, cameraAllowed, true, true, true, true); } } else { if (mediaType == AUDIO) { // only once, a renegotation would reconnect audio if(!duration) duration = ITime::TimeStampMilliseconds() / 1000; if (audioIoChannel && registration) { if (audioIo) audioIo->InitializeChannel(audioIoChannel, (enum AudioCoder)audioCoder, false, 20); audioIoChannel->Open(); } if (registration->signaling->user) { unsigned offset = registration->ExternalLinePrefixOffset(remote.num); char remoteSip[1024]; registration->signaling->user->ReportCallConnected(confId, error == nullptr, remote.num + offset, registration->signaling->AppendSipDomain(remote.sip, remoteSip, sizeof(remoteSip)), remote.dn, this->diverting.dn ? this->diverting.dn : this->diverting.sip ? this->diverting.sip : this->diverting.num ? this->diverting.num : NULL, this->originalCalled.dn ? this->originalCalled.dn : this->originalCalled.sip ? this->originalCalled.sip : this->originalCalled.num ? this->originalCalled.num : NULL, cameraAllowed, true, true, true, true); } audioMediaConnected = true; if (!dtmfs.empty()) queuedDTMFTimer.Start(1000); } else if (mediaType == VIDEO) { if (!videoIoChannel) { videoIoChannel = videoIo->CreateChannel(this); } videoIoChannel->Initialize(NULL, (const char *)confId, (enum VideoCoder)videoCoder, ConnectionType::REMOTE_RTP); videoIoChannel->Open(); } } } void SignalingCall::MediaEventReceived(IMedia * const media, enum MediaEndpointEvent event, void * ctx) { if (event == MediaEndpointEvent::CHANNEL_MEDIA_STATISTICS) { if (ctx) { class EventMediaStatistics * s = (class EventMediaStatistics*)ctx; if (s) { #if 1 if (s->xmit) { tx.jitter = s->jitter; tx.loss = s->loss; tx.roundTrip = s->roundTrip; } else { rx.jitter = s->jitter; rx.loss = s->loss; rx.roundTrip = s->roundTrip; } #else char temp[128]; char* tmp = temp; char cfg[1024]; class json_io send(cfg); debug->printf("%x/%i:SignalingCall::MediaEventReceived CHANNEL_MEDIA_STATISTICS conf=%s (%s)", this, call, confId, (media == this->media2 ? "Video" : media == this->media ? "Audio" : "AppSharing")); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "mt", "MediaStatistics"); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", "RemoteRtp"); send.add_bool(sendBase, "xmit", s->xmit); send.add_unsigned(sendBase, "loss", s->loss, tmp); send.add_unsigned(sendBase, "jitter", s->jitter, tmp); send.add_unsigned(sendBase, "roundTrip", s->roundTrip, tmp); if (registration && registration->signaling && registration->signaling->session) registration->signaling->session->SendResponse(send, cfg); delete s; #endif } } } else if (event == MediaEndpointEvent::CHANNEL_MEDIA_ERROR) { if (ctx) { class EventMediaError * e = (class EventMediaError *)ctx; if (e) { char temp[128]; char* tmp = temp; char cfg[1024]; class json_io send(cfg); debug->printf("%x/%i:SignalingCall::MediaEventReceived CHANNEL_MEDIA_ERROR code=%d error=%s conf=%s (%s)", this, call, e->code, e->errorText ? e->errorText : "", confId, (media == this->media2 ? "video" : media == this->media ? "audio" : "unknown")); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", "RemoteRtp"); send.add_string(sendBase, "kind", this->media == media ? "audio" : "video"); send.add_unsigned(sendBase, "code", e->code, tmp); send.add_string(sendBase, "errorText", e->errorText); send.add_string(sendBase, "src", e->src); MonitorXmit("media_error", send, sendBase); delete e; } } } else if (event == MediaEndpointEvent::CHANNEL_MEDIA_WARNING) { if (ctx) { class EventMediaError* e = (class EventMediaError*)ctx; if (e) { char temp[128]; char* tmp = temp; char cfg[1024]; class json_io send(cfg); debug->printf("%x/%i:SignalingCall::MediaEventReceived CHANNEL_MEDIA_WARNING code=%d error=%s conf=%s (%s)", this, call, e->code, e->errorText ? e->errorText : "", confId, (media == this->media2 ? "video" : media == this->media ? "audio" : "unknown")); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", "RemoteRtp"); send.add_string(sendBase, "kind", this->media == media ? "audio" : "video"); send.add_unsigned(sendBase, "code", e->code, tmp); send.add_string(sendBase, "warningText", e->errorText); send.add_string(sendBase, "src", e->src); MonitorXmit("media_warning", send, sendBase); delete e; } } } else if (videoIoChannel) videoIoChannel->EventRecv(event, ctx); } void SignalingCall::MediaRtpRecvResult(IMedia * const media, void * buf, size_t len, dword timestamp, short sequenceNumberDiff, bool marker) { enum MediaType mediaType = AUDIO; if (media == this->media) mediaType = AUDIO; if (media == this->media2) mediaType = VIDEO; //debug->printf("%x/%i:SignalingCall::MediaRtpRecvResult(%u) mediaType=%d audioIoChannel=%x videoIoChannel=%x rtptp=%x", this, call, len, mediaType, audioIoChannel, videoIoChannel, rtptp); if (mediaType == AUDIO) { if (audioDataOff) { char cfg[1024]; class json_io send(cfg); debug->printf("%x/%i:SignalingCall::MediaRtpRecvResult audio packets received again", this, call); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", "audio"); MonitorXmit("media_data_on", send, sendBase); audioDataOff = false; } numAudioPackets++; if (audioIoChannel) audioIoChannel->RtpRecv(buf, len, timestamp, sequenceNumberDiff, marker); else if (rtptp) rtptp->RTPTPRecv(buf, len); } else if (mediaType == VIDEO) { if (videoDataOff) { char cfg[1024]; class json_io send(cfg); debug->printf("%x/%i:SignalingCall::MediaRtpRecvResult video packets received again", this, call); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", "video"); MonitorXmit("media_data_on", send, sendBase); videoDataOff = false; } numVideoPackets++; if (videoIoChannel) videoIoChannel->RtpRecv(buf, len, timestamp, sequenceNumberDiff, marker); } } void SignalingCall::MediaIoSendFIR() { if (media2) media2->SendFIR(); } void SignalingCall::MediaRtpRecv(IMedia* const media, const char* src_addr, word src_port, dword ssrc, word pt) { debug->printf("%x/%i:SignalingCall::MediaRtpRecv conf=%s (%s)", this, call, confId, (media == this->media2 ? "video" : media == this->media ? "audio" : "unknown")); #if 0 char cfg[2000]; char b[128]; char* tmp = b; class json_io send(cfg); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "mt", "RtpRecv"); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", (media == this->media2 ? "video" : media == this->media ? "audio" : "unknown")); send.add_string(sendBase, "src_addr", src_addr); send.add_int(sendBase, "src_port", src_port, tmp); send.add_unsigned(sendBase, "ssrc", ssrc, tmp); send.add_int(sendBase, "pt", pt, tmp); //if (registration && registration->signaling && registration->signaling->session) registration->signaling->session->SendResponse(send, cfg); #else if (registration && registration->signaling) { if (audioIo) MediaTones::StopTone(audioIo); } #endif } void SignalingCall::MediaRtpDtmfNearStart(char digit) { #if 0 debug->printf("%x/%i:SignalingCall::MediaRtpDtmfNearStart conf=%s (%s) %c", this, call, confId, "audio", digit); char digitString[2]; char cfg[2000]; class json_io send(cfg); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "mt", "RtpDtmfNearStart"); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", "audio")); digitString[0] = digit; digitString[1] = '\0'; send.add_string(sendBase, "digit", digitString); // UI does not process this message //if (registration && registration->signaling && registration->signaling->session) registration->signaling->session->SendResponse(send, cfg); #endif } void SignalingCall::MediaRtpDtmfNear(char digit) { #if 0 debug->printf(("%x/%i:SignalingCall::MediaRtpDtmfNear conf=%s (%s) %c", this, call, confId, "audio", digit)); char digitString[2]; char cfg[2000]; class json_io send(cfg); word sendBase = 0xffff; sendBase = send.add_object(0xffff, 0); send.add_string(sendBase, "mt", "RtpDtmfNear"); send.add_string(sendBase, "channel", confId); send.add_string(sendBase, "type", "audio")); digitString[0] = digit; digitString[1] = '\0'; send.add_string(sendBase, "digit", digitString); // UI does not process this message //if (registration && registration->signaling && registration->signaling->session) registration->signaling->session->SendResponse(send, cfg); #endif } void SignalingCall::MediaCloseComplete(IMedia * const media) { debug->printf("%x/%i:SignalingCall::MediaCloseComplete() deleting=%d media=%x audio=%x,%x video=%x,%x audioIoChannel=%x videoIoChannel=%x initializing=%x connecting=%x", this, call, deleting, media, this->media, this->mediaClosing, this->media2, this->media2Closing, audioIoChannel, videoIoChannel, initializing, connecting); if (media == this->media) { if (audioIoChannel) { audioIoChannel->Close(); MediaIoCloseComplete(audioIoChannel); } delete this->media; this->media = NULL; } if (media == this->mediaClosing) { delete this->mediaClosing; this->mediaClosing = NULL; } if (media == this->media2) { if (videoIoChannel) { videoIoChannel->Close(); // MediaIoCloseComplete(videoIoChannel) will be called by class VideoIoChannel } delete this->media2; this->media2 = NULL; } if (media == this->media2Closing) { delete this->media2Closing; this->media2Closing = NULL; } if (deleting && this->media == NULL && this->mediaClosing == NULL && this->media2 == NULL && this->media2Closing == NULL && audioIoChannel == NULL && videoIoChannel == NULL && videoIoChannelClosing == NULL) delete this; } void SignalingCall::RecordingStopped(const char * error) { if (registration && registration->signaling && registration->signaling->log) registration->signaling->log->Log(LOG_SIGNALING, "%x/%i:SignalingCall::RecordingStopped reason: %s",this, call, error); if (error) this->recordingInfo.recordingError = _strdup(error); SignalingSigMessage msg(call, "facility", alloca(1000), alloca(100)); MonitorXmit("facility_recording_stopped", msg, msg.sig); } void SignalingCall::RecordingStarted() { if (registration && registration->signaling && registration->signaling->log) registration->signaling->log->Log(LOG_SIGNALING, "%x/%i:SignalingCall::RecordingStarted",this, call); SignalingSigMessage msg(call, "facility", alloca(1000), alloca(100)); MonitorXmit("facility_recording_started", msg, msg.sig); } static enum AudioCoder AvailableAudioCoders[] = { AudioCoder::OPUS_WB, AudioCoder::G722, AudioCoder::OPUS_NB, AudioCoder::G711_A, AudioCoder::G711_U, AudioCoder::G729, AudioCoder::G729A, AudioCoder::G729B, AudioCoder::G729AB, AudioCoder::DTMF, }; #define AVAILABLE_AUDIO_CODERS sizeof(AvailableAudioCoders)/sizeof(AvailableAudioCoders[0]) enum AudioCoder SignalingCall::AvailableAudioCoder(unsigned coderNumber) { if (callType == CallTypeVoice) { ASSERT(coderNumber < AVAILABLE_AUDIO_CODERS, "Out of range!"); return AvailableAudioCoders[coderNumber]; } if (callType == CallTypeData) { return CLEARCHANNEL; } return CODER_UNDEFINED; } unsigned SignalingCall::AvailableAudioCoderCount() { return AVAILABLE_AUDIO_CODERS; } static enum VideoCoder AvailableVideoCoders[] = { VideoCoder::VP8, #if !defined __ANDROID__ && !defined _IOS_ //VideoCoder::VP9, #endif #if defined _IOS_ VideoCoder::H264, #elif defined _WINDOWS VideoCoder::H264, VideoCoder::AV1, #endif }; #define AVAILABLE_VIDEO_CODERS sizeof(AvailableVideoCoders)/sizeof(AvailableVideoCoders[0]) enum VideoCoder SignalingCall::AvailableVideoCoder(unsigned coderNumber) { if (callType == CallTypeVoice) { ASSERT(coderNumber < AVAILABLE_VIDEO_CODERS, "Out of range!"); return AvailableVideoCoders[coderNumber]; } return AvailableVideoCoders[0]; } unsigned SignalingCall::AvailableVideoCoderCount() { if (callType == CallTypeVoice) { return AVAILABLE_VIDEO_CODERS; } return 0; } void SignalingCall::MonitorRecv(const char * type, class json_io & msg, word base) { if (!strcmp(type, "facility") && monitors.empty()) { debug->printf("%x/%u:SignalingCall::MonitorRecv(type=%s) no monitor available", this, call, type); if (fty_ct_initiate.num || fty_ct_initiate.sip) { // must replace this SignalingCall from PBX by a new outbound SignalingCall to PBX class SignalingCall* newCall = new SignalingCall(registration, CallTypeVoice, 0, fty_ct_initiate.num, fty_ct_initiate.sip, fty_ct_initiate.dn, true); newCall->cameraAllowed = cameraAllowed; newCall->videoDisabled = videoDisabled; newCall->ct_setup_id = fty_ct_initiate.id; // detach replacedCall from this SignalingRccCall class SignalingCall* replacedCall = this; newCall->StartCall(audioIo, videoIo, 0, 0); // release replacedCall now (better wait for ALERT/CONN before send REL) replacedCall->fty_ct_initiate.cleanup(); replacedCall->clearCall = true; } } for (class SignalingCallMonitor * m = monitors.front(); m; m = m->goNext()) { m->MonitorRecv(type, msg, base); } } void SignalingCall::MonitorXmit(const char * type, class json_io & msg, word base) { for (class SignalingCallMonitor * m = monitors.front(); m; m = m->goNext()) { m->MonitorXmit(type, msg, base); } } void SignalingCall::MediaIoRtpSend(const void * buf, size_t len, dword timestamp, enum MediaType mediaType, bool isConference) { if (mediaType == MediaType::AUDIO && media) { media->RtpSend(buf, len, timestamp, mediaType); } else if ((mediaType == MediaType::VIDEO || mediaType == MediaType::VIDEO_APPSHARING) && media2) { media2->RtpSend(buf, len, timestamp, mediaType); } } void SignalingCall::MediaIoRtpForward(const void* buf, size_t len, dword timestamp, short sequenceNumberDiff, bool marker, enum MediaType mediaType) { if (mediaType == MediaType::AUDIO && media) { media->RtpForward(buf, len, (dword)timestamp, sequenceNumberDiff, marker); } else if ((mediaType == MediaType::VIDEO || mediaType == MediaType::VIDEO_APPSHARING) && media2) { media2->RtpForward(buf, len, (dword)timestamp, sequenceNumberDiff, marker); } } void SignalingCall::MediaIoRtcpSend(const void* buf, size_t len, enum MediaType mediaType) { if (mediaType == MediaType::AUDIO && media) { media->RtcpSend(buf, len); } else if ((mediaType == MediaType::VIDEO || mediaType == MediaType::VIDEO_APPSHARING) && media2) { media2->RtcpSend(buf, len); } } void SignalingCall::MediaIoSctpSend(const void * buf, size_t len, unsigned num) { } void SignalingCall::MediaIoCloseComplete(class IMediaIoChannel * const mediaIoChannel) { debug->printf("%x/%i:SignalingCall::MediaIoCloseComplete del=%d media=%p media2=%p mediaIoChannel=%x audioIoChannel=%x videoIoChannel=%x", this, call, deleting, this->media, this->media2, mediaIoChannel, audioIoChannel, videoIoChannel); if (audioIoChannel == mediaIoChannel) { DELETE_OBJ(audioIoChannel); } if (videoIoChannel == mediaIoChannel) { DELETE_OBJ(videoIoChannel); } if (videoIoChannelClosing == mediaIoChannel) { DELETE_OBJ(videoIoChannelClosing); } if (deleting && this->media == NULL && this->mediaClosing == NULL && this->media2 == NULL && this->media2Closing == NULL && audioIoChannel == NULL && videoIoChannel == NULL && videoIoChannelClosing == NULL) delete this; } void SignalingCall::SendSetup() { debug->printf("%x/%i:SignalingCall::SendSetup()", this, call); class SignalingSigMessage send(call, "setup", alloca(10000), alloca(4000)); if (registration->signaling->clir) { word cg = send.add_object(send.sig, "cg"); send.add_string(cg, "flags", "R"); } word cd = send.add_object(send.sig, "cd"); send.add_string(cd, "num", remote.num); send.add_string(cd, "sip", remote.sip); send.add_int(send.sig, "channel", -1, send.tmp); if(sendingComplete) send.add_bool(send.sig, "complete", sendingComplete); SendLocalMedia(send, send.sig, send.tmp); word ftys = send.add_array(send.sig, "fty"); if (true) { word fty = send.add_object(ftys, 0); send.add_string(fty, "type", "video_capabilities"); send.add_bool(fty, "multivideo", true); } if (ct_setup_id) { word fty = send.add_object(ftys, 0); send.add_string(fty, "type", "ct_setup"); send.add_unsigned(fty, "id", ct_setup_id, send.tmp); } send.add_hexstring(send.sig, "confId", conf.b, sizeof(conf.b), send.tmp); send.add_printf(send.sig, "sourceInfo", send.tmp, "%s %s", _PRODUCT_NAME_, _BUILD_STRING_); ChangeState(SIG_CALL_STATE_SETUP); MonitorXmit("setup", send, send.sig); send.Send(registration); } void SignalingCall::SendInfo(const char * num, const char * sip) { debug->printf("%x/%i:SignalingCall::SendInfo(%s:%s)", this, call, num ? num : "", sip ? sip : ""); class SignalingSigMessage send(call, "info", alloca(1000), alloca(100)); word cd = send.add_object(send.sig, "cd"); send.add_string(cd, "num", num); send.add_string(cd, "sip", sip); MonitorXmit("info", send, send.sig); send.Send(registration); } void SignalingCall::SendAlert() { debug->printf("%x/%i:SignalingCall::SendAlert() state=%x callWaiting=%u", this, call, state, callWaiting); class SignalingSigMessage send(call, "alert", alloca(1000), alloca(100)); if (state & SIG_CALL_STATE_OUTG) { if (callWaiting && !(state & SIG_CALL_STATE_CALL_WAITING)) { ChangeState(SIG_CALL_STATE_ALERT | SIG_CALL_STATE_OUTG | SIG_CALL_STATE_CALL_WAITING); word ftys = send.add_array(send.sig, "fty"); word fty = send.add_object(ftys, 0); send.add_string(fty, "type", "call_waiting"); } else if (!callWaiting && (state & SIG_CALL_STATE_CALL_WAITING)) { ChangeState(SIG_CALL_STATE_ALERT | SIG_CALL_STATE_OUTG); word ftys = send.add_array(send.sig, "fty"); word fty = send.add_object(ftys, 0); send.add_string(fty, "type", "co_alerting"); } else if (registration->dnd) { word ftys = send.add_array(send.sig, "fty"); word fty = send.add_object(ftys, 0); send.add_string(fty, "type", "call_waiting"); } else { ChangeState(SIG_CALL_STATE_ALERT | SIG_CALL_STATE_OUTG); } } MonitorXmit("alert", send, send.sig); send.Send(registration); } void SignalingCall::SendConn() { debug->printf("%x/%i:SignalingCall::SendConn() localAudioConfig=%x", this, call, localAudioConfig); ChangeState(SIG_CALL_STATE_CONN | SIG_CALL_STATE_OUTG); if (localAudioConfig) { class SignalingSigMessage send(call, "conn", alloca(10000), alloca(4000)); SendLocalMedia(send, send.sig, send.tmp); // SendLocalMedia may start media->Connect() // If there is something wrong with the message, no ICE, no candidates, no coders, ... // MediaConnectResult will come right away with an error and the call will be deleted before calling send.Send(registration) // and registration is resetted to null -> crash. if (!deleting) { MonitorXmit("conn", send, send.sig); send.Send(registration); if (audioDeviceId) { registration->signaling->SendHookKey(audioDeviceId, KEY_OFFHOOK, call, remote.num ? remote.num : remote.sip, remote.dn); hookState = KEY_OFFHOOK; } } connPostponed = false; } else { connPostponed = true; // do SendConn() when localAudioConfig is available } } void SignalingCall::SendChannels() { debug->printf("%x/%i:SignalingCall::SendChannels() localAudioConfig=%x", this, call, localAudioConfig); if (localAudioConfig) { class SignalingSigMessage send(call, "channels", alloca(10000), alloca(4000)); SendLocalMedia(send, send.sig, send.tmp); MonitorXmit("channels", send, send.sig); send.Send(registration); } } void SignalingCall::SendRel() { class SignalingSigMessage send(call, "rel", alloca(1000), alloca(100)); if (cause) { word cau = send.add_object(send.sig, "cau"); send.add_int(cau, "num", cause, send.tmp); } ChangeState(0); MonitorXmit("rel", send, send.sig); send.Send(registration); Delete(); } int SignalingCall::SelectCoder(class MediaConfig * remoteMediaConfig, class MediaConfig * localMediaConfig) { Codec * remote = 0; for (remote = remoteMediaConfig->codecList.front(); remote; remote = remote->goNext()) { Codec * local = 0; for (local = localMediaConfig->codecList.front(); local; local = local->goNext()) { if (remote->coder == local->coder) break; } if (local) break; } if (remote) { remote->remove(); while (remoteMediaConfig->codecList.front()) delete remoteMediaConfig->codecList.front(); remoteMediaConfig->codecList.push_back(remote); return remote->coder; } return 0; } static word default_coder_rate(word coder) { switch (coder) { case AudioCoder::OPUS_WB: case AudioCoder::OPUS_NB: return 48000; default: return 8000; } } void SignalingCall::SendLocalMedia(class json_io & json, word base, char * & tmp) { int localMediaCmd = 0; debug->printf("%x/%i:SignalingCall::SendLocalMedia() localAudioConfig=%x remoteAudioConfig=%x localVideoConfig=%x remoteVideoConfig=%x remoteMediaCmd=%s audioCoder=%d videoCoder=%d", this, call, localAudioConfig, remoteAudioConfig, localVideoConfig, remoteVideoConfig, ChannelsCmdName(remoteMediaCmd) ? ChannelsCmdName(remoteMediaCmd) : "NONE", audioCoder, videoCoder); switch (remoteMediaCmd) { case MEDIA_CMD_PROPOSAL: localMediaCmd = MEDIA_CMD_SELECT; json.add_string(base, "channels_cmd", "SELECT"); if (remoteAudioConfig && localAudioConfig) audioCoder = SelectCoder(remoteAudioConfig, localAudioConfig); if (remoteVideoConfig && localVideoConfig) videoCoder = SelectCoder(remoteVideoConfig, localVideoConfig); break; case 0: case MEDIA_CMD_REQUEST: audioCoder = CODER_UNDEFINED; videoCoder = CODER_UNDEFINED; localMediaCmd = MEDIA_CMD_PROPOSAL; json.add_string(base, "channels_cmd", "PROPOSAL"); break; } word channels = json.add_object(base, "channels"); json.add_string(channels, "source", "REMOTE"); word chs = json.add_array(channels, "ch"); class Codec * audio = 0; for (Codec * codec = localAudioConfig->codecList.front(); codec; codec = codec->goNext()) { if (audioCoder == codec->coder || localMediaCmd == MEDIA_CMD_PROPOSAL) { word rate = codec->rate ? codec->rate : default_coder_rate(codec->coder); word pt = (codec->coder == AudioCoder::DTMF) ? localDTMF : codec->pt; word ch = json.add_object(chs, 0); json.add_string(ch, "coder", CoderName(codec->coder)); json.add_unsigned(ch, "rate", rate, tmp); json.add_unsigned(ch, "pt", pt, tmp); json.add_unsigned(ch, "xmit", 20, tmp); json.add_unsigned(ch, "recv", 20, tmp); json.add_string(ch, "addr", codec->addr); json.add_unsigned(ch, "port", codec->port, tmp); if (!audio) audio = codec; } } if (localVideoConfig) { // add video codecs for (Codec* codec = localVideoConfig->codecList.front(); codec; codec = codec->goNext()) { if (videoCoder == codec->coder || localMediaCmd == MEDIA_CMD_PROPOSAL) { word ch = json.add_object(chs, 0); json.add_string(ch, "coder", CoderName(codec->coder)); json.add_unsigned(ch, "pt", codec->pt, tmp); json.add_string(ch, "addr", codec->addr); json.add_unsigned(ch, "port", codec->port, tmp); } } } if (localAudioConfig && localAudioConfig->ice && (audioCoder || localMediaCmd == MEDIA_CMD_PROPOSAL)) { if (localAudioConfig->ice->rtcpMux) json.add_bool(channels, "audio_rtcp_mux", true); word ice = json.add_object(channels, "audio_ice"); json.add_string(ice, "usr", localAudioConfig->ice->iceUfrag); json.add_string(ice, "pwd", localAudioConfig->ice->icePwd); json.add_string(ice, "fingerprint", localAudioConfig->ice->fingerprint); if (localAudioConfig->ice->candidateList.front()) { word candidates = json.add_array(ice, "candidate"); for (IceCandidate* c = localAudioConfig->ice->candidateList.front(); c; c = c->goNext()) { word candidate = json.add_object(candidates, 0); json.add_string(candidate, "foundation", c->foundation); json.add_string(candidate, "addr", c->addr); json.add_unsigned(candidate, "rtp", c->rtpPort, tmp); json.add_unsigned(candidate, "rtp_prio", c->rtcpPriority, tmp); json.add_string(candidate, "type", CandidateName(c->type)); } } } if (localVideoConfig && localVideoConfig->ice && (videoCoder || localMediaCmd == MEDIA_CMD_PROPOSAL)) { if (localVideoConfig->ice->rtcpMux) json.add_bool(channels, "video_rtcp_mux", true); word ice = json.add_object(channels, "video_ice"); json.add_string(ice, "usr", localVideoConfig->ice->iceUfrag); json.add_string(ice, "pwd", localVideoConfig->ice->icePwd); json.add_string(ice, "fingerprint", localVideoConfig->ice->fingerprint); if (localVideoConfig->ice->candidateList.front()) { word candidates = json.add_array(ice, "candidate"); for (IceCandidate* c = localVideoConfig->ice->candidateList.front(); c; c = c->goNext()) { word candidate = json.add_object(candidates, 0); json.add_string(candidate, "foundation", c->foundation); json.add_string(candidate, "addr", c->addr); json.add_unsigned(candidate, "rtp", c->rtpPort, tmp); json.add_unsigned(candidate, "rtp_prio", c->rtcpPriority, tmp); json.add_string(candidate, "type", CandidateName(c->type)); } } } if (remoteAudioConfig) { // if we send SELECT, remote is ICE controlling and we are active on DTLS bool iceRemoteRole = true; enum SetupRole remoteDtlsRole = SETUP_ROLE_PASSIVE; connecting |= (1 << AUDIO); media->Connect(remoteAudioConfig, iceRemoteRole, remoteDtlsRole); remoteAudioConfig = 0; // remoteVideoConfig maybe not null from a previous negotiation where video came with a PROPOSAL but we just SELECT audio. // It means remoteAudioConfig would be null but remoteVideoConfig was set if we for example add now video. // there is no situation where remoteVideoConfig is allocated but remoteAudioConfig no. if (media2 && remoteVideoConfig) { bool iceRemoteRole = true; enum SetupRole remoteDtlsRole = SETUP_ROLE_PASSIVE; connecting |= (1 << VIDEO); media2->Connect(remoteVideoConfig, iceRemoteRole, remoteDtlsRole); remoteVideoConfig = 0; } } remoteMediaCmd = MEDIA_CMD_NONE; } enum SetupRole decode(const char * str) { if (str) { if (!strcmp(str, "active")) return SETUP_ROLE_ACTIVE; if (!strcmp(str, "passive")) return SETUP_ROLE_PASSIVE; if (!strcmp(str, "actpass")) return SETUP_ROLE_ACTPASS; if (!strcmp(str, "holdconn")) return SETUP_ROLE_HOLDCONN; } return SETUP_ROLE_NONE; } void SignalingCall::RecvRemoteMedia(class json_io & json, word base) { // Every incoming message except "media_info" resets the remoteMediaCmd // This fix is added because between media->Initialize(..) and MediaInitializeComplete(..) // there might be these kind of message which reset the remoteMediaCmd to null and disturb // the logic flow. Especially after an client RETRIEVE followed by a PBX REQUEST unsigned int oldRemoteMediaCmd = remoteMediaCmd; remoteMediaCmd = ChannelsCmdType( json.get_string(base, "channels_cmd") ); word media_info = find_fty(json, base, "media_info"); if (media_info != JSON_ID_NONE) { remoteMediaCmd = oldRemoteMediaCmd; return; } word channels = json.get_object(base, "channels"); if (channels != 0xffff) { delete remoteAudioConfig; remoteAudioConfig = new MediaConfig(); remoteAudioConfig->type = AUDIO; delete remoteVideoConfig; remoteVideoConfig = new MediaConfig(); remoteVideoConfig->type = VIDEO; word chs = json.get_array(channels, "ch"); word ch = 0; for (ch = chs != 0xffff ? json.get_object(chs, ch) : 0xffff; ch != 0xffff; ch = json.get_object(chs, ch)) { const char * coderName = json.get_string(ch, "coder"); if (coderName && !strcmp(coderName, "DTMF")) { remoteDTMF = json.get_unsigned(ch, "pt"); if (remoteMediaCmd == MEDIA_CMD_PROPOSAL) localDTMF = remoteDTMF; remoteAudioConfig->localDTMF = localDTMF; remoteAudioConfig->remoteDTMF = remoteDTMF; continue; } unsigned coder = CoderType(coderName); if (coder) { enum MediaType mediaType = CoderMediaType(coder); class Codec * codec = new class Codec(); codec->coder = coder; codec->number = json.get_unsigned(ch, "number"); codec->xmitPacket = json.get_unsigned(ch, "xmit"); codec->recvPacket = json.get_unsigned(ch, "recv"); codec->rate = json.get_unsigned(ch, "rate"); codec->pt = json.get_unsigned(ch, "pt"); if (!codec->pt) codec->pt = CoderPt(codec->coder); codec->addr = _strdup(json.get_string(ch, "addr")); codec->port = json.get_unsigned(ch, "port"); codec->mcAddr = _strdup(json.get_string(ch, "mc_addr")); codec->mcPort = json.get_int(ch, "mc_port"); if (codec->mcAddr && codec->mcPort && !remoteAudioConfig->mcAddr) { remoteAudioConfig->mcAddr = _strdup(codec->mcAddr); remoteAudioConfig->mcPort = codec->mcPort; } codec->flags = json.get_int(ch, "flags"); if (mediaType == AUDIO) { remoteAudioConfig->codecList.push_back(codec); if (remoteMediaCmd == MEDIA_CMD_SELECT) audioCoder = coder; } if (mediaType == VIDEO) { remoteVideoConfig->codecList.push_back(codec); if (remoteMediaCmd == MEDIA_CMD_SELECT) videoCoder = coder; } } } word ice = json.get_object(channels, "audio_ice"); if (ice != 0xffff) { remoteAudioConfig->ice = new IceCandidates(); remoteAudioConfig->ice->rtcpMux = json.get_bool(channels, "audio_rtcp_mux"); remoteAudioConfig->ice->dtlsSetupRole = decode(json.get_string(channels, "audio_setup_role")); remoteAudioConfig->ice->iceUfrag = _strdup(json.get_string(ice, "usr")); remoteAudioConfig->ice->icePwd = _strdup(json.get_string(ice, "pwd")); remoteAudioConfig->ice->fingerprint = _strdup(json.get_string(ice, "fingerprint")); word candidates = json.get_array(ice, "candidate"); if(candidates != 0xffff) { word candidate = 0; for (candidate = json.get_object(candidates, candidate); candidate != 0xffff; candidate = json.get_object(candidates, candidate)) { class IceCandidate * c = new IceCandidate(); c->foundation = _strdup(json.get_string(candidate, "foundation")); c->addr = _strdup(json.get_string(candidate, "addr")); c->rtpPort = json.get_unsigned(candidate, "rtp"); c->rtpPriority = json.get_unsigned(candidate, "rtp_prio"); c->type = CandidateType(json.get_string(candidate, "type")); remoteAudioConfig->ice->candidateList.push_back(c); } } } ice = json.get_object(channels, "video_ice"); if (ice != 0xffff) { remoteVideoConfig->ice = new IceCandidates(); remoteVideoConfig->ice->rtcpMux = json.get_bool(channels, "video_rtcp_mux"); remoteVideoConfig->ice->dtlsSetupRole = decode(json.get_string(channels, "video_setup_role")); remoteVideoConfig->ice->iceUfrag = _strdup(json.get_string(ice, "usr")); remoteVideoConfig->ice->icePwd = _strdup(json.get_string(ice, "pwd")); remoteVideoConfig->ice->fingerprint = _strdup(json.get_string(ice, "fingerprint")); word candidates = json.get_array(ice, "candidate"); if (candidates != 0xffff) { word candidate = 0; for (candidate = json.get_object(candidates, candidate); candidate != 0xffff; candidate = json.get_object(candidates, candidate)) { class IceCandidate* c = new IceCandidate(); c->foundation = _strdup(json.get_string(candidate, "foundation")); c->addr = _strdup(json.get_string(candidate, "addr")); c->rtpPort = json.get_unsigned(candidate, "rtp"); c->rtpPriority = json.get_unsigned(candidate, "rtp_prio"); c->type = CandidateType(json.get_string(candidate, "type")); remoteVideoConfig->ice->candidateList.push_back(c); } } } } if (remoteAudioConfig && !remoteAudioConfig->codecList.front()) { delete remoteAudioConfig; remoteAudioConfig = 0; } if (remoteVideoConfig && !remoteVideoConfig->codecList.front()) { delete remoteVideoConfig; remoteVideoConfig = 0; } switch (remoteMediaCmd) { case 0: if (remoteAudioConfig) remoteMediaCmd = MEDIA_CMD_SELECT; break; case MEDIA_CMD_PROPOSAL: case MEDIA_CMD_SELECT: if (!remoteAudioConfig) remoteMediaCmd = MEDIA_CMD_NONE; if (audioIo) { if (!audioIoChannel) audioIoChannel = audioIo->CreateChannel(this); } break; case MEDIA_CMD_REQUEST: if (audioIo) { if (!audioIoChannel) audioIoChannel = audioIo->CreateChannel(this); if (!media) { MediaInitialize(MediaType::AUDIO); } else if(localAudioConfig) { GenerateIceCredentials(localAudioConfig->ice); media->RewriteIceCredentials(localAudioConfig->ice->iceUfrag, localAudioConfig->ice->icePwd); } else { debug->printf("%x/%i:SignalingCall::RecvRemoteMedia localAudioConfig not set. Audio being initialized media:%x", this, call, media); } } if (videoIo && cameraAllowed && !videoDisabled) { if (!videoIoChannel) videoIoChannel = videoIo->CreateChannel(this); // UI not opened and a call comes in, autostart the softphone, no webcam was started if (!videoDeviceId && remoteVideoConfig) { videoDeviceId = _strdup(videoIo->GetDeviceIo(registration->GetHwId())); if (videoDeviceId) videoIo->StartVideoDevice((void*)this, (void*)this, videoDeviceId, 1280, 720, 0); } if (!media2) { MediaInitialize(MediaType::VIDEO); } else if (localVideoConfig) { GenerateIceCredentials(localVideoConfig->ice); media2->RewriteIceCredentials(localVideoConfig->ice->iceUfrag, localVideoConfig->ice->icePwd); } else { debug->printf("%x/%i:SignalingCall::RecvRemoteMedia localVideoConfig not set. Video being initialized media2:%x", this, call, media2); } } MediaInitialized(); break; case MEDIA_CMD_CLOSE: if (audioIoChannel) { audioIoChannel->Close(); MediaIoCloseComplete(audioIoChannel); } if (videoIoChannel) { videoIoChannel->Close(); videoIoChannelClosing = videoIoChannel; videoIoChannel = NULL; } audioCoder = CODER_UNDEFINED; videoCoder = CODER_UNDEFINED; connReceived = false; if (media && localAudioConfig) media->CleanSrtpKeys(); if (media2 && localVideoConfig) media2->CleanSrtpKeys(); break; default: DELETE_OBJ(remoteAudioConfig); DELETE_OBJ(remoteVideoConfig); } debug->printf("%x/%i:SignalingCall::RecvRemoteMedia() local=%x remote=%x localVideo=%x remoteVideo=%x cmd=%s video=%d,%d media2=%p", this, call, localAudioConfig, remoteAudioConfig, localVideoConfig, remoteVideoConfig, ChannelsCmdName(remoteMediaCmd), cameraAllowed, videoDisabled, media2); if (localAudioConfig && remoteMediaCmd) { if ((state & SIG_CALL_STATE_OUTG) && ((state & 0xF) < SIG_CALL_STATE_CONN)) { debug->printf("%x/%i:SignalingCall::RecvRemoteMedia() incoming call receiving command before connect state=%x", this, call, state); selectByConn = true; } else if (remoteMediaCmd == MEDIA_CMD_SELECT) { // if we send PROPOSAL, remote is ICE controlled and remote is active on DTLS (by default) enum SetupRole remoteDtlsRole = SETUP_ROLE_ACTIVE; if (remoteAudioConfig->ice && remoteAudioConfig->ice->dtlsSetupRole == SETUP_ROLE_PASSIVE) remoteDtlsRole = SETUP_ROLE_PASSIVE; connecting |= (1 << AUDIO); media->Connect(remoteAudioConfig, false, remoteDtlsRole); remoteAudioConfig = 0; if (media2 && remoteVideoConfig) { enum SetupRole remoteDtlsRole = SETUP_ROLE_ACTIVE; if (remoteVideoConfig->ice && remoteVideoConfig->ice->dtlsSetupRole == SETUP_ROLE_PASSIVE) remoteDtlsRole = SETUP_ROLE_PASSIVE; connecting |= (1 << VIDEO); media2->Connect(remoteVideoConfig, false, remoteDtlsRole); } else { // SELECT with Audio only if (videoIo && videoDeviceId) { videoIo->StopDevice((void*)this, videoDeviceId); free(videoDeviceId); videoDeviceId = NULL; } if (videoIoChannel) { videoIoChannel->Close(); videoIoChannelClosing = videoIoChannel; videoIoChannel = NULL; } if (media2) { media2->Close(); media2Closing = media2; media2 = NULL; DELETE_OBJ(localVideoConfig); } delete remoteVideoConfig; } remoteVideoConfig = 0; remoteMediaCmd = MEDIA_CMD_NONE; } // We got a PROPOSAL with Video but we had no video allocated. It means we should first allocate Video if enabled. // This will be done outside this function after RecvRemoteMedia else if ((remoteMediaCmd == MEDIA_CMD_PROPOSAL) && !videoDisabled && (remoteVideoConfig && remoteVideoConfig->codecList.front()) && !media2) { // Ignore this case, outside following will be executed: // if (!media2 && !videoDisabled && (remoteVideoConfig && remoteVideoConfig->codecList.front())) { } // We got a REQUEST from the PBX, remote did not offer anything yet but we support Video, transfer? else if ((remoteMediaCmd == MEDIA_CMD_REQUEST) && cameraAllowed && !videoDisabled && (!media2 || (initializing & (1<printf("%x/%i:SignalingCall::Delete() deleting=%d muted=%d registration=%p", this, call, deleting, muted, registration); if (deleting) return; deleting = true; while (monitors.front()) delete monitors.front(); if (registration) { registration->calls = registration->calls->btree_get(this); if (registration->calls == nullptr) { registration->signaling->hookDeviceState = 0; // call is muted when terminating the call and no more calls if (muted) audioIo->Mute(false, false); if (registration->signaling->changedAudioDeviceId) { free(registration->signaling->changedAudioDeviceId); registration->signaling->changedAudioDeviceId = 0; audioIo->StartHookDevice(registration->signaling->defaultAudioDeviceId); } } if (registration->activePhoneCall == this) registration->activePhoneCall = 0; registration = 0; } DELETE_OBJ(localAudioConfig); DELETE_OBJ(localVideoConfig); if (media) { media->Close(); } if (media2) { media2->Close(); } if (media == NULL && mediaClosing == NULL && media2 == NULL && media2Closing == NULL) delete this; } SignalingCallMonitor::SignalingCallMonitor(class SignalingCall * call) { this->call = call; call->monitors.push_back(this); } SignalingCallMonitor::~SignalingCallMonitor() { } SignalingSigMessage::SignalingSigMessage(int call, const char * type, void * sb, void * tmp) : JsonIo((char *)sb) { this->sb = (char *)sb; this->tmp = (char *)tmp; base = add_object(0xffff, 0); add_string(base, "mt", "Signaling"); add_int(base, "call", call, this->tmp); sig = add_object(base, "sig"); add_string(sig, "type", type); } void SignalingSigMessage::Send(class SignalingRegistration * registration) { registration->MessageSend(*this, base, sb); } /*-----------------------------------------------------------------------------------------------*/ VoipEndpoint::VoipEndpoint(const char * num, const char * sip, const char * dn) { this->num = _strdup(num); this->sip = _strdup(sip); this->dn = _strdup(dn); restricted = false; } void VoipEndpoint::SetNum(const char * num) { free(this->num); this->num = _strdup(num); } void VoipEndpoint::AddtoNum(const char * num) { if (this->num) { if (num) { char * s = (char *)alloca(strlen(num) + strlen(this->num) + 1); strcpy(s, this->num); strcpy(&s[strlen(this->num)], num); SetNum(s); } } else { SetNum(num); } } void VoipEndpoint::SetSip(const char * sip) { free(this->sip); this->sip = _strdup(sip); } void VoipEndpoint::SetDn(const char * dn) { free(this->dn); this->dn = _strdup(dn); } void VoipEndpoint::Clear() { free(num), free(sip), free(dn); num = sip = dn = nullptr; } /*-----------------------------------------------------------------------------------------------*/ SignalingRegistrationMonitor::SignalingRegistrationMonitor(enum CallType callType) { this->callType = callType; } /*-----------------------------------------------------------------------------------------------*/ /* SignalingRcc */ /*-----------------------------------------------------------------------------------------------*/ SignalingRcc::SignalingRcc(class JsonApiContext * context) { debug->printf("%x:SignalingRcc()", this); context->RegisterJsonApi(this); } class JsonApi * SignalingRcc::CreateJsonApi(class IJsonApiConnection * connection, class json_io & msg, word base) { debug->printf("%x:SignalingRcc::CreateJsonApi()", this); return 0; } class JsonApi * SignalingRcc::JsonApiRequested(class IJsonApiConnection * connection) { debug->printf("%x:SignalingRcc::JsonApiRequested()", this); return new SignalingRccSession(this, connection); } /*-----------------------------------------------------------------------------------------------*/ SignalingRccSession::SignalingRccSession(class SignalingRcc * rcc, class IJsonApiConnection * connection) : SignalingRegistrationMonitor(CallTypeVoice) { debug->printf("%x:SignalingRccSession() rcc: %x", this, rcc); this->rcc = rcc; this->connection = connection; user = 0; userInitializeSrc = 0; userInitializeRx = false; lastSessionHandle = 0; handles = 0; stopping = false; httpClient = nullptr; rcc->AddRegistrationMonitor(this); connection->RegisterJsonApi(this); rcc->RccSessionOpened(); } SignalingRccSession::~SignalingRccSession() { ASSERT(httpClient == nullptr, "HTTP client still running..."); class SignalingRegistration* registration = rcc->GetRegistration(); debug->printf("%x:~SignalingRccSession() registration=%x", this, registration); // Clear video calls if (registration && !videoConfCalls.empty()) { class SignalingRccVideoConfCall* c = videoConfCalls.front(); while (c) { class SignalingRccVideoConfCall* n = c->goNext(); char* buf = (char*)alloca(10000), * tmp = (char*)alloca(1000); class json_io out(buf); word base = out.add_object(0xffff, 0); out.add_string(base, "mt", "Signaling"); out.add_int(base, "call", c->id, tmp); word sig = out.add_object(base, "sig"); out.add_string(sig, "type", "rel"); word len = out.encode(); registration->MessageSend(out, base, buf); (void)len; c->remove(); delete c; c = n; } } rcc->RccSessionClosed(); rcc->RemoveRegistrationMonitor(this); while (users.front()) delete users.front(); } void SignalingRccSession::JsonApiStart() { debug->printf("%x:SignalingRccSession::JsonApiStart()", this); } void SignalingRccSession::Message(class json_io & msg, word base, const char * mt, const char * src) { debug->printf("%x:SignalingRccSession::Message(mt=%s)", this, mt); if (!strcmp(mt, "Version")) Version(msg, base, src); else if (!strcmp(mt, "DiversionInterrogate")) DiversionInterrogate(msg, base, src); else if (!strcmp(mt, "DiversionDeactivate")) DiversionActivateDeactivate(msg, base, src); else if (!strcmp(mt, "DiversionActivate")) DiversionActivateDeactivate(msg, base, src); else if (!strcmp(mt, "UserInitialize")) UserInitialize(msg, base, src); else if (!strcmp(mt, "UserCall")) UserCall(msg, base, src); else if (!strcmp(mt, "UserClear")) UserClear(msg, base, src); else if (!strcmp(mt, "UserConnect")) UserConnect(msg, base, src); else if (!strcmp(mt, "UserRc")) UserRc(msg, base, src); else if (!strcmp(mt, "UserInfo")) UserInfo(msg, base, src); else if (!strcmp(mt, "UserDTMF")) UserDTMF(msg, base, src); else if (!strcmp(mt, "UserUUI")) UserUUI(msg, base, src); // UserTransfer, one call on hold, one connected. // One connected call (A), one outgoing alerting call (B). else if (!strcmp(mt, "UserTransfer")) UserTransfer(msg, base, src); // UserRedirect, only one connected call. Blind Transfer to another peer. else if (!strcmp(mt, "UserRedirect")) UserRedirect(msg, base, src); // UserReroute, only one alerting call. Call forwarding to another peer. else if (!strcmp(mt, "UserReroute")) UserReroute(msg, base, src); else if (!strcmp(mt, "UserHold")) UserHold(msg, base, src); else if (!strcmp(mt, "UserRetrieve")) UserRetrieve(msg, base, src); else if (!strcmp(mt, "UserPark")) UserPark(msg, base, src); else if (!strcmp(mt, "UserOptions")) UserOptions(msg, base, src); else if (!strcmp(mt, "PbxSignal")) PbxSignal(msg, base, src); else if (!strcmp(mt, "PlaySound")) PlaySound(msg, base, src); connection->JsonApiMessageComplete(); } void SignalingRccSession::JsonApiConnectionClosed() { stopping = true; debug->printf("%x:SignalingRccSession::JsonApiConnectionClosed() registration=%x httpClient=%x", this, rcc->GetRegistration(), httpClient); class SignalingRegistration* registration = rcc->GetRegistration(); if (registration) { if (registration->signaling) { registration->signaling->startVideo = false; registration->signaling->callWaiting = false; registration->signaling->onhookTransfer = false; registration->signaling->autoHangup = AUTOHANGUP_OFF; } if (registration->diversionSession == this) registration->diversionSession = NULL; } if(!httpClient) delete this; } void SignalingRccSession::Version(class json_io & json, word base, const char * src) { SignalingRCCMessage send("VersionResult", src, this, alloca(1000), alloca(100)); send.add_string(send.base, "ver", _VERSION_STR_); send.add_string(send.base, "build", _BUILD_STRING_); send.Send(); } void SignalingRccSession::UserInitializeResult() { class SignalingRegistration * registration = rcc->GetRegistration(); user = new SignalingRccUser(this, userInitializeSrc); class SignalingRCCMessage * userInitializeMsg = new class SignalingRCCMessage("UserInitializeResult", userInitializeSrc, this, alloca(1000), alloca(100)); if (registration->registered) { userInitializeMsg->add_unsigned(userInitializeMsg->base, "user", user->handle, userInitializeMsg->tmp); userInitializeMsg->add_string(userInitializeMsg->base, "addr", registration->signaling->localIpAddr); userInitializeMsg->add_bool(userInitializeMsg->base, "video", registration->signaling->videoLicense); userInitializeMsg->add_bool(userInitializeMsg->base, "recording", registration->signaling->recordingLicense); registration->dialingLocation.write(userInitializeMsg); class SignalingMediaConfig* c = registration->signaling->service->mediaConfig; userInitializeMsg->add_string(userInitializeMsg->base, "stun", c->stunServers); userInitializeMsg->add_string(userInitializeMsg->base, "turn", c->turnServers); userInitializeMsg->add_string(userInitializeMsg->base, "turnUsr", c->turnUsername); userInitializeMsg->add_string(userInitializeMsg->base, "turnPwd", c->turnPassword); } else if (registration->err) { userInitializeMsg->add_string(userInitializeMsg->base, "error", registration->err); } userInitializeMsg->Send(); rcc->MonitorSync(user); user = 0; delete userInitializeMsg; if(userInitializeSrc) free(userInitializeSrc); userInitializeSrc = 0; userInitializeRx = false; } const char * diversion_types[] = { "CFU", "CFB", "CFNR" }; void SignalingRccSession::DiversionInterrogate(class json_io & json, word base, const char * src) { class SignalingRegistration * registration = rcc->GetRegistration(); if (!registration) { debug->printf("%x:SignalingRccSession::DiversionInterrogate no registration available", this); return; } const char * procedure = json.get_string(base, "procedure"); SignalingSigMessage setup(diversionCallId, "setup", alloca(1000), alloca(100)); setup.add_int(setup.sig, "channel", 0, setup.tmp); word ftys = setup.add_array(setup.sig, "fty"); for (unsigned i = 0; i < 3; i++) { const char * type = diversion_types[i]; if (!procedure || !strcmp(procedure, type)) { word fty = setup.add_object(ftys, 0); setup.add_string(fty, "type", "diversion_interrogate"); setup.add_string(fty, "procedure", type); } } registration->diversionSession = this; setup.Send(registration); } void SignalingRccSession::DiversionActivateDeactivate(class json_io & json, word base, const char * src) { class SignalingRegistration * registration = rcc->GetRegistration(); if (!registration) { debug->printf("%x:SignalingRccSession::DiversionActivateDeactivate no registration available", this); return; } const char * mt = json.get_string(base, "mt"); const char * procedure = json.get_string(base, "procedure"); const char * num = json.get_string(base, "num"); const char * sip = json.get_string(base, "sip"); const char * type = 0; if (mt && !strcmp(mt, "DiversionActivate")) type = "diversion_activate"; if (mt && !strcmp(mt, "DiversionDeactivate")) type = "diversion_deactivate"; SignalingSigMessage setup(diversionCallId, "setup", alloca(1000), alloca(100)); setup.add_int(setup.sig, "channel", 0, setup.tmp); word ftys = setup.add_array(setup.sig, "fty"); word fty = setup.add_object(ftys, 0); setup.add_string(fty, "type", type); setup.add_string(fty, "procedure", procedure); word diverted_to = setup.add_object(fty, "diverted_to"); if (num) setup.add_string(diverted_to, "num", num); if (sip) setup.add_string(diverted_to, "sip", sip); //word served = setup.add_object(fty, "served"); //if (myNum) setup.add_string(served, "num", myNum); //if (mySip) setup.add_string(served, "sip", mySip); registration->diversionSession = this; setup.Send(registration); } void SignalingRccSession::UserInitialize(class json_io & json, word base, const char * src) { class SignalingRegistration * registration = rcc->GetRegistration(); if (user) { delete user; user = 0; } debug->printf("%x:SignalingRccSession::UserInitialize() reg=%p up=%d err=%s videoLicense=%d", this, registration, registration ? registration->registered : 0, (registration && registration->err) ? registration->err : "-", registration ? registration->signaling->videoLicense : false); if (userInitializeSrc) { free(userInitializeSrc); userInitializeSrc = NULL; } userInitializeSrc = _strdup(src); userInitializeRx = true; if (registration && registration->clientUp && (registration->registered || registration->err)) { UserInitializeResult(); } } void SignalingRccSession::RegistrationState(bool up) { class SignalingRegistration* registration = rcc->GetRegistration(); debug->printf("%x:SignalingRccSession::RegisterResult() err=%s user=%p userInitializeRx=%d reg=%p up=%d", this, (registration && registration->err) ? registration->err : "-", user, userInitializeRx, registration, registration ? registration->registered : 0); if (userInitializeRx) { UserInitializeResult(); } } void SignalingRccSession::UserCall(class json_io & json, word base, const char * src) { class SignalingRegistration * registration = rcc->GetRegistration(); class SignalingRccUser * user = FindUser(json.get_unsigned(base, "user")); class SignalingRccCall * rccCall = 0; const char* e164 = json.get_string(base, "e164"); const char* h323 = json.get_string(base, "h323"); const char* dn = json.get_string(base, "dn"); bool sendingCompletePresent; bool sendingComplete = json.get_bool(base, "complete", &sendingCompletePresent); debug->printf("%x:SignalingRccSession::UserCall() reg: %x sig: %x user: %x e164: %s h323: %s", this, registration, registration ? registration->signaling : 0, user, e164 ? e164 : "-", h323 ? h323 : "-"); SignalingRCCMessage send("UserCallResult", src, this, alloca(1000), alloca(100)); if (registration) { if (user) { class SignalingCall* call = new SignalingCall(registration, CallTypeVoice, 0, e164, h323, dn, sendingCompletePresent ? sendingComplete : false); if (registration->signaling->videoLicense) { call->cameraAllowed = json.get_bool(base, "video"); } rccCall = new SignalingRccCall(this, user, call); class IAudioIo* audioIo = rcc->GetAudioIo(); class IVideoIo* videoIo = rcc->GetVideoIo(); if (audioIo) call->StartCall(audioIo, videoIo, 0, user); send.add_unsigned(send.base, "call", rccCall ? rccCall->handle : 0, send.tmp); } else send.add_string(send.base, "error", "No user available"); } else send.add_string(send.base, "error", "No registration available"); send.Send(); } void SignalingRccSession::UserClear(class json_io & json, word base, const char * src) { debug->printf("%x:SignalingRccSession::UserClear()", this); class SignalingRccCall * call = FindCall(json.get_unsigned(base, "call")); SignalingRCCMessage send("UserClearResult", src, this, alloca(1000), alloca(100)); send.Send(); if (call) { call->call->ClearCall(); } } void SignalingRccSession::UserConnect(class json_io & json, word base, const char * src) { debug->printf("%x:SignalingRccSession::UserConnect()", this); class SignalingRegistration * registration = rcc->GetRegistration(); class SignalingRccCall * call = FindCall(json.get_unsigned(base, "call")); SignalingRCCMessage send("UserConnectResult", src, this, alloca(1000), alloca(100)); send.Send(); if (registration && call && call->call) { if (registration->signaling->videoLicense) { call->call->cameraAllowed = json.get_bool(base, "video"); } class IAudioIo * audioIo = rcc->GetAudioIo(); class IVideoIo * videoIo = rcc->GetVideoIo(); if (audioIo) call->call->ConnectCall(audioIo, videoIo); } } static struct { const char * str; unsigned code; } rc_codes[] = { { "FTY_REMOTE_CONTROL_REC_ON", FTY_REMOTE_CONTROL_REC_ON }, { "FTY_REMOTE_CONTROL_REC_OFF", FTY_REMOTE_CONTROL_REC_OFF }, { "FTY_REMOTE_CONTROL_3PTY_ON", FTY_REMOTE_CONTROL_3PTY_ON }, { "FTY_REMOTE_CONTROL_MIC_ON", FTY_REMOTE_CONTROL_MIC_ON }, { "FTY_REMOTE_CONTROL_MIC_OFF", FTY_REMOTE_CONTROL_MIC_OFF }, { "FTY_REMOTE_CONTROL_ADD_VIDEO", FTY_REMOTE_CONTROL_ADD_VIDEO }, { "FTY_REMOTE_CONTROL_REM_VIDEO", FTY_REMOTE_CONTROL_REM_VIDEO }, { "FTY_REMOTE_CONTROL_START_CAMERA", FTY_REMOTE_CONTROL_START_CAMERA }, { "FTY_REMOTE_CONTROL_STOP_CAMERA", FTY_REMOTE_CONTROL_STOP_CAMERA }, { "FTY_REMOTE_CONTROL_ADD_APPSHARING", FTY_REMOTE_CONTROL_ADD_APPSHARING }, { "FTY_REMOTE_CONTROL_REM_APPSHARING", FTY_REMOTE_CONTROL_REM_APPSHARING }, { "FTY_REMOTE_CONTROL_ALLOW_VIDEO", FTY_REMOTE_CONTROL_ALLOW_VIDEO }, }; void SignalingRccSession::UserRc(class json_io & json, word base, const char * src) { debug->printf("%x:SignalingRccSession::UserRc()", this); class SignalingRccCall * call = FindCall(json.get_unsigned(base, "call")); SignalingRCCMessage send("UserRcResult", src, this, alloca(1000), alloca(100)); send.Send(); if (call) { // #130651 - Allow "UserRc" with textual command // (e.g. 10 or "FTY_REMOTE_CONTROL_3PTY_ON") unsigned rc = 0; bool present = false; const char * str = json.get_string(base, "rc", &present); if (present && str) { const unsigned max = sizeof(rc_codes) / sizeof(*rc_codes); for (unsigned i = 0; i < max; i++) { if (!strcmp(str, rc_codes[i].str)) { rc = rc_codes[i].code; break; } } } else { rc = json.get_unsigned(base, "rc", &present); } call->call->Rc(rc); } } void SignalingRccSession::UserInfo(class json_io & json, word base, const char * src) { debug->printf("%x:SignalingRccSession::UserInfo()", this); class SignalingRccCall * call = FindCall(json.get_unsigned(base, "call")); SignalingRCCMessage send("UserInfoResult", src, this, alloca(1000), alloca(100)); send.Send(); const char * e164 = json.get_string(base, "e164"); const char * h323 = json.get_string(base, "h323"); if (call && (e164 || h323)) { call->call->Info(e164, h323); } } void SignalingRccSession::UserDTMF(class json_io & json, word base, const char * src) { debug->printf("%x:SignalingRccSession::UserDTMF()", this); class SignalingRccCall * call = FindCall(json.get_unsigned(base, "call")); SignalingRCCMessage send("UserDTMFResult", src, this, alloca(1000), alloca(100)); send.Send(); if (call) { call->call->SendDTMF(json.get_string(base, "dtmf"), true); } } void SignalingRccSession::UserUUI(class json_io & json, word base, const char * src) { debug->printf("%x:SignalingRccSession::UserUUI()", this); class SignalingRccCall * call = FindCall(json.get_unsigned(base, "call")); SignalingRCCMessage send("UserUUIResult", src, this, alloca(1000), alloca(100)); send.Send(); if (call) { call->call->UUI(json.get_unsigned(base, "pd"), json.get_string(base, "uui")); } } void SignalingRccSession::UserTransfer(class json_io & json, word base, const char * src) { debug->printf("%x:SignalingRccSession::UserTransfer()", this); class SignalingRccCall * a = FindCall(json.get_unsigned(base, "a")); class SignalingRccCall * b = FindCall(json.get_unsigned(base, "b")); SignalingRCCMessage msg("UserTransferResult", src, this, alloca(1000), alloca(100)); msg.Send(); if (a && b) { a->call->StartTransfer(b->call); } } void SignalingRccSession::UserRedirect(class json_io & json, word base, const char * src) { debug->printf("%x:SignalingRccSession::UserRedirect()", this); class SignalingRccCall * call = FindCall(json.get_unsigned(base, "call")); const char * e164 = json.get_string(base, "e164"); const char * h323 = json.get_string(base, "h323"); SignalingRCCMessage msg("UserRedirectResult", src, this, alloca(1000), alloca(100)); msg.Send(); if (call && (e164 || h323)) { call->call->StartRedirect(e164, h323); } } void SignalingRccSession::UserReroute(class json_io & json, word base, const char * src) { debug->printf("%x:SignalingRccSession::UserReroute()", this); class SignalingRccCall * call = FindCall(json.get_unsigned(base, "call")); const char * e164 = json.get_string(base, "e164"); const char * h323 = json.get_string(base, "h323"); SignalingRCCMessage msg("UserRerouteResult", src, this, alloca(1000), alloca(100)); msg.Send(); if (call && (e164 || h323)) { call->call->StartReroute(e164, h323); } } void SignalingRccSession::UserHold(class json_io & json, word base, const char * src) { debug->printf("%x:SignalingRccSession::UserHold()", this); class SignalingRccCall * call = FindCall(json.get_unsigned(base, "call")); SignalingRCCMessage msg("UserHoldResult", src, this, alloca(1000), alloca(100)); msg.Send(); if (call && call->call) { call->call->ChangeState(call->call->state | SIG_CALL_STATE_HOLD); call->call->Hold(); } } void SignalingRccSession::UserRetrieve(class json_io & json, word base, const char * src) { debug->printf("%x:SignalingRccSession::UserRetrieve()", this); class SignalingRccCall * call = FindCall(json.get_unsigned(base, "call")); SignalingRCCMessage msg("UserRetrieveResult", src, this, alloca(1000), alloca(100)); msg.Send(); if (call && call->call) { call->call->ChangeState(call->call->state & ~SIG_CALL_STATE_HOLD); call->call->Retrieve(); } } void SignalingRccSession::UserPark(class json_io& json, word base, const char* src) { debug->printf("%x:SignalingRccSession::UserPark()", this); class SignalingRccCall* call = FindCall(json.get_unsigned(base, "call")); SignalingRCCMessage msg("UserParkResult", src, this, alloca(1000), alloca(100)); msg.Send(); const char* parked_to_num = 0, * parked_to_sip = 0; word parked_to = json.get_object(base, "parked_to"); if (parked_to != 0xffff) { parked_to_num = json.get_string(parked_to, "num"); parked_to_sip = json.get_string(parked_to, "sip"); } if (call && call->call) { call->call->Park(parked_to_num, parked_to_sip); } } void SignalingRccSession::UserOptions(class json_io& json, word base, const char* src) { debug->printf("%x:SignalingRccSession::UserOptions()", this); class SignalingRegistration* registration = rcc->GetRegistration(); if (registration && registration->signaling) { registration->signaling->startVideo = json.get_bool(base, "startVideo"); registration->signaling->callWaiting = json.get_bool(base, "callWaiting"); registration->signaling->onhookTransfer = json.get_bool(base, "onhookTransfer"); registration->signaling->clir = json.get_bool(base, "clir"); if (const char* autoHangup = json.get_string(base, "autoHangup")) { if (!strcmp(autoHangup, "off")) registration->signaling->autoHangup = AUTOHANGUP_OFF; else if (!strcmp(autoHangup, "allCalls")) registration->signaling->autoHangup = AUTOHANGUP_ALLCALLS; else if (!strcmp(autoHangup, "connectedCalls")) registration->signaling->autoHangup = AUTOHANGUP_CONNECTEDCALLS; } // URL could be removed, call the function even if url is null registration->signaling->SetRecordingParameters(json.get_string(base, "recordingUrl"), false, false, json.get_bool(base, "isRecordingByDefaultOn"), false, json.get_bool(base, "recordExternalOnly"), false); } } void SignalingRccSession::PbxSignal(class json_io& json, word base, const char* src) { class SignalingRegistration* registration = rcc->GetRegistration(); int call = json.get_int(base, "call"); class SignalingRccCall* rccCall = FindCall(call); int callId = rccCall && rccCall->call ? rccCall->call->call : call; debug->printf("%x:SignalingRccSession::PbxSignal() call=%d rccCall=%p(%d)", this, call, rccCall, callId); word sig = json.get_object(base, "sig"); if (sig != 0xffff) { char* sig_buf = (char*)alloca(10000); word sig_len = json.encode(sig, sig_buf); char* buf = (char*)alloca(10000), * tmp = (char*)alloca(1000); class json_io out(buf); word base = out.add_object(0xffff, 0); out.add_string(base, "mt", "Signaling"); out.add_int(base, "call", callId, tmp); out.add_json(base, "sig", sig_buf, sig_len); // Keep track of video conference calls // If UI is closed, a rel message will be sent to the PBX const char * msg = json.get_string(sig, "type"); if (msg && !strcmp(msg, "setup")) { // Search voice call if (registration && registration->signaling && registration->signaling->FindCallByConfId(json.get_string(sig, "confId"))) { debug->printf("%x:SignalingRccSession::PbxSignal() add video conference call=%d", this, callId); videoConfCalls.push_back(new SignalingRccVideoConfCall(json.get_string(sig, "confId"), callId)); } } else if (msg && !strcmp(msg, "rel")) { if (!videoConfCalls.empty()) { class SignalingRccVideoConfCall * c = videoConfCalls.front(); while(c) { if (c->id == callId) { debug->printf("%x:SignalingRccSession::PbxSignal() remove video conference call=%d", this, callId); c->remove(); delete c; break; } c = c->goNext(); } } } word len = out.encode(); registration->MessageSend(out, base, buf); (void)len; } } void SignalingRccSession::PlaySound(class json_io& json, word base, const char* src) { debug->printf("%x:SignalingRccSession::PlaySound() url=%s", this, json.get_string(base, "url") ? json.get_string(base, "url") : ""); class SignalingRegistration * registration = rcc->GetRegistration(); _snprintf(webaccessTone, 4096, "%swebaccess.entrance.mp3", registration->signaling->GetWorkingPath()); if (!FileSystem::FileExists(webaccessTone, registration->signaling)) { if(!httpClient) httpClient = new class SignalingRccSessionHttpClient(registration->signaling, this, json.get_string(base, "url")); } else { const char * toneFilename = "webaccess.entrance.mp3"; #if defined __ANDROID__ || defined _IOS_ char toneId[4096]; _snprintf(toneId, sizeof(toneId), "%s%s%s", "file://", registration->signaling->GetWorkingPath(), toneFilename); #else const char * toneId = toneFilename; #endif registration->signaling->GetRingerIo()->NotificationTonePlay(registration->signaling->GetAudioDevice(), 0, toneId); } } void SignalingRccSession::SignalingRccSessionHttpClientClosed(class SignalingRccSessionHttpClient* httpClient) { debug->printf("%x:SignalingRccSession::SignalingRccSessionHttpClientClosed() stopping:%d Success:%d", this, stopping, httpClient->Success()); if (this->stopping) { delete httpClient; this->httpClient = nullptr; delete this; } else { if (httpClient->Success()) { class SignalingRegistration* registration = rcc->GetRegistration(); const char * toneFilename = "webaccess.entrance.mp3"; #if defined __ANDROID__ || defined _IOS_ char toneId[4096]; _snprintf(toneId, sizeof(toneId), "%s%s%s", "file://", registration->signaling->GetWorkingPath(), toneFilename); #else const char * toneId = toneFilename; #endif registration->signaling->GetRingerIo()->NotificationTonePlay(registration->signaling->GetAudioDevice(), 0, toneId); } delete httpClient; this->httpClient = nullptr; } } class SignalingRccUser * SignalingRccSession::FindUser(word handle) { class btree * b = handles->btree_find((void *)(uintp)handle); if (b) { if (((class SignalingRccHandle *)b)->type == RCC_TYPE_USER) { return (class SignalingRccUser *)(class SignalingRccHandle *)b; } } return 0; } class SignalingRccCall * SignalingRccSession::FindCall(word handle) { class btree * b = handles->btree_find((void *)(uintp)handle); if (b) { if (((class SignalingRccHandle *)b)->type == RCC_TYPE_CALL) { return (class SignalingRccCall *)(class SignalingRccHandle *)b; } } return 0; } SignalingRccHandle::SignalingRccHandle(class SignalingRccSession * session, word type) { this->session = session; this->type = type; do { session->lastSessionHandle++; } while(!session->lastSessionHandle || session->handles->btree_find((void *)(uintp)session->lastSessionHandle)); handle = session->lastSessionHandle; session->handles = session->handles->btree_put(this); } SignalingRccHandle::~SignalingRccHandle() { session->handles = session->handles->btree_get(this); } int SignalingRccHandle::btree_compare(void * key) { return (word)(uintp)key-handle; } int SignalingRccHandle::btree_compare(class btree * b) { return ((class SignalingRccHandle *)b)->handle-handle; } SignalingRCCMessage::SignalingRCCMessage(const char * mt, const char * src, class SignalingRccSession * session, void * sb, void * tmp) : JsonIo((char *)sb) { this->session = session; this->sb = (char *)sb; this->tmp = (char *)tmp; base = add_object(0xffff, 0); add_string(base, "mt", mt); add_string(base, "src", src); add_string(base, "api", session->Name()); } void SignalingRCCMessage::Send() { session->connection->JsonApiMessage(*this, sb); } /*-----------------------------------------------------------------------------------------------*/ /* SignalingRccUser */ /*-----------------------------------------------------------------------------------------------*/ SignalingRccUser::SignalingRccUser(class SignalingRccSession * session, const char * src) : SignalingRccHandle(session, RCC_TYPE_USER), SignalingRegistrationMonitor(CallTypeVoice) { debug->printf("%x/%u:SignalingRccUser()", this, handle); this->src = _strdup(src); session->rcc->AddRegistrationMonitor(this); session->users.push_back(this); } SignalingRccUser::~SignalingRccUser() { debug->printf("%x/%u:~SignalingRccUser()", this, handle); while (calls.front()) delete calls.front(); } void SignalingRccUser::RegistrationState(bool up) { debug->printf("%x/%u:SignalingRccUser::RegistrationState(up=%u)", this, handle, up); } class SignalingCallMonitor * SignalingRccUser::CallAdded(class SignalingCall * call) { debug->printf("%x/%u:SignalingRccUser::CallAdded(%i)", this, handle, call->call); call->RccSessionOpened(); return new SignalingRccCall(session, this, call); } void SignalingRccUser::UnknownCallMessage(const char * type, class json_io & msg, word base) { word sig = msg.get_object(base, "sig"); if (sig != 0xffff) { int call = msg.get_int(base, "call"); char* sig_buf = (char*)alloca(10000); word sig_len = msg.encode(sig, sig_buf); char* buf = (char*)alloca(10000), * tmp = (char*)alloca(1000); class json_io out(buf); word base = out.add_object(0xffff, 0); out.add_string(base, "api", "RCC"); out.add_int(base, "user", handle, tmp); out.add_string(base, "mt", "PbxSignal"); out.add_int(base, "call", call, tmp); out.add_json(base, "sig", sig_buf, sig_len); word len = out.encode(); session->JsonApiMessage(out, buf); (void)len; } } /*-----------------------------------------------------------------------------------------------*/ /* SignalingRccCall */ /*-----------------------------------------------------------------------------------------------*/ SignalingRccCall::SignalingRccCall(SignalingRccSession * session, SignalingRccUser * user, class SignalingCall * call) : SignalingRccHandle(session, RCC_TYPE_CALL), SignalingCallMonitor(call) { debug->printf("%x/%u:SignalingRccCall(user=%u)", this, handle, user->handle); this->session = session; this->user = user; user->calls.push_back(this); } SignalingRccCall::~SignalingRccCall() { debug->printf("%x/%u:~SignalingRccCall(user=%u)", this, handle, user->handle); class SignalingRCCMessage send("CallInfo", user->src, session, alloca(10000), alloca(4000)); SendCallInfo(send, send.base, send.tmp); send.add_bool(send.base, "del", true); send.add_string(send.base, "msg", "del"); if (call->cause) { word info = send.add_array(send.base, "info"); info = send.add_object(info, 0); send.add_string(info, "type", "cause"); send.add_int(info, "vali", call->cause, send.tmp); } send.Send(); call->RccSessionClosed(); } void SignalingRccCall::MonitorXmit(const char * type, class json_io & msg, word base) { class SignalingRegistration* registration = session->rcc ? session->rcc->GetRegistration() : NULL; if (registration && registration->signaling && registration->signaling->log) registration->signaling->log->Log(LOG_SIGNALING, "%x/%u:SignalingRccCall::MonitorXmit(type=%s)", this, handle, type); class SignalingRCCMessage send("CallInfo", user->src, session, alloca(10000), alloca(4000)); if (!strcmp(type, "setup")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "x-setup"); send.Send(); } else if (!strcmp(type, "alert")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "x-alert"); send.Send(); } else if (!strcmp(type, "conn")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "x-conn"); send.Send(); } else if (!strcmp(type, "channels")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "x-channels"); send.Send(); } else if (!strcmp(type, "rel")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "x-rel"); send.Send(); } else if (!strcmp(type, "facility_hold")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "x-facility"); word ftys = send.add_array(send.base, "info"); word fty = send.add_object(ftys, 0); send.add_string(fty, "type", "remote_hold"); send.add_bool(fty, "muted", this->call->muted); send.Send(); } else if (!strcmp(type, "facility_retrieve")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "x-facility"); word ftys = send.add_array(send.base, "info"); word fty = send.add_object(ftys, 0); send.add_string(fty, "type", "remote_retrieve"); send.add_bool(fty,"muted",this->call->muted); send.Send(); } else if (!strcmp(type, "facility_mute")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "x-facility"); word ftys = send.add_array(send.base, "info"); word fty = send.add_object(ftys, 0); send.add_bool(fty,"muted",this->call->muted); send.Send(); } else if (!strcmp(type, "facility_audio_route_changed")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "x-facility"); word ftys = send.add_array(send.base, "info"); word fty = send.add_object(ftys, 0); send.add_string(fty, "changedAudioRoute", this->call->getAudioDeviceId()); send.Send(); } else if (!strcmp(type, "media_data_on")) { class SignalingRCCMessage send("MediaDataOn", user->src, session, alloca(5000), alloca(1000)); send.add_string(send.base, "channel", msg.get_string(base, "channel")); send.add_string(send.base, "type", msg.get_string(base, "type")); send.Send(); } else if (!strcmp(type, "media_data_off")) { class SignalingRCCMessage send("MediaDataOff", user->src, session, alloca(5000), alloca(1000)); send.add_string(send.base, "channel", msg.get_string(base, "channel")); send.add_string(send.base, "type", msg.get_string(base, "type")); send.Send(); } else if (!strcmp(type, "media_error")) { class SignalingRCCMessage send("MediaError", user->src, session, alloca(5000), alloca(1000)); send.add_unsigned(send.base, "call", handle, send.tmp); send.add_string(send.base, "channel", msg.get_string(base, "channel")); send.add_string(send.base, "type", msg.get_string(base, "type")); send.add_string(send.base, "kind", msg.get_string(base, "kind")); send.add_unsigned(send.base, "code", msg.get_unsigned(base, "code"), send.tmp); send.add_string(send.base, "errorText", msg.get_string(base, "errorText")); send.add_string(send.base, "src", msg.get_string(base, "src")); send.Send(); } else if (!strcmp(type, "media_warning")) { class SignalingRCCMessage send("MediaWarning", user->src, session, alloca(5000), alloca(1000)); send.add_unsigned(send.base, "call", handle, send.tmp); send.add_string(send.base, "channel", msg.get_string(base, "channel")); send.add_string(send.base, "type", msg.get_string(base, "type")); send.add_string(send.base, "kind", msg.get_string(base, "kind")); send.add_unsigned(send.base, "code", msg.get_unsigned(base, "code"), send.tmp); send.add_string(send.base, "warningText", msg.get_string(base, "warningText")); send.add_string(send.base, "src", msg.get_string(base, "src")); send.Send(); } else if (!strcmp(type, "hook_device_denied")) { class SignalingRCCMessage send("HookDeviceError", user->src, session, alloca(5000), alloca(1000)); send.add_unsigned(send.base, "call", handle, send.tmp); send.add_string(send.base, "channel", msg.get_string(base, "channel")); send.add_string(send.base, "errorText", msg.get_string(base, "errorText")); send.add_string(send.base, "src", msg.get_string(base, "src")); send.Send(); } else if (!strcmp(type, "hook_device_not_found")) { class SignalingRCCMessage send("HookDeviceError", user->src, session, alloca(5000), alloca(1000)); send.add_unsigned(send.base, "call", handle, send.tmp); send.add_string(send.base, "channel", msg.get_string(base, "channel")); send.add_string(send.base, "errorText", msg.get_string(base, "errorText")); send.add_string(send.base, "src", msg.get_string(base, "src")); send.Send(); } else if (!strcmp(type, "no_ice_candidates")) { class SignalingRCCMessage send("NoIceCandidates", user->src, session, alloca(5000), alloca(1000)); send.add_unsigned(send.base, "call", handle, send.tmp); send.add_string(send.base, "channel", msg.get_string(base, "channel")); send.add_string(send.base, "type", msg.get_string(base, "type")); send.add_string(send.base, "kind", msg.get_string(base, "kind")); send.add_string(send.base, "errorText", msg.get_string(base, "errorText")); send.Send(); } else if (!strcmp(type, "facility_recording_started")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "x-facility"); word ftys = send.add_array(send.base, "info"); word fty = send.add_object(ftys, 0); send.add_bool(fty,"recording",true); send.add_bool(fty,"isTrunkRecording",this->call->recordingInfo.isTrunkRecording); send.add_string(fty,"recordingError",this->call->recordingInfo.recordingError); send.Send(); } else if (!strcmp(type, "facility_recording_stopped")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "x-facility"); word ftys = send.add_array(send.base, "info"); word fty = send.add_object(ftys, 0); send.add_bool(fty,"recording",false); send.add_string(fty,"recordingError",this->call->recordingInfo.recordingError); send.Send(); } else if (!strcmp(type, "contact")) { SendCallInfo(send, send.base, send.tmp); char contactBuffer[16 * 1024]; word contactObject = msg.get_object(base, "contact"); word contactLength = msg.encode(contactObject, contactBuffer); send.add_json(send.base, "contact", contactBuffer, contactLength); send.Send(); } } void SignalingRccCall::MonitorRecv(const char * type, class json_io & msg, word base) { class SignalingRegistration* registration = session->rcc ? session->rcc->GetRegistration() : NULL; if (registration && registration->signaling && registration->signaling->log) registration->signaling->log->Log(LOG_SIGNALING, "%x/%u:SignalingRccCall::MonitorRecv(type=%s)", this, handle, type); class SignalingRCCMessage send("CallInfo", user->src, session, alloca(10000), alloca(4000)); if (!strcmp(type, "setup")) { word no = 0xffff; SendCallInfo(send, send.base, send.tmp); if (call->diverting.num || call->diverting.sip || call->diverting.dn) { if (no == 0xffff) no = send.add_array(send.base, "no"); word diverting = send.add_object(no, 0); send.add_string(diverting, "type", "leg2"); send.add_string(diverting, "e164", call->diverting.num); send.add_string(diverting, "h323", call->diverting.sip); send.add_string(diverting, "dn", call->diverting.dn); } if (call->originalCalled.num || call->originalCalled.sip || call->originalCalled.dn) { if (no == 0xffff) no = send.add_array(send.base, "no"); word originalCalled = send.add_object(no, 0); send.add_string(originalCalled, "type", "leg2orig"); send.add_string(originalCalled, "e164", call->originalCalled.num); send.add_string(originalCalled, "h323", call->originalCalled.sip); send.add_string(originalCalled, "dn", call->originalCalled.dn); } if (call->transfering.num || call->transfering.sip || call->transfering.dn) { if (no == 0xffff) no = send.add_array(send.base, "no"); word transfering = send.add_object(no, 0); send.add_string(transfering, "type", "ct"); send.add_string(transfering, "e164", call->transfering.num); send.add_string(transfering, "h323", call->transfering.sip); send.add_string(transfering, "dn", call->transfering.dn); } send.add_string(send.base, "msg", "r-setup"); send.Send(); } else if (!strcmp(type, "setup_ack")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "r-setup-ack"); send.Send(); } else if (!strcmp(type, "call_proc")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "r-call-proc"); send.Send(); } else if (!strcmp(type, "alert")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "r-alert"); send.Send(); } else if (!strcmp(type, "conn")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "r-conn"); send.Send(); } else if (!strcmp(type, "channels")) { SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "r-channels"); if (call->sendVideoProposal) { if(CoderName(call->videoCoder) == 0) send.add_bool(send.base, "videoRejected", true); call->sendVideoProposal = false; } send.Send(); } else if (!strcmp(type, "facility")) { if (call->fty_ct_initiate.num || call->fty_ct_initiate.sip) { // must replace this SignalingCall from PBX by a new outbound SignalingCall to PBX // the SignalingRccCall with the frontend can stay the same, it will only change its direction attibute const char* num = call->fty_ct_initiate.num; const char* sip = call->fty_ct_initiate.sip; const char* dn = call->fty_ct_initiate.dn; class SignalingRcc* rcc = session->rcc; class SignalingRegistration* registration = rcc->GetRegistration(); class SignalingCall* newCall = new SignalingCall(registration, CallTypeVoice, 0, num, sip, dn, true); newCall->cameraAllowed = call->cameraAllowed; newCall->videoDisabled = call->videoDisabled; newCall->ct_setup_id = call->fty_ct_initiate.id; // detach replacedCall from this SignalingRccCall class SignalingCall* replacedCall = call; this->SignalingCallMonitor::remove(); // attach newCall to this SignalingRccCall call = newCall; call->monitors.push_back(this); class IAudioIo* audioIo = rcc->GetAudioIo(); class IVideoIo* videoIo = rcc->GetVideoIo(); if (audioIo) call->StartCall(audioIo, videoIo, 0, user); // release replacedCall now (better wait for ALERT/CONN before send REL) replacedCall->fty_ct_initiate.cleanup(); replacedCall->clearCall = true; } SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "r-facility"); send.Send(); } else if (!strcmp(type, "disc") || !strcmp(type, "rel")) { SendCallInfo(send, send.base, send.tmp); if (call->cause) { word info = send.add_array(send.base, "info"); info = send.add_object(info, 0); send.add_string(info, "type", "cause"); send.add_int(info, "vali", call->cause, send.tmp); } send.add_printf(send.base, "msg", send.tmp, "r-%s", type); send.Send(); } } void SignalingRccCall::MonitorSync() { debug->printf("%x/%u:SignalingRccCall::MonitorSync()", this, handle); class SignalingRCCMessage send("CallInfo", user->src, session, alloca(10000), alloca(4000)); SendCallInfo(send, send.base, send.tmp); send.add_string(send.base, "msg", "sync"); send.add_int(send.base, "duration", call->duration ? (int)((ITime::TimeStampMilliseconds()/1000) - call->duration) : 0, send.tmp); send.Send(); } void SignalingRccCall::SendCallInfo(class json_io & send, word base, char * & tmp) { send.add_unsigned(base, "user", user->handle, tmp); send.add_unsigned(base, "call", handle, tmp); send.add_unsigned(base, "state", call->state, tmp); send.add_printf(base, "conf", tmp, "%.*H", sizeof(call->conf), call->conf.b); send.add_string(base, "conferenceGuid", call->conferenceGuid); send.add_string(base, "conferenceType", call->conferenceType); if (call->multiVideoSupport) send.add_bool(base, "multiVideoSupport", call->multiVideoSupport); word peer = send.add_object(base, "peer"); if (call->remote.restricted) { send.add_bool(peer, "restricted", true); } else { send.add_string(peer, "e164", call->remote.num); send.add_string(peer, "h323", call->remote.sip); send.add_string(peer, "dn", call->remote.dn); } word codecs = send.add_object(base, "codecs"); send.add_string(codecs, "audio", CoderName(call->audioCoder)); send.add_string(codecs, "video", CoderName(call->videoCoder)); send.add_string(base, "camera", call->IsCameraOn() ? "on" : "off"); if (call->muted) send.add_bool(base, "muted", true); if (call->conferenceInfo.reason) { word conference_info = send.add_object(base, "conference_info"); call->conferenceInfo.write(send, conference_info, tmp); } if (call->chatMessage.data) { word im_message = send.add_object(base, "im_message"); call->chatMessage.write(send, im_message, tmp); } if (call->remoteVideoPriority) { send.add_bool(base, "videoPriority", true); } } /*-----------------------------------------------------------------------------------------------*/ /* SignalingRTPTP */ /*-----------------------------------------------------------------------------------------------*/ SignalingRTPTP::SignalingRTPTP(class Signaling * signaling) : SignalingRegistrationMonitor(CallTypeData), retry(signaling->service->iomux, this) { debug->printf("%x:SignalingRTPTP()", this); this->signaling = signaling; user = 0; sip = 0; timeout = TIMEOUT_MIN; up = false; call = 0; } SignalingRTPTP::~SignalingRTPTP() { } class SignalingRTPTP * SignalingRTPTP::Update(class URTPTP * user, const char * sip, unsigned timeout) { debug->printf("%x:SignalingRTPTP::Update(sip=%s) timeout=%d", this, sip, timeout); this->user = user; if (sip) { if (timeout) this->timeout = timeout; if (!this->sip) { this->sip = _strdup(sip); signaling->AddRegistrationMonitor(this); } else if (strcmp(this->sip, sip)) { if (call) { call->call->ClearCall(); call = 0; } free(this->sip); this->sip = _strdup(sip); signaling->RemoveRegistrationMonitor(this); signaling->AddRegistrationMonitor(this); } } else { free(this->sip); this->sip = 0; call->call->ClearCall(); call = 0; retry.Cancel(); signaling->RemoveRegistrationMonitor(this); if (!user) { delete this; return 0; } } return this; } void SignalingRTPTP::Sync(class json_io & msg, word base) { debug->printf("%x:SignalingRTPTP::Sync()", this); } void SignalingRTPTP::RegistrationState(bool up) { debug->printf("%x:SignalingRTPTP::RegistrationState(up=%x)", this, up); if (up) { class SignalingCall * c = new SignalingCall(signaling->registration, CallTypeData, 0, 0, sip, 0, true); call = new SignalingRTPTPCall(this, c); c->StartCall(0, 0, user, this); } } void SignalingRTPTP::State(bool up) { signaling->RTPTPState(this, up); if (user) user->RTPTPState(up); } void SignalingRTPTP::TimerOnTimeout(ITimer * timer) { } void SignalingRTPTP::Send(void * buf, size_t len) { } /*-----------------------------------------------------------------------------------------------*/ SignalingRTPTPCall::SignalingRTPTPCall(class SignalingRTPTP * rtptp, class SignalingCall * call) : SignalingCallMonitor(call) { debug->printf("%x:SignalingRTPTPCall()", this); this->rtptp = rtptp; } SignalingRTPTPCall::~SignalingRTPTPCall() { debug->printf("%x:~SignalingRTPTPCall()", this); } void SignalingRTPTPCall::MonitorXmit(const char * type, class json_io & msg, word base) { debug->printf("%x:SignalingRTPTPCall::MonitorXmit(%s)", this, type); if (!strcmp(type, "rel")) { rtptp->State(false); } } void SignalingRTPTPCall::MonitorRecv(const char * type, class json_io & msg, word base) { debug->printf("%x:SignalingRTPTPCall::MonitorRecv(%s)", this, type); if (!strcmp(type, "conn")) { rtptp->State(true); } else if (!strcmp(type, "rel")) { rtptp->State(false); } } void SignalingRTPTPCall::MonitorSync() { debug->printf("%x:SignalingRTPTPCall::MonitorSync()", this); } /*-----------------------------------------------------------------------------------------------*/ void ConferenceInfo::zeroize() { reason = 0; memset(&participant, 0, sizeof(participant)); } void ConferenceInfo::cleanup() { if (reason) free(reason); if (participant.name) free(participant.name); if (participant.inRoomState) free(participant.inRoomState); if (participant.activeSpeaker) free(participant.activeSpeaker); if (participant.muted) free(participant.muted); if (participant.mutedByMod) free(participant.mutedByMod); if (participant.mutedByUser) free(participant.mutedByUser); if (participant.videoOffByMod) free(participant.videoOffByMod); if (participant.videoOffByUser) free(participant.videoOffByUser); if (participant.appSharing) free(participant.appSharing); if (participant.hand) free(participant.hand); zeroize(); } void ConferenceInfo::read(class json_io& msg, word base) { cleanup(); this->reason = _strdup(msg.get_string(base, "reason")); word participant = msg.get_object(base, "participant"); if (participant != 0xffff) { this->participant.id = msg.get_unsigned(participant, "id"); this->participant.name = _strdup(msg.get_string(participant, "name")); this->participant.inRoomState = _strdup(msg.get_string(participant, "inRoomState")); // get boolean values as strings ("true" or "false" or null) this->participant.activeSpeaker = _strdup(msg.get_value(participant, 0, "activeSpeaker")); this->participant.muted = _strdup(msg.get_value(participant, 0, "muted")); this->participant.mutedByMod = _strdup(msg.get_value(participant, 0, "mutedByMod")); this->participant.mutedByUser = _strdup(msg.get_value(participant, 0, "mutedByUser")); this->participant.videoOffByMod = _strdup(msg.get_value(participant, 0, "videoOffByMod")); this->participant.videoOffByUser = _strdup(msg.get_value(participant, 0, "videoOffByUser")); this->participant.appSharing = _strdup(msg.get_value(participant, 0, "appSharing")); this->participant.hand = _strdup(msg.get_value(participant, 0, "hand")); word remote = msg.get_object(participant, "remote"); if (remote != 0xffff) { this->participant.remote.flags = _strdup(msg.get_string(remote, "flags")); this->participant.remote.num = _strdup(msg.get_string(remote, "num")); this->participant.remote.sip = _strdup(msg.get_string(remote, "sip")); } word videoChannels = msg.get_array(participant, "videoChannels"); if (videoChannels != 0xffff) { word ch = msg.get_object(videoChannels, 0); word max = sizeof(this->participant.videoChannels) / sizeof(this->participant.videoChannels[0]); for (word idx = 0; idx < max; idx++) { if (ch != 0xffff) { this->participant.videoChannels[idx].id = msg.get_unsigned(ch, "id"); this->participant.videoChannels[idx].bitrate = msg.get_unsigned(ch, "bitrate"); ch = msg.get_object(videoChannels, ch); } } } } } void ConferenceInfo::write(class json_io& msg, word base, char*& tmp) { msg.add_string(base, "reason", this->reason); word participant = msg.add_object(base, "participant"); msg.add_unsigned(participant, "id", this->participant.id, tmp); msg.add_string(participant, "name", this->participant.name); msg.add_string(participant, "inRoomState", this->participant.inRoomState); // write boolean values as unquoted strings (true or false) msg.add(JSON_TYPE_VALUE, 0, participant, "activeSpeaker", this->participant.activeSpeaker); msg.add(JSON_TYPE_VALUE, 0, participant, "muted", this->participant.muted); msg.add(JSON_TYPE_VALUE, 0, participant, "mutedByMod", this->participant.mutedByMod); msg.add(JSON_TYPE_VALUE, 0, participant, "mutedByUser", this->participant.mutedByUser); msg.add(JSON_TYPE_VALUE, 0, participant, "videoOffByMod", this->participant.videoOffByMod); msg.add(JSON_TYPE_VALUE, 0, participant, "videoOffByUser", this->participant.videoOffByUser); msg.add(JSON_TYPE_VALUE, 0, participant, "appSharing", this->participant.appSharing); msg.add(JSON_TYPE_VALUE, 0, participant, "hand", this->participant.hand); if (this->participant.remote.num || this->participant.remote.sip) { word remote = msg.add_object(participant, "remote"); msg.add_string(remote, "flags", this->participant.remote.flags); msg.add_string(remote, "num", this->participant.remote.num); msg.add_string(remote, "sip", this->participant.remote.sip); } if (this->participant.videoChannels[0].id) { word videoChannels = msg.add_array(participant, "videoChannels"); word max = sizeof(this->participant.videoChannels) / sizeof(this->participant.videoChannels[0]); for (word idx = 0; idx < max; idx++) { if (this->participant.videoChannels[idx].id) { word ch = msg.add_object(videoChannels, 0); msg.add_unsigned(ch, "id", this->participant.videoChannels[idx].id, tmp); msg.add_unsigned(ch, "bitrate", this->participant.videoChannels[idx].bitrate, tmp); } } } } /*-----------------------------------------------------------------------------------------------*/ void ChatMessage::zeroize() { type = sender = sender_dn = data = guid = mime = 0; time = 0; } void ChatMessage::cleanup() { if (type) free(type); if (sender) free(sender); if (sender_dn) free(sender_dn); if (data) free(data); if (guid) free(guid); if (mime) free(mime); zeroize(); } void ChatMessage::read(class json_io& msg, word base) { /* { "type": "im_message", "sender": "endeavour", "sender_dn": "Endeavour", "data": "abcdefg", "guid": "8256ff9f332f620133c30090334113d8", "time": 1647260575, "mime": "text/html" } */ cleanup(); type = _strdup(msg.get_string(base, "type")); sender = _strdup(msg.get_string(base, "sender")); sender_dn = _strdup(msg.get_string(base, "sender_dn")); data = _strdup(msg.get_string(base, "data")); guid = _strdup(msg.get_string(base, "guid")); time = msg.get_unsigned(base, "time"); mime = _strdup(msg.get_string(base, "mime")); } void ChatMessage::write(class json_io& msg, word base, char*& tmp) { msg.add_string(base, "type", type); msg.add_string(base, "sender", sender); msg.add_string(base, "sender_dn", sender_dn); msg.add_string(base, "data", data); msg.add_string(base, "guid", guid); msg.add_unsigned(base, "time", time, tmp); msg.add_string(base, "mime", mime); } /*---------------------------------------------------------------------------*/ /* SignalingRccSessionHttpClient */ /*---------------------------------------------------------------------------*/ SignalingRccSessionHttpClient::SignalingRccSessionHttpClient(class Signaling * signaling, class SignalingRccSession * rccSession, const char * url) : STaskContext(signaling->GetIoMux()) { debug->printf("SignalingRccSessionHttpClient::SignalingRccSessionHttpClient"); this->signaling = signaling; this->rccSession = rccSession; this->recvBuffer = nullptr; this->httpOk = false; this->fileOk = false; this->closing = false; this->httpClient = nullptr; this->url = _strdup(url); this->fWrite = new TaskFileWrite(this, this->signaling->GetWorkingPath(), "webaccess.entrance.mp3", false); this->fWrite->Start(this); } SignalingRccSessionHttpClient::~SignalingRccSessionHttpClient() { ASSERT(this->httpClient == nullptr, "SignalingRccSessionHttpClient::~SignalingRccSessionHttpClient httpClient!=nullptr"); ASSERT(this->fWrite == nullptr, "SignalingRccSessionHttpClient::~SignalingRccSessionHttpClient fWrite!=nullptr"); free(this->recvBuffer); free(this->url); } void SignalingRccSessionHttpClient::HTTPClientConnectComplete(IHTTPClient* const httpClient) { debug->printf("SignalingRccSessionHttpClient::HTTPClientConnectComplete"); uri_dissector uri; size_t len = strlen(this->url); char* resourceName = (char*)alloca(len); *resourceName = '\0'; if (uri.dissect_uri((char*)this->url)) { _snprintf(resourceName, len, "%.*s%.*s%s%.*s", (dword)(uri.e_path - uri.path), uri.path, (dword)(uri.e_file - uri.file), uri.file, uri.query ? "?" : "", (dword)(uri.e_query - uri.query), uri.query); } if (!*resourceName) { this->TryClose(); return; } debug->printf("SignalingRccSessionHttpClient::HTTPClientConnectComplete resourceName:%s", resourceName); this->recvBuffer = (byte*)malloc(sizeof(byte) * 65536); httpClient->SetRequestType(HTTP_GET, resourceName); httpClient->Send(); } void SignalingRccSessionHttpClient::HTTPClientShutdown(IHTTPClient* const httpClient, http_shutdown_reason_t reason) { debug->printf("SignalingRccSessionHttpClient::HTTPClientShutdown reason:%d", reason); delete httpClient; this->httpClient = nullptr; TryClose(); } void SignalingRccSessionHttpClient::HTTPClientSendResult(IHTTPClient* const httpClient) { } void SignalingRccSessionHttpClient::HTTPClientRecvResult(IHTTPClient* const httpClient, byte* buffer, size_t len, bool transferComplete) { debug->printf("SignalingRccSessionHttpClient::HTTPClientRecvResult len:%d transferComplete:%d", len, transferComplete); fWrite->write(buffer, len, transferComplete); if (transferComplete) { httpOk = true; } } void SignalingRccSessionHttpClient::HTTPClientRecvCanceled(IHTTPClient* const httpClient, byte* buffer) { debug->printf("SignalingRccSessionHttpClient::HTTPClientRecvCanceled"); // freed in destructor } void SignalingRccSessionHttpClient::HTTPClientRequestComplete(IHTTPClient* const httpClient) { debug->printf("SignalingRccSessionHttpClient::HTTPClientRequestComplete"); httpClient->Recv(this->recvBuffer, sizeof(byte) * 65536); } void SignalingRccSessionHttpClient::TaskComplete(class ITask* const task) { debug->printf("SignalingRccSessionHttpClient::TaskComplete"); fileOk = true; delete task; this->fWrite = nullptr; TryClose(); } void SignalingRccSessionHttpClient::TaskFailed(class ITask* const task) { debug->printf("SignalingRccSessionHttpClient::TaskFailed"); delete task; this->fWrite = nullptr; TryClose(); } void SignalingRccSessionHttpClient::TaskProgress(class ITask* const task, dword progress) { debug->printf("SignalingRccSessionHttpClient::TaskProgress closing:%d this->httpClient:%p", closing, this->httpClient); if (this->closing) { TryClose(); } else { if (this->httpClient == nullptr) { // there is a first TaskProgress, so we establish the HTTP client connection now this->httpClient = IHTTPClient::Create(this->signaling->GetIoMux(), this->signaling->GetTcpSocketProvider(), this->signaling->GetTlsSocketProvider(), this, this->signaling); this->httpClient->Connect(this->url); } else { this->httpClient->Recv(this->recvBuffer, sizeof(byte) * 65536); } } } void SignalingRccSessionHttpClient::TryClose() { debug->printf("SignalingRccSessionHttpClient::TryClose closing:%d httpClient:%p fWrite:%p", closing, httpClient, fWrite); if (!this->closing) { this->closing = true; if (this->httpClient) { this->httpClient->Shutdown(); } if (this->fWrite) { // fWrite doesn't support Stop :( this->fWrite->write(nullptr, 0, true); //this->fWrite->Stop(); } } if (this->httpClient) { return; } if (this->fWrite) { return; } this->rccSession->SignalingRccSessionHttpClientClosed(this); } void SignalingRccSessionHttpClient::Close() { this->TryClose(); } bool SignalingRccSessionHttpClient::Success() { return this->httpOk && this->fileOk; } /*-----------------------------------------------------------------------------------------------*/ static const char* CoderName(int type) { switch (type) { // audio codecs case G711_A: return "G711A"; case G711_U: return "G711u"; case G723_53: return "G723-53"; case G723_63: return "G723-63"; case G729: return "G729"; case G729A: return "G729A"; case G729B: return "G729B"; case G729AB: return "G729AB"; case G726_40: return "G726-40"; case G726_32: return "G726-32"; case G726_24: return "G726-24"; case G726_16: return "G726-16"; case GSM: return "GSM"; case DVI4: return "DVI4"; case LPC: return "LPC"; case L16: return "L16"; case ILBC: return "ILBC"; case SPEEX: return "SPEEX"; case DTMF: return "DTMF"; case CN: return "CN"; case RED: return "RED"; case G722: return "G722"; case G7221: return "G7221"; case CLEARCHANNEL: return "XPARENT"; case OPUS_NB: return "OPUS-NB"; case OPUS_WB: return "OPUS-WB"; case AMR_WB: return "AMR-WB"; // video codecs case H264: return "H264"; case VP8: return "VP8"; case VP9: return "VP9"; case AV1: return "AV1"; case JRFB: return "JRFB"; } return 0; } static int CoderPt(int type) { switch (type) { // audio codecs case G711_A: return DEFAULT_PAYLOAD_TYPE_G711_A; case G711_U: return DEFAULT_PAYLOAD_TYPE_G711_U; case G722: return DEFAULT_PAYLOAD_TYPE_G722; case G729: return DEFAULT_PAYLOAD_TYPE_G729; case G729A: return DEFAULT_PAYLOAD_TYPE_G729; case G729B: return DEFAULT_PAYLOAD_TYPE_G729; case G729AB: return DEFAULT_PAYLOAD_TYPE_G729; case OPUS_NB: return DEFAULT_PAYLOAD_TYPE_OPUS; case OPUS_WB: return DEFAULT_PAYLOAD_TYPE_OPUS; case DTMF: return DEFAULT_PAYLOAD_TYPE_DTMF; case CLEARCHANNEL: return 97; // video codecs case H264: return DEFAULT_PAYLOAD_TYPE_H264; case VP8: return DEFAULT_PAYLOAD_TYPE_VP8; case VP9: return DEFAULT_PAYLOAD_TYPE_VP9; case AV1: return DEFAULT_PAYLOAD_TYPE_AV1; } return 0; } static int CoderType(const char * name) { if (name) { // audio codecs if (!str::casecmp(name, "G711A")) return G711_A; if (!str::casecmp(name, "G711u")) return G711_U; if (!str::casecmp(name, "G722")) return G722; if (!str::casecmp(name, "G729")) return G729; if (!str::casecmp(name, "G729A")) return G729A; if (!str::casecmp(name, "G729B")) return G729B; if (!str::casecmp(name, "G729AB")) return G729AB; if (!str::casecmp(name, "OPUS-NB")) return OPUS_NB; if (!str::casecmp(name, "OPUS-WB")) return OPUS_WB; if (!str::casecmp(name, "XPARENT")) return CLEARCHANNEL; // video codecs if (!str::casecmp(name, "H264")) return H264; if (!str::casecmp(name, "VP8")) return VP8; if (!str::casecmp(name, "VP9")) return VP9; if (!str::casecmp(name, "AV1")) return AV1; } return CODER_UNDEFINED; } static const char * candidate_name[] = { "HOST", "SRFLX", "PRFLX", "RELAY" }; static const char * CandidateName(int type) { return type <= 3 ? candidate_name[type] : 0; } static int CandidateType(const char * name) { int type = 0; if (name) for (type = 0; type <= 3 && strcmp(name, candidate_name[type]); type++); return type <= 3 ? type : 0; } static const char * channels_cmd[] = { 0, "SELECT", "PROPOSAL", "REQUEST", "CLOSE", "INFO" }; static const char * ChannelsCmdName(unsigned type) { return type && type <= 5 ? channels_cmd[type] : 0; } static int ChannelsCmdType(const char * name) { int type = 0; if (name) for (type = 1; type <= 5 && strcmp(name, channels_cmd[type]); type++); return type <= 5 ? type : 0; }