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
{
///
/// Class used to make SD talk to Linx using Adi's protocol
/// Accepts more than one sip call at a time
///
public class SipClientClassSD : SipClientClass
{
#region Fields
private Dictionary _linxDevicesToRespondTo = new Dictionary();
private Dictionary _linxDevicesToCall = new Dictionary();
private object _lockerCallingLinx = new object();
private object _lockerCalledLinx = new object();
private Dictionary> _hangTimeTimers = new Dictionary>();
private object _lockerlinxGroups = new object();
private HashSet _linxGroupsInDialogWith = new HashSet();
///
/// Simulated hangTime duration, in ms
///
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
///
/// Constructor for the SipClientClassSD
///
/// domain name, name or IP address of sip server
/// port number of the sip server
/// port number of the local computer used for sip protocol
/// user name on the sip server
/// password on the sip server
/// interval to send sip registration requests. Value is in seconds
/// Miliseconds for the buffer that stores the received voice packets
/// Number of ms to wait before the sip request times out
/// port number use for socket.IO
/// Local Ip adress. If not specified, the class will search for a local ip on the same network with the sip server ip
/// True to send ars on on creation and off when calling Stop()
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
///
/// Gets or sets a value indicating if the Asterisk sends sms confirmations on delivery
/// Default value is false
///
public bool SmsConfirmationFromAsterisk
{
get { return base._smsConfirmationFromServer; }
set { _smsConfirmationFromServer = true; }
}
///
/// Send Invite to a Linx android Device
///
/// Linx ID to call
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);
}
///
/// Send Invite to a linx group, using socket.IO signalling
///
/// Linx group to call
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);
}
///
/// Method used to send Gps Request to a Linx device
///
/// The sip id to send the gps request
/// The sequence id
public void SendGPSRequest(string idToRequestGps, string seqID)
{
base.SendLinxGpsRequest(idToRequestGps, seqID);
}
///
/// Sends a group sms on socket IO
///
/// SC ID of the sender
/// The sip group ID
/// SC ID of the group
/// The text
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
///
/// Method used to return the type of call from a received invite
///
/// The received invite
/// The requested type of call
protected override TypeOfCall GetTypeOfCall(Request receivedInvite)
{
return TypeOfCall.FULL_DUPLEX;
}
///
/// Method used to return the type of call from a received Session description
///
/// The received session description
/// The requested type of call
protected override TypeOfCall GetTypeOfCall(SessionDescription sdp)
{
return TypeOfCall.FULL_DUPLEX;
}
/* Part that handles Sip message signalling
///
/// Function that processes all the received Sip Message requests
///
/// The Sip Message request
/// The ID of the sender
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);
}
*/
///
/// Method used to decide if the app is ringing or is sending Busy response to sender
/// Default behaviour is to ring (sends Ringing response)
///
/// The received invite
/// The sender of the invite
/// True to send Ringing response, false to send Busy Here
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;
}
}
}
///
/// This is the method that raises the DialogClosed event
///
/// Data for the DialogClosed event
/// Do not forget to call the base version when overrinding,
/// otherwize the DialogClosed Event will not fire
///
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 timer;
if (_hangTimeTimers.ContainsKey(idInHangtime))
{
timer = _hangTimeTimers[idInHangtime];
}
else
{
timer = new TimerWithTag(_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);
}
///
/// This is the method that raises the DialogCreated event
///
/// Data for the DialogCreatedEvent
/// Do not forget to call the base version when overrinding,
/// otherwize the DialogCreated Event will not fire
///
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);
}
///
/// This is the method that raises the VoiceReceived event
///
/// Data for the VoiceReceived event
/// Do not forget to call the base version when overrinding,
/// otherwise the VoiceReceived Event will not fire
///
protected override void OnVoiceReceived(AudioEventArgs e)
{
LinxAudioEventArgs ev = (LinxAudioEventArgs)e;
OnLinxVoiceReceived(ev);
}
///
/// Stops registration by sending unregister request to the sip server
/// Releases all the used resources
/// Disconnects from the socket.IO server
///
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();
///
/// Occurs when a GPS report is received from a Linx device
///
public event EventHandler GpsReportReceived
{
add
{
lock (locker)
{
base.LinxGpsReportReceived += value;
}
}
remove
{
lock (locker)
{
base.LinxGpsReportReceived -= value;
}
}
}
private object locker1 = new object();
///
/// Occurs when an emergency alarm is received from a Linx device
///
public event EventHandler EmergencyAlarmReceived
{
add
{
lock (locker1)
{
base.LinxEmergencyAlarmReceived += value;
}
}
remove
{
lock (locker1)
{
base.LinxEmergencyAlarmReceived -= value;
}
}
}
private object locker8 = new object();
///
/// Occurs when an Ars command is received from a Linx device
///
public event EventHandler ArsReceived
{
add
{
lock (locker8)
{
base.LinxArsReceived += value;
}
}
remove
{
lock (locker8)
{
base.LinxArsReceived -= value;
}
}
}
///
/// Occurs when hangtime is ended
///
public event EventHandler HangtimeEnded;
private void OnHangtimeEnded(HangtimeEndedEventArgs e)
{
HangtimeEnded?.Invoke(this, e);
}
///
/// Occurs when a voice session (a dialog) has been established
///
public new event EventHandler DialogCreated;
private void OnLinxDialogCreated(LinxDialogCreatedEventArgs e)
{
DialogCreated?.Invoke(this, e);
}
///
/// Occurs when a voice session (a dialog) is closed
///
public new event EventHandler DialogClosed;
private void OnLinxDialogClosed(LinxDialogClosedEventArgs e)
{
DialogClosed?.Invoke(this, e);
}
///
/// Ocurs when a voice buffer is received from a sip id
///
public new event EventHandler VoiceReceived;
private void OnLinxVoiceReceived(LinxAudioEventArgs e)
{
VoiceReceived?.Invoke(this, e);
}
///
/// Occurs when a group sms is received on socketIO
///
public event EventHandler 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 timer = (TimerWithTag)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;
}
}
}
///
/// Provides data for the DialogCreated event
///
public class LinxDialogCreatedEventArgs : DialogCreatedEventArgs
{
///
/// Gets the group sip id if group call, otherwise is null
///
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;
}
}
///
/// Provides data for the DialogClosedEvent
///
public class LinxDialogClosedEventArgs : DialogClosedEventArgs
{
///
/// Gets the group id if group call, otherwise is null
///
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;
}
}
///
/// Provides data for the HangtimeEnded Event
///
public class HangtimeEndedEventArgs : SipEventArgs
{
///
/// Returns true for group call, false for private call
///
public bool IsGroupCall {
get {
return GroupID != null;
}
}
///
/// Gets the group id if group call, otherwise is null
///
public string GroupID { get; private set; }
internal HangtimeEndedEventArgs(string sipIDinDialogWith, string groupID = null) : base(sipIDinDialogWith)
{
GroupID = groupID;
}
}
///
/// Provides data for the VoiceReceived event
///
public class LinxAudioEventArgs : AudioEventArgs
{
///
/// Gets the group id if group call, otherwise is null
///
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;
}
}
///
/// Provides data for the GroupSmsReceived event
///
public class SocketIOSmsEventArgs : EventArgs
{
///
/// SequenceID
///
public string SeqID { get; private set; }
///
/// Sip ID that sends the sms
///
public string FromSipID { get; private set; }
///
/// Destination group sip ID
///
public string ToSipID { get; private set; }
///
/// The message
///
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;
}
}
///
/// Provides data for the SocketIoSmsAckReceived event
///
public class SocketIoSmsAckEventArgs : EventArgs
{
///
/// Seq id of the sms
///
public string SeqID { get; private set; }
///
/// Sip ID for the ack
///
public string AckBySipID { get; private set; }
internal SocketIoSmsAckEventArgs(string seqID, string ackBySipID)
{
this.SeqID = seqID;
this.AckBySipID = ackBySipID;
}
}
}