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; } } }