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

3623 lines
150 KiB
C#

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
{
/// <summary>
/// <para>SipClientClass is an abstract class responsible for sending and receiving Sip messages and RTP packets,</para>
/// <para>in accordance with the DMR Ais Specification</para>
/// </summary>
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;
/// <summary>
/// Nr de secunde cat este mentinut call-ul cand nu se transmite voce
/// </summary>
private int _hangTimeDuration = -1;
private int _bufferMiliseconds;
private string _localIPaddress;
private Random _rand = new Random();
private bool _sipClassClosed = false;
private readonly bool _forSimoco = false;
/// <summary>
/// Gets or sets a value indicating if the Asterisk server confirms sms
/// <para>Default value is false</para>
/// </summary>
protected bool _smsConfirmationFromServer = false;
private Dictionary<string, Invite> _IDsentInviteDict = new Dictionary<string, Invite>();
private Dictionary<string, Request> _IDreceivedInviteDict = new Dictionary<string, Request>();
private Dictionary<string, Tuple<UdpClient, RTPSender2, RTPListener2, TimerWithTag<string>>> _IDdialogTuple = new Dictionary<string, Tuple<UdpClient, RTPSender2, RTPListener2, TimerWithTag<string>>>();
private Dictionary<string, bool?> _smsSentDict = new Dictionary<string, bool?>();
private Dictionary<string, Tuple<System.Timers.Timer, RegistrationStatus>> _sipID_regTimer_regStatus_Dict =
new Dictionary<string, Tuple<System.Timers.Timer, RegistrationStatus>>();
private Dictionary<string, Tuple<byte[], TimerWithTag<string>>> _ID_pingBytest_timer_dict = new Dictionary<string,Tuple<byte[], TimerWithTag<string>>>();
private Dictionary<string, string> _simocoID_emergencyStatusReport_dict = new Dictionary<string, string>();
private object _lockerSmsSet = new object();
private List<string> _IDsCalledByMeList = new List<string>();
private List<string> _IDsCallingMeList = new List<string>();
private List<string> _IDsInDialogWithList = new List<string>();
private List<string> _IDsregisteredList = new List<string>();
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
/// <summary>
/// Gets or sets the minimum port number in the port range used for RTP voice transmission
/// </summary>
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));
}
}
/// <summary>
/// Gets or sets the maximum port number in the port range used for RTP voice transmission
/// </summary>
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));
}
}
/// <summary>
/// Gets the sip ID
/// </summary>
public string UserName
{
get { return _sipClient.Username; }
}
/// <summary>
/// <para>Gets or sets the maximum number of simultaneous dialogs that the SipClientClass accepts</para>
/// <para>The default is 3</para>
/// </summary>
public int MaxNbOfDialogs
{
get { return _maxNbOfDialogs; }
set
{
_maxNbOfDialogs = value;
}
}
/// <summary>
/// <para>Gets or sets the number of seconds the call is maintained when no voice is transmited</para>
/// <para>Value is in seconds</para>
/// <para>Default value is -1, call is maintained indefinitely</para>
/// </summary>
public int HangTimeDuration
{
get { return _hangTimeDuration; }
set
{
if (value > 0)
_hangTimeDuration = value;
}
}
/// <summary>
/// <para>Gets or sets a value indicating the nb of seconds to wait</para>
/// <para>for a sms confirmation from sip server</para>
/// <para>Default value is 3</para>
/// </summary>
public int SecondsToWaitForSmsConfirmation
{
get { return _secondsToWaitForSmsConfirmation; }
set
{
if (value > 0 && value < 10)
_secondsToWaitForSmsConfirmation = value;
}
}
/// <summary>
/// Audio BitRate, default value is 16
/// </summary>
public int AudioBitrate
{
get { return _audioBitRate; }
set { _audioBitRate = value; }
}
/// <summary>
/// Gets a readonly collection of sip ids that you sent Invite for
/// </summary>
public ReadOnlyCollection<string> SipIDsCalledByMe
{
get { return new ReadOnlyCollection<string>(_IDsCalledByMeList); }
}
/// <summary>
/// Gets a readonly collection of sip ids that sent you Invite's
/// </summary>
public ReadOnlyCollection<string> SipIDsCallingMe
{
get { return new ReadOnlyCollection<string>(_IDsCallingMeList); }
}
/// <summary>
/// Gets a readonly collection of sip ids that you are in dialog with
/// </summary>
public ReadOnlyCollection<string> SipIDsInDialogWith
{
get { return new ReadOnlyCollection<string>(_IDsInDialogWithList); }
}
/// <summary>
/// Gets a readonly collection of sip ids that you are registered to
/// <para>This means your sip id and the group ids that you are listening to</para>
/// </summary>
public ReadOnlyCollection<string> SipIDsRegisteredTo
{
get
{
return new ReadOnlyCollection<string>(_IDsregisteredList);
}
}
#endregion
// constructor
/// <summary>
/// Constructor for the SipClientClass
/// </summary>
/// <param name="sipDomain">domain name, name or IP address of sip server</param>
/// <param name="sipDomainPort">port number of the sip server</param>
/// <param name="localSipPort">port number of the local computer used for sip protocol</param>
/// <param name="userName">user name on the sip server</param>
/// <param name="password">password on the sip server</param>
/// <param name="registrationInterval">interval to send sip registration requests. Value is in seconds</param>
/// <param name="bufferMiliseconds">Miliseconds for the buffer that stores the received voice packets</param>
/// <param name="requestTimeout">Number of ms to wait before the sip request times out</param>
/// <param name="localIPAddress">Local Ip adress. If not specified, the class will search for a local ip on the same network with the sip server ip</param>
/// <param name="simoco">True if the class is used for sip communication with Simoco ais-gateway</param>
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<System.Timers.Timer,RegistrationStatus>(registrationTimer, RegistrationStatus.RegistrationNotStarted
));
_IDsregisteredList.Add(userName);
StartRegistrationTimer();
}
#region Public Methods
/// <summary>
/// Sends a Sip Invite command to an user
/// </summary>
/// <param name="idToInvite">The sip id of the user</param>
/// <param name="typeOfCall">Type of call: full duplex or half duplex</param>
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);
}
}
}
/// <summary>
/// Sends a Sip Invite command to a Simoco Group
/// </summary>
/// <param name="groupIDtoInvite">The sip id of the group</param>
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);
}
}
/// <summary>
/// Cancels an Invite sent to an user
/// </summary>
/// <param name="idToCancel">The sip id of the user</param>
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;
}
/// <summary>
/// Accepts an Invite from an user
/// </summary>
/// <param name="callingSipID">The sip ID of the user</param>
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] = "<sip:" + _sipClient.Username + "@" + _sipClient.LocalIPEndPoint.ToString() + ";transport=udp>";
// 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));
}
}
/// <summary>
/// Closes a sip dialog (voice session) with an user
/// </summary>
/// <param name="idToClose">The sip id of the user</param>
/// <returns>true if the dialog was closed, else false</returns>
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;
}
/// <summary>
/// Declines a received Invite from an user
/// </summary>
/// <param name="idToDeclineCall">The sip ID of the user</param>
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));
}
/// <summary>
/// Accepts a PTT Request from an user
/// </summary>
/// <param name="idToGrantPTT">The sip ID of the user</param>
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");
}
/// <summary>
/// Method used to check if you received PTT Grant from the specified user
/// </summary>
/// <param name="sipIDinDialogWith">The sip id of the user</param>
/// <returns>True if you received ptt Grant, else false</returns>
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");
}
/// <summary>
/// Method used to send Deny when you receive a PTT Request
/// </summary>
/// <param name="idToDenyPTT">The sip ID sending PTT Request</param>
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");
}
/// <summary>
/// Method used to start sending PTT Requests to a sip id
/// </summary>
/// <param name="sipID">The sip ID to send PTT Requests</param>
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);
}
}
}
/// <summary>
/// Method used to send End PTT to a sip id
/// </summary>
/// <param name="sipID">The sip id to send End PTT</param>
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<string> timer = _IDdialogTuple[sipID].Item4;
if (timer != null)
{
timer.Start();
}
}
}
}
}
/// <summary>
/// Sends a voice buffer in a specified format to the user in dialog with
/// </summary>
/// <param name="idToSendVoice">The sip ID of the user</param>
/// <param name="audioBuffer">The audio buffer</param>
/// <param name="bufferLength">The length of the buffer</param>
/// <param name="format">The audio format of the buffer</param>
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);
}
}
}
/// <summary>
/// Method used to send a Gps Request to a Simoco radio
/// </summary>
/// <param name="idToRequestGPS">The simoco ID to send Gps Request</param>
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");
}
/// <summary>
/// Method used to send Gps Request to a Linx device
/// </summary>
/// <param name="idToRequestGps">The sip id to send the gps request</param>
/// <param name="seqID">The sequence id</param>
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");
}
/// <summary>
/// Method used to send a Ping command to a Simoco radio
/// </summary>
/// <param name="idToSendPing">The Simoco ID of the radio</param>
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<string> pingTimer = new TimerWithTag<string>(2000, idToSendPing);
pingTimer.Elapsed += pingTimer_Elapsed;
pingTimer.AutoReset = false;
pingTimer.Start();
_ID_pingBytest_timer_dict.Add(idToSendPing, new Tuple<byte[], TimerWithTag<string>>(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");
}
/// <summary>
/// Method used to send a Ping command to an Excera radio
/// </summary>
/// <param name="idToSendPing">The Excera ID of the radio</param>
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<string>)sender).Tag;
if (_ID_pingBytest_timer_dict.ContainsKey(id))
{
_ID_pingBytest_timer_dict.Remove(id);
// Fire event
OnSimocoPingRequestFailed(new SipEventArgs(id));
}
}
/// <summary>
/// Method used to acknowledge a Simoco emergency status report
/// </summary>
/// <param name="simocoID">The id of the Simoco radio</param>
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");
}
/// <summary>
/// Method used to acknowledge an emergency alarm sent by a Linx device
/// </summary>
/// <param name="linxID">The sip id of the Linx device</param>
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");
}
/// <summary>
/// Just for testing purposes
/// </summary>
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");
}
/// <summary>
/// Method used to send an sms to sip id
/// <para>This method does not block the calling thread while waiting for the confirmation</para>
/// </summary>
/// <param name="idToSendSMS">The sip id where to send the sms</param>
/// <param name="text">The sms text</param>
/// <param name="isGroupSms">True if the sms is a sip group sms</param>
/// <returns>True if the sms was received, else returns false</returns>
public async Task<bool> 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<bool>(SmsConfirmedBySipServer, unconfirmedSmsKey);
}
else if (idToSendSMS == null)
throw new ArgumentNullException("idToSendSMS");
else
throw new ArgumentNullException("text");
}
/*
/// <summary>
/// Generates a Sip message using the specified parameters
/// </summary>
/// <param name="destinationID"></param>
/// <param name="senderID"></param>
/// <param name="sipServer"></param>
/// <param name="sipServerPort"></param>
/// <param name="text"></param>
/// <param name="simoco"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Mehod used to check if you are in dialog with a sip id
/// </summary>
/// <param name="sipID">The sip id</param>
/// <returns>True if you are in dialog, else returns false</returns>
public bool InDialogWith(string sipID)
{
if (_sipClassClosed)
throw new ObjectDisposedException("SipClientClass");
lock (_lockerSipDialog)
{
return _IDdialogTuple.ContainsKey(sipID);
}
}
/// <summary>
/// Stops registration by sending unregister request to the sip server
/// <para>Releases all the used resources</para>
/// <param name="async">If true, method returns before all the resources are released</param>
/// </summary>
public virtual void Stop(bool async = true)
{
if (!_sipClassClosed)
{
_sipClassClosed = true;
List<string> sipIDs = new List<string>();
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();
}
}
/// <summary>
/// Stops registration by sending unregister request to the sip server
/// </summary>
public void StopRegisterToSipServer()
{
// Stop registering to groups
List<string> idsToUnregisterFrom = new List<string>(_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);
}
*/
}
/// <summary>
/// Register the Sip class to a simoco group id
/// </summary>
/// <param name="simocoGroupID">id of the simoco group</param>
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<System.Timers.Timer, RegistrationStatus>(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<string> 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<string> 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;
}
}
/// <summary>
/// Method used to decide if the app is ringing or is sending Busy response to sender
/// <para>Default behaviour is to ring (sends Ringing response)</para>
/// </summary>
/// <param name="receivedInvite">The received invite</param>
/// <param name="senderSipId">The sender of the invite</param>
/// <returns>True to send Ringing response, false to send Busy Here</returns>
protected virtual bool SendResponseRinging(Request receivedInvite, string senderSipId)
{
return true;
}
/// <summary>
/// Function that processes all the received Sip Message requests
/// </summary>
/// <param name="sipMessageRequest">The Sip Message request</param>
/// <param name="senderID">The ID of the sender</param>
protected 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<PTTEventArgs>();
//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<string> timerHangTime = null;
if (HangTimeDuration != -1)
{
timerHangTime = new TimerWithTag<string>(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<UdpClient, RTPSender2, RTPListener2, TimerWithTag<string>> 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<string> timer = (TimerWithTag<string>)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<UdpClient, RTPSender2, RTPListener2, TimerWithTag<string>> 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<System.Timers.Timer, RegistrationStatus>(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);
// }
//}
}
/// <summary>
/// Method used to return the type of call from a received invite
/// </summary>
/// <param name="receivedInvite">The received invite</param>
/// <returns>The requested type of call</returns>
protected virtual TypeOfCall GetTypeOfCall(Request receivedInvite)
{
if (receivedInvite.SessionDescription != null)
{
return GetTypeOfCall(receivedInvite.SessionDescription);
}
else
throw new ArgumentNullException("No session description info specified", "receivedInvite");
}
/// <summary>
/// Method used to return the type of call from a received Session description
/// </summary>
/// <param name="sdp">The received session description</param>
/// <returns>The requested type of call</returns>
protected 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=<userName> <sessionId> <version> <network type> <address type>
// <sessionId> must be unique
int sessionID = _rand.Next();
// <version> - 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=<media> <port> <transport> <fmt list>
// <port> - 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
/// <summary>
/// Retrieves the idInDialogWith and the call type from the received ACK request
/// </summary>
/// <param name="ackFromSipID">The sip id specified in the From field</param>
/// <param name="ackToSipId">The sip id specified in the To field</param>
/// <param name="idInDialogWith">The id that we are in dialog with</param>
/// <param name="isGroupCall">True if is a group call, else false</param>
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;
}
}
/// <summary>
/// Save the message in a dictionary because it has to be resent to the radio when we acknowledge the alarm
/// </summary>
/// <param name="senderID">The sender</param>
/// <param name="alarmInfo">The body of the alarm message</param>
protected void AddSimocoAlarmToDictionary(string senderID, string alarmInfo)
{
_simocoID_emergencyStatusReport_dict[senderID] = alarmInfo;
}
/// <summary>
/// Checks if the ping response is valid and handles the ping data dictionary
/// </summary>
/// <param name="senderID">The sender id</param>
/// <param name="pingData">The ping data</param>
/// <returns>True if the ping response is valid, else false</returns>
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;
}
/// <summary>
/// Checks if an integer can be used as a rtp port
/// </summary>
/// <param name="rtpPort">The integer to be cecked</param>
/// <returns>True if is a valid rtp port, else false</returns>
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));
}
/// <summary>
/// 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.
/// </summary>
/// <param name="remoteIPEndPoint">The remote end point</param>
/// <returns>The selected local end point</returns>
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
/// <summary>
/// Occurs when you send an invite to a sip id
/// </summary>
public event EventHandler<SipEventArgs> InviteSent;
/// <summary>
/// Occurs when you cancel a sent invite
/// </summary>
public event EventHandler<SipEventArgs> InviteSentCanceled;
/// <summary>
/// Occurs when you receive an invite
/// </summary>
public event EventHandler<InviteReceivedArgs> InviteReceived;
/// <summary>
/// Occurs when an invite is cancelled before you had time to accept or reject it.
/// </summary>
public event EventHandler<SipEventArgs> InviteReceivedCanceled;
/// <summary>
/// Occurs when you decline an invite from another user
/// </summary>
public event EventHandler<SipEventArgs> InviteReceivedDeclined;
/// <summary>
/// An error occured while attempting to establish a voice session
/// </summary>
public event EventHandler<ErrorEventArgs> ErrorOnCreatingDialog;
/// <summary>
/// Occurs when a voice session (a dialog) has been established
/// </summary>
public event EventHandler<DialogCreatedEventArgs> DialogCreated;
/// <summary>
/// Occurs when a voice session (a dialog) is closed
/// </summary>
public event EventHandler<DialogClosedEventArgs> DialogClosed;
/// <summary>
/// Ocurs when a voice buffer is received from a sip id
/// </summary>
public event EventHandler<AudioEventArgs> VoiceReceived;
/// <summary>
/// Ocurs when you send the maximum number of PTT requests and you do not receive an answer
/// </summary>
protected event EventHandler<SipEventArgs> PTTrequestFailed;
/// <summary>
/// Ocurs when your PTT request is denied
/// </summary>
protected event EventHandler<SipEventArgs> PTTrequestDenied;
/// <summary>
/// Occurs when your PTT request is accepted
/// </summary>
protected event EventHandler<SipInfoEventArgs> PTTrequestGranted;
/// <summary>
/// Occurs when you receive a PTT request
/// </summary>
protected event EventHandler<PTTrequestEventArgs> PTTrequestReceived;
/// <summary>
/// Occurs when you receive a PTT Start packet
/// </summary>
protected event EventHandler<RtpEventArgs> PTTStartReceived;
/// <summary>
/// Occurs when you receive a PTT End packet
/// </summary>
protected event EventHandler<RtpEventArgs> PTTEndReceived;
/// <summary>
/// Occurs when you receive an sms
/// </summary>
public event EventHandler<SmsReceivedEventsArgs> SmsReceived;
/*
/// <summary>
/// Represents the method that will handle the RegistrationStateChanged event
/// </summary>
/// <param name="sender">A reference to SipClientClass that generated the event</param>
/// <param name="registeredID">The registered sip id</param>
/// <param name="reason"></param>
public delegate void RegistrationStateChangedDelegate(object sender, string registeredID, RegistrationStatus reason);
*/
/// <summary>
/// Occurs when the sip registration status changes
/// </summary>
public event EventHandler<RegistrationStateChangedEventArgs> RegistrationStateChanged;
/// <summary>
/// Occurs when a GPS report is received from a Simoco radio
/// </summary>
protected event EventHandler<GpsDataEventArgs> SimocoGpsReportReceived;
/// <summary>
/// Occurs when a GPS report is received from a Linx device
/// </summary>
protected event EventHandler<LinxGpsDataEventArgs> LinxGpsReportReceived;
/// <summary>
/// Occurs when a periodically GPS report is received
/// </summary>
public event EventHandler<GpsDataEventArgs> GpsPeriodicallyReportReceived;
/// <summary>
/// Occurs when a Simoco radio responds to a ping request
/// </summary>
protected event EventHandler<SipEventArgs> SimocoPingResponseReceived;
/// <summary>
/// Occurs when a Simoco radio fails to respond to a ping request
/// </summary>
protected event EventHandler<SipEventArgs> SimocoPingRequestFailed;
/// <summary>
/// Occurs when a Simoco radio sends a status report
/// </summary>
protected event EventHandler<SimocoDeviceStatusReportEventArgs> SimocoStatusReportReceived;
/// <summary>
/// Occurs when an emergency alarm is received from a Linx device
/// </summary>
protected event EventHandler<Linx.LinxEmergencyAlarmReceivedEventArgs> LinxEmergencyAlarmReceived;
/// <summary>
/// Occurs when an Ars command is received from a Linx device
/// </summary>
protected event EventHandler<Linx.ArsReceivedEventArgs> LinxArsReceived;
private void OnInviteSent(SipEventArgs e)
{
EventHandler<SipEventArgs> handler = InviteSent;
if (handler != null)
handler(this, e);
}
private void OnInviteSentCanceled(SipEventArgs e)
{
EventHandler<SipEventArgs> handler = InviteSentCanceled;
if (handler != null)
handler(this, e);
}
/// <summary>
/// This is the method that raises the ErrorOnCreatingDialog event
/// </summary>
/// <param name="e">Data for the ErrorOnCreatingDialog event</param>
/// <remarks>Do not forget to call the base version when overrinding,
/// <para>otherwize the ErrorOnCreatingDialog event will not fire</para>
/// </remarks>
protected virtual void OnError(ErrorEventArgs e)
{
EventHandler<ErrorEventArgs> handler = ErrorOnCreatingDialog;
if (handler != null)
handler(this, e);
}
private void OnInviteReceived(InviteReceivedArgs e)
{
EventHandler<InviteReceivedArgs> handler = InviteReceived;
if (handler != null)
handler(this, e);
}
private void OnInviteReceivedCanceled(SipEventArgs e)
{
EventHandler<SipEventArgs> handler = InviteReceivedCanceled;
if (handler != null)
handler(this, e);
}
private void OnInviteReceivedDeclined(SipEventArgs e)
{
EventHandler<SipEventArgs> handler = InviteReceivedDeclined;
if (handler != null)
handler(this, e);
}
/// <summary>
/// This is the method that raises the DialogCreated event
/// </summary>
/// <param name="e">Data for the DialogCreatedEvent</param>
/// <remarks>Do not forget to call the base version when overrinding,
/// <para>otherwize the DialogCreated Event will not fire</para>
/// </remarks>
protected virtual void OnDialogCreated(DialogCreatedEventArgs e)
{
EventHandler<DialogCreatedEventArgs> handler = DialogCreated;
if (handler != null)
handler(this, e);
}
/// <summary>
/// This is the method that raises the DialogClosed event
/// </summary>
/// <param name="e">Data for the DialogClosed event</param>
/// <remarks>Do not forget to call the base version when overrinding,
/// <para>otherwize the DialogClosed Event will not fire</para>
/// </remarks>
protected virtual void OnDialogClosed(DialogClosedEventArgs e)
{
EventHandler<DialogClosedEventArgs> handler = DialogClosed;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// This is the method that raises the VoiceReceived event
/// </summary>
/// <param name="e">Data for the VoiceReceived event</param>
/// <remarks>Do not forget to call the base version when overrinding,
/// <para>otherwise the VoiceReceived Event will not fire</para>
/// </remarks>
protected virtual void OnVoiceReceived(AudioEventArgs e)
{
EventHandler<AudioEventArgs> handler = VoiceReceived;
if (handler != null)
handler(this, e);
}
private void OnPTTrequestFailed(string sipIDinDialogWith)
{
EventHandler<SipEventArgs> handler = PTTrequestFailed;
if (handler != null)
handler(this, new SipEventArgs(sipIDinDialogWith));
}
private void OnPTTrequestDenied(string sipIDinDialogWith)
{
EventHandler<SipEventArgs> handler = PTTrequestDenied;
if (handler != null)
handler(this, new SipEventArgs(sipIDinDialogWith));
}
private void OnPTTrequestGranted(string sipID, bool isGroup)
{
EventHandler<SipInfoEventArgs> handler = PTTrequestGranted;
if (handler != null)
handler(this, new SipInfoEventArgs(sipID, isGroup));
}
private void OnPTTrequestReceived(PTTrequestEventArgs e)
{
EventHandler<PTTrequestEventArgs> handler = PTTrequestReceived;
if (handler != null)
handler(this, e);
}
private void OnPTTStartReceived(RtpEventArgs e)
{
EventHandler<RtpEventArgs> handler = PTTStartReceived;
if (handler != null)
handler(this, e);
}
private void OnPTTEndReceived(RtpEventArgs e)
{
EventHandler<RtpEventArgs> handler = PTTEndReceived;
if (handler != null)
handler(this, e);
}
private void OnRegistrationStateChanged(RegistrationStateChangedEventArgs e)
{
EventHandler<RegistrationStateChangedEventArgs> handler = RegistrationStateChanged;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Raises the SmsReceived event
/// </summary>
/// <param name="e">Data for the event</param>
protected void OnSmsReceived(SmsReceivedEventsArgs e)
{
EventHandler<SmsReceivedEventsArgs> handler = SmsReceived;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Raises the SimocoGpsReportReceived event
/// </summary>
/// <param name="e">Data for the Gps event</param>
protected void OnSimocoGpsReportReceived(GpsDataEventArgs e)
{
EventHandler<GpsDataEventArgs> handler = SimocoGpsReportReceived;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Raises the GpsPeriodicallyReportReceived event
/// </summary>
/// <param name="e">Data for the Gps event</param>
protected void OnGpsPeriodicallyReportReceived(GpsDataEventArgs e)
{
EventHandler<GpsDataEventArgs> handler = GpsPeriodicallyReportReceived;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Raises SimocoPingResponseReceived event
/// </summary>
/// <param name="e">Data about the event</param>
protected void OnSimocoPingResponseReceived(SipEventArgs e)
{
EventHandler<SipEventArgs> handler = SimocoPingResponseReceived;
if (handler != null)
handler(this, e);
}
private void OnSimocoPingRequestFailed(SipEventArgs e)
{
EventHandler<SipEventArgs> handler = SimocoPingRequestFailed;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Raises the SimocoStatusReportReceived event
/// </summary>
/// <param name="e">Data for the event</param>
protected void OnSimocoStatusReportReceived(SimocoDeviceStatusReportEventArgs e)
{
EventHandler<SimocoDeviceStatusReportEventArgs> handler = SimocoStatusReportReceived;
if (handler != null)
handler(this, e);
}
private void OnLinxEmergencyAlarmReceived(Linx.LinxEmergencyAlarmReceivedEventArgs e)
{
EventHandler<Linx.LinxEmergencyAlarmReceivedEventArgs> handler = LinxEmergencyAlarmReceived;
if (handler != null)
handler(this, e);
}
private void OnLinxGpsReportReceived(LinxGpsDataEventArgs e)
{
EventHandler<LinxGpsDataEventArgs> handler = LinxGpsReportReceived;
if (handler != null)
handler(this, e);
}
private void OnLinxArsReceived(Linx.ArsReceivedEventArgs e)
{
EventHandler<Linx.ArsReceivedEventArgs> handler = LinxArsReceived;
if (handler != null)
handler(this, e);
}
#endregion
}
/// <summary>
/// Provides data about a sip related event
/// </summary>
public class SipEventArgs : EventArgs
{
/// <summary>
/// Contains the id of the sip user that we are in dialog with
/// </summary>
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;
}
}
/// <summary>
/// Provides data about the message bus related events
/// </summary>
public class MBusEventArgs : SipEventArgs
{
/// <summary>
/// The sequence id
/// </summary>
public string SeqID { get; private set; }
internal MBusEventArgs(string sipIDinDialogWith, string seqID) :
base(sipIDinDialogWith)
{
SeqID = seqID;
}
}
/// <summary>
/// Provides data about a RTP related event
/// </summary>
public class RtpEventArgs : SipEventArgs
{
/// <summary>
/// The sip ID of the user sending the RTP packet
/// </summary>
public string SipIDsendingRTP { get; private set; }
internal RtpEventArgs(string sipIDinDialogWith, string sipIDsendingRTP) :
base(sipIDinDialogWith)
{
SipIDsendingRTP = sipIDsendingRTP;
}
}
/// <summary>
/// Provides data for the InviteReceived event
/// </summary>
public class InviteReceivedArgs : SipEventArgs
{
/// <summary>
/// Contains the sip id that sends the invite
/// </summary>
public string SipIDsendingInvite { get; private set; }
/// <summary>
/// True if is an emergency call, else false
/// </summary>
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;
}
/// <summary>
/// Gets the type of call: Full or half duplex
/// </summary>
public TypeOfCall TypeOfCall { get; private set; }
}
/// <summary>
/// Provides data for the DialogClosed event
/// </summary>
public class DialogClosedEventArgs : SipEventArgs
{
/// <summary>
/// The sip id who closed the dialog
/// </summary>
public string SipIDwhoClosed { get; private set; }
/// <summary>
/// The reason for dialog closing
/// </summary>
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;
}
}
/// <summary>
/// Provides data for the ErrorOnCreatingDialog event
/// </summary>
public class ErrorEventArgs : EventArgs
{
/// <summary>
/// Reason for the error
/// </summary>
public string Reason { get; private set; }
/// <summary>
/// The sip ID who sent the invite
/// </summary>
public string FromID { get; private set; }
/// <summary>
/// The sip ID who received the invite
/// </summary>
public string ToID { get; private set; }
internal ErrorEventArgs(string toID, string fromID, string reason)
{
Reason = reason;
FromID = fromID;
ToID = toID;
}
}
/// <summary>
/// Provides data for the RegistrationStateChanged event
/// </summary>
public class RegistrationStateChangedEventArgs : EventArgs
{
/// <summary>
/// The registered ID
/// </summary>
public string RegisteredId { get; private set; }
/// <summary>
/// The registration status changed that is the reason for this event
/// </summary>
public RegistrationStatus Reason { get; private set; }
internal RegistrationStateChangedEventArgs(string registeredId, RegistrationStatus reason)
{
RegisteredId = registeredId;
Reason = reason;
}
}
/// <summary>
/// Provides data for the DialogCreated event
/// </summary>
public class DialogCreatedEventArgs : EventArgs
{
/// <summary>
/// The sip ID who sent the invite
/// </summary>
public string FromID { get; private set; }
/// <summary>
/// The sip ID who received the invite
/// </summary>
public string ToID { get; private set; }
/// <summary>
/// True if is a group call, else false
/// </summary>
public bool IsGroupCall { get; private set; }
/// <summary>
/// Returns the type of dialog: full or half-duplex
/// </summary>
public TypeOfCall TypeOfDialog { get; private set; }
internal DialogCreatedEventArgs(string toID, string fromID, TypeOfCall typeOfDialog, bool isGroupCall)
{
FromID = fromID;
ToID = toID;
TypeOfDialog = typeOfDialog;
IsGroupCall = isGroupCall;
}
}
/// <summary>
/// Provides data for the PTTrequestReceived event
/// </summary>
public class PTTrequestEventArgs : RtpEventArgs
{
/// <summary>
/// <para>Gets or sets a value indicating whether the PTT request should be accepted or denied</para>
/// <para>The default value is null, meaning that no response is sent (yet)</para>
/// </summary>
public bool? Grant { get; set; }
internal PTTrequestEventArgs(string sipIDinDialogWith)
: base(sipIDinDialogWith, sipIDinDialogWith)
{
Grant = null;
}
}
/// <summary>
/// Provides data for the SmsReceived event
/// </summary>
public class SmsReceivedEventsArgs : RtpEventArgs
{
/// <summary>
/// The sms text
/// </summary>
public string Message { get; private set; }
internal SmsReceivedEventsArgs(string sipIDinDialogWith, string message)
: base(sipIDinDialogWith, sipIDinDialogWith)
{
Message = message;
}
}
/// <summary>
/// Provides data for the GPS related events
/// </summary>
public class GpsDataEventArgs : SipEventArgs
{
/// <summary>
/// Latitude
/// </summary>
public double Latitude { get; private set; }
/// <summary>
/// Longitude
/// </summary>
public double Longitude { get; private set; }
/// <summary>
/// Speed
/// </summary>
public double Speed { get; private set; }
/// <summary>
/// Utc time
/// </summary>
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;
}
}
/// <summary>
/// Provides data for the LinxGpsReportReceived event
/// </summary>
public class LinxGpsDataEventArgs : GpsDataEventArgs
{
/// <summary>
/// Contains the sequence id
/// </summary>
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;
}
}
/// <summary>
/// Provides data for the SimocoStatusReportReceived event
/// </summary>
public class SimocoDeviceStatusReportEventArgs : SipEventArgs
{
/// <summary>
/// Latitude
/// </summary>
public double Latitude { get; private set; }
/// <summary>
/// Longitude
/// </summary>
public double Longitude { get; private set; }
/// <summary>
/// Type of Alarm
/// </summary>
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
{
/// <summary>
/// Interval to send sip registration requests. Value is in seconds
/// </summary>
internal int Expires { get; private set; }
/// <summary>
/// Sip ID to register
/// </summary>
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;
}
}
/// <summary>
/// Contains a list of audio formats
/// </summary>
public enum AudioFormat
{
/// <summary>
/// Pulse-code modulation, the standard format (used by Safedispatch)
/// </summary>
PCM,
/// <summary>
/// The G.711 codec, ALAW type (or PCMA), used by the RTP protocol
/// </summary>
PCMA
}
/// <summary>
/// Contains a list of sip registration statuses
/// </summary>
public enum RegistrationStatus
{
/// <summary>
/// Registered to sip server
/// </summary>
Registered,
/// <summary>
/// Not registered to sip server
/// </summary>
NotRegistered,
/// <summary>
/// Register request did not get to the sip server
/// </summary>
RequestTimeout,
/// <summary>
/// User credentials are wrong
/// </summary>
WrongCredentials,
/// <summary>
/// Not registered becouse an Unregister request was sent to the sip server
/// </summary>
UnregisteredAtRequest,
/// <summary>
/// The initial value, registration procedure not started
/// </summary>
RegistrationNotStarted,
/// <summary>
/// Error in IndependentSoft library
/// </summary>
SocketError
}
/// <summary>
/// Contains a list of reasons for closing a sip dialog
/// </summary>
public enum DialogClosedReason
{
/// <summary>
/// Sip Bye was received or sent
/// </summary>
NormalClosing,
/// <summary>
/// The interval the dialog is maintained without voice transmision has passed
/// </summary>
TimerHangTimeExpired,
/// <summary>
/// Rtp packet was sent to a closed port
/// </summary>
Error,
/// <summary>
/// The interval the dialog is maintained without receiving heartbeat has passed
/// </summary>
TimerAliveTimeExpired
}
/// <summary>
/// The exception that is thrown when there is an error in the SipClient class
/// </summary>
[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) { }
}
}