using Independentsoft.Sip; using Independentsoft.Sip.Methods; using Independentsoft.Sip.Responses; using Independentsoft.Sip.Sdp; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace SipComponent { /// /// SipClientClass is an abstract class responsible for sending and receiving Sip messages and RTP packets, /// in accordance with the DMR Ais Specification /// public abstract class SipClientClass { #region Private Fields private SipClient _sipClient; //private System.Timers.Timer _registrationTimer; 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; private readonly bool _forSimoco = 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 #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); } } #endregion // 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 /// Local Ip adress. If not specified, the class will search for a local ip on the same network with the sip server ip /// True if the class is used for sip communication with Simoco ais-gateway internal SipClientClass(string sipDomain, int sipDomainPort, int localSipPort, string userName, string password, int registrationInterval, int bufferMiliseconds, int requestTimeout, string localIPAddress = null, bool simoco = false) { _forSimoco = simoco; if (localIPAddress == null) //_localIPaddress = GetLocalAddresOnNetworkWith(IPAddress.Parse(sipDomain)); { _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(); } #region Public Methods /// /// Sends a Sip Invite command to an user /// /// The sip id of the user /// Type of call: full duplex or half duplex protected void Invite(string idToInvite, TypeOfCall typeOfCall) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (idToInvite == null) throw new ArgumentNullException("idToInvite"); if (_forSimoco && typeOfCall != TypeOfCall.HALF_DUPLEX) throw new SipClassException("Cannot make a full duplex call with Simoco"); lock (_lockerSipDialog) { if (!_IDsentInviteDict.ContainsKey(idToInvite) && !_IDreceivedInviteDict.ContainsKey(idToInvite) && !_IDdialogTuple.ContainsKey(idToInvite)) { SendSipInvite(idToInvite, typeOfCall); } } } /// /// Sends a Sip Invite command to a Simoco Group /// /// The sip id of the group protected 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 (!_IDdialogTuple.ContainsKey(groupIDtoInvite)) SendSipInvite(groupIDtoInvite, TypeOfCall.HALF_DUPLEX, true); } } /// /// 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 // Check if invite is full or half duplex TypeOfCall typeOfCall = GetTypeOfCall(receivedInvite); // Check if it was group invite bool isGroupCall = false; if (receivedInvite.Header.ContainsKey("Ais-Reach")) if (receivedInvite.Header["Ais-Reach"] == "group") isGroupCall = true; int port = ReturnAvailablePort(); okResponseToInvite.SessionDescription = CreateSDP(port, typeOfCall); // Try to create the dialog try { CreatingDialog(callingSipID, isGroupCall, receivedInvite.SessionDescription, typeOfCall, port, false); // 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 bool wasGroupCall; ClosingDialog(callingSipID, _sipClient.Username, out wasGroupCall); 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 /// true if the dialog was closed, else false 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; bool wasGroupCall = false; lock (_lockerSipDialog) { Dialog dialogToClose = _sipClient.GetDialogWith(idToClose); if (dialogToClose != null) { if (ClosingDialog(idToClose, _sipClient.Username, out wasGroupCall)) { 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 DialogClosedEventArgs( idToClose, _sipClient.Username, wasGroupCall, reason)); 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)); } /// /// Accepts a PTT Request from an user /// /// The sip ID of the user protected void GrantPTT(string idToGrantPTT) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (idToGrantPTT != null) { lock (_lockerSipDialog) { if (_IDdialogTuple.ContainsKey(idToGrantPTT)) { RTPListener2 rtpListener = _IDdialogTuple[idToGrantPTT].Item3; if (rtpListener.ReceivedTSNStatus == TSNstatus.Requesting) { rtpListener.SetRequestedTSNstatus(new TSNinfo(rtpListener.LastRequestedTSNReceived) { Status = TSNstatus.Granted }); _IDdialogTuple[idToGrantPTT].Item2.SendPTTGrant(rtpListener.LastRequestedTSNReceived); } } } } else throw new ArgumentNullException("idToGrantPTT"); } /// /// Method used to check if you received PTT Grant from the specified user /// /// The sip id of the user /// True if you received ptt Grant, else false protected bool PTTGrantReceived(string sipIDinDialogWith) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (sipIDinDialogWith != null) { if (_IDdialogTuple.ContainsKey(sipIDinDialogWith)) return _IDdialogTuple[sipIDinDialogWith].Item2.SentTSNstatus == TSNstatus.Granted; else return false; } else throw new ArgumentNullException("idInDialogWith"); } /// /// Method used to send Deny when you receive a PTT Request /// /// The sip ID sending PTT Request protected void DenyPTT(string idToDenyPTT) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (idToDenyPTT != null) { lock (_lockerSipDialog) { if (_IDdialogTuple.ContainsKey(idToDenyPTT)) { RTPListener2 rtpListener = _IDdialogTuple[idToDenyPTT].Item3; if (rtpListener.ReceivedTSNStatus == TSNstatus.Requesting) { rtpListener.SetRequestedTSNstatus(new TSNinfo(rtpListener.LastRequestedTSNReceived) { Status = TSNstatus.Denied }); _IDdialogTuple[idToDenyPTT].Item2.SendPTTDeny(rtpListener.LastRequestedTSNReceived); } } } } else throw new ArgumentNullException("idToDenyPTT"); } /// /// Method used to start sending PTT Requests to a sip id /// /// The sip ID to send PTT Requests protected void RequestPTT(string sipID) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); byte tsn = GenerateNewTSN(); lock (_lockerSipDialog) { if (_IDdialogTuple.ContainsKey(sipID)) { // Send PTT request RTPSender2 rtpSender = _IDdialogTuple[sipID].Item2; rtpSender.StartSendingPTTrequests(tsn); } } } /// /// Method used to send End PTT to a sip id /// /// The sip id to send End PTT protected void EndPTT(string sipID) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); lock (_lockerSipDialog) { if (_IDdialogTuple.ContainsKey(sipID)) { if (_IDdialogTuple[sipID].Item2.SentTSNstatus == TSNstatus.Granted) { // Sent PTT End RTPSender2 rtpSender = _IDdialogTuple[sipID].Item2; for (int i = 0; i < DMR.T_numend; i++) { rtpSender.SendPTTEnd(); System.Threading.Thread.Sleep(DMR.T_end); } // Restart the hangTime timer TimerWithTag timer = _IDdialogTuple[sipID].Item4; if (timer != null) { timer.Start(); } } } } } /// /// 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 a Gps Request to a Simoco radio /// /// The simoco ID to send Gps Request public void SendSimocoGpsRequest(string idToRequestGPS) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (idToRequestGPS != null) { if (_forSimoco) { // Send Gps request for Simoco Message gpsRequest = GenerateSipGpsRequestMessage(idToRequestGPS, UserName, _sipClient.Domain, _sipDomainPort); Task.Factory.StartNew(() => { try { _sipClient.SendRequest(gpsRequest); } catch (Exception) { // Probably timeout exception ; } }); } else throw new SipClassException("To use SendSimocoGpsRequest() set Simoco value in constructor"); } else throw new ArgumentNullException("idToRequestGPS"); } /// /// Method used to send Gps Request to a Linx device /// /// The sip id to send the gps request /// The sequence id protected void SendLinxGpsRequest(string idToRequestGps, string seqID) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (!_forSimoco) { if (idToRequestGps != null) { // Send Gps request for Linx // [#msgLen]#seqID#154# string cmdText = string.Format("#{0}#154#", seqID); string cmd = AddMsgLenForMBus(cmdText); Message pollRequestForLinx = SipMessageGenerator.Generate(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"); } else throw new SipClassException("SendLinxGpsRequest does not work for Simoco"); } /// /// Method used to send a Ping command to a Simoco radio /// /// The Simoco ID of the radio protected void SendSimocoPing(string idToSendPing) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (idToSendPing != null) { if (_forSimoco) { Byte[] bytesToSend = new byte[3]; _rand.NextBytes(bytesToSend); if (!_ID_pingBytest_timer_dict.ContainsKey(idToSendPing)) { TimerWithTag pingTimer = new TimerWithTag(2000, idToSendPing); pingTimer.Elapsed += pingTimer_Elapsed; pingTimer.AutoReset = false; pingTimer.Start(); _ID_pingBytest_timer_dict.Add(idToSendPing, new Tuple>(bytesToSend, pingTimer)); // Send ping to simoco Message pingMessage = GenerateSipPingMessage(idToSendPing, UserName, _sipClient.Domain, _sipDomainPort, bytesToSend); Task.Factory.StartNew(() => { try { RequestResponse rr = _sipClient.SendRequest(pingMessage); } catch (Exception) { // probably timeout exception // don't do anything ; } }); } } else throw new ApplicationException("Cannot send ping command if simoco parameter was not specified in constructor"); } else throw new ArgumentNullException("idToSendPing"); } /// /// Method used to send a Ping command to an Excera radio /// /// The Excera ID of the radio protected void SendExceraPing(string idToSendPing) { Message ping = GenerateExceraRadioCheckMessage(idToSendPing, UserName, _sipClient.Domain, _sipDomainPort); Task.Factory.StartNew(() => { try { RequestResponse rr = _sipClient.SendRequest(ping); if (rr.Response.StatusCode == 200) // I received OK { OnSimocoPingResponseReceived(new SipEventArgs(idToSendPing)); } else { OnSimocoPingRequestFailed(new SipEventArgs(idToSendPing)); } } catch (Independentsoft.Sip.TimeoutException tEx) { throw new SipClassException("Timeout exception, could not reach the Excera repeater", tEx); } catch (Exception ex) { throw new SipClassException("Error when trying to send ping to excera", ex); } }); } void pingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { string id = ((TimerWithTag)sender).Tag; if (_ID_pingBytest_timer_dict.ContainsKey(id)) { _ID_pingBytest_timer_dict.Remove(id); // Fire event OnSimocoPingRequestFailed(new SipEventArgs(id)); } } /// /// Method used to acknowledge a Simoco emergency status report /// /// The id of the Simoco radio protected void AcknowledgeSimocoEmergencyStatusReport(string simocoID) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (!_forSimoco) throw new ApplicationException("AcknowledgeSimocoEmergencyStatusReport() is used just for Simoco"); if (simocoID != null) { if (_simocoID_emergencyStatusReport_dict.ContainsKey(simocoID)) { Message ackMessage = GenerateSimocoAckForDeviceStatusReport(simocoID, UserName, _sipClient.Domain, _sipDomainPort, _simocoID_emergencyStatusReport_dict[simocoID]); Task.Factory.StartNew(() => { try { _sipClient.SendRequest(ackMessage); } catch (Exception) { // don't do anything } }); } } else throw new ArgumentNullException("simocoID"); } /// /// 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 (_forSimoco) throw new ApplicationException("AcknowledgeLinxEmergencyAlarm cannot be used for Simoco"); if (linxID != null) { // Send [#msgLen]#seqID#238# string textToSend = string.Format("#{0}#238#", _rand.Next().ToString()); string cmdToSend = AddMsgLenForMBus(textToSend); Message sipMessage = SipMessageGenerator.Generate(linxID, UserName, _sipClient.Domain, _sipDomainPort, cmdToSend); Task.Factory.StartNew(() => { try { _sipClient.SendRequest(sipMessage); } catch (Exception) { // Do nothing } }); } else throw new ArgumentNullException("linxID"); } /// /// Just for testing purposes /// public void SendLocation(string idToSendLocation, double latitude, double longitude, double speed) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (_forSimoco) throw new ApplicationException("Cannot send location to Simoco Ais-Gateway"); if (idToSendLocation != null) { // #msgLen#seqID#Opcode#remoteRadioId#locationTime#speed#latitude# longitude# // #seqID#Opcode#remoteRadioId#locationTime#speed#latitude# longitude# string textToSend = string.Format("#12345#131#{0}#{1}#{2}#{3}#{4}#", UserName, DateTimeToUnixTime(DateTime.UtcNow).ToString(CultureInfo.InvariantCulture), speed.ToString(CultureInfo.InvariantCulture), latitude.ToString(CultureInfo.InvariantCulture), longitude.ToString(CultureInfo.InvariantCulture)); string cmd = AddMsgLenForMBus(textToSend); Message gpsMessage = SipMessageGenerator.Generate(idToSendLocation, UserName, _sipClient.Domain, _sipDomainPort, cmd); gpsMessage.Header.Add("Ais-Service", "mbus"); Task.Factory.StartNew(() => { try { _sipClient.SendRequest(gpsMessage); } catch (Exception) { ; // timout expired - do nothing } }); } else throw new ArgumentNullException("idToSendLocation"); } /// /// Method used to send an sms to sip id /// 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 is a sip group sms /// True if the sms was received, else returns false public async Task SendSmsAsync(string idToSendSMS, string text, bool isGroupSms = false) { 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 = SipMessageGenerator.Generate(idToSendSMS, _sipClient.Username, _sipClient.Domain, _sipDomainPort, text, isGroupSms); 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"); } /* /// /// Generates a Sip message using the specified parameters /// /// /// /// /// /// /// /// protected virtual Message GenerateSipMessage(string destinationID, string senderID, string sipServer, int sipServerPort, string text, bool simoco = false) { 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)); if (simoco) { sipMessage.ContentType = "application/octet-stream"; // Softul de pe AIS are nevoie de punct si virgula (;) dupa short-data sipMessage.Header.Add("Ais-Service", "short-data;"); sipMessage.Header.Add("Ais-Options", "service-attributes; coding=unicode-16"); sipMessage.Header.Add("Content-Transfer-Encoding", "base64"); sipMessage.Body = Convert.ToBase64String(Encoding.BigEndianUnicode.GetBytes(text)); } else { sipMessage.ContentType = "text/plain;charset=UTF-8"; sipMessage.Body = text; } return sipMessage; } */ private Message GenerateSipGpsMessage(string destinationID, string senderID, string sipServer, int sipServerPort, string gpsText) { Message sipGpsMessage = new Message(); sipGpsMessage.Uri = "sip:" + destinationID + "@" + sipServer + ":" + sipServerPort; sipGpsMessage.From = new ContactInfo(string.Format("sip:{0}@{1}", senderID, sipServer)); sipGpsMessage.To = new ContactInfo(string.Format("sip:{0}@{1}", destinationID, sipServer)); sipGpsMessage.ContentType = "text/plain;charset=UTF-8"; //sipGpsMessage.ContentType = "application/octet-stream"; sipGpsMessage.Header[StandardHeader.ContentEncoding] = "base64"; sipGpsMessage.Header.Add("Ais-Service", "short-data"); sipGpsMessage.Header.Add("Ais-Options", "service-attributes; coding=nmea"); sipGpsMessage.Body = gpsText; return sipGpsMessage; } private Message GenerateSipPingMessage(string destinationID, string senderID, string sipServer, int sipServerPort, byte[] bytesToSend) { Message pingMessage = new Message(); pingMessage.Uri = "sip:" + destinationID + "@" + sipServer + ":" + sipServerPort; pingMessage.From = new ContactInfo(string.Format("sip:{0}@{1}", senderID, sipServer)); pingMessage.To = new ContactInfo(string.Format("sip:{0}@{1}", destinationID, sipServer)); //message.Contact = new Contact("sip:" + tbUsername.Text + "@" + _sipClient.LocalIPEndPoint.ToString()); // byte 0 - 1 length (2 - bytes) = nr of bytes following // byte 2 Type (1 - byte) = 0x02 = SUP Type - UI // byte 3 Command = 0xA0 // byte 4 - 7 Radio ID (Target or source) // byte 8 - end Management payload = as described in 3.2 - Management Payload byte[] ping = Simoco.SP6.PingCommand(bytesToSend); pingMessage.ContentType = "application/octet-stream"; pingMessage.Body = Convert.ToBase64String(ping); pingMessage.Header.Add("Ais-Reach", "individual"); // Softul de pe AIS are nevoie de punct si virgula (;) dupa short-data pingMessage.Header.Add("Ais-Service", "short-data;"); pingMessage.Header.Add("Ais-Options", "service-attributes; coding=iso8"); pingMessage.Header.Add("Content-Transfer-Encoding", "base64"); return pingMessage; } private Message GenerateExceraRadioCheckMessage(string destinationID, string senderID, string sipServer, int sipServerPort) { Message pingMessage = new Message(); pingMessage.Uri = "sip:" + destinationID + "@" + sipServer + ":" + sipServerPort; pingMessage.From = new ContactInfo(string.Format("sip:{0}@{1}", senderID, sipServer)); pingMessage.To = new ContactInfo(string.Format("sip:{0}@{1}", destinationID, sipServer)); pingMessage.Header.Add("Ais-Msg-id", "radio-check-rq"); pingMessage.Header.Add("Ais-Service", "ms-supp"); return pingMessage; } private Message GenerateSimocoAckForDeviceStatusReport(string destinationID, string senderID, string sipServer, int sipServerPort, string text) { Message ackMessage = new Message(); ackMessage.Uri = "sip:" + destinationID + "@" + sipServer + ":" + sipServerPort; ackMessage.From = new ContactInfo(string.Format("sip:{0}@{1}", senderID, sipServer)); ackMessage.To = new ContactInfo(string.Format("sip:{0}@{1}", destinationID, sipServer)); //message.Contact = new Contact("sip:" + tbUsername.Text + "@" + _sipClient.LocalIPEndPoint.ToString()); ackMessage.ContentType = "application/octet-stream"; ackMessage.Body = text; // Softul de pe AIS are nevoie de punct si virgula (;) dupa short-data ackMessage.Header.Add("Ais-Service", "short-data;"); ackMessage.Header.Add("Ais-Options", "service-attributes; coding=iso8"); return ackMessage; } private Message GenerateSipGpsRequestMessage(string destinationID, string senderID, string sipServer, int sipServerPort) { Message gpsReqMessage = new Message(); gpsReqMessage.Uri = "sip:" + destinationID + "@" + sipServer + ":" + sipServerPort; gpsReqMessage.From = new ContactInfo(string.Format("sip:{0}@{1}", senderID, sipServer)); gpsReqMessage.To = new ContactInfo(string.Format("sip:{0}@{1}", destinationID, sipServer)); gpsReqMessage.ContentType = "application/octet-stream"; gpsReqMessage.ContentEncoding = "base64"; byte[] gpsRequest = Simoco.SP6.GPSRequestCommand(); gpsReqMessage.Body = Convert.ToBase64String(gpsRequest); // Softul de pe AIS are nevoie de punct si virgula (;) dupa short-data gpsReqMessage.Header.Add("Ais-Service", "short-data;"); gpsReqMessage.Header.Add("Ais-Options", "service-attributes; coding=iso8"); gpsReqMessage.Header.Add("Content-Transfer-Encoding", "base64"); return gpsReqMessage; } 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; } /// /// 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; 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); } //foreach (string groupID in _groupID_regTimer_regStatus_Dict.Keys) //{ // RegistrationData zeroRegistrationGroup = new RegistrationData(groupID, 0, true); // _groupID_regTimer_regStatus_Dict[groupID].Item1.Stop(); // SendSipRegister(zeroRegistrationGroup); //} /* // Stop registering to the sip id if (_registrationTimer.Enabled) { _registrationTimer.Stop(); RegistrationData zeroRegistration = new RegistrationData(_sipClient.Username, 0); SendSipRegister(zeroRegistration); } */ } /// /// Register the Sip class to a simoco group id /// /// id of the simoco group protected void RegisterToSimocoGroup(string simocoGroupID) { if (_sipClassClosed) throw new ObjectDisposedException("SipClientClass"); if (simocoGroupID != null) { if (_forSimoco) { if (!_sipID_regTimer_regStatus_Dict.ContainsKey(simocoGroupID)) { // Create a timer for sending registration to simoco group // Simoco accepts only an expire value of max 30s int registrationInterval = _rand.Next(17, 31); System.Timers.Timer timer = new System.Timers.Timer(registrationInterval * 1000); timer.Elapsed += (sender, e) => { SendSipRegister(new RegistrationData(simocoGroupID, registrationInterval + 2, true)); }; // Start the timer timer.Start(); _sipID_regTimer_regStatus_Dict.Add(simocoGroupID, new Tuple(timer, RegistrationStatus.RegistrationNotStarted )); _IDsregisteredList.Add(simocoGroupID); // Send register to group Task.Factory.StartNew(() => { SendSipRegister(new RegistrationData(simocoGroupID, registrationInterval + 2, true)); }); } } else throw new SipClassException("Cannot register to Simoco group if simoco parameter was not set in constructor!"); } else throw new ArgumentNullException("simocoGroupID"); } #endregion #region RTP Listener Event Handlers private void ReceivedVoice(object sender, AudioEventArgs e) { OnVoiceReceived(e); } void PTTControlReceived(object sender, PTTEventArgs e) { string sipIDSendingRTP = e.RadioID.ToString(); string sipIDinDialogWith = e.SipIDinDialogWith.ToString(); if (!_IDdialogTuple.ContainsKey(sipIDinDialogWith)) return; RTPSender2 rtpSender = _IDdialogTuple[sipIDinDialogWith].Item2; RTPListener2 rtpListener = _IDdialogTuple[sipIDinDialogWith].Item3; TimerWithTag timerHangtime = _IDdialogTuple[sipIDinDialogWith].Item4; switch (e.PTTTypeEnum) { case PTT_Type.Request: PTTrequestEventArgs ev = new PTTrequestEventArgs(sipIDSendingRTP); OnPTTrequestReceived(ev); if (ev.Grant.HasValue) { if (ev.Grant.Value) { rtpListener.SetRequestedTSNstatus(new TSNinfo(e.TSN) { Status = TSNstatus.Granted }); rtpSender.SendPTTGrant(e.TSN); } else { rtpListener.SetRequestedTSNstatus(new TSNinfo(e.TSN) { Status = TSNstatus.Denied }); rtpSender.SendPTTDeny(e.TSN); } } break; case PTT_Type.Grant: // Received grant for ptt // Verific tsn-ul if (rtpSender.IsRequestingPTT(e.TSN)) { // Stop sending PTT requests rtpSender.StopSendingPTTrequests(true); OnPTTrequestGranted(sipIDSendingRTP, rtpListener.IsGroupCall); } break; case PTT_Type.Progress: break; case PTT_Type.End: // Start the hangtime timer if (timerHangtime != null) { timerHangtime.Start(); } OnPTTEndReceived(new RtpEventArgs(sipIDinDialogWith, sipIDSendingRTP)); break; case PTT_Type.Start: // Stop the hangtime timer if (timerHangtime != null) { timerHangtime.Stop(); } OnPTTStartReceived(new RtpEventArgs(sipIDinDialogWith, sipIDSendingRTP)); break; case PTT_Type.Deny: // Received Deny for ptt // Verific tsn-ul if (rtpSender.IsRequestingPTT(e.TSN)) { // Stop sending PTT requests rtpSender.StopSendingPTTrequests(false); // Send PTT End rtpSender.SendPTTEnd(); // Fire Event OnPTTrequestDenied(sipIDSendingRTP); } break; case PTT_Type.Heartbeat: // Stop sending PTT heartbeat query rtpSender.StopSendingHeartbeatQuery(); // Start sending PTT heartbeat rtpSender.StartSendingHeartbeat(); break; case PTT_Type.HeartbeatQuery: // Respond whith Heartbeat // Start heatbeat queary rtpSender.StartSendingHeartbeat(); break; default: break; } } void TimerAliveExpired(object sender, EventArgs e) { // Close the Sip Session with Bye RTPListener2 rtpListener = (RTPListener2)sender; //Close(rtpListener.SipIDinDialogWith.ToString()); Task t = null; Close_private(rtpListener.SipIDinDialogWith.ToString(), out t, DialogClosedReason.TimerAliveTimeExpired); } void ExceptionThrown(object sender, EventArgs e) { RTPListener2 rtpListener = (RTPListener2)sender; // Close the Sip Session with Bye /* Task t = null; Close_private(rtpListener.SipIDinDialogWith.ToString(), out t, DialogClosedReason.Error); */ // A better (?) solution would be to start the listener again rtpListener.Start(); } #endregion #region RTP Sender Event Handlers void MaximumNbOfPttRequestsWasSent(object sender, EventArgs e) { RTPSender2 rtpSender = (RTPSender2)sender; OnPTTrequestFailed(rtpSender.DMR_Destination.ToString()); } void rtpSender_SendingPTTStart(object sender, EventArgs e) { // Stop the hangtime timer RTPSender2 rtpSender = (RTPSender2)sender; string key = rtpSender.DMR_Destination.ToString(); if (_IDdialogTuple.ContainsKey(key)) { TimerWithTag timer = _IDdialogTuple[key].Item4; if (timer != null) { timer.Stop(); } } } #endregion #region Abstract internal abstract SipMessageGenerator SipMessageGenerator { get; } internal abstract RTPListener2 CreateRTPListener(UdpClient udpClient, bool initiatedByMe, int bufferMiliseconds, int sipIdInDialogWith, bool isGroupCall, TypeOfCall typeOfCall); internal abstract RTPSender2 CreateRTPSender(UdpClient udpClient, int audioBitrate, IPEndPoint iPEndPoint, int dmrSourceID, int dmrDestinationID, bool initiatedByMe, TypeOfCall typeOfCall, RtpCallType halfDuplexCallType = RtpCallType.Private); #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 = ""; bool isGroupCall = false; TypeOfCall typeOfCall = TypeOfCall.HALF_DUPLEX; // Gasesc Invite-ul pentru care am primit OK lock (_lockerSipDialog) { if (_IDsentInviteDict.ContainsKey(responserRadioID)) { Invite sentInvite = _IDsentInviteDict[responserRadioID]; // Check if it was group invite if (sentInvite.Header.ContainsKey("Ais-Reach")) if (sentInvite.Header["Ais-Reach"] == "group") isGroupCall = true; // Get the type: full or half duplex typeOfCall = GetTypeOfCall(resp.SessionDescription); try { // Send sip Ack _sipClient.Ack(resp); // Creez Dialogul CreatingDialog(responserRadioID, isGroupCall, resp.SessionDescription, typeOfCall, sentInvite.SessionDescription.Media[0].Port, true); success = true; } catch (Exception ex) { // Nu am reusit sa setez dialogul errorMsg = ex.Message; // Fac DialogClosed(); bool wasGroupCall; if (ClosingDialog(responserRadioID, _sipClient.Username, out wasGroupCall)) { // 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 DialogCreatedEventArgs(responserRadioID, _sipClient.Username, typeOfCall, isGroupCall)); 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; TypeOfCall typeOfCall = TypeOfCall.FULL_DUPLEX; // Send quickly a trying before analysing the request (Excera suggestion) _sipClient.SendResponseTrying(req); lock (_lockerCallingMe) { if (!_IDreceivedInviteDict.ContainsKey(requesterRadioId)) { // Check if is group invite (Simoco) if (req.Header.Contains("Ais-Reach")) { if (req.Header["Ais-Reach"] == "group") { if (_forSimoco) { idInDialogWith = requesterTargetId; } else { _sipClient.SendResponseBusyHere(req); break; } } } // Check if is emergency call (Simoco) if (req.Header.Contains(StandardHeader.Priority)) if (req.Header[StandardHeader.Priority] == "emergency") isEmergencyCall = true; // Check if is full or half duplex try { typeOfCall = GetTypeOfCall(req); } catch (ArgumentException) { // Invite was not correctly formated // send busyHere #if DEBUG Console.WriteLine("Received the following incorrect invite:" + System.Environment.NewLine + req.ToString()); #endif _sipClient.SendResponseBusyHere(req); break; } catch (Exception) { // TO DO - for now do nothing to see the error //_sipClient.SendResponseBusyHere(req); //break; } 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); } } } if (fireEvent) { OnInviteReceived(new InviteReceivedArgs(idInDialogWith, requesterRadioId, typeOfCall, isEmergencyCall)); } break; case SipMethod.Bye: // Accept the request _sipClient.AcceptRequest(req); string idToClose = requesterRadioId; if (requesterTargetId != _sipClient.Username) idToClose = requesterTargetId; // This is for Simoco group calls, when a radio sends Bye bool wasGroupCall; if (ClosingDialog(idToClose, requesterRadioId, out wasGroupCall)) { // FireEvent; OnDialogClosed(new DialogClosedEventArgs( idToClose, requesterRadioId, wasGroupCall, DialogClosedReason.NormalClosing)); } 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: // Since Excera, I don't require the ACK message to have a Contact header //if (req.Header.Contains(StandardHeader.Contact)) //{ // We received an Invite, sent Ok and now we receive ack bool isGroupCall = false; GetInfoFromAckRequest(requesterRadioId, requesterTargetId, out idInDialogWith, out isGroupCall); // Dialog confirmed ? if (_IDdialogTuple.ContainsKey(idInDialogWith)) { OnDialogCreated(new DialogCreatedEventArgs( requesterTargetId, requesterRadioId, _IDdialogTuple[idInDialogWith].Item2.TypeOfCall, isGroupCall)); } //} 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) { return true; } /// /// 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 Simoco (Or gps messages sent exactly as Simoco does) // Get Ais-Options Header value if (sipMessageRequest.Header.Contains("Ais-Options")) { string ais_options = sipMessageRequest.Header["Ais-Options"]; switch (ais_options) { case "service-attributes; coding=nmea": // GPS periodically report double lat, lon, speed; DateTime time; // Get latitude, longitude, speed and time Simoco.Utils.GPSInfo(Convert.FromBase64String(sipMessageRequest.Body), out lat, out lon, out speed, out time); // Raise Event OnGpsPeriodicallyReportReceived(new GpsDataEventArgs( senderID, lat, lon, speed, time)); break; case "service-attributes; coding=iso8": // Management message report // Get the Type and the command bytes byte[] data = Convert.FromBase64String(sipMessageRequest.Body); byte type = SipComponent.Simoco.SP6.GetType(data); byte command = SipComponent.Simoco.SP6.GetCommand(data); switch (type) { case 0: // GPS position report byte[] nmeaData = new byte[10]; Array.Copy(data, 5, nmeaData, 0, nmeaData.Length); // Get latitude, longitude, speed and time from nmea data Simoco.Utils.GPSInfo(nmeaData, out lat, out lon, out speed, out time); // Raise event OnSimocoGpsReportReceived(new GpsDataEventArgs(senderID, lat, lon, speed, time)); break; case 1: // Device status switch (command) { case 1: // Device status report // Sent on request or when alarm conditions change // Is sent to remote radio to acknowledge receipt of alarm conditions // Get GPS from status report Simoco.Utils.GetGPSFromStatusReport(data, out lat, out lon); Simoco.AlarmType alarmType = Simoco.SP6.GetAlarmType(data); if (alarmType != Simoco.AlarmType.NO_ALARM) { // Save the message in a dictionary // It have to be resent to the radio to confirm the receipt AddSimocoAlarmToDictionary(senderID, sipMessageRequest.Body); } // Raise event OnSimocoStatusReportReceived(new SimocoDeviceStatusReportEventArgs( senderID, lat, lon, alarmType)); break; } break; case 255: // Ping int nbOfReceivedBytes = data.Length - 5; byte[] pingData = new byte[nbOfReceivedBytes]; Array.Copy(data, 5, pingData, 0, pingData.Length); if (HandleSimocoPingResponse(senderID, pingData)) { // Fire Event OnSimocoPingResponseReceived(new SipEventArgs(senderID)); } break; } break; default: // This is surelly incorect - TO DO // Sms from Simoco if (_forSimoco) { string text = sipMessageRequest.Body; if (text != null) // body is null in case of status message - future implementation - TO DO { OnSmsReceived(new SmsReceivedEventsArgs(senderID, Encoding.BigEndianUnicode.GetString(Convert.FromBase64String(text)) )); } } break; } } else if (!_forSimoco) { // 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") { OnLinxGpsReportReceived(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]; OnSmsReceived(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 sipIdInDialogWith, bool isGroupCall, SessionDescription receivedSDP, TypeOfCall typeOfCall, int localRTPport, bool initiatedByMe) { 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(sipIdInDialogWith)) { // 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 = CreateRTPSender( udpClient, AudioBitrate, new IPEndPoint(ipToSendAudio, portToSendAudio), int.Parse(_sipClient.Username), int.Parse(sipIdInDialogWith), initiatedByMe, typeOfCall, isGroupCall ? RtpCallType.Group : RtpCallType.Private); rtpSender.SentMaxNumberOfPTTrequests += MaximumNbOfPttRequestsWasSent; rtpSender.SendingPTTStart += rtpSender_SendingPTTStart; // //_pttControlBlockingCollection = new BlockingCollection(); //Task.Factory.StartNew(() => //{ // ProcessPTTReceived(); //}); // Incep sa ascult RTP trimis de Simoco RTPListener2 rtpListener = CreateRTPListener(udpClient, initiatedByMe, _bufferMiliseconds, int.Parse(sipIdInDialogWith), isGroupCall, typeOfCall); //_rtpListener.PTTControlReceived += PTTControlReceived; rtpListener.VoiceReceived += ReceivedVoice; rtpListener.PTTControlReceived += PTTControlReceived; rtpListener.TimerAliveExpired += TimerAliveExpired; rtpListener.ExceptionThrown += ExceptionThrown; rtpListener.Start(); // Timer hangtime TimerWithTag timerHangTime = null; if (HangTimeDuration != -1) { timerHangTime = new TimerWithTag(HangTimeDuration * 1000, sipIdInDialogWith); timerHangTime.AutoReset = false; timerHangTime.Elapsed += timerHangTime_Elapsed; timerHangTime.Start(); } // here I store in the dictionary all the info about this dialog Tuple> sipDialogTuple = Tuple.Create(udpClient, rtpSender, rtpListener, timerHangTime); // Sunt in dialog cu statia // Add to dictionary _IDdialogTuple.Add(sipIdInDialogWith, sipDialogTuple); _IDsInDialogWithList.Add(sipIdInDialogWith); } else throw new SipClassException(string.Format("Already in dialog with {0}", sipIdInDialogWith)); } } 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; bool wasGroupCall; if (ClosingDialog(sipIDidDialogWith, _sipClient.Username, out wasGroupCall)) { // Send Bye Task.Factory.StartNew((dialogToCloseObj) => { _sipClient.Bye((Dialog)dialogToCloseObj); }, dialogToClose); // Fire event OnDialogClosed(new DialogClosedEventArgs( sipIDidDialogWith, _sipClient.Username, wasGroupCall, DialogClosedReason.TimerHangTimeExpired)); } } } } private bool ClosingDialog(string idInDialogWith, string idWhoClosedTheDialog, out bool wasGroupCall) { lock (_lockerSipDialog) { wasGroupCall = false; if (_IDdialogTuple.ContainsKey(idInDialogWith)) { //lock (_lockerPTTGranted) //{ // if (_canSendVoiceToSimoco) // { // OnEndPTTRequest(); // // Inform SD that cannot send voice // SendInitiateCallResponse(_callType, _seqIDForCallRequest, _rtpSender.DMR_Destination, false); // } //} Tuple> dialogTuple = _IDdialogTuple[idInDialogWith]; // Opresc timerele (send ptt hearbeat, heartbeat query, etc) din sender dialogTuple.Item2.Stop(); // Unregister form the sender events dialogTuple.Item2.SentMaxNumberOfPTTrequests -= MaximumNbOfPttRequestsWasSent; dialogTuple.Item2.SendingPTTStart -= rtpSender_SendingPTTStart; // Get type of call wasGroupCall = dialogTuple.Item3.IsGroupCall; // Opresc citirea pachetelor rtp dialogTuple.Item3.Stop(); // Unregister form the lister events dialogTuple.Item3.PTTControlReceived -= PTTControlReceived; dialogTuple.Item3.TimerAliveExpired -= TimerAliveExpired; 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); 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, TypeOfCall typeOfCall, bool isSimocoGroup = false) { 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, typeOfCall); 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; inv.Subject = typeOfCall.ToString(); if (isSimocoGroup) inv.Header.Add("Ais-Reach", "group"); else inv.Header.Add("Ais-Reach", "individual"); // 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; if (regData.SimocoGroupRegistration) { reg.Header.Add("Ais-Reach", "group"); } 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(new RegistrationStateChangedEventArgs(sipIDto, regStatus)); //OnRegistrationStateChanged( sipIDto, regStatus); } //try //{ // RequestResponse reqResp = _sipClient.SendRequest(reg); // if (reqResp.Response.StatusCode == 200) //OK // { // if (isUnregisterRequest) // { // Registered = false; // OnRegistrationStateChanged(sipIDto, RegistrationStatus.UnregisteredAtRequest); // } // else if (!Registered) // { // Registered = true; // OnRegistrationStateChanged(sipIDto, RegistrationStatus.Registered); // } // //Console.WriteLine("Registered to id {0}", sipServerIP); // } // else if (reqResp.Response.StatusCode == 403) // 403 = Forbidden // { // Registered = false; // OnRegistrationStateChanged(sipIDto, RegistrationStatus.WrongCredentials); // } // else // { // if (Registered) // { // Registered = false; // OnRegistrationStateChanged(sipIDto, RegistrationStatus.NotRegistered); // } // //Console.WriteLine("Registration to id {0} failed", sipServerIP); // } //} //catch (Exception ex) //{ // if (Registered) // { // Registered = false; // OnRegistrationStateChanged(sipIDto, RegistrationStatus.RequestTimeout); // } //} } /// /// Method used to return the type of call from a received invite /// /// The received invite /// The requested type of call protected virtual TypeOfCall GetTypeOfCall(Request receivedInvite) { if (receivedInvite.SessionDescription != null) { return GetTypeOfCall(receivedInvite.SessionDescription); } else throw new ArgumentNullException("No session description info specified", "receivedInvite"); } /// /// Method used to return the type of call from a received Session description /// /// The received session description /// The requested type of call protected virtual TypeOfCall GetTypeOfCall(SessionDescription sdp) { if (sdp.Media.Count > 0) { foreach (Independentsoft.Sip.Sdp.Attribute attribute in sdp.Media[0].Attributes) { if (attribute.Name == "floor") return TypeOfCall.HALF_DUPLEX; } return TypeOfCall.FULL_DUPLEX; } else throw new ArgumentException("No types of media specified", "receivedInvite"); } private SessionDescription CreateSDP(int rtpPort, TypeOfCall typeOfCall) { 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"); if (typeOfCall == TypeOfCall.HALF_DUPLEX) media1.Attributes.Add("floor"); 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 /// /// Retrieves the idInDialogWith and the call type from the received ACK request /// /// The sip id specified in the From field /// The sip id specified in the To field /// The id that we are in dialog with /// True if is a group call, else false protected void GetInfoFromAckRequest(string ackFromSipID, string ackToSipId, out string idInDialogWith, out bool isGroupCall) { // We received an Invite, sent Ok and now we receive ack idInDialogWith = ackFromSipID; // Check if is a Simoco group invite isGroupCall = false; if (ackToSipId != _sipClient.Username) { isGroupCall = true; idInDialogWith = ackToSipId; } } /// /// Save the message in a dictionary because it has to be resent to the radio when we acknowledge the alarm /// /// The sender /// The body of the alarm message protected void AddSimocoAlarmToDictionary(string senderID, string alarmInfo) { _simocoID_emergencyStatusReport_dict[senderID] = alarmInfo; } /// /// Checks if the ping response is valid and handles the ping data dictionary /// /// The sender id /// The ping data /// True if the ping response is valid, else false protected bool HandleSimocoPingResponse(string senderID, byte[] pingData) { bool returnValue = false; if (_ID_pingBytest_timer_dict.ContainsKey(senderID)) { if (Enumerable.SequenceEqual(pingData, _ID_pingBytest_timer_dict[senderID].Item1)) { _ID_pingBytest_timer_dict[senderID].Item2.Stop(); _ID_pingBytest_timer_dict.Remove(senderID); returnValue = true; } } return returnValue; } private byte GenerateNewTSN() { // TSN are doar 7 biti, al optulea este rezervat pentru L (pare a fi 0 pentru Simoco) // Deci TSN poate fi maxim 0111 1111 = 7F = 127 return (byte)_rand.Next(128); } 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; } private Byte[] GenerateNmea(double latitude, double longitude, double speed, DateTime timeUtc) { #region variables declaration byte crypted = 0; byte Q = 1; Byte[] result = new Byte[10]; byte latitudeDegrees, latitudeMinutes; byte longitudeDegrees, longitudeMinutes; #endregion variables declaration #region latitude sbyte NS = (latitude > 0) ? (sbyte)1 : (sbyte)0; latitude = (latitude > 0) ? latitude : latitude * (-1); latitudeDegrees = (byte)Math.Truncate(latitude); double latFractionPartMinutes = (latitude - latitudeDegrees) * 60; latitudeMinutes = (byte)Math.Truncate(latFractionPartMinutes); double difflat = latFractionPartMinutes - Math.Truncate(latFractionPartMinutes); ushort latitudeFractionOfMin = 0; if (difflat.ToString().Split(Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator)).Count() > 1) { string difflat1 = difflat.ToString().Split(Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator))[1]; latitudeFractionOfMin = Convert.ToUInt16(difflat1.Length > 4 ? difflat1.Substring(0, 4) : difflat1.Substring(0, difflat1.Length - 1)); } #endregion latitude #region longitude sbyte EW = (longitude > 0) ? (sbyte)1 : (sbyte)0; longitude = (longitude > 0) ? longitude : longitude * (-1); longitudeDegrees = (byte)Math.Truncate(longitude); double lngFractionPartMinutes = (longitude - longitudeDegrees) * 60; longitudeMinutes = (byte)Math.Truncate(lngFractionPartMinutes); double difflng = lngFractionPartMinutes - Math.Truncate(lngFractionPartMinutes); ushort longitudeFractionOfMin = 0; if (difflng.ToString().Split(Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator)).Count() > 1) { string difflng1 = difflng.ToString().Split(Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator))[1]; longitudeFractionOfMin = Convert.ToUInt16(difflng1.Length > 4 ? difflng1.Substring(0, 4) : difflng1.Substring(0, difflng1.Length - 1)); } #endregion longitude #region speed // viteza in knotes byte speed_knots = Convert.ToByte(speed / 1.852); #endregion speed #region time // time as unix time byte hours = (byte)timeUtc.Hour; byte minutes = (byte)timeUtc.Minute; byte ten_sec = (byte)(timeUtc.Second / 10); #endregion time #region compute byte array // | C NS EW Q speed speed speed speed | speed speed speed ndeg ndeg ndeg ndeg ndeg | ndeg ndeg nmin nmin nmin nmin nmin nmin | // | byte 0 | byte 1 | byte 2 result[0] = (byte)((crypted << 7) | (NS << 6) | (EW << 5) | (Q << 4) | (speed_knots >> 3) /*& 0x07*/); result[1] = (byte)((speed_knots << 5) /*& 0x1F*/ | (latitudeDegrees >> 2) /*& 0x0F*/); result[2] = (byte)((latitudeDegrees << 6) /*& 0x3F*/ | latitudeMinutes); // | nminf nminf nminf nminf nminf nminf nminf nminf | nminf nminf nminf nminf nminf nminf edeg edeg | // | byte 3 | byte 4 | byte[] latitudeFractionOfMinBytes = BitConverter.GetBytes(latitudeFractionOfMin); if (BitConverter.IsLittleEndian) Array.Reverse(latitudeFractionOfMinBytes); result[3] = (byte)((latitudeFractionOfMinBytes[0] << 2) | (latitudeFractionOfMinBytes[1] >> 6)); result[4] = (byte)((latitudeFractionOfMinBytes[1] << 2) | (longitudeDegrees >> 6)); // | edeg edeg edeg edeg edeg edeg eminmm eminmm | eminmm eminmm eminmm eminmm eminf eminf eminf eminf | // | byte 5 | byte 6 | result[5] = (byte)((longitudeDegrees << 2) | (longitudeMinutes >> 4)); byte[] longitudeFractionOfMinBytes = BitConverter.GetBytes(longitudeFractionOfMin); if (BitConverter.IsLittleEndian) Array.Reverse(longitudeFractionOfMinBytes); result[6] = (byte)((longitudeMinutes << 4) | (longitudeFractionOfMinBytes[0] >> 2)); // | eminf eminf eminf eminf eminf eminf eminf eminf | eminf eminf h h h h h m | m m m m m s s s | // | byte 7 | byte 8 | byte 9 | result[7] = (byte)((longitudeFractionOfMinBytes[0] << 6) | (longitudeFractionOfMinBytes[1] >> 2)); result[8] = (byte)((longitudeFractionOfMinBytes[1] << 6) | (hours << 1) | (minutes >> 5)); result[9] = (byte)((minutes << 3) | ten_sec); #endregion compute byte array return result; } private string GetLocalAddresOnNetworkWith(IPAddress ipAddress) { foreach (NetworkInterface networkAdapter in NetworkInterface.GetAllNetworkInterfaces()) { if (networkAdapter.OperationalStatus == OperationalStatus.Up) { foreach (UnicastIPAddressInformation ip in networkAdapter.GetIPProperties().UnicastAddresses) { if (ip.Address.AddressFamily == AddressFamily.InterNetwork) { if (IsInSameSubnet(ip.Address, ipAddress, ip.IPv4Mask)) return ip.Address.ToString(); } } } } throw new SipClassException(string.Format("No ip address that could comunicate with AIS Gateway: {0}", ipAddress)); } /// /// 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; /// /// Ocurs when you send the maximum number of PTT requests and you do not receive an answer /// protected event EventHandler PTTrequestFailed; /// /// Ocurs when your PTT request is denied /// protected event EventHandler PTTrequestDenied; /// /// Occurs when your PTT request is accepted /// protected event EventHandler PTTrequestGranted; /// /// Occurs when you receive a PTT request /// protected event EventHandler PTTrequestReceived; /// /// Occurs when you receive a PTT Start packet /// protected event EventHandler PTTStartReceived; /// /// Occurs when you receive a PTT End packet /// protected event EventHandler PTTEndReceived; /// /// Occurs when you receive an sms /// public event EventHandler SmsReceived; /* /// /// Represents the method that will handle the RegistrationStateChanged event /// /// A reference to SipClientClass that generated the event /// The registered sip id /// public delegate void RegistrationStateChangedDelegate(object sender, string registeredID, RegistrationStatus reason); */ /// /// Occurs when the sip registration status changes /// public event EventHandler RegistrationStateChanged; /// /// Occurs when a GPS report is received from a Simoco radio /// protected event EventHandler SimocoGpsReportReceived; /// /// Occurs when a GPS report is received from a Linx device /// protected event EventHandler LinxGpsReportReceived; /// /// Occurs when a periodically GPS report is received /// public event EventHandler GpsPeriodicallyReportReceived; /// /// Occurs when a Simoco radio responds to a ping request /// protected event EventHandler SimocoPingResponseReceived; /// /// Occurs when a Simoco radio fails to respond to a ping request /// protected event EventHandler SimocoPingRequestFailed; /// /// Occurs when a Simoco radio sends a status report /// protected event EventHandler SimocoStatusReportReceived; /// /// Occurs when an emergency alarm is received from a Linx device /// protected event EventHandler LinxEmergencyAlarmReceived; /// /// Occurs when an Ars command is received from a Linx device /// protected event EventHandler LinxArsReceived; 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(DialogCreatedEventArgs 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(DialogClosedEventArgs 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(AudioEventArgs e) { EventHandler handler = VoiceReceived; if (handler != null) handler(this, e); } private void OnPTTrequestFailed(string sipIDinDialogWith) { EventHandler handler = PTTrequestFailed; if (handler != null) handler(this, new SipEventArgs(sipIDinDialogWith)); } private void OnPTTrequestDenied(string sipIDinDialogWith) { EventHandler handler = PTTrequestDenied; if (handler != null) handler(this, new SipEventArgs(sipIDinDialogWith)); } private void OnPTTrequestGranted(string sipID, bool isGroup) { EventHandler handler = PTTrequestGranted; if (handler != null) handler(this, new SipInfoEventArgs(sipID, isGroup)); } private void OnPTTrequestReceived(PTTrequestEventArgs e) { EventHandler handler = PTTrequestReceived; if (handler != null) handler(this, e); } private void OnPTTStartReceived(RtpEventArgs e) { EventHandler handler = PTTStartReceived; if (handler != null) handler(this, e); } private void OnPTTEndReceived(RtpEventArgs e) { EventHandler handler = PTTEndReceived; if (handler != null) handler(this, e); } private void OnRegistrationStateChanged(RegistrationStateChangedEventArgs e) { EventHandler handler = RegistrationStateChanged; if (handler != null) handler(this, e); } /// /// Raises the SmsReceived event /// /// Data for the event protected void OnSmsReceived(SmsReceivedEventsArgs e) { EventHandler handler = SmsReceived; if (handler != null) handler(this, e); } /// /// Raises the SimocoGpsReportReceived event /// /// Data for the Gps event protected void OnSimocoGpsReportReceived(GpsDataEventArgs e) { EventHandler handler = SimocoGpsReportReceived; if (handler != null) handler(this, e); } /// /// Raises the GpsPeriodicallyReportReceived event /// /// Data for the Gps event protected void OnGpsPeriodicallyReportReceived(GpsDataEventArgs e) { EventHandler handler = GpsPeriodicallyReportReceived; if (handler != null) handler(this, e); } /// /// Raises SimocoPingResponseReceived event /// /// Data about the event protected void OnSimocoPingResponseReceived(SipEventArgs e) { EventHandler handler = SimocoPingResponseReceived; if (handler != null) handler(this, e); } private void OnSimocoPingRequestFailed(SipEventArgs e) { EventHandler handler = SimocoPingRequestFailed; if (handler != null) handler(this, e); } /// /// Raises the SimocoStatusReportReceived event /// /// Data for the event protected void OnSimocoStatusReportReceived(SimocoDeviceStatusReportEventArgs e) { EventHandler handler = SimocoStatusReportReceived; if (handler != null) handler(this, e); } private void OnLinxEmergencyAlarmReceived(Linx.LinxEmergencyAlarmReceivedEventArgs e) { EventHandler handler = LinxEmergencyAlarmReceived; if (handler != null) handler(this, e); } private void OnLinxGpsReportReceived(LinxGpsDataEventArgs e) { EventHandler handler = LinxGpsReportReceived; if (handler != null) handler(this, e); } private void OnLinxArsReceived(Linx.ArsReceivedEventArgs e) { EventHandler handler = LinxArsReceived; if (handler != null) handler(this, e); } #endregion } /// /// Provides data about a sip related event /// public class SipEventArgs : EventArgs { /// /// Contains the id of the sip user that we are in dialog with /// public string SipIDinDialogWith { get; private set; } internal SipEventArgs(string sipIDinDialogWith) { SipIDinDialogWith = sipIDinDialogWith; } } public class SipInfoEventArgs : SipEventArgs { public bool IsGroup { get; private set; } internal SipInfoEventArgs(string sipIdInDialogWith, bool isGroup) : base(sipIdInDialogWith) { IsGroup = isGroup; } } /// /// Provides data about the message bus related events /// public class MBusEventArgs : SipEventArgs { /// /// The sequence id /// public string SeqID { get; private set; } internal MBusEventArgs(string sipIDinDialogWith, string seqID) : base(sipIDinDialogWith) { SeqID = seqID; } } /// /// Provides data about a RTP related event /// public class RtpEventArgs : SipEventArgs { /// /// The sip ID of the user sending the RTP packet /// public string SipIDsendingRTP { get; private set; } internal RtpEventArgs(string sipIDinDialogWith, string sipIDsendingRTP) : base(sipIDinDialogWith) { SipIDsendingRTP = sipIDsendingRTP; } } /// /// Provides data for the InviteReceived event /// public class InviteReceivedArgs : SipEventArgs { /// /// Contains the sip id that sends the invite /// public string SipIDsendingInvite { get; private set; } /// /// True if is an emergency call, else false /// public bool IsEmergencyCall { get; private set; } internal InviteReceivedArgs(string sipIDinDialogWith, string sipIDsendingInvite, TypeOfCall typeOfCall, bool isEmergencyCall = false) : base(sipIDinDialogWith) { SipIDsendingInvite = sipIDsendingInvite; IsEmergencyCall = isEmergencyCall; TypeOfCall = typeOfCall; } /// /// Gets the type of call: Full or half duplex /// public TypeOfCall TypeOfCall { get; private set; } } /// /// Provides data for the DialogClosed event /// public class DialogClosedEventArgs : SipEventArgs { /// /// The sip id who closed the dialog /// public string SipIDwhoClosed { get; private set; } /// /// The reason for dialog closing /// public DialogClosedReason Reason { get; private set; } public bool WasGroupCall { get; private set; } internal DialogClosedEventArgs(string sipIDinDialogWith, string sipIDWhoClosed, bool wasGroupCall, DialogClosedReason reason) : base(sipIDinDialogWith) { SipIDwhoClosed = sipIDWhoClosed; WasGroupCall = wasGroupCall; Reason = reason; } } /// /// Provides data for the ErrorOnCreatingDialog event /// public class ErrorEventArgs : EventArgs { /// /// Reason for the error /// public string Reason { get; private set; } /// /// The sip ID who sent the invite /// public string FromID { get; private set; } /// /// The sip ID who received the invite /// public string ToID { get; private set; } internal ErrorEventArgs(string toID, string fromID, string reason) { Reason = reason; FromID = fromID; ToID = toID; } } /// /// Provides data for the RegistrationStateChanged event /// public class RegistrationStateChangedEventArgs : EventArgs { /// /// The registered ID /// public string RegisteredId { get; private set; } /// /// The registration status changed that is the reason for this event /// public RegistrationStatus Reason { get; private set; } internal RegistrationStateChangedEventArgs(string registeredId, RegistrationStatus reason) { RegisteredId = registeredId; Reason = reason; } } /// /// Provides data for the DialogCreated event /// public class DialogCreatedEventArgs : EventArgs { /// /// The sip ID who sent the invite /// public string FromID { get; private set; } /// /// The sip ID who received the invite /// public string ToID { get; private set; } /// /// True if is a group call, else false /// public bool IsGroupCall { get; private set; } /// /// Returns the type of dialog: full or half-duplex /// public TypeOfCall TypeOfDialog { get; private set; } internal DialogCreatedEventArgs(string toID, string fromID, TypeOfCall typeOfDialog, bool isGroupCall) { FromID = fromID; ToID = toID; TypeOfDialog = typeOfDialog; IsGroupCall = isGroupCall; } } /// /// Provides data for the PTTrequestReceived event /// public class PTTrequestEventArgs : RtpEventArgs { /// /// Gets or sets a value indicating whether the PTT request should be accepted or denied /// The default value is null, meaning that no response is sent (yet) /// public bool? Grant { get; set; } internal PTTrequestEventArgs(string sipIDinDialogWith) : base(sipIDinDialogWith, sipIDinDialogWith) { Grant = null; } } /// /// Provides data for the SmsReceived event /// public class SmsReceivedEventsArgs : RtpEventArgs { /// /// The sms text /// public string Message { get; private set; } internal SmsReceivedEventsArgs(string sipIDinDialogWith, string message) : base(sipIDinDialogWith, sipIDinDialogWith) { Message = message; } } /// /// Provides data for the GPS related events /// public class GpsDataEventArgs : SipEventArgs { /// /// Latitude /// public double Latitude { get; private set; } /// /// Longitude /// public double Longitude { get; private set; } /// /// Speed /// public double Speed { get; private set; } /// /// Utc time /// public DateTime Time { get; private set; } internal GpsDataEventArgs(string sipIDinDialogWith, double latitude, double longitude, double speed, DateTime time) :base(sipIDinDialogWith) { Latitude = latitude; Longitude = longitude; Speed = speed; Time = time; } } /// /// Provides data for the LinxGpsReportReceived event /// public class LinxGpsDataEventArgs : GpsDataEventArgs { /// /// Contains the sequence id /// public string SeqID { get; private set; } internal LinxGpsDataEventArgs(string sipIDinDialogWith, string seqID, double latitude, double longitude, double speed, DateTime time) :base(sipIDinDialogWith, latitude, longitude, speed, time) { SeqID = seqID; } } /// /// Provides data for the SimocoStatusReportReceived event /// public class SimocoDeviceStatusReportEventArgs : SipEventArgs { /// /// Latitude /// public double Latitude { get; private set; } /// /// Longitude /// public double Longitude { get; private set; } /// /// Type of Alarm /// public Simoco.AlarmType AlarmType { get; private set; } internal SimocoDeviceStatusReportEventArgs(string sipIDinDialogWith, double latitude, double longitude, Simoco.AlarmType alarmType) :base(sipIDinDialogWith) { Latitude = latitude; Longitude = longitude; AlarmType = alarmType; } } internal class RegistrationData { /// /// Interval to send sip registration requests. Value is in seconds /// internal int Expires { get; private set; } /// /// Sip ID to register /// internal string SipID { get; private set; } internal bool SimocoGroupRegistration { get; private set; } internal RegistrationData(string sipID, int expires, bool simocoGroupRegistration = false) { SipID = sipID; Expires = expires; SimocoGroupRegistration = simocoGroupRegistration; } } /// /// Contains a list of audio formats /// public enum AudioFormat { /// /// Pulse-code modulation, the standard format (used by Safedispatch) /// PCM, /// /// The G.711 codec, ALAW type (or PCMA), used by the RTP protocol /// PCMA } /// /// Contains a list of sip registration statuses /// public enum RegistrationStatus { /// /// Registered to sip server /// Registered, /// /// Not registered to sip server /// NotRegistered, /// /// Register request did not get to the sip server /// RequestTimeout, /// /// User credentials are wrong /// WrongCredentials, /// /// Not registered becouse an Unregister request was sent to the sip server /// UnregisteredAtRequest, /// /// The initial value, registration procedure not started /// RegistrationNotStarted, /// /// Error in IndependentSoft library /// SocketError } /// /// Contains a list of reasons for closing a sip dialog /// public enum DialogClosedReason { /// /// Sip Bye was received or sent /// NormalClosing, /// /// The interval the dialog is maintained without voice transmision has passed /// TimerHangTimeExpired, /// /// Rtp packet was sent to a closed port /// Error, /// /// The interval the dialog is maintained without receiving heartbeat has passed /// TimerAliveTimeExpired } /// /// The exception that is thrown when there is an error in the SipClient class /// [Serializable] public class SipClassException : Exception { public SipClassException() { } public SipClassException(string message) : base(message) { } public SipClassException(string message, Exception inner) : base(message, inner) { } protected SipClassException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } }