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 { /// /// Class used to make SD talk to Linx using Adi's protocol, version 2 /// Accepts more than one sip call at a time /// Does not use socket.IO requests for calls /// 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; /// /// Nr de secunde cat este mentinut call-ul cand nu se transmite voce /// private int _hangTimeDuration = -1; private int _bufferMiliseconds; private string _localIPaddress; private Random _rand = new Random(); private bool _sipClassClosed = false; /// /// Gets or sets a value indicating if the Asterisk server confirms sms /// Default value is false /// protected bool _smsConfirmationFromServer = false; private Dictionary _IDsentInviteDict = new Dictionary(); private Dictionary _IDreceivedInviteDict = new Dictionary(); private Dictionary>> _IDdialogTuple = new Dictionary>>(); private Dictionary _smsSentDict = new Dictionary(); private Dictionary> _sipID_regTimer_regStatus_Dict = new Dictionary>(); private Dictionary>> _ID_pingBytest_timer_dict = new Dictionary>>(); private Dictionary _simocoID_emergencyStatusReport_dict = new Dictionary(); private object _lockerSmsSet = new object(); private List _IDsCalledByMeList = new List(); private List _IDsCallingMeList = new List(); private List _IDsInDialogWithList = new List(); private List _IDsregisteredList = new List(); 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 _linxGroupSipIDsInDialogWith = new HashSet(); #endregion #region Public Properties /// /// Gets or sets the minimum port number in the port range used for RTP voice transmission /// 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)); } } /// /// Gets or sets the maximum port number in the port range used for RTP voice transmission /// 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)); } } /// /// Gets the sip ID /// public string UserName { get { return _sipClient.Username; } } /// /// Gets or sets the maximum number of simultaneous dialogs that the SipClientClass accepts /// The default is 3 /// public int MaxNbOfDialogs { get { return _maxNbOfDialogs; } set { _maxNbOfDialogs = value; } } /// /// Gets or sets the number of seconds the call is maintained when no voice is transmited /// Value is in seconds /// Default value is -1, call is maintained indefinitely /// public int HangTimeDuration { get { return _hangTimeDuration; } set { if (value > 0) _hangTimeDuration = value; } } /// /// Gets or sets a value indicating the nb of seconds to wait /// for a sms confirmation from sip server /// Default value is 3 /// public int SecondsToWaitForSmsConfirmation { get { return _secondsToWaitForSmsConfirmation; } set { if (value > 0 && value < 10) _secondsToWaitForSmsConfirmation = value; } } /// /// Audio BitRate, default value is 16 /// public int AudioBitrate { get { return _audioBitRate; } set { _audioBitRate = value; } } /// /// Gets a readonly collection of sip ids that you sent Invite for /// public ReadOnlyCollection SipIDsCalledByMe { get { return new ReadOnlyCollection(_IDsCalledByMeList); } } /// /// Gets a readonly collection of sip ids that sent you Invite's /// public ReadOnlyCollection SipIDsCallingMe { get { return new ReadOnlyCollection(_IDsCallingMeList); } } /// /// Gets a readonly collection of sip ids that you are in dialog with /// public ReadOnlyCollection SipIDsInDialogWith { get { return new ReadOnlyCollection(_IDsInDialogWithList); } } /// /// Gets a readonly collection of sip ids that you are registered to /// This means your sip id and the group ids that you are listening to /// public ReadOnlyCollection SipIDsRegisteredTo { get { return new ReadOnlyCollection(_IDsregisteredList); } } /// /// Gets or sets a value indicating if the Asterisk sends sms confirmations on delivery /// Default value is false /// public bool SmsConfirmationFromAsterisk { get { return _smsConfirmationFromServer; } set { _smsConfirmationFromServer = true; } } #endregion #region Constructor // constructor /// /// Constructor for the SipClientClass /// /// 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 /// True to send ars on on creation and off when calling Stop() /// Local Ip adress. If not specified, the class will search for a local ip on the same network with the sip server ip 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; _sipDomainPort = sipDomainPort; _sipClient = CreateSipClientClass(sipDomain, _sipDomainPort, _localIPaddress, localSipPort, userName, password, requestTimeout); _bufferMiliseconds = bufferMiliseconds; // Registration Timer _registrationInterval = registrationInterval; _registrationData = new RegistrationData(userName, _registrationInterval + 2); System.Timers.Timer registrationTimer = new System.Timers.Timer(); // Set up the registration timer registrationTimer.Interval = _registrationInterval * 1000; registrationTimer.Elapsed += _registrationTimer_Elapsed; _sipID_regTimer_regStatus_Dict.Add(userName, new Tuple(registrationTimer, RegistrationStatus.RegistrationNotStarted )); _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 /// /// Sends a Sip Invite command to an user /// /// The sip id of the user public void Invite(string idToInvite) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (idToInvite == null) throw new ArgumentNullException("idToInvite"); lock (_lockerSipDialog) { if (!_IDsentInviteDict.ContainsKey(idToInvite) && !_IDreceivedInviteDict.ContainsKey(idToInvite) && !_IDdialogTuple.ContainsKey(idToInvite)) { SendSipInvite(idToInvite, false); } } } /// /// Sends a Sip Invite command to a Simoco Group /// /// The sip id of the group public void InviteGroup(string groupIDtoInvite) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (groupIDtoInvite == null) throw new ArgumentNullException("groupIDtoInvite"); // 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")); } } /// /// Cancels an Invite sent to an user /// /// The sip id of the user public void CancelInvite(string idToCancel) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); 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; }); 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); } // Remove from dict _IDsentInviteDict.Remove(idToCancel); _IDsCalledByMeList.Remove(idToCancel); } return sendingCancelTask; } /// /// Accepts an Invite from an user /// /// The sip ID of the user 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] = ""; // 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)); } } /// /// Closes a sip dialog (voice session) with an user /// /// The sip id of the user 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; } /// /// Declines a received Invite from an user /// /// The sip ID of the user 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)); } /// /// Sends a voice buffer in a specified format to the user in dialog with /// /// The sip ID of the user /// The audio buffer /// The length of the buffer /// The audio format of the buffer public virtual void SendAudio(string idToSendVoice, byte[] audioBuffer, int bufferLength, AudioFormat format) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); lock (_lockerSipDialog) { if (_IDdialogTuple.ContainsKey(idToSendVoice)) { _IDdialogTuple[idToSendVoice].Item2.SendAudio(audioBuffer, bufferLength, format); } } } /// /// Method used to send Gps Request to a Linx device /// /// The sip id to send the gps request /// The sequence id protected void SendGpsRequest(string idToRequestGps, string seqID) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (idToRequestGps != null) { // 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(() => { try { _sipClient.SendRequest(pollRequestForLinx); } catch (Exception) { ; // Probably timeout exception, do not do anything } }); } else throw new ArgumentNullException("idToRequestGps"); } /// /// Method used to acknowledge an emergency alarm sent by a Linx device /// /// The sip id of the Linx device protected void AcknowledgeLinxEmergencyAlarm(string linxID) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (linxID != null) { // 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(() => { try { _sipClient.SendRequest(sipMessage); } catch (Exception) { // Do nothing } }); } else throw new ArgumentNullException("linxID"); } /// /// Method used to send an sms to sip id using the sip protocol /// This method does not block the calling thread while waiting for the confirmation /// /// The sip id where to send the sms /// The sms text /// True if the sms was received, else returns false public async Task SendSmsAsync(string idToSendSMS, string text) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (idToSendSMS != null && text != null) { bool sendSipMessage = true; string unconfirmedSmsKey = null; 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; } } } 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(SmsConfirmedBySipServer, unconfirmedSmsKey); } else if (idToSendSMS == null) throw new ArgumentNullException("idToSendSMS"); else throw new ArgumentNullException("text"); } private Message GenerateSipMessage(string destinationID, string senderID, string sipServer, int sipServerPort, string text) { Message sipMessage = new Message(); sipMessage.Uri = "sip:" + destinationID + "@" + sipServer + ":" + sipServerPort; sipMessage.From = new ContactInfo(string.Format("sip:{0}@{1}", senderID, sipServer)); sipMessage.To = new ContactInfo(string.Format("sip:{0}@{1}", destinationID, sipServer)); sipMessage.ContentType = "text/plain;charset=UTF-8"; sipMessage.Body = text; 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; } /// /// Sends an sms on socket IO /// /// Unique seqID of the sender. It should be in the format sipID.timestamp /// SC ID of the sender /// The sip ID of the destination /// SC ID of the destination /// The text /// True if is group sms, else false 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)); } /// /// Mehod used to check if you are in dialog with a sip id /// /// The sip id /// True if you are in dialog, else returns false public bool InDialogWith(string sipID) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); lock (_lockerSipDialog) { return _IDdialogTuple.ContainsKey(sipID); } } /// /// Stops registration by sending unregister request to the sip server /// Releases all the used resources /// If true, method returns before all the resources are released /// public virtual void Stop(bool async = true) { if (!_sipClassClosed) { _sipClassClosed = true; // Send ARS OFF and disconnect from socket IO if (_sendArsOnOff) _socketIOClass.SendArs(new ArsInfo(false, "0", UserName)); _socketIOClass.Disconect(); // List sipIDs = new List(); 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(); }); if (!async) t.Wait(); } } /// /// Stops registration by sending unregister request to the sip server /// public void StopRegisterToSipServer() { // Stop registering to groups List idsToUnregisterFrom = new List(_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); _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; } } /// /// 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 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; } /// /// Function that processes all the received Sip Message requests /// /// The Sip Message request /// The ID of the sender 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; else if (lastWord.Contains("failed")) return false; else throw new ApplicationException("Error on parsing the sms confirmation from sip server"); } #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; if (IPAddress.TryParse(receivedSDP.Connection.Address, out ipToSendAudio)) { portToSendAudio = receivedSDP.Media[0].Port; } else throw new ApplicationException("Canot determine ip where to send audio"); // 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(); //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 timerHangTime = null; if (HangTimeDuration != -1) { timerHangTime = new TimerWithTag(HangTimeDuration * 1000, radioInDialogWith); timerHangTime.AutoReset = false; timerHangTime.Elapsed += timerHangTime_Elapsed; timerHangTime.Start(); } Tuple> 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 timer = (TimerWithTag)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> 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) { SipClient sipClient; // Set up the master SIP class sipClient = new SipClient(sipDomain, sipDomainPort, Independentsoft.Sip.ProtocolType.Udp, userName, password); 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); 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; RegistrationData regData = (RegistrationData)registrationDataObj; string sipIDfrom = _sipClient.Username; string sipIDto = regData.SipID; int expiresValue = regData.Expires; bool isUnregisterRequest = (expiresValue == 0); Register reg = new Register(); reg.Uri = "sip:" + sipServerIP; reg.From = new ContactInfo( sipIDfrom.ToString(), "sip:" + sipIDfrom + "@" + sipServerIP); reg.To = new ContactInfo( sipIDto.ToString(), "sip:" + sipIDto.ToString() + "@" + sipServerIP); 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(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=
// must be unique int sessionID = _rand.Next(); // - 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= // - 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); // 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; } if (rtpPort < MaxRtpPortNumber) return rtpPort; else throw new SipClassException( 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; }); if (inv != null) return true; else return false; } private bool IsPortAllreadyInUse(int portNumber) { return (from p in System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners() where p.Port == portNumber select p).Count() == 1; } /// /// Checks if an integer can be used as a rtp port /// /// The integer to be cecked /// True if is a valid rtp port, else false public static bool ValidRtpPort(int rtpPort) { if (rtpPort < 1024 || rtpPort > 65534) { return false; } else if (rtpPort % 2 == 1) { return false; } else return true; } /// /// 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. /// /// The remote end point /// The selected local end point 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 /// /// Occurs when you send an invite to a sip id /// public event EventHandler InviteSent; /// /// Occurs when you cancel a sent invite /// public event EventHandler InviteSentCanceled; /// /// Occurs when you receive an invite /// public event EventHandler InviteReceived; /// /// Occurs when an invite is cancelled before you had time to accept or reject it. /// public event EventHandler InviteReceivedCanceled; /// /// Occurs when you decline an invite from another user /// public event EventHandler InviteReceivedDeclined; /// /// An error occured while attempting to establish a voice session /// public event EventHandler ErrorOnCreatingDialog; /// /// Occurs when a voice session (a dialog) has been established /// public event EventHandler DialogCreated; /// /// Occurs when a voice session (a dialog) is closed /// public event EventHandler DialogClosed; /// /// Ocurs when a voice buffer is received from a sip id /// public event EventHandler VoiceReceived; /// /// Occurs when you receive an sms as a Sip message /// public event EventHandler SipSmsReceived; /// /// Occurs when the sip registration status changes /// public event EventHandler RegistrationStateChanged; /// /// Occurs when a GPS report is received from a Linx device /// public event EventHandler GpsReportReceived; /// /// Occurs when a periodically GPS report is received /// public event EventHandler GpsPeriodicallyReportReceived; /// /// Occurs when an emergency alarm is received from a Linx device /// public event EventHandler EmergencyAlarmReceived; /// /// Occurs when an Ars command is received from a Linx device /// protected event EventHandler LinxArsReceived; /// /// Occurs when an sms is received on socketIO /// public event EventHandler SocketIOSmsReceived; /// /// Occurs when an sms ack is received on socketIO /// public event EventHandler SocketIOSmsAckReceived; /// /// Occurs when hangtime is ended /// public event EventHandler 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 handler = InviteSent; if (handler != null) handler(this, e); } private void OnInviteSentCanceled(SipEventArgs e) { EventHandler handler = InviteSentCanceled; if (handler != null) handler(this, e); } /// /// This is the method that raises the ErrorOnCreatingDialog event /// /// Data for the ErrorOnCreatingDialog event /// Do not forget to call the base version when overrinding, /// otherwize the ErrorOnCreatingDialog event will not fire /// protected virtual void OnError(ErrorEventArgs e) { EventHandler handler = ErrorOnCreatingDialog; if (handler != null) handler(this, e); } private void OnInviteReceived(InviteReceivedArgs e) { EventHandler handler = InviteReceived; if (handler != null) handler(this, e); } private void OnInviteReceivedCanceled(SipEventArgs e) { EventHandler handler = InviteReceivedCanceled; if (handler != null) handler(this, e); } private void OnInviteReceivedDeclined(SipEventArgs e) { EventHandler handler = InviteReceivedDeclined; if (handler != null) handler(this, e); } /// /// 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 virtual void OnDialogCreated(LinxDialogCreatedEventArgs e) { EventHandler handler = DialogCreated; if (handler != null) handler(this, e); } /// /// 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 virtual void OnDialogClosed(LinxDialogClosedEventArgs e) { EventHandler handler = DialogClosed; if (handler != null) { handler(this, e); } } /// /// 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 virtual void OnVoiceReceived(LinxAudioEventArgs e) { EventHandler handler = VoiceReceived; if (handler != null) handler(this, e); } private void OnRegistrationStateChanged(RegistrationStateChangedEventArgs e) { EventHandler handler = RegistrationStateChanged; if (handler != null) handler(this, e); } private void OnSipSmsReceived(SmsReceivedEventsArgs e) { EventHandler handler = SipSmsReceived; if (handler != null) handler(this, e); } private void OnGpsPeriodicallyReportReceived(GpsDataEventArgs e) { EventHandler handler = GpsPeriodicallyReportReceived; if (handler != null) handler(this, e); } private void OnLinxEmergencyAlarmReceived(Linx.LinxEmergencyAlarmReceivedEventArgs e) { EventHandler handler = EmergencyAlarmReceived; if (handler != null) handler(this, e); } private void OnGpsReportReceived(LinxGpsDataEventArgs e) { EventHandler handler = GpsReportReceived; if (handler != null) handler(this, e); } private void OnLinxArsReceived(Linx.ArsReceivedEventArgs e) { EventHandler handler = LinxArsReceived; if (handler != null) handler(this, e); } #endregion } }