3623 lines
150 KiB
C#
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) { }
|
|
}
|
|
}
|