SafeDispatch/SipComponent/SipClientClass2.cs

2171 lines
90 KiB
C#
Raw Normal View History

2024-02-22 16:43:59 +00:00
using Independentsoft.Sip;
using Independentsoft.Sip.Methods;
using Independentsoft.Sip.Responses;
using Independentsoft.Sip.Sdp;
using SocketIOComponent;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SipComponent
{
/// <summary>
/// Class used to make SD talk to Linx using Adi's protocol, version 2
/// <para>Accepts more than one sip call at a time</para>
/// <para>Does not use socket.IO requests for calls</para>
/// </summary>
public class SipClientClass2
{
#region Private Fields
private SipClient _sipClient;
private int _registrationInterval = 100; // 100s
private RegistrationData _registrationData;
private int _minPortNb = 25284;
private int _maxPortNb = 25300;
private int _audioBitRate = 16;
private int _maxNbOfDialogs = 3;
private int _secondsToWaitForSmsConfirmation = 3;
private int _sipDomainPort;
/// <summary>
/// Nr de secunde cat este mentinut call-ul cand nu se transmite voce
/// </summary>
private int _hangTimeDuration = -1;
private int _bufferMiliseconds;
private string _localIPaddress;
private Random _rand = new Random();
private bool _sipClassClosed = false;
/// <summary>
/// Gets or sets a value indicating if the Asterisk server confirms sms
/// <para>Default value is false</para>
/// </summary>
protected bool _smsConfirmationFromServer = false;
private Dictionary<string, Invite> _IDsentInviteDict = new Dictionary<string, Invite>();
private Dictionary<string, Request> _IDreceivedInviteDict = new Dictionary<string, Request>();
private Dictionary<string, Tuple<UdpClient, RTPSender2, RTPListener2, TimerWithTag<string>>> _IDdialogTuple = new Dictionary<string, Tuple<UdpClient, RTPSender2, RTPListener2, TimerWithTag<string>>>();
private Dictionary<string, bool?> _smsSentDict = new Dictionary<string, bool?>();
private Dictionary<string, Tuple<System.Timers.Timer, RegistrationStatus>> _sipID_regTimer_regStatus_Dict =
new Dictionary<string, Tuple<System.Timers.Timer, RegistrationStatus>>();
private Dictionary<string, Tuple<byte[], TimerWithTag<string>>> _ID_pingBytest_timer_dict = new Dictionary<string, Tuple<byte[], TimerWithTag<string>>>();
private Dictionary<string, string> _simocoID_emergencyStatusReport_dict = new Dictionary<string, string>();
private object _lockerSmsSet = new object();
private List<string> _IDsCalledByMeList = new List<string>();
private List<string> _IDsCallingMeList = new List<string>();
private List<string> _IDsInDialogWithList = new List<string>();
private List<string> _IDsregisteredList = new List<string>();
private static object _lockerRtpPort = new object();
private object _lockerSipDialog = new object();
private object _lockerCallingMe = new object();
#region Protection against multiple sip messages sent (sometimes) by Asterisk
string _previousEmergencySeqID = "";
string _previousLocationSeqID = "";
string _previousArsSeqID = "";
string _previousSmsSeqID = "";
#endregion
SocketIOClass _socketIOClass = null;
bool _sendArsOnOff = true;
HashSet<string> _linxGroupSipIDsInDialogWith = new HashSet<string>();
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the minimum port number in the port range used for RTP voice transmission
/// </summary>
public int MinRtpPortNumber
{
get { return _minPortNb; }
set
{
if (ValidRtpPort(value))
{
_minPortNb = value;
}
else
throw new SipClassException(
string.Format("Port {0} is not a valid RTP port", value));
}
}
/// <summary>
/// Gets or sets the maximum port number in the port range used for RTP voice transmission
/// </summary>
public int MaxRtpPortNumber
{
get { return _maxPortNb; }
set
{
if (ValidRtpPort(value))
{
_maxPortNb = value;
}
else
throw new SipClassException(
string.Format("Port {0} is not a valid RTP port", value));
}
}
/// <summary>
/// Gets the sip ID
/// </summary>
public string UserName
{
get { return _sipClient.Username; }
}
/// <summary>
/// <para>Gets or sets the maximum number of simultaneous dialogs that the SipClientClass accepts</para>
/// <para>The default is 3</para>
/// </summary>
public int MaxNbOfDialogs
{
get { return _maxNbOfDialogs; }
set
{
_maxNbOfDialogs = value;
}
}
/// <summary>
/// <para>Gets or sets the number of seconds the call is maintained when no voice is transmited</para>
/// <para>Value is in seconds</para>
/// <para>Default value is -1, call is maintained indefinitely</para>
/// </summary>
public int HangTimeDuration
{
get { return _hangTimeDuration; }
set
{
if (value > 0)
_hangTimeDuration = value;
}
}
/// <summary>
/// <para>Gets or sets a value indicating the nb of seconds to wait</para>
/// <para>for a sms confirmation from sip server</para>
/// <para>Default value is 3</para>
/// </summary>
public int SecondsToWaitForSmsConfirmation
{
get { return _secondsToWaitForSmsConfirmation; }
set
{
if (value > 0 && value < 10)
_secondsToWaitForSmsConfirmation = value;
}
}
/// <summary>
/// Audio BitRate, default value is 16
/// </summary>
public int AudioBitrate
{
get { return _audioBitRate; }
set { _audioBitRate = value; }
}
/// <summary>
/// Gets a readonly collection of sip ids that you sent Invite for
/// </summary>
public ReadOnlyCollection<string> SipIDsCalledByMe
{
get { return new ReadOnlyCollection<string>(_IDsCalledByMeList); }
}
/// <summary>
/// Gets a readonly collection of sip ids that sent you Invite's
/// </summary>
public ReadOnlyCollection<string> SipIDsCallingMe
{
get { return new ReadOnlyCollection<string>(_IDsCallingMeList); }
}
/// <summary>
/// Gets a readonly collection of sip ids that you are in dialog with
/// </summary>
public ReadOnlyCollection<string> SipIDsInDialogWith
{
get { return new ReadOnlyCollection<string>(_IDsInDialogWithList); }
}
/// <summary>
/// Gets a readonly collection of sip ids that you are registered to
/// <para>This means your sip id and the group ids that you are listening to</para>
/// </summary>
public ReadOnlyCollection<string> SipIDsRegisteredTo
{
get
{
return new ReadOnlyCollection<string>(_IDsregisteredList);
}
}
/// <summary>
/// Gets or sets a value indicating if the Asterisk sends sms confirmations on delivery
/// <para>Default value is false</para>
/// </summary>
public bool SmsConfirmationFromAsterisk
{
get { return _smsConfirmationFromServer; }
set { _smsConfirmationFromServer = true; }
}
#endregion
#region Constructor
// constructor
/// <summary>
/// Constructor for the SipClientClass
/// </summary>
/// <param name="sipDomain">domain name, name or IP address of sip server</param>
/// <param name="sipDomainPort">port number of the sip server</param>
/// <param name="localSipPort">port number of the local computer used for sip protocol</param>
/// <param name="userName">user name on the sip server</param>
/// <param name="password">password on the sip server</param>
/// <param name="registrationInterval">interval to send sip registration requests. Value is in seconds</param>
/// <param name="bufferMiliseconds">Miliseconds for the buffer that stores the received voice packets</param>
/// <param name="requestTimeout">Number of ms to wait before the sip request times out</param>
/// <param name="socketIOport">Port number use for socket.IO</param>
/// <param name="sendArsOnOff">True to send ars on on creation and off when calling Stop()</param>
/// <param name="localIPAddress">Local Ip adress. If not specified, the class will search for a local ip on the same network with the sip server ip</param>
public SipClientClass2(string sipDomain, int sipDomainPort, int localSipPort, string userName, string password, int registrationInterval, int bufferMiliseconds,
int requestTimeout, int socketIOport, bool sendArsOnOff, string localIPAddress = null)
{
if (localIPAddress == null)
{
_localIPaddress = BestLocalEndPoint(new IPEndPoint(IPAddress.Parse(sipDomain), sipDomainPort)).Address.ToString();
#if DEBUG
Console.WriteLine("Local IP selected: " + _localIPaddress);
#endif
}
else
_localIPaddress = localIPAddress;
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
_sipDomainPort = sipDomainPort;
_sipClient = CreateSipClientClass(sipDomain, _sipDomainPort, _localIPaddress, localSipPort, userName, password, requestTimeout);
_bufferMiliseconds = bufferMiliseconds;
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
// Registration Timer
_registrationInterval = registrationInterval;
_registrationData = new RegistrationData(userName, _registrationInterval + 2);
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
System.Timers.Timer registrationTimer = new System.Timers.Timer();
// Set up the registration timer
registrationTimer.Interval = _registrationInterval * 1000;
registrationTimer.Elapsed += _registrationTimer_Elapsed;
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
_sipID_regTimer_regStatus_Dict.Add(userName,
new Tuple<System.Timers.Timer, RegistrationStatus>(registrationTimer, RegistrationStatus.RegistrationNotStarted
));
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
_IDsregisteredList.Add(userName);
StartRegistrationTimer();
// Socket IO
this._socketIOClass = new SocketIOClass($"ws://{sipDomain}:{socketIOport}");
this._socketIOClass.SmsReceived += _socketIOClass_SmsReceived;
this._socketIOClass.SmsAckReceived += _socketIOClass_SmsAckReceived;
// Send ARS ON after 1 second since the socketIOClass instantiation
_sendArsOnOff = sendArsOnOff;
if (_sendArsOnOff)
Task.Delay(1000).ContinueWith(t => { this._socketIOClass.SendArs(new ArsInfo(true, "0", userName)); });
}
#endregion
#region Public Methods
/// <summary>
/// Sends a Sip Invite command to an user
/// </summary>
/// <param name="idToInvite">The sip id of the user</param>
public void Invite(string idToInvite)
{
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
if (idToInvite == null)
throw new ArgumentNullException("idToInvite");
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
lock (_lockerSipDialog)
{
if (!_IDsentInviteDict.ContainsKey(idToInvite) && !_IDreceivedInviteDict.ContainsKey(idToInvite)
&& !_IDdialogTuple.ContainsKey(idToInvite))
{
SendSipInvite(idToInvite, false);
}
}
}
/// <summary>
/// Sends a Sip Invite command to a Simoco Group
/// </summary>
/// <param name="groupIDtoInvite">The sip id of the group</param>
public void InviteGroup(string groupIDtoInvite)
{
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
if (groupIDtoInvite == null)
throw new ArgumentNullException("groupIDtoInvite");
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
// Check if registered to the group id
lock (_lockerSipDialog)
{
if (!_linxGroupSipIDsInDialogWith.Contains(groupIDtoInvite))
SendSipInvite(groupIDtoInvite, true);
else
OnError(new ErrorEventArgs(groupIDtoInvite, UserName, $"Cannot call group {groupIDtoInvite} while I receive voice from it"));
}
}
/// <summary>
/// Cancels an Invite sent to an user
/// </summary>
/// <param name="idToCancel">The sip id of the user</param>
public void CancelInvite(string idToCancel)
{
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
Task t = null;
CancelInvite_private(idToCancel, out t);
}
private void CancelInvite_private(string idToCancel, out Task sendingCancelTask)
{
sendingCancelTask = null;
lock (_lockerSipDialog)
{
if (_IDsentInviteDict.ContainsKey(idToCancel))
{
sendingCancelTask = SendCancelRequest(idToCancel, sendingCancelTask);
}
}
// fire event
OnInviteSentCanceled(new SipEventArgs(idToCancel));
}
private Task SendCancelRequest(string idToCancel, Task sendingCancelTask)
{
// Send Cancel Request
if (_IDsentInviteDict.ContainsKey(idToCancel))
{
Dialog d = _sipClient.Dialogs.FirstOrDefault((dialog) =>
{
return dialog.CallID == _IDsentInviteDict[idToCancel].CallID;
});
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
if (d != null)
{
//Send Cancel Request
sendingCancelTask = Task.Factory.StartNew((dialogObj) =>
{
try
{
_sipClient.Cancel((Dialog)dialogObj);
}
catch (Independentsoft.Sip.TimeoutException)
{
;
// Don't to anything if timeout exception
// the receiver did not answer;
}
}, d);
}
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
// Remove from dict
_IDsentInviteDict.Remove(idToCancel);
_IDsCalledByMeList.Remove(idToCancel);
}
return sendingCancelTask;
}
/// <summary>
/// Accepts an Invite from an user
/// </summary>
/// <param name="callingSipID">The sip ID of the user</param>
public void AcceptInvite(string callingSipID)
{
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
bool fireEvent = false;
bool success = true;
bool sendBusy = true;
string failedMsg = "";
lock (_lockerCallingMe)
{
if (_IDreceivedInviteDict.ContainsKey(callingSipID))
{
fireEvent = true;
Request receivedInvite = _IDreceivedInviteDict[callingSipID];
// Answer the call
OK okResponseToInvite = new OK();
// TO DO - fara asta (adaugare header Contact) nu merge Simoco (Zoiper-ul e ok);
okResponseToInvite.Header[StandardHeader.Contact] = "<sip:" + _sipClient.Username + "@" + _sipClient.LocalIPEndPoint.ToString() + ";transport=udp>";
// Generate SDP
int port = ReturnAvailablePort();
okResponseToInvite.SessionDescription = CreateSDP(port);
// Get linxGroupID from received invite (if is a group invite)
string linxGroupID = null;
if (receivedInvite.Header.Contains("toGroupSipID"))
{
int linxGroupIDint;
if (int.TryParse(receivedInvite.Header["toGroupSipID"], out linxGroupIDint))
linxGroupID = linxGroupIDint.ToString();
}
// Try to create the dialog
try
{
CreatingDialog(callingSipID, receivedInvite.SessionDescription, port, false, linxGroupID);
// Trimit Ok
_sipClient.SendResponse(okResponseToInvite, receivedInvite);
}
catch (SipClassException)
{
// TO DO - verify this
// This is the strange case of "Already in dialog with....."
// generated by Asterisk sending multiple invite request
// I should ignore the isssue (not send busy, not fire event)
sendBusy = false;
fireEvent = false;
success = false;
}
catch (Exception ex)
{
// Nu am reusit sa creez dialogul
ClosingDialog(callingSipID, _sipClient.Username, out linxGroupID);
failedMsg = ex.Message;
success = false;
}
finally
{
// Remove from dictionary
_IDreceivedInviteDict.Remove(callingSipID);
_IDsCallingMeList.Remove(callingSipID);
}
if (!success)
{
if (sendBusy)
{
// Trimit Busy Here
Task.Factory.StartNew(() => { _sipClient.SendResponseBusyHere(receivedInvite); });
}
}
}
}
// Fire event
if (fireEvent)
{
if (!success)
OnError(new ErrorEventArgs(_sipClient.Username, callingSipID, failedMsg));
}
}
/// <summary>
/// Closes a sip dialog (voice session) with an user
/// </summary>
/// <param name="idToClose">The sip id of the user</param>
public bool Close(string idToClose)
{
bool dialogClosed = false;
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
if (idToClose != null)
{
Task t = null;
dialogClosed = Close_private(idToClose, out t, DialogClosedReason.NormalClosing);
}
else throw new ArgumentNullException("idToClose");
return dialogClosed;
}
private bool Close_private(string idToClose, out Task sendingByeTask, DialogClosedReason reason)
{
bool fireEvent = false;
sendingByeTask = null;
string sipGroupID = null;
lock (_lockerSipDialog)
{
Dialog dialogToClose = _sipClient.GetDialogWith(idToClose);
if (dialogToClose != null)
{
if (ClosingDialog(idToClose, _sipClient.Username, out sipGroupID))
{
fireEvent = true;
sendingByeTask = Task.Factory.StartNew((dialogToCloseObj) =>
{
try
{
_sipClient.Bye((Dialog)dialogToCloseObj);
}
catch (Exception ex)
{
// To do
// For now, do nothing
;
}
}, dialogToClose);
}
}
}
// FireEvent
if (fireEvent)
OnDialogClosed(new LinxDialogClosedEventArgs(
idToClose, _sipClient.Username, reason, sipGroupID));
return fireEvent;
}
/// <summary>
/// Declines a received Invite from an user
/// </summary>
/// <param name="idToDeclineCall">The sip ID of the user</param>
public void Decline(string idToDeclineCall)
{
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
if (idToDeclineCall != null)
{
Task t = null;
Decline_private(idToDeclineCall, out t);
}
else
throw new ArgumentNullException("idToDeclineCall");
}
private void Decline_private(string idToDeclineCall, out Task sendBusyTask)
{
bool fireEvent = false;
sendBusyTask = null;
lock (_lockerCallingMe)
{
if (_IDreceivedInviteDict.ContainsKey(idToDeclineCall))
{
fireEvent = true;
Request inviteReq = _IDreceivedInviteDict[idToDeclineCall];
// Remove from dictionary
_IDreceivedInviteDict.Remove(idToDeclineCall);
_IDsCallingMeList.Remove(idToDeclineCall);
// Send busy here
sendBusyTask = Task.Factory.StartNew(() =>
{
_sipClient.SendResponseBusyHere(inviteReq);
});
}
}
// Fire event
if (fireEvent)
OnInviteReceivedDeclined(new SipEventArgs(idToDeclineCall));
}
/// <summary>
/// Sends a voice buffer in a specified format to the user in dialog with
/// </summary>
/// <param name="idToSendVoice">The sip ID of the user</param>
/// <param name="audioBuffer">The audio buffer</param>
/// <param name="bufferLength">The length of the buffer</param>
/// <param name="format">The audio format of the buffer</param>
public virtual void SendAudio(string idToSendVoice, byte[] audioBuffer, int bufferLength, AudioFormat format)
{
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
lock (_lockerSipDialog)
{
if (_IDdialogTuple.ContainsKey(idToSendVoice))
{
_IDdialogTuple[idToSendVoice].Item2.SendAudio(audioBuffer, bufferLength, format);
}
}
}
/// <summary>
/// Method used to send Gps Request to a Linx device
/// </summary>
/// <param name="idToRequestGps">The sip id to send the gps request</param>
/// <param name="seqID">The sequence id</param>
protected void SendGpsRequest(string idToRequestGps, string seqID)
{
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
2024-06-20 16:03:35 +00:00
if (idToRequestGps == null)
throw new ArgumentNullException("idToRequestGps");
// Send Gps request for Linx
// [#msgLen]#seqID#154#
string cmdText = string.Format("#{0}#154#", seqID);
string cmd = AddMsgLenForMBus(cmdText);
Message pollRequestForLinx = GenerateSipMessage(idToRequestGps, UserName, _sipClient.Domain, _sipDomainPort, cmd);
pollRequestForLinx.Header.Add("Ais-Service", "mbus");
Task.Factory.StartNew(() =>
2024-02-22 16:43:59 +00:00
{
2024-06-20 16:03:35 +00:00
try
2024-02-22 16:43:59 +00:00
{
2024-06-20 16:03:35 +00:00
_sipClient.SendRequest(pollRequestForLinx);
}
catch (Exception)
{
; // Probably timeout exception, do not do anything
}
});
2024-02-22 16:43:59 +00:00
}
/// <summary>
/// Method used to acknowledge an emergency alarm sent by a Linx device
/// </summary>
/// <param name="linxID">The sip id of the Linx device</param>
protected void AcknowledgeLinxEmergencyAlarm(string linxID)
{
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
2024-06-20 16:03:35 +00:00
if (linxID == null)
throw new ArgumentNullException("linxID");
// Send [#msgLen]#seqID#238#
string textToSend = string.Format("#{0}#238#", _rand.Next().ToString());
string cmdToSend = AddMsgLenForMBus(textToSend);
Message sipMessage = GenerateSipMessage(linxID, UserName, _sipClient.Domain, _sipDomainPort, cmdToSend);
Task.Factory.StartNew(() =>
2024-02-22 16:43:59 +00:00
{
2024-06-20 16:03:35 +00:00
try
2024-02-22 16:43:59 +00:00
{
2024-06-20 16:03:35 +00:00
_sipClient.SendRequest(sipMessage);
}
catch (Exception)
{
// Do nothing
}
});
2024-02-22 16:43:59 +00:00
}
/// <summary>
/// Method used to send an sms to sip id using the sip protocol
/// <para>This method does not block the calling thread while waiting for the confirmation</para>
/// </summary>
/// <param name="idToSendSMS">The sip id where to send the sms</param>
/// <param name="text">The sms text</param>
/// <returns>True if the sms was received, else returns false</returns>
public async Task<bool> SendSmsAsync(string idToSendSMS, string text)
{
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
if (idToSendSMS != null && text != null)
{
bool sendSipMessage = true;
string unconfirmedSmsKey = null;
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
if (_smsConfirmationFromServer)
{
unconfirmedSmsKey = idToSendSMS + text.GetHashCode();
// Add smsKey to unconfirmed sms set
lock (_lockerSmsSet)
{
if (!_smsSentDict.ContainsKey(unconfirmedSmsKey))
{
_smsSentDict.Add(unconfirmedSmsKey, null);
}
else
{
// Already the same msg to the same id in the dict
// Will not send sip message, just wait for the later to be confirmed
sendSipMessage = false;
}
}
}
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
if (sendSipMessage)
{
// Create a new thread on which:
// 1. Send sip message
// 2. Wait for confirmation that the mess reach the server
// 3. Wait for confirmation that the mess reach the destination id
return await Task.Factory.StartNew(() =>
{
// Send Sms using sip library
try
{
//RequestResponse rr = _sipClient.SendMessage(
// string.Format("sip:{0}@{1}", _sipClient.Username, _sipClient.Domain),
// string.Format("sip:{0}@{1}", idToSendSMS, _sipClient.Domain),
// text);
Message sipMsg = GenerateSipMessage(idToSendSMS, _sipClient.Username, _sipClient.Domain,
_sipDomainPort, text);
RequestResponse rr = _sipClient.SendRequest(sipMsg);
// Message got to the sip server
if (rr.Response.Description == "OK" || rr.Response.Description == "Accepted")
{
if (unconfirmedSmsKey != null)
{
if (SmsConfirmedBySipServer(unconfirmedSmsKey))
return true;
else
return false;
}
else
return true;
}
else // Sip server did not accept the message
return false;
}
catch (Exception)
{
// Probably timeout exception, msg didn't reach the sip server
return false;
}
});
}
else
return await Task.Factory.StartNew<bool>(SmsConfirmedBySipServer, unconfirmedSmsKey);
}
else if (idToSendSMS == null)
throw new ArgumentNullException("idToSendSMS");
else
throw new ArgumentNullException("text");
}
2024-06-20 16:03:35 +00:00
private Message GenerateSipMessage(string destinationID, string senderID, string sipServer, int sipServerPort, string text)
2024-02-22 16:43:59 +00:00
{
2024-06-20 16:03:35 +00:00
Message sipMessage = new Message()
{
Uri = $"sip:{destinationID}@{sipServer}:{sipServerPort}",
From = new ContactInfo($"sip:{senderID}@{sipServer}"),
To = new ContactInfo($"sip:{destinationID}@{sipServer}"),
ContentType = "text/plain;charset=UTF-8",
Body = text
};
2024-02-22 16:43:59 +00:00
return sipMessage;
}
private bool SmsConfirmedBySipServer(object unconfirmedSmsKey)
{
bool smsConfirmed = true;
bool timeoutNotExpired = true;
string smsKey = (string)unconfirmedSmsKey;
lock (_lockerSmsSet)
{
while (_smsSentDict.ContainsKey(smsKey) && !_smsSentDict[smsKey].HasValue)
{
smsConfirmed = false;
timeoutNotExpired = System.Threading.Monitor.Wait(_lockerSmsSet, SecondsToWaitForSmsConfirmation * 1000);
if (!timeoutNotExpired)
{
// timer expired, no confiramtion received
// delete the message from set
_smsSentDict.Remove(smsKey);
break;
}
}
}
if (timeoutNotExpired) // check confirmation status
{
lock (_lockerSmsSet)
{
if (_smsSentDict.ContainsKey(smsKey) && _smsSentDict[smsKey].HasValue)
{
// Check confirmation status
smsConfirmed = _smsSentDict[smsKey].Value;
// Remove from dictionary
_smsSentDict.Remove(smsKey);
}
else
throw new ApplicationException("Error on working with unconfirmed sms dict");
}
}
return smsConfirmed;
}
/// <summary>
/// Sends an sms on socket IO
/// </summary>
/// <param name="seqID">Unique seqID of the sender. It should be in the format sipID.timestamp</param>
/// <param name="fromScID">SC ID of the sender</param>
/// <param name="destinationSipID">The sip ID of the destination</param>
/// <param name="destinationScID">SC ID of the destination</param>
/// <param name="text">The text</param>
/// <param name="isGroupSms">True if is group sms, else false</param>
public void SendSocketIOSms(string seqID, int fromScID, int destinationSipID, int destinationScID, string text, bool isGroupSms)
{
_socketIOClass.SendSms(new SmsInfo(seqID, UserName, fromScID.ToString(), destinationSipID.ToString(), destinationScID.ToString(), text, isGroupSms));
}
/// <summary>
/// Mehod used to check if you are in dialog with a sip id
/// </summary>
/// <param name="sipID">The sip id</param>
/// <returns>True if you are in dialog, else returns false</returns>
public bool InDialogWith(string sipID)
{
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
lock (_lockerSipDialog)
{
return _IDdialogTuple.ContainsKey(sipID);
}
}
/// <summary>
/// Stops registration by sending unregister request to the sip server
/// <para>Releases all the used resources</para>
/// <param name="async">If true, method returns before all the resources are released</param>
/// </summary>
public virtual void Stop(bool async = true)
{
if (!_sipClassClosed)
{
_sipClassClosed = true;
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
// Send ARS OFF and disconnect from socket IO
if (_sendArsOnOff)
_socketIOClass.SendArs(new ArsInfo(false, "0", UserName));
2024-06-20 16:03:35 +00:00
_socketIOClass.Disconect();
2024-02-22 16:43:59 +00:00
//
List<string> sipIDs = new List<string>();
Task task = null;
Task t = Task.Factory.StartNew(() =>
{
// 1. Reject all calls not answered yet
sipIDs.Clear();
lock (_lockerCallingMe)
{
foreach (string callingID in _IDreceivedInviteDict.Keys)
sipIDs.Add(callingID);
}
foreach (string sipID in sipIDs)
{
Decline_private(sipID, out task);
if (task != null)
task.Wait();
}
// 2. Cancel all sent calls
sipIDs.Clear();
lock (_lockerSipDialog)
{
foreach (string calledSipID in _IDsentInviteDict.Keys)
sipIDs.Add(calledSipID);
}
foreach (string sipID in sipIDs)
{
CancelInvite_private(sipID, out task);
if (task != null)
task.Wait();
}
// 3. Close all curent dialogs
sipIDs.Clear();
lock (_lockerSipDialog)
{
foreach (string idInDialog in _IDdialogTuple.Keys)
sipIDs.Add(idInDialog);
}
foreach (string sipID in sipIDs)
{
Close_private(sipID, out task, DialogClosedReason.NormalClosing);
if (task != null)
task.Wait();
}
// Stop registering to sip server
StopRegisterToSipServer();
// Unregister events
_sipClient.ReceiveRequest -= ProcessSipRequest;
_sipClient.ReceiveResponse -= ProcessSipResponse;
// Stop sip class
_sipClient.Disconnect();
});
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
if (!async)
t.Wait();
}
}
/// <summary>
/// Stops registration by sending unregister request to the sip server
/// </summary>
public void StopRegisterToSipServer()
{
// Stop registering to groups
List<string> idsToUnregisterFrom = new List<string>(_IDsregisteredList);
RegistrationData zeroRegistrationData = null;
foreach (string id in idsToUnregisterFrom)
{
if (id != _sipClient.Username)
zeroRegistrationData = new RegistrationData(id, 0, true);
else
zeroRegistrationData = new RegistrationData(id, 0);
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
_sipID_regTimer_regStatus_Dict[id].Item1.Stop();
SendSipRegister(zeroRegistrationData);
}
}
#endregion
#region RTP Listener Event Handlers
private void ReceivedVoice(object sender, AudioEventArgs e)
{
OnVoiceReceived((LinxAudioEventArgs)e);
}
void ExceptionThrown(object sender, EventArgs e)
{
// Close the Sip Session with Bye
RTPListener2 rtpListener = (RTPListener2)sender;
Task t = null;
Close_private(rtpListener.SipIDinDialogWith.ToString(), out t, DialogClosedReason.Error);
}
#endregion
#region Private Sip Functions
private void ProcessSipResponse(object sender, ResponseEventArgs e)
{
Response resp = e.Response;
string responserRadioID = resp.To.Address.Split(':', '@')[1];
switch (resp.StatusCode)
{
case 200: // OK response
if (resp.CSeq.Contains("INVITE")) // OK response for an Invite
{
bool success = false;
string errorMsg = "";
string linxGroupID = null;
// Gasesc Invite-ul pentru care am primit OK
lock (_lockerSipDialog)
{
if (_IDsentInviteDict.ContainsKey(responserRadioID))
{
Invite sentInvite = _IDsentInviteDict[responserRadioID];
// Get sipGroupID from the sent invite (if is a group invite)
if (sentInvite.Header.Contains("toGroupSipID"))
{
int linxGroupIDint;
if (int.TryParse(sentInvite.Header["toGroupSipID"], out linxGroupIDint))
linxGroupID = linxGroupIDint.ToString();
}
try
{
// Send sip Ack
_sipClient.Ack(resp);
// Creez Dialogul
CreatingDialog(responserRadioID,
resp.SessionDescription,
sentInvite.SessionDescription.Media[0].Port,
true, linxGroupID);
success = true;
}
catch (Exception ex)
{
// Nu am reusit sa setez dialogul
errorMsg = ex.Message;
// Fac DialogClosed();
if (ClosingDialog(responserRadioID, _sipClient.Username, out linxGroupID))
{
// Trimit Bye
Task.Factory.StartNew((respObj) =>
{
_sipClient.Bye((Response)respObj);
}, resp);
}
}
finally
{
// Remove from sent invites dictionary
_IDsentInviteDict.Remove(responserRadioID);
_IDsCalledByMeList.Remove(responserRadioID);
}
}
else // OK primit pentru un Invite anulat...
{
errorMsg = "Received OK for a cancelled Invite";
// Trimit Bye sau Cancel???
// Din standard se pare ca Bye
//Task.Factory.StartNew((respObj) =>
//{
// _sipClient.Bye((Response)respObj);
//}, resp);
// TO DO - to verify this
// That weird situation with Asterisk sending multiple requests / response
// Don't do anything, just return
break;
}
}
if (success)
OnDialogCreated(new LinxDialogCreatedEventArgs(responserRadioID, _sipClient.Username, TypeOfCall.FULL_DUPLEX, linxGroupID != null, linxGroupID));
else
OnError(new ErrorEventArgs(responserRadioID, _sipClient.Username, errorMsg));
}
break;
case 100: // Trying
break;
case 180: // Ringing
break;
case 401: // Unauthorized (temporarely)
break;
case 202: // Accepted
break;
default: // Probabil eroare
// trimit Ack
// I don't send ack because the sip.NET library does not send it to the correct udp port
// This happens only with Asterisk, for Linx project
// It use to work for simoco
//_sipClient.Ack(resp);
bool fireEvent = false;
lock (_lockerSipDialog)
{
if (_IDsentInviteDict.ContainsKey(responserRadioID))
{
_IDsentInviteDict.Remove(responserRadioID);
_IDsCalledByMeList.Remove(responserRadioID);
fireEvent = true;
}
}
if (fireEvent)
OnError(new ErrorEventArgs(responserRadioID, _sipClient.Username, resp.Description));
break;
}
}
private void ProcessSipRequest(object sender, RequestEventArgs e)
{
Request req = e.Request;
string requesterRadioId = req.From.Address.Split(':', '@')[1];
string requesterTargetId = req.To.Address.Split(':', '@')[1];
switch (req.Method)
{
case SipMethod.Invite:
bool fireEvent = false;
string idInDialogWith = requesterRadioId;
bool isEmergencyCall = false;
lock (_lockerCallingMe)
{
if (!_IDreceivedInviteDict.ContainsKey(requesterRadioId))
{
// Check if is emergency call (Simoco)
//if (req.Header.Contains(StandardHeader.Priority))
// if (req.Header[StandardHeader.Priority] == "emergency")
// isEmergencyCall = true;
if (SendResponseRinging(req, requesterRadioId))
{
// Sent response Ringing
_sipClient.SendResponseRinging(req);
// Add to dictionary
_IDreceivedInviteDict.Add(idInDialogWith, req);
_IDsCallingMeList.Add(idInDialogWith);
// fire event
fireEvent = true;
}
else
{
_sipClient.SendResponseBusyHere(req);
}
}
else
{
// Invite primit de doua ori de la acelasi Id, putin probabil
// Trimit Busy
_sipClient.SendResponseBusyHere(req);
}
}
if (fireEvent)
{
OnInviteReceived(new InviteReceivedArgs(idInDialogWith, requesterRadioId, TypeOfCall.FULL_DUPLEX, isEmergencyCall));
}
break;
case SipMethod.Bye:
// Accept the request
_sipClient.AcceptRequest(req);
string idToClose = requesterRadioId;
string linxGroupID = null;
if (ClosingDialog(idToClose, requesterRadioId, out linxGroupID))
{
// FireEvent;
OnDialogClosed(new LinxDialogClosedEventArgs(
idToClose, requesterRadioId, DialogClosedReason.NormalClosing, linxGroupID));
}
break;
case SipMethod.Cancel:
// Accept the request
_sipClient.AcceptRequest(req);
fireEvent = false;
// Check if Bye request is for an open dialog
lock (_lockerCallingMe)
{
if (_IDreceivedInviteDict.ContainsKey(requesterRadioId))
{
// remove from dictionary
_IDreceivedInviteDict.Remove(requesterRadioId);
_IDsCallingMeList.Remove(requesterRadioId);
// Fire event
fireEvent = true;
}
}
if (fireEvent)
OnInviteReceivedCanceled(new SipEventArgs(requesterRadioId));
break;
case SipMethod.Ack:
if (req.Header.Contains(StandardHeader.Contact))
{
// We received an Invite, sent Ok and now we receive ack
idInDialogWith = requesterRadioId;
// Dialog confirmed
if (_IDdialogTuple.ContainsKey(idInDialogWith))
{
linxGroupID = ((RTPListenerLinx)_IDdialogTuple[idInDialogWith].Item3).LinxGroupID;
OnDialogCreated(new LinxDialogCreatedEventArgs(
requesterTargetId,
requesterRadioId,
_IDdialogTuple[idInDialogWith].Item2.TypeOfCall,
linxGroupID != null,
linxGroupID));
}
}
break;
case SipMethod.Message:
// Accept the request
_sipClient.AcceptRequest(req);
ProcessReceivedSipMessage(req, requesterRadioId);
break;
case SipMethod.Info:
case SipMethod.Notify:
case SipMethod.Options:
case SipMethod.Prack:
case SipMethod.Publish:
case SipMethod.Refer:
case SipMethod.Register:
case SipMethod.Subscribe:
case SipMethod.Update:
default:
// Accept request
_sipClient.AcceptRequest(req);
break;
}
}
/// <summary>
/// Method used to decide if the app is ringing or is sending Busy response to sender
/// <para>Default behaviour is to ring (sends Ringing response)</para>
/// </summary>
/// <param name="receivedInvite">The received invite</param>
/// <param name="senderSipId">The sender of the invite</param>
/// <returns>True to send Ringing response, false to send Busy Here</returns>
protected virtual bool SendResponseRinging(Request receivedInvite, string senderSipId)
{
bool sendRinging = true;
// Check that the invite is proper formatted
if (receivedInvite.Header.ContainsKey("isPTT") && receivedInvite.Header.ContainsKey("toGroupSipID"))
{
if (receivedInvite.Header["isPTT"] == "false")
{
sendRinging = false;
OnError(new ErrorEventArgs(UserName, senderSipId, $"Will reject invite from {senderSipId} because it is a full duplex call"));
}
else
{
// Call is PTT
// Check if is group call
string linxGroupID = null;
if (string.IsNullOrEmpty(linxGroupID = receivedInvite.Header["toGroupSipID"]))
{
// PTT group call
// Check if I am allready in group call
lock (_lockerSipDialog)
{
if (_linxGroupSipIDsInDialogWith.Contains(linxGroupID))
{
sendRinging = false;
OnError(new ErrorEventArgs(UserName, senderSipId, $"Will reject group invite from {senderSipId} because I'm already in group call with {linxGroupID}"));
}
}
}
}
}
else
{
sendRinging = false;
OnError(new ErrorEventArgs(UserName, senderSipId, $"Will reject invite from {senderSipId} because it is not propely formated"));
}
return sendRinging;
}
/// <summary>
/// Function that processes all the received Sip Message requests
/// </summary>
/// <param name="sipMessageRequest">The Sip Message request</param>
/// <param name="senderID">The ID of the sender</param>
protected virtual void ProcessReceivedSipMessage(Request sipMessageRequest, string senderID)
{
// Handle messages when working with Asterisk server (set up by Andrei)
if (sipMessageRequest.ContentType == "text/plain;charset=UTF-8")
{
// SMS received
if (senderID == "Unknown")
{
// SMS confirmation from Sip server
if (_smsConfirmationFromServer)
{
// Get sms destination and message
string smsKey = GetSmsKeyFromConfirmation(sipMessageRequest.Body);
lock (_lockerSmsSet)
{
if (_smsSentDict.ContainsKey(smsKey))
{
// Set message as delivered or failed
_smsSentDict[smsKey] = GetDeliveredStatusFromConfirmation(sipMessageRequest.Body);
// Notify that we received a confirmation
Monitor.PulseAll(_lockerSmsSet);
}
}
}
}
else if (sipMessageRequest.Header.Contains("Ais-Service"))
{
if (sipMessageRequest.Header["Ais-Service"] == "mbus")
{
string messageBody = sipMessageRequest.Body;
if (messageBody != null)
{
string[] tmp = messageBody.Split('#');
if (tmp.Length >= 4 && messageBody.Length == Convert.ToInt32(tmp[1]))
{
switch (tmp[3])
{
case "138":
// Emergency from Linx
if (_previousEmergencySeqID != tmp[2]) // Protection against multiple message error
{
_previousEmergencySeqID = tmp[2];
Linx.LinxEmergencyType type = Linx.LinxEmergencyType.REGULAR;
if (tmp[5] != null)
{
int emergencyType;
if (int.TryParse(tmp[5], out emergencyType))
{
if (Enum.IsDefined(typeof(Linx.LinxEmergencyType), emergencyType))
{
type = (Linx.LinxEmergencyType)emergencyType;
}
}
}
OnLinxEmergencyAlarmReceived(new Linx.LinxEmergencyAlarmReceivedEventArgs(senderID, tmp[2], type));
}
break;
case "131": // Periadically gps from Linx
case "231": // Polled gps from Linx
int unixTime;
double speed, latitude, longitude;
if (tmp.Length >= 9)
{
// Protection against multiple message error
if (_previousLocationSeqID != tmp[2])
_previousLocationSeqID = tmp[2];
else
break;
if (double.TryParse(tmp[6], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out speed))
{
if (int.TryParse(tmp[5], out unixTime))
{
if (double.TryParse(tmp[7], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingWhite | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out latitude))
{
if (double.TryParse(tmp[8], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingWhite | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out longitude))
{
if (tmp[3] == "131")
{
// Periodically gps from Linx
OnGpsPeriodicallyReportReceived(new GpsDataEventArgs(
senderID, latitude, longitude, speed, UnixTimeStampToDateTime(unixTime)));
}
else if (tmp[3] == "231")
{
OnGpsReportReceived(new LinxGpsDataEventArgs(
senderID, tmp[2], latitude, longitude, speed, UnixTimeStampToDateTime(unixTime)));
}
}
}
}
}
}
break;
case "130": // Ars from Linx
if (tmp.Length >= 6)
{
if (_previousArsSeqID != tmp[2]) // Protection against multiple message error
{
_previousArsSeqID = tmp[2];
OnLinxArsReceived(new Linx.ArsReceivedEventArgs(senderID, tmp[2], tmp[5] == "ON" ? true : false));
}
}
break;
}
}
}
}
}
else
{
// Must be sms (:::seqID:::text:::)
string messageBody = sipMessageRequest.Body;
if (messageBody != null)
{
string[] tmp = messageBody.Split(new string[] { ":::" }, StringSplitOptions.None);
if (tmp.Length == 4)
{
if (_previousSmsSeqID != tmp[1])
{
_previousSmsSeqID = tmp[1];
OnSipSmsReceived(new SmsReceivedEventsArgs(senderID, tmp[2]));
}
}
}
}
}
}
#region Sms confirmation private helper functions
private string GetSmsKeyFromConfirmation(string messageBody)
{
string toReturn = "";
int destinationSipID;
string text;
if (int.TryParse(GetDestinationIDFromConfirmation(messageBody), out destinationSipID))
{
text = GetTextFromConfirmation(messageBody);
toReturn = destinationSipID.ToString() + text.GetHashCode();
}
return toReturn;
}
private string GetTextFromConfirmation(string source)
{
string start = "Your message - ";
string end = " - to ";
int startIndex, endIndex;
string strToReturn = "";
if (source.Contains(start) && source.Contains(end))
{
startIndex = source.IndexOf(start) + start.Length;
endIndex = source.LastIndexOf(end);
strToReturn = source.Substring(startIndex, endIndex - startIndex);
}
return strToReturn;
}
private string GetDestinationIDFromConfirmation(string source)
{
string start = " - to ";
string end = " has ";
int startIndex, endIndex;
string strToReturn = "";
if (source.Contains(start) && source.Contains(end))
{
startIndex = source.LastIndexOf(start) + start.Length;
endIndex = source.LastIndexOf(end);
strToReturn = source.Substring(startIndex, endIndex - startIndex);
}
return strToReturn;
}
private bool GetDeliveredStatusFromConfirmation(string source)
{
string lastWord = source.Substring(source.LastIndexOf(' ') + 1);
if (lastWord.Contains("delivered"))
return true;
2024-06-20 16:03:35 +00:00
if (lastWord.Contains("failed"))
2024-02-22 16:43:59 +00:00
return false;
2024-06-20 16:03:35 +00:00
throw new ApplicationException("Error on parsing the sms confirmation from sip server");
2024-02-22 16:43:59 +00:00
}
#endregion
private void CreatingDialog(string radioInDialogWith, SessionDescription receivedSDP, int localRTPport, bool initiatedByMe, string linxGroupID = null)
{
lock (_lockerSipDialog)
{
if (_IDdialogTuple.Count == MaxNbOfDialogs)
throw new ApplicationException("Exceeded the maximum number of simultaneous dialogs: " + MaxNbOfDialogs);
// Verific daca sunt deja in dialog cu userul
if (!_IDdialogTuple.ContainsKey(radioInDialogWith))
{
// Extract ip and port where to send voice to simoco
IPAddress ipToSendAudio = null;
int portToSendAudio;
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
if (IPAddress.TryParse(receivedSDP.Connection.Address, out ipToSendAudio))
{
portToSendAudio = receivedSDP.Media[0].Port;
}
2024-06-20 16:03:35 +00:00
else
throw new ApplicationException("Canot determine ip where to send audio");
2024-02-22 16:43:59 +00:00
// Creez clientul de UDP conectat la portul pe care voi primi voce
UdpClient udpClient = new UdpClient(localRTPport);
//UdpClient udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(_localIPaddress), localRTPport));
// Create RTPSender (trebuie sa fie gata sa trimita mesaje catre simoco)
RTPSender2 rtpSender = new RTPSender2(
udpClient,
AudioBitrate,
new IPEndPoint(ipToSendAudio, portToSendAudio),
int.Parse(_sipClient.Username),
int.Parse(radioInDialogWith),
initiatedByMe,
TypeOfCall.FULL_DUPLEX);
//
//_pttControlBlockingCollection = new BlockingCollection<PTTEventArgs>();
//Task.Factory.StartNew(() =>
//{
// ProcessPTTReceived();
//});
// Incep sa ascult RTP trimis de Simoco
RTPListener2 rtpListener = new RTPListenerLinx(udpClient, initiatedByMe, _bufferMiliseconds, int.Parse(radioInDialogWith), TypeOfCall.FULL_DUPLEX, linxGroupID);
//_rtpListener.PTTControlReceived += PTTControlReceived;
rtpListener.VoiceReceived += ReceivedVoice;
rtpListener.ExceptionThrown += ExceptionThrown;
rtpListener.Start();
// Timer hangtime
TimerWithTag<string> timerHangTime = null;
if (HangTimeDuration != -1)
{
timerHangTime = new TimerWithTag<string>(HangTimeDuration * 1000, radioInDialogWith);
timerHangTime.AutoReset = false;
timerHangTime.Elapsed += timerHangTime_Elapsed;
timerHangTime.Start();
}
Tuple<UdpClient, RTPSender2, RTPListener2, TimerWithTag<string>> sipDialogTuple = Tuple.Create(udpClient, rtpSender, rtpListener, timerHangTime);
// Sunt in dialog cu statia
// Add to dictionary
_IDdialogTuple.Add(radioInDialogWith, sipDialogTuple);
_IDsInDialogWithList.Add(radioInDialogWith);
// Add to list of group calls
if (linxGroupID != null)
_linxGroupSipIDsInDialogWith.Add(linxGroupID);
}
else
throw new SipClassException(string.Format("Already in dialog with {0}",
radioInDialogWith));
}
}
void timerHangTime_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// End call
TimerWithTag<string> timer = (TimerWithTag<string>)sender;
if (_IDdialogTuple.ContainsKey(timer.Tag))
{
Dialog dialogToClose = _sipClient.GetDialogWith(timer.Tag);
if (dialogToClose != null)
{
string sipIDidDialogWith = timer.Tag;
string linxGroupID = null;
if (ClosingDialog(sipIDidDialogWith, _sipClient.Username, out linxGroupID))
{
// Send Bye
Task.Factory.StartNew((dialogToCloseObj) =>
{
_sipClient.Bye((Dialog)dialogToCloseObj);
}, dialogToClose);
// Fire event
OnDialogClosed(new LinxDialogClosedEventArgs(
sipIDidDialogWith, _sipClient.Username, DialogClosedReason.TimerHangTimeExpired, linxGroupID));
}
}
}
}
private bool ClosingDialog(string idInDialogWith, string idWhoClosedTheDialog, out string linxGroupID)
{
linxGroupID = null;
lock (_lockerSipDialog)
{
if (_IDdialogTuple.ContainsKey(idInDialogWith))
{
Tuple<UdpClient, RTPSender2, RTPListener2, TimerWithTag<string>> dialogTuple = _IDdialogTuple[idInDialogWith];
// Opresc timerele (send ptt hearbeat, heartbeat query, etc) din sender
dialogTuple.Item2.Stop();
// Get the linxGroupID
linxGroupID = ((RTPListenerLinx)dialogTuple.Item3).LinxGroupID;
// Opresc citirea pachetelor rtp
dialogTuple.Item3.Stop();
// Unregister form the lister events
dialogTuple.Item3.VoiceReceived -= ReceivedVoice;
// Close the udp port
dialogTuple.Item1.Close();
// dispose pentru _rtpListener
dialogTuple.Item3.Dispose();
// Opresc timerul hangtime
if (dialogTuple.Item4 != null)
{
dialogTuple.Item4.Elapsed -= timerHangTime_Elapsed;
dialogTuple.Item4.Stop();
}
_IDdialogTuple.Remove(idInDialogWith);
_IDsInDialogWithList.Remove(idInDialogWith);
// remove from list of group dialogs
if (linxGroupID != null)
_linxGroupSipIDsInDialogWith.Remove(linxGroupID);
return true;
}
else
{
return false;
}
}
}
private SipClient CreateSipClientClass(string sipDomain, int sipDomainPort, string localIPaddress, int localSipPort, string userName, string password, int requestTimeout)
{
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
// Set up the master SIP class
2024-06-20 16:03:35 +00:00
SipClient sipClient = new SipClient(sipDomain, sipDomainPort, Independentsoft.Sip.ProtocolType.Udp, userName, password);
2024-02-22 16:43:59 +00:00
sipClient.LocalIPEndPoint = new IPEndPoint(IPAddress.Parse(localIPaddress), localSipPort);
// Turn on logging
//sipClient.Logger = new Logger(AppDomain.CurrentDomain.BaseDirectory + "\\sipLog.txt");
sipClient.ReceiveRequest += ProcessSipRequest;
sipClient.ReceiveResponse += ProcessSipResponse;
sipClient.Timeout = requestTimeout;
sipClient.Connect();
_sipClassClosed = false;
return sipClient;
}
private void SendSipInvite(string idToCall, bool isGroup)
{
lock (_lockerSipDialog)
{
if (!_IDsentInviteDict.ContainsKey(idToCall))
{
// Send invite to somewone
string sipServerIP = _sipClient.Domain;
string sipID = _sipClient.Username;
int rtpPort = ReturnAvailablePort();
SessionDescription sdp = CreateSDP(rtpPort);
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
Invite inv = new Invite();
inv.Uri = "sip:" + idToCall + "@" + sipServerIP;
inv.From = new ContactInfo("sip:" + sipID.ToString() + "@" + sipServerIP);
inv.To = new ContactInfo("sip:" + idToCall + "@" + sipServerIP);
inv.Contact = new Contact("sip:" + sipID.ToString() + "@" + _sipClient.LocalIPEndPoint.ToString());
inv.SessionDescription = sdp;
// Add Adi headers
inv.Header.Add("fromSipID", UserName);
inv.Header.Add("fromUserID", "");
inv.Header.Add("isPTT", "true");
if (isGroup)
{
inv.Header.Add("toGroupSipID", idToCall);
inv.Header.Add("toGroupID", "");
inv.Header.Add("toSipID", "");
inv.Header.Add("toUserID", "");
inv.Header.Add("callType", "group");
}
else
{
inv.Header.Add("toGroupSipID", "");
inv.Header.Add("toGroupID", "");
inv.Header.Add("toSipID", idToCall);
inv.Header.Add("toUserID", "");
inv.Header.Add("callType", "private");
}
//
// Add to dictionary
_IDsentInviteDict.Add(idToCall, inv);
_IDsCalledByMeList.Add(idToCall);
// Send invite request
Task.Factory.StartNew((idToCallObj) =>
{
string id = (string)idToCallObj;
OnInviteSent(new SipEventArgs(id));
try
{
_sipClient.SendRequest(inv);
}
catch (Exception tEx)
{
// Timout exception
// Cancel the invite
Task t = null;
SendCancelRequest(id, t);
// Fire event
OnError(new ErrorEventArgs(id, _sipClient.Username, tEx.Message));
}
}, idToCall);
}
}
}
private void SendSipRegister(object registrationDataObj)
{
string sipServerIP = _sipClient.Domain;
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
RegistrationData regData = (RegistrationData)registrationDataObj;
string sipIDfrom = _sipClient.Username;
2024-06-20 16:03:35 +00:00
string sipIDto = regData.SipID;
2024-02-22 16:43:59 +00:00
int expiresValue = regData.Expires;
bool isUnregisterRequest = (expiresValue == 0);
Register reg = new Register();
reg.Uri = "sip:" + sipServerIP;
2024-06-20 16:03:35 +00:00
reg.From = new ContactInfo( sipIDfrom.ToString(), $"sip:{sipIDfrom}@{sipServerIP}");
reg.To = new ContactInfo( sipIDto.ToString(), $"sip:{sipIDto}@{sipServerIP}");
2024-02-22 16:43:59 +00:00
reg.Contact = new Contact("sip:" + sipIDfrom.ToString() + "@" + _sipClient.LocalIPEndPoint.ToString());
reg.Expires = expiresValue;
RegistrationStatus regStatus = _sipID_regTimer_regStatus_Dict[sipIDto].Item2;
try
{
RequestResponse reqResp = _sipClient.SendRequest(reg);
if (reqResp.Response.StatusCode == 200) //OK
{
if (isUnregisterRequest)
{
regStatus = RegistrationStatus.UnregisteredAtRequest;
}
else
{
regStatus = RegistrationStatus.Registered;
}
}
else if (reqResp.Response.StatusCode == 403) // 403 = Forbidden
{
regStatus = RegistrationStatus.WrongCredentials;
}
else
{
regStatus = RegistrationStatus.NotRegistered;
// to do - delete this
Console.WriteLine($"Received response: {reqResp.Response.StatusCode} to sip registration request");
}
}
catch (SocketException socketEx)
{
regStatus = RegistrationStatus.SocketError;
}
catch (Independentsoft.Sip.TimeoutException)
{
regStatus = RegistrationStatus.RequestTimeout;
}
catch (Exception ex)
{
regStatus = RegistrationStatus.NotRegistered;
// to do - delete this
Console.WriteLine("Exception when sending registration to Sip server\n" + ex.ToString());
}
if (regStatus != _sipID_regTimer_regStatus_Dict[sipIDto].Item2)
{
if (regStatus != RegistrationStatus.UnregisteredAtRequest)
{
// Just change the registration status
System.Timers.Timer currentTimer = _sipID_regTimer_regStatus_Dict[sipIDto].Item1;
_sipID_regTimer_regStatus_Dict[sipIDto] = new Tuple<System.Timers.Timer, RegistrationStatus>(currentTimer, regStatus);
}
else
{
// Unregistered - Remove from dictionary
_sipID_regTimer_regStatus_Dict.Remove(sipIDto);
_IDsregisteredList.Remove(sipIDto);
}
//OnRegistrationStateChanged(sipIDto, regStatus);
OnRegistrationStateChanged(new RegistrationStateChangedEventArgs(sipIDto, regStatus));
}
}
private SessionDescription CreateSDP(int rtpPort)
{
SessionDescription sdpSession = new SessionDescription();
// Origin (Owner) - 0=<userName> <sessionId> <version> <network type> <address type>
// <sessionId> must be unique
int sessionID = _rand.Next();
// <version> - must be increased when a modification is made to the session data.
Owner owner = new Owner(_sipClient.Username, sessionID, 18299, _sipClient.LocalIPEndPoint.Address.ToString());
Connection connection = new Connection(_sipClient.LocalIPEndPoint.Address.ToString());
sdpSession.Owner = owner;
sdpSession.Name = "_";
sdpSession.Connection = connection;
sdpSession.Time.Add(new Time(0, 0));
// m=<media> <port> <transport> <fmt list>
// <port> - 1024 to 65535 for UDP
// - must be even for RTP
Media media1 = new Media("audio", rtpPort, "RTP/AVP");
media1.MediaFormats.Add("8");
media1.Attributes.Add("rtpmap", "8 PCMA/8000");
sdpSession.Media.Add(media1);
return sdpSession;
}
private void StartRegistrationTimer()
{
// Send instant registration request
Task.Factory.StartNew((state) =>
{
SendSipRegister(state);
}, _registrationData);
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
// Start registration timer
_sipID_regTimer_regStatus_Dict[UserName].Item1.Start();
}
private void _registrationTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
SendSipRegister(_registrationData);
}
#endregion
#region Private Helper Methods
private void _socketIOClass_SmsReceived(object sender, SmsReceivedEventArgs e)
{
SmsInfo smsInfo = e.SmsInfo;
#if DEBUG
Console.WriteLine($"Socket.IO sms received by sip {UserName}: {smsInfo}");
#endif
// Fire event
OnSocketIoSmsReceived(new SocketIOSmsEventArgs(smsInfo.SeqID, smsInfo.FromSipID, smsInfo.ToSipID, smsInfo.Message, smsInfo.IsGroup));
}
private void _socketIOClass_SmsAckReceived(object sender, SmsAckReceivedEventArgs e)
{
SmsAckInfo smsAckInfo = e.SmsAckInfo;
#if DEBUG
Console.WriteLine($"Socket.IO sms ack received by sip {UserName}: {smsAckInfo}");
#endif
// Fire event
OnSocketIoSmsAckReceived(new SocketIoSmsAckEventArgs(smsAckInfo.SeqID, smsAckInfo.AckBySipID.ToString()));
}
private int ReturnAvailablePort()
{
lock (_lockerRtpPort)
{
int rtpPort = MinRtpPortNumber;
while (
(IsPortAllreadyInUse(rtpPort) || PortNumerProposedInSentInvites(rtpPort))
&& rtpPort < MaxRtpPortNumber)
{
rtpPort += 2;
}
2024-06-20 16:03:35 +00:00
2024-02-22 16:43:59 +00:00
if (rtpPort < MaxRtpPortNumber)
return rtpPort;
2024-06-20 16:03:35 +00:00
throw new SipClassException(
2024-02-22 16:43:59 +00:00
string.Format("Nu gasesc port liber in range-ul {0} - {1}", MinRtpPortNumber, MaxRtpPortNumber));
}
}
private bool PortNumerProposedInSentInvites(int portNb)
{
Invite inv = _IDsentInviteDict.Values.FirstOrDefault((invite) =>
{
return invite.SessionDescription.Media[0].Port == portNb;
});
2024-06-20 16:03:35 +00:00
return (inv != null);
2024-02-22 16:43:59 +00:00
}
private bool IsPortAllreadyInUse(int portNumber)
{
return (from p in System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()
where p.Port == portNumber
select p).Count() == 1;
}
/// <summary>
/// Checks if an integer can be used as a rtp port
/// </summary>
/// <param name="rtpPort">The integer to be cecked</param>
/// <returns>True if is a valid rtp port, else false</returns>
public static bool ValidRtpPort(int rtpPort)
{
if (rtpPort < 1024 || rtpPort > 65534)
return false;
2024-06-20 16:03:35 +00:00
if (rtpPort % 2 == 1)
2024-02-22 16:43:59 +00:00
return false;
2024-06-20 16:03:35 +00:00
return true;
2024-02-22 16:43:59 +00:00
}
/// <summary>
/// Determines the most appropriate local end point to contact the provided remote end point.
/// Testing shows this method takes on average 1.6ms to return.
/// </summary>
/// <param name="remoteIPEndPoint">The remote end point</param>
/// <returns>The selected local end point</returns>
private static IPEndPoint BestLocalEndPoint(IPEndPoint remoteIPEndPoint)
{
Socket testSocket = new Socket(remoteIPEndPoint.AddressFamily, SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
testSocket.Connect(remoteIPEndPoint);
return (IPEndPoint)testSocket.LocalEndPoint;
}
private bool IsInSameSubnet(IPAddress address2, IPAddress address, IPAddress subnetMask)
{
IPAddress network1 = GetNetworkAddress(address, subnetMask);
IPAddress network2 = GetNetworkAddress(address2, subnetMask);
return network1.Equals(network2);
}
private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
{
byte[] ipAdressBytes = address.GetAddressBytes();
byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
if (ipAdressBytes.Length != subnetMaskBytes.Length)
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
for (int i = 0; i < broadcastAddress.Length; i++)
{
broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
}
return new IPAddress(broadcastAddress);
}
private string AddMsgLenForMBus(string textToSend)
{
String cmdok = textToSend;
Int32 tmp = cmdok.Length + 1;
tmp += tmp.ToString().Length;
String TMPcmdok = "#" + tmp.ToString() + cmdok;
if (tmp != TMPcmdok.Length) cmdok = "#" + TMPcmdok.Length + cmdok;
else cmdok = TMPcmdok;
return cmdok;
}
private DateTime UnixTimeStampToDateTime(double unixTimeStamp)
{
// Unix timestamp is seconds past epoch
System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
dtDateTime = dtDateTime.AddSeconds(unixTimeStamp);
return dtDateTime;
}
private double DateTimeToUnixTime(DateTime utcTime)
{
System.DateTime baseTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
return (utcTime - baseTime).TotalSeconds;
}
#endregion
#region Events
/// <summary>
/// Occurs when you send an invite to a sip id
/// </summary>
public event EventHandler<SipEventArgs> InviteSent;
/// <summary>
/// Occurs when you cancel a sent invite
/// </summary>
public event EventHandler<SipEventArgs> InviteSentCanceled;
/// <summary>
/// Occurs when you receive an invite
/// </summary>
public event EventHandler<InviteReceivedArgs> InviteReceived;
/// <summary>
/// Occurs when an invite is cancelled before you had time to accept or reject it.
/// </summary>
public event EventHandler<SipEventArgs> InviteReceivedCanceled;
/// <summary>
/// Occurs when you decline an invite from another user
/// </summary>
public event EventHandler<SipEventArgs> InviteReceivedDeclined;
/// <summary>
/// An error occured while attempting to establish a voice session
/// </summary>
public event EventHandler<ErrorEventArgs> ErrorOnCreatingDialog;
/// <summary>
/// Occurs when a voice session (a dialog) has been established
/// </summary>
public event EventHandler<LinxDialogCreatedEventArgs> DialogCreated;
/// <summary>
/// Occurs when a voice session (a dialog) is closed
/// </summary>
public event EventHandler<LinxDialogClosedEventArgs> DialogClosed;
/// <summary>
/// Ocurs when a voice buffer is received from a sip id
/// </summary>
public event EventHandler<LinxAudioEventArgs> VoiceReceived;
/// <summary>
/// Occurs when you receive an sms as a Sip message
/// </summary>
public event EventHandler<SmsReceivedEventsArgs> SipSmsReceived;
/// <summary>
/// Occurs when the sip registration status changes
/// </summary>
public event EventHandler<RegistrationStateChangedEventArgs> RegistrationStateChanged;
/// <summary>
/// Occurs when a GPS report is received from a Linx device
/// </summary>
public event EventHandler<LinxGpsDataEventArgs> GpsReportReceived;
/// <summary>
/// Occurs when a periodically GPS report is received
/// </summary>
public event EventHandler<GpsDataEventArgs> GpsPeriodicallyReportReceived;
/// <summary>
/// Occurs when an emergency alarm is received from a Linx device
/// </summary>
public event EventHandler<Linx.LinxEmergencyAlarmReceivedEventArgs> EmergencyAlarmReceived;
/// <summary>
/// Occurs when an Ars command is received from a Linx device
/// </summary>
protected event EventHandler<Linx.ArsReceivedEventArgs> LinxArsReceived;
/// <summary>
/// Occurs when an sms is received on socketIO
/// </summary>
public event EventHandler<SocketIOSmsEventArgs> SocketIOSmsReceived;
/// <summary>
/// Occurs when an sms ack is received on socketIO
/// </summary>
public event EventHandler<SocketIoSmsAckEventArgs> SocketIOSmsAckReceived;
/// <summary>
/// Occurs when hangtime is ended
/// </summary>
public event EventHandler<HangtimeEndedEventArgs> HangtimeEnded;
private void OnSocketIoSmsReceived(SocketIOSmsEventArgs e)
{
// New pattern for triggering events
SocketIOSmsReceived?.Invoke(this, e);
}
private void OnSocketIoSmsAckReceived(SocketIoSmsAckEventArgs e)
{
SocketIOSmsAckReceived?.Invoke(this, e);
}
private void OnInviteSent(SipEventArgs e)
{
EventHandler<SipEventArgs> handler = InviteSent;
if (handler != null)
handler(this, e);
}
private void OnInviteSentCanceled(SipEventArgs e)
{
EventHandler<SipEventArgs> handler = InviteSentCanceled;
if (handler != null)
handler(this, e);
}
/// <summary>
/// This is the method that raises the ErrorOnCreatingDialog event
/// </summary>
/// <param name="e">Data for the ErrorOnCreatingDialog event</param>
/// <remarks>Do not forget to call the base version when overrinding,
/// <para>otherwize the ErrorOnCreatingDialog event will not fire</para>
/// </remarks>
protected virtual void OnError(ErrorEventArgs e)
{
EventHandler<ErrorEventArgs> handler = ErrorOnCreatingDialog;
if (handler != null)
handler(this, e);
}
private void OnInviteReceived(InviteReceivedArgs e)
{
EventHandler<InviteReceivedArgs> handler = InviteReceived;
if (handler != null)
handler(this, e);
}
private void OnInviteReceivedCanceled(SipEventArgs e)
{
EventHandler<SipEventArgs> handler = InviteReceivedCanceled;
if (handler != null)
handler(this, e);
}
private void OnInviteReceivedDeclined(SipEventArgs e)
{
EventHandler<SipEventArgs> handler = InviteReceivedDeclined;
if (handler != null)
handler(this, e);
}
/// <summary>
/// This is the method that raises the DialogCreated event
/// </summary>
/// <param name="e">Data for the DialogCreatedEvent</param>
/// <remarks>Do not forget to call the base version when overrinding,
/// <para>otherwize the DialogCreated Event will not fire</para>
/// </remarks>
protected virtual void OnDialogCreated(LinxDialogCreatedEventArgs e)
{
EventHandler<LinxDialogCreatedEventArgs> handler = DialogCreated;
if (handler != null)
handler(this, e);
}
/// <summary>
/// This is the method that raises the DialogClosed event
/// </summary>
/// <param name="e">Data for the DialogClosed event</param>
/// <remarks>Do not forget to call the base version when overrinding,
/// <para>otherwize the DialogClosed Event will not fire</para>
/// </remarks>
protected virtual void OnDialogClosed(LinxDialogClosedEventArgs e)
{
EventHandler<LinxDialogClosedEventArgs> handler = DialogClosed;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// This is the method that raises the VoiceReceived event
/// </summary>
/// <param name="e">Data for the VoiceReceived event</param>
/// <remarks>Do not forget to call the base version when overrinding,
/// <para>otherwise the VoiceReceived Event will not fire</para>
/// </remarks>
protected virtual void OnVoiceReceived(LinxAudioEventArgs e)
{
EventHandler<LinxAudioEventArgs> handler = VoiceReceived;
if (handler != null)
handler(this, e);
}
private void OnRegistrationStateChanged(RegistrationStateChangedEventArgs e)
{
EventHandler<RegistrationStateChangedEventArgs> handler = RegistrationStateChanged;
if (handler != null)
handler(this, e);
}
private void OnSipSmsReceived(SmsReceivedEventsArgs e)
{
EventHandler<SmsReceivedEventsArgs> handler = SipSmsReceived;
if (handler != null)
handler(this, e);
}
private void OnGpsPeriodicallyReportReceived(GpsDataEventArgs e)
{
EventHandler<GpsDataEventArgs> handler = GpsPeriodicallyReportReceived;
if (handler != null)
handler(this, e);
}
private void OnLinxEmergencyAlarmReceived(Linx.LinxEmergencyAlarmReceivedEventArgs e)
{
EventHandler<Linx.LinxEmergencyAlarmReceivedEventArgs> handler = EmergencyAlarmReceived;
if (handler != null)
handler(this, e);
}
private void OnGpsReportReceived(LinxGpsDataEventArgs e)
{
EventHandler<LinxGpsDataEventArgs> handler = GpsReportReceived;
if (handler != null)
handler(this, e);
}
private void OnLinxArsReceived(Linx.ArsReceivedEventArgs e)
{
EventHandler<Linx.ArsReceivedEventArgs> handler = LinxArsReceived;
if (handler != null)
handler(this, e);
}
#endregion
}
}