SafeDispatch/SipComponent/SipClientClassSD.cs
2024-02-22 18:43:59 +02:00

776 lines
28 KiB
C#

using Independentsoft.Sip;
using Independentsoft.Sip.Sdp;
using SocketIOComponent;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using SipComponent.Linx;
namespace SipComponent
{
/// <summary>
/// Class used to make SD talk to Linx using Adi's protocol
/// <para>Accepts more than one sip call at a time</para>
/// </summary>
public class SipClientClassSD : SipClientClass
{
#region Fields
private Dictionary<string, CallInfo> _linxDevicesToRespondTo = new Dictionary<string, CallInfo>();
private Dictionary<string, CallInfo> _linxDevicesToCall = new Dictionary<string, CallInfo>();
private object _lockerCallingLinx = new object();
private object _lockerCalledLinx = new object();
private Dictionary<string, TimerWithTag<LinxIDInfo>> _hangTimeTimers = new Dictionary<string, TimerWithTag<LinxIDInfo>>();
private object _lockerlinxGroups = new object();
private HashSet<int> _linxGroupsInDialogWith = new HashSet<int>();
/// <summary>
/// Simulated hangTime duration, in ms
/// </summary>
private readonly int _hangTimeDuration = 3000;
// Socket.IO class
private SocketIOClass _socketIOClass = null;
private bool _sendArsOnOff = true;
// Random for sending seqIDs and other things....
private Random _rand = new Random();
// Sip message generator
private LinxSipMessageGenerator _sipMessageGenerator = new LinxSipMessageGenerator();
#endregion
// constructor
/// <summary>
/// Constructor for the SipClientClassSD
/// </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="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>
/// <param name="sendArsOnOff">True to send ars on on creation and off when calling Stop()</param>
public SipClientClassSD(string sipDomain, int sipDomainPort, int localSipPort, string userName, string password, int registrationInterval, int bufferMiliseconds,
int requestTimeout, int socketIOport, string localIPAddress = null, bool sendArsOnOff = true) :
base(sipDomain, sipDomainPort, localSipPort, userName, password, registrationInterval, bufferMiliseconds, requestTimeout, localIPAddress)
{
// Socket IO
this._socketIOClass = new SocketIOClass($"ws://{sipDomain}:{socketIOport}");
this._socketIOClass.PrivateCallRequestReceived += _socketIOClass_PrivateCallRequestReceived;
this._socketIOClass.GroupCallRequestReceived += _socketIOClass_GroupCallRequestReceived;
this._socketIOClass.SmsReceived += _socketIOClass_SmsReceived;
// 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)); });
}
#region Public Members
/// <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 base._smsConfirmationFromServer; }
set { _smsConfirmationFromServer = true; }
}
/// <summary>
/// Send Invite to a Linx android Device
/// </summary>
/// <param name="linxID">Linx ID to call</param>
public void Invite(string linxID)
{
// Send private call request on socket.IO
PrivateCallInfo callInfo = new PrivateCallInfo(int.Parse(this.UserName), int.Parse(linxID), true);
_linxDevicesToCall[linxID] = callInfo;
_socketIOClass.SendPrivateCallRequest(callInfo);
// send invite
base.Invite(linxID, TypeOfCall.FULL_DUPLEX);
}
/// <summary>
/// Send Invite to a linx group, using socket.IO signalling
/// </summary>
/// <param name="linxGroupID">Linx group to call</param>
public new void InviteGroup(string linxGroupID)
{
// Send group call request on socket.IO
GroupCallInfo callInfo = new GroupCallInfo(int.Parse(this.UserName), int.Parse(linxGroupID), true);
_linxDevicesToCall[linxGroupID] = callInfo;
_socketIOClass.SendGroupCallRequest(callInfo);
// send invite
base.Invite(linxGroupID, TypeOfCall.FULL_DUPLEX);
}
/// <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>
public void SendGPSRequest(string idToRequestGps, string seqID)
{
base.SendLinxGpsRequest(idToRequestGps, seqID);
}
/// <summary>
/// Sends a group sms on socket IO
/// </summary>
/// <param name="fromScID">SC ID of the sender</param>
/// <param name="sipGroupID">The sip group ID</param>
/// <param name="groupScID">SC ID of the group</param>
/// <param name="text">The text</param>
public void SendGroupSms(int fromScID, int sipGroupID, int groupScID, string text)
{
_socketIOClass.SendSms(new SmsInfo(_rand.Next(300, 1000).ToString(), UserName, fromScID.ToString(), sipGroupID.ToString(), groupScID.ToString(), text, true));
}
#endregion
#region Abstract Members Implementation
internal override RTPListener2 CreateRTPListener(UdpClient udpClient, bool initiatedByMe, int bufferMiliseconds, int sipIdInDialogWith, bool isGroupCall, TypeOfCall typeOfCall)
{
// Check if is private or group call
CallInfo callInfo = null;
string groupID = null;
if (_linxDevicesToRespondTo.ContainsKey(sipIdInDialogWith.ToString()))
callInfo = _linxDevicesToRespondTo[sipIdInDialogWith.ToString()];
else if (_linxDevicesToCall.ContainsKey(sipIdInDialogWith.ToString()))
callInfo = _linxDevicesToCall[sipIdInDialogWith.ToString()];
if (callInfo is GroupCallInfo)
{
groupID = ((GroupCallInfo)callInfo).ToGroupSipID.ToString();
}
return new RTPListenerLinx(udpClient, initiatedByMe, bufferMiliseconds, sipIdInDialogWith, typeOfCall, groupID);
}
internal override RTPSender2 CreateRTPSender(UdpClient udpClient, int audioBitrate, IPEndPoint iPEndPoint, int dmrSourceID, int dmrDestinationID, bool initiatedByMe, TypeOfCall typeOfCall,
RtpCallType halfDuplexCallType = RtpCallType.Private)
{
return new RTPSender2(udpClient, audioBitrate, iPEndPoint, dmrSourceID, dmrDestinationID, initiatedByMe, typeOfCall);
}
internal override SipMessageGenerator SipMessageGenerator
{
get
{
return _sipMessageGenerator;
}
}
#endregion
#region Virtual Members Implementation
/// <summary>
/// Method used to return the type of call from a received invite
/// </summary>
/// <param name="receivedInvite">The received invite</param>
/// <returns>The requested type of call</returns>
protected override TypeOfCall GetTypeOfCall(Request receivedInvite)
{
return TypeOfCall.FULL_DUPLEX;
}
/// <summary>
/// Method used to return the type of call from a received Session description
/// </summary>
/// <param name="sdp">The received session description</param>
/// <returns>The requested type of call</returns>
protected override TypeOfCall GetTypeOfCall(SessionDescription sdp)
{
return TypeOfCall.FULL_DUPLEX;
}
/* Part that handles Sip message signalling
/// <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 override void ProcessReceivedSipMessage(Request sipMessageRequest, string senderID)
{
string requestBody;
if ((requestBody = sipMessageRequest.Body) != null)
{
if (requestBody == _halfDuplexIdentifier)
{
lock (_locker)
{
_linxDevicesToRespondTo.Add(senderID);
}
return;
}
}
base.ProcessReceivedSipMessage(sipMessageRequest, senderID);
}
*/
/// <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 override bool SendResponseRinging(Request receivedInvite, string senderSipId)
{
// 1. Check if the caller is the one that has send / received the sip message. Else send busy
lock (_lockerCallingLinx)
{
if (_linxDevicesToRespondTo.ContainsKey(senderSipId))
{
CallInfo callInfo = _linxDevicesToRespondTo[senderSipId];
if (callInfo.FromSipID.ToString() == senderSipId)
{
if (callInfo.IsPTT)
{
GroupCallInfo groupCallInfo = callInfo as GroupCallInfo;
if (groupCallInfo == null)
{
// this is a private call request
return true;
}
else
{
// call is group call
// reject a group call if already in dialog with that group
bool reject = false;
lock (_lockerlinxGroups)
{
if (!_linxGroupsInDialogWith.Contains(groupCallInfo.ToGroupSipID))
_linxGroupsInDialogWith.Add(groupCallInfo.ToGroupSipID);
else
reject = true;
}
if (reject)
{
OnError(new ErrorEventArgs(UserName, senderSipId, $"Will reject group invite from {senderSipId} because I'm already in group call with {groupCallInfo.ToGroupSipID} "));
return false;
}
else
return true;
}
}
else
{
#if DEBUG
Console.WriteLine($"Will send busy to {senderSipId} because he initiated a full duplex call");
#endif
return false;
}
}
else
{
#if DEBUG
Console.WriteLine($"Will send busy to {senderSipId} because he did not send request for ptt");
#endif
OnError(new ErrorEventArgs(UserName, senderSipId, $"{senderSipId} did not send request for ptt"));
return false;
}
}
else
{
#if DEBUG
Console.WriteLine($"Will send busy to {senderSipId} because he did not send request for ptt");
#endif
OnError(new ErrorEventArgs(UserName, senderSipId, $"{senderSipId} did not send request for ptt"));
return false;
}
}
}
/// <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 override void OnDialogClosed(DialogClosedEventArgs e)
{
CallInfo closingCallInfo = null;
if (_linxDevicesToCall.ContainsKey(e.SipIDinDialogWith))
closingCallInfo = _linxDevicesToCall[e.SipIDinDialogWith];
else if (_linxDevicesToRespondTo.ContainsKey(e.SipIDinDialogWith))
closingCallInfo = _linxDevicesToRespondTo[e.SipIDinDialogWith];
string idInHangtime = "";
GroupCallInfo groupCallInfo = closingCallInfo as GroupCallInfo;
if (groupCallInfo != null)
{
// group call
idInHangtime = groupCallInfo.ToGroupSipID.ToString();
// Remove entry from linx groups dictionary
lock (_lockerlinxGroups)
{
_linxGroupsInDialogWith.Remove(groupCallInfo.ToGroupSipID);
}
}
else
{
// private call
idInHangtime = e.SipIDinDialogWith;
}
// Remove sip id from list of id's that sent request on socket.IO
lock (_lockerCallingLinx)
{
if (_linxDevicesToRespondTo.ContainsKey(e.SipIDinDialogWith))
_linxDevicesToRespondTo.Remove(e.SipIDinDialogWith);
else if (_linxDevicesToCall.ContainsKey(e.SipIDinDialogWith))
_linxDevicesToCall.Remove(e.SipIDinDialogWith);
}
// Create hangtime timer for each sip id
TimerWithTag<LinxIDInfo> timer;
if (_hangTimeTimers.ContainsKey(idInHangtime))
{
timer = _hangTimeTimers[idInHangtime];
}
else
{
timer = new TimerWithTag<LinxIDInfo>(_hangTimeDuration, new LinxIDInfo(e.SipIDinDialogWith, idInHangtime != e.SipIDinDialogWith ? idInHangtime : null));
timer.AutoReset = false;
timer.Elapsed += TimerHangtime_Elapsed;
_hangTimeTimers.Add(idInHangtime, timer);
}
timer.Start();
LinxDialogClosedEventArgs ev = new LinxDialogClosedEventArgs(
e.SipIDinDialogWith, e.SipIDwhoClosed, e.Reason,
idInHangtime != e.SipIDinDialogWith ? idInHangtime : null);
this.OnLinxDialogClosed(ev);
}
/// <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 override void OnDialogCreated(DialogCreatedEventArgs e)
{
string linxID = (e.FromID == base.UserName ? e.ToID : e.FromID);
bool isGroupCall = false;
string groupID = null;
CallInfo callInfo = null;
if (_linxDevicesToRespondTo.ContainsKey(linxID))
callInfo = _linxDevicesToRespondTo[linxID];
else if (_linxDevicesToCall.ContainsKey(linxID))
callInfo = _linxDevicesToCall[linxID];
// Check if is private or group call
if (callInfo is GroupCallInfo)
{
isGroupCall = true;
linxID = groupID = ((GroupCallInfo)callInfo).ToGroupSipID.ToString();
}
// Check if the Linx was in in hangtime, to stop the timer
if (_hangTimeTimers.ContainsKey(linxID))
{
_hangTimeTimers[linxID].Stop();
}
LinxDialogCreatedEventArgs ev = new LinxDialogCreatedEventArgs(
e.ToID, e.FromID, e.TypeOfDialog, isGroupCall, groupID);
this.OnLinxDialogCreated(ev);
}
/// <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 override void OnVoiceReceived(AudioEventArgs e)
{
LinxAudioEventArgs ev = (LinxAudioEventArgs)e;
OnLinxVoiceReceived(ev);
}
/// <summary>
/// Stops registration by sending unregister request to the sip server
/// <para>Releases all the used resources</para>
/// <para>Disconnects from the socket.IO server</para>
/// </summary>
public override void Stop(bool async = true)
{
// Send ARS OFF and disconect from socket IO
if (_sendArsOnOff)
_socketIOClass.SendArs(new ArsInfo(false, "0", UserName));
_socketIOClass.Disconect();
base.Stop();
}
#endregion
#region Events
private object locker = new object();
/// <summary>
/// Occurs when a GPS report is received from a Linx device
/// </summary>
public event EventHandler<LinxGpsDataEventArgs> GpsReportReceived
{
add
{
lock (locker)
{
base.LinxGpsReportReceived += value;
}
}
remove
{
lock (locker)
{
base.LinxGpsReportReceived -= value;
}
}
}
private object locker1 = new object();
/// <summary>
/// Occurs when an emergency alarm is received from a Linx device
/// </summary>
public event EventHandler<LinxEmergencyAlarmReceivedEventArgs> EmergencyAlarmReceived
{
add
{
lock (locker1)
{
base.LinxEmergencyAlarmReceived += value;
}
}
remove
{
lock (locker1)
{
base.LinxEmergencyAlarmReceived -= value;
}
}
}
private object locker8 = new object();
/// <summary>
/// Occurs when an Ars command is received from a Linx device
/// </summary>
public event EventHandler<Linx.ArsReceivedEventArgs> ArsReceived
{
add
{
lock (locker8)
{
base.LinxArsReceived += value;
}
}
remove
{
lock (locker8)
{
base.LinxArsReceived -= value;
}
}
}
/// <summary>
/// Occurs when hangtime is ended
/// </summary>
public event EventHandler<HangtimeEndedEventArgs> HangtimeEnded;
private void OnHangtimeEnded(HangtimeEndedEventArgs e)
{
HangtimeEnded?.Invoke(this, e);
}
/// <summary>
/// Occurs when a voice session (a dialog) has been established
/// </summary>
public new event EventHandler<LinxDialogCreatedEventArgs> DialogCreated;
private void OnLinxDialogCreated(LinxDialogCreatedEventArgs e)
{
DialogCreated?.Invoke(this, e);
}
/// <summary>
/// Occurs when a voice session (a dialog) is closed
/// </summary>
public new event EventHandler<LinxDialogClosedEventArgs> DialogClosed;
private void OnLinxDialogClosed(LinxDialogClosedEventArgs e)
{
DialogClosed?.Invoke(this, e);
}
/// <summary>
/// Ocurs when a voice buffer is received from a sip id
/// </summary>
public new event EventHandler<LinxAudioEventArgs> VoiceReceived;
private void OnLinxVoiceReceived(LinxAudioEventArgs e)
{
VoiceReceived?.Invoke(this, e);
}
/// <summary>
/// Occurs when a group sms is received on socketIO
/// </summary>
public event EventHandler<SocketIOSmsEventArgs> GroupSmsReceived;
private void OnGroupSmsReceived(SocketIOSmsEventArgs e)
{
// New pattern for triggering events
GroupSmsReceived?.Invoke(this, e);
}
#endregion
#region Private Methods
private void TimerHangtime_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
TimerWithTag<LinxIDInfo> timer = (TimerWithTag<LinxIDInfo>)sender;
LinxIDInfo linxIDifo = timer.Tag;
OnHangtimeEnded(new HangtimeEndedEventArgs(linxIDifo.SipID, linxIDifo.GroupID));
}
#region SocketIO Event Handlers
private void _socketIOClass_PrivateCallRequestReceived(object sender, PrivateCallRequestReceivedEventArgs e)
{
PrivateCallInfo callInfo = e.CallInfo;
if (callInfo.ToSipID.ToString() == this.UserName && callInfo.IsPTT)
{
lock (_lockerCallingLinx)
{
_linxDevicesToRespondTo[callInfo.FromSipID.ToString()] = callInfo;
}
}
#if DEBUG
Console.WriteLine($"Private call request received by sip {UserName}: {e.CallInfo.ToString()}");
#endif
}
private void _socketIOClass_GroupCallRequestReceived(object sender, GroupCallRequestReceivedEventArgs e)
{
GroupCallInfo callInfo = e.CallInfo;
if (callInfo.IsPTT)
{
lock (_lockerCallingLinx)
{
_linxDevicesToRespondTo[callInfo.FromSipID.ToString()] = callInfo;
}
}
#if DEBUG
Console.WriteLine($"Group call request received by sip {UserName}: {e.CallInfo.ToString()}");
#endif
}
private void _socketIOClass_SmsReceived(object sender, SmsReceivedEventArgs e)
{
SmsInfo smsInfo = e.SmsInfo;
#if DEBUG
Console.WriteLine($"Group sms received by sip {UserName}: {smsInfo}");
#endif
if(smsInfo.IsGroup)
// Fire event
OnGroupSmsReceived(new SocketIOSmsEventArgs(smsInfo.SeqID, smsInfo.FromSipID, smsInfo.ToSipID, smsInfo.Message, smsInfo.IsGroup));
}
#endregion
#endregion
struct LinxIDInfo
{
public string SipID { get; private set; }
public bool IsGroup {
get {
return GroupID != null;
}
}
public string GroupID { get; private set; }
public LinxIDInfo(string sipID, string groupID = null)
{
SipID = sipID;
GroupID = groupID;
}
}
}
/// <summary>
/// Provides data for the DialogCreated event
/// </summary>
public class LinxDialogCreatedEventArgs : DialogCreatedEventArgs
{
/// <summary>
/// Gets the group sip id if group call, otherwise is null
/// </summary>
public string GroupID { get; private set; }
internal LinxDialogCreatedEventArgs(string toID, string fromID, TypeOfCall typeOfDialog, bool isGroupCall, string groupID = null):
base(toID, fromID, typeOfDialog, isGroupCall)
{
GroupID = groupID;
}
}
/// <summary>
/// Provides data for the DialogClosedEvent
/// </summary>
public class LinxDialogClosedEventArgs : DialogClosedEventArgs
{
/// <summary>
/// Gets the group id if group call, otherwise is null
/// </summary>
public string GroupID { get; private set; }
internal LinxDialogClosedEventArgs(string sipIDinDialogWith, string sipIDWhoClosed, DialogClosedReason reason, string groupID = null) :
base(sipIDinDialogWith, sipIDWhoClosed, groupID != null, reason)
{
GroupID = groupID;
}
}
/// <summary>
/// Provides data for the HangtimeEnded Event
/// </summary>
public class HangtimeEndedEventArgs : SipEventArgs
{
/// <summary>
/// Returns true for group call, false for private call
/// </summary>
public bool IsGroupCall {
get {
return GroupID != null;
}
}
/// <summary>
/// Gets the group id if group call, otherwise is null
/// </summary>
public string GroupID { get; private set; }
internal HangtimeEndedEventArgs(string sipIDinDialogWith, string groupID = null) : base(sipIDinDialogWith)
{
GroupID = groupID;
}
}
/// <summary>
/// Provides data for the VoiceReceived event
/// </summary>
public class LinxAudioEventArgs : AudioEventArgs
{
/// <summary>
/// Gets the group id if group call, otherwise is null
/// </summary>
public string GroupID { get; private set; }
internal LinxAudioEventArgs(byte[] data, int linxSourceId, string groupID = null) :
base(data, linxSourceId,
groupID != null ? int.Parse(groupID) : linxSourceId)
{
GroupID = groupID;
}
}
/// <summary>
/// Provides data for the GroupSmsReceived event
/// </summary>
public class SocketIOSmsEventArgs : EventArgs
{
/// <summary>
/// SequenceID
/// </summary>
public string SeqID { get; private set; }
/// <summary>
/// Sip ID that sends the sms
/// </summary>
public string FromSipID { get; private set; }
/// <summary>
/// Destination group sip ID
/// </summary>
public string ToSipID { get; private set; }
/// <summary>
/// The message
/// </summary>
public string Message { get; private set; }
public bool IsGroup { get; private set; }
internal SocketIOSmsEventArgs(string seqID, string fromSipID, string toSipID, string message, bool isGroup)
{
this.SeqID = seqID;
this.FromSipID = fromSipID;
this.ToSipID = toSipID;
this.Message = message;
this.IsGroup = isGroup;
}
}
/// <summary>
/// Provides data for the SocketIoSmsAckReceived event
/// </summary>
public class SocketIoSmsAckEventArgs : EventArgs
{
/// <summary>
/// Seq id of the sms
/// </summary>
public string SeqID { get; private set; }
/// <summary>
/// Sip ID for the ack
/// </summary>
public string AckBySipID { get; private set; }
internal SocketIoSmsAckEventArgs(string seqID, string ackBySipID)
{
this.SeqID = seqID;
this.AckBySipID = ackBySipID;
}
}
}