SafeDispatch/MotoTrbo_GW/LINX/TrboSipGateway.cs
2024-02-22 18:43:59 +02:00

546 lines
25 KiB
C#

using SafeMobileLib;
using SipComponent;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MotoTrbo_GW.LINX
{
public class TrboSipGateway
{
private string _sipServerIP;
private int _sipServerPort, _webServicePort, _minSipPort, _maxSipPort;
private Dictionary<int, RadioSipInfo> _sipIDToRadioSipInfoDict = new Dictionary<int, RadioSipInfo>();
private bool _connectedToAsterisk = false;
private object _lockerConnectedToAstersik = new object();
private volatile bool _unregistering = false;
private IEnumerable<ContactLinx> _contactsToRegister;
private Dictionary<LINX.RadioIdKey, int> _motoRadioIDToSipID_dict = new Dictionary<RadioIdKey, int>();
private Dictionary<int, int> _radioGroupSipIDToListeningLINX = new Dictionary<int, int>();
private Dictionary<int, int> _linxRadioIdToLinxSipID_dict = new Dictionary<int, int>();
private Dictionary<int, int> _linxSipIDToLinxRadioID_dict = new Dictionary<int, int>();
private Dictionary<int, int> _radioIDtoLinxSipIDforPrivateCall_dict = new Dictionary<int, int>();
private string _msgForErrorOnRegister = "Not all the radios are connected to Linx server.Check sip ports range in config file";
private HashSet<int> _setOfUnavalablePorts = new HashSet<int>();
DBcontactsManager _dbContactsManager = null;
DBgroupsManager _dbgroupsManager = null;
public TrboSipGateway(string appServerIP, string dbSchema, string user, string password, string dbPort, int minSipPort, int maxSipPort, out bool errorOnRegister)
{
// Get sip server ip and port from database
// Get asterisk server's ip and sip port
DBsettingsManager dbSettingsManager = new DBsettingsManager(
appServerIP, dbSchema, user, password, dbPort);
_sipServerIP = dbSettingsManager.getSettingValue(0, "lanLinxServer");
string sSipServerPort = dbSettingsManager.getSettingValue(0, "lanSipPort");
string sWebServicePort = dbSettingsManager.getSettingValue(0, "lanSigPort");
_minSipPort = minSipPort;
_maxSipPort = maxSipPort;
errorOnRegister = false;
if (!string.IsNullOrEmpty(_sipServerIP) && !string.IsNullOrEmpty(sWebServicePort) && !string.IsNullOrEmpty(sSipServerPort))
{
if (int.TryParse(sSipServerPort, out _sipServerPort) && int.TryParse(sWebServicePort, out _webServicePort))
{
SafeMobileLib.Utils.WriteLine($"LINX version\nRegistering with sip server {_sipServerIP}:{_sipServerPort}, signalling on {_sipServerIP}:{_webServicePort}",
ConsoleColor.Green);
// Get list of motorla units and groups to register
_dbContactsManager = new DBcontactsManager(appServerIP, dbSchema, user, password, dbPort);
_dbgroupsManager = new DBgroupsManager(appServerIP, dbSchema, user, password, dbPort);
_contactsToRegister = GetListOfContactsToRegister(_dbContactsManager);
// Register
RegisterToSipServer(_contactsToRegister, out errorOnRegister);
}
}
}
#region Public Members
public bool ConnectedToAstersisk
{
get
{
lock (_lockerConnectedToAstersik)
{
return _connectedToAsterisk;
}
}
}
public SipClientClass2 GetSipClass(int radioSipID)
{
return _sipIDToRadioSipInfoDict[radioSipID].SipClass;
}
public void SendInvite(int radioSipID, string linxSipID)
{
if (LinxSipIDisGroup(int.Parse(linxSipID)))
this.GetSipClass(radioSipID).InviteGroup(linxSipID.ToString());
else
this.GetSipClass(radioSipID).Invite(linxSipID.ToString());
}
public bool LinxSipIDisGroup(int linxSipID)
{
return !_linxSipIDToLinxRadioID_dict.ContainsKey(linxSipID);
}
internal int? GetLinxInRadioGroup(int groupRadioID)
{
int groupSipID = GetSipIDFromRadioID(groupRadioID, true);
if (_radioGroupSipIDToListeningLINX.ContainsKey(groupSipID))
{
return _radioGroupSipIDToListeningLINX[groupSipID];
}
else
return null;
}
internal int? GetLinxForPrivateCall(int radioID)
{
if (_radioIDtoLinxSipIDforPrivateCall_dict.ContainsKey(radioID))
return _radioIDtoLinxSipIDforPrivateCall_dict[radioID];
else
return null;
}
/// <summary>
/// <para>Returns the sip id of a radio unit or group,</para>
/// <para>or the received radioID if the radio is not on the list of registered radios</para>
/// </summary>
/// <param name="radioID">The radio id of a radio or a radio group</param>
/// <param name="isGroup">True if is id is for radio group, otherwise false</param>
/// <returns>The sip id of a radio unit or group, or the received radio id if the sip id is not found</returns>
internal int GetSipIDFromRadioID(int radioID, bool isGroup)
{
LINX.RadioIdKey radioKey = new RadioIdKey(radioID, isGroup);
if (_motoRadioIDToSipID_dict.ContainsKey(radioKey))
return _motoRadioIDToSipID_dict[radioKey];
else
return radioID;
}
public void ListOfSipIDHasChanged()
{
//Unregister from sip server
UnregisterFromSipServer();
// Read new data from database
_contactsToRegister = GetListOfContactsToRegister(_dbContactsManager);
// Register again to Sip server for the new ID's
bool errorOnregister = false;
RegisterToSipServer(_contactsToRegister, out errorOnregister);
}
public async Task<bool> SendSmsToLinx(string seqID, int linxRadioID, int remoteRadioID, string text)
{
// Get linx sip id from linx radio id
int linxSipID = -1;
bool smsReceivedByLinx = false;
if (_linxRadioIdToLinxSipID_dict.ContainsKey(linxRadioID))
{
linxSipID = _linxRadioIdToLinxSipID_dict[linxRadioID];
int radioSipID = GetSipIDFromRadioID(remoteRadioID, false);
if (_sipIDToRadioSipInfoDict.ContainsKey(radioSipID))
{
smsReceivedByLinx = await _sipIDToRadioSipInfoDict[radioSipID].SipClass.SendSmsAsync(linxSipID.ToString(), text);
}
}
return smsReceivedByLinx;
}
public void PrivateCallRequestFromRadioReceived(int remoteRadioID, int linxRadioID)
{
// Radio with "remoteRadioID" wants to make private call to linx with "linxRadioID"
// Check if the radio has right (in Admin) to make private call to this linx
if (RadioCanMakePrivateCallToLinx(remoteRadioID, linxRadioID))
{
if (_linxRadioIdToLinxSipID_dict.ContainsKey(linxRadioID))
{
// Create (update) the correspondence between the radio and linx for private call
_radioIDtoLinxSipIDforPrivateCall_dict[remoteRadioID] = _linxRadioIdToLinxSipID_dict[linxRadioID];
// Notify the radio by sending an sms
SmsFromLinxToRadio?.Invoke(_sipIDToRadioSipInfoDict[GetSipIDFromRadioID(remoteRadioID, false)].ContactInfo,
"Private PTT to dispatcher is redirected to me", linxRadioID);
}
else if (linxRadioID == 0)
{
if (_radioIDtoLinxSipIDforPrivateCall_dict.ContainsKey(remoteRadioID))
{
int linxSipID = _radioIDtoLinxSipIDforPrivateCall_dict[remoteRadioID];
// this means that the radio want's to talk with the dispatcher (to reset the link with Linx)
_radioIDtoLinxSipIDforPrivateCall_dict.Remove(remoteRadioID);
// Notify the radio by sending an sms
SmsFromLinxToRadio?.Invoke(_sipIDToRadioSipInfoDict[GetSipIDFromRadioID(remoteRadioID, false)].ContactInfo,
"Private PTT is now redirected to dispatcher", _linxSipIDToLinxRadioID_dict[linxSipID]);
}
}
}
}
public void Stop()
{
UnregisterFromSipServer();
}
#endregion
#region Private Sip methods
private void RegisterToSipServer(IEnumerable<ContactLinx> contactsToRegister, out bool exceptionOnRegister)
{
SafeMobileLib.Utils.WriteLine("Starting registration......", ConsoleColor.Green);
int linxListeningToGroup = -1;
string localIPAddres = BestLocalEndPoint(new IPEndPoint(IPAddress.Parse(_sipServerIP), _sipServerPort)).Address.ToString();
exceptionOnRegister = false;
foreach (ContactLinx c in contactsToRegister)
{
Random r = new Random();
// Get available port for sip messages
int sipPort = ReturnAvailableSIPport(_minSipPort, _maxSipPort);
// Create sip Class
SipClientClass2 sipClass = null;
try
{
sipClass = CreateSipClass(_sipServerIP, _sipServerPort, _webServicePort, sipPort, c.SipId, c.Sippwd, r.Next(30, 57), 32, 10000,
localIPAddres);
}
catch (Exception ex)
{
SafeMobileLib.Utils.WriteLine("Could not register to Asterisk for motorola " + c.Name + $"(radio id {c.Id} )" +
$" with sipID {c.SipId} on local sip port {sipPort}\n" + ex.ToString(), ConsoleColor.Red);
_setOfUnavalablePorts.Add(sipPort);
exceptionOnRegister = true;
continue;
}
_sipIDToRadioSipInfoDict.Add(c.SipId, new RadioSipInfo(sipClass, c));
_motoRadioIDToSipID_dict.Add(new RadioIdKey(c.Id, c.Type == ContactType.GROUP), c.SipId);
// LINX listening to radio Groups
if (c.Type == ContactType.GROUP)
{
// Check if a LINX is listening to this radio group
if ((linxListeningToGroup = _dbgroupsManager.getLinxSIPIdForGroupSIPid(c.SipId)) != -1)
_radioGroupSipIDToListeningLINX.Add(c.SipId, linxListeningToGroup);
}
}
}
private void UnregisterFromSipServer()
{
foreach (ContactLinx c in _contactsToRegister)
{
if (_sipIDToRadioSipInfoDict.ContainsKey(c.SipId))
{
SipClientClass2 sipClass = _sipIDToRadioSipInfoDict[c.SipId].SipClass;
RemoveEventHandlers(sipClass);
sipClass.Stop(false);
Thread.Sleep(10);
}
}
_sipIDToRadioSipInfoDict.Clear();
_motoRadioIDToSipID_dict.Clear();
_radioGroupSipIDToListeningLINX.Clear();
}
private SipClientClass2 CreateSipClass(string sipServerIP, int sipServerPort, int socketI0Port, int localSipPort, int userName, string password,
int registrationInterval, int bufferMiliseconds, int requestTimeout, string localIP)
{
SipClientClass2 sipClass = new SipClientClass2(sipServerIP, sipServerPort, localSipPort, userName.ToString(), password,
registrationInterval, bufferMiliseconds, requestTimeout, socketI0Port, false, localIP);
sipClass.MaxNbOfDialogs = 1; // Just one sip dialog permited
sipClass.SmsConfirmationFromAsterisk = true;
sipClass.MinRtpPortNumber = 26284;
sipClass.MaxRtpPortNumber = 26300;
// Add event handlers
AddEventHandlers(sipClass);
return sipClass;
}
private void AddEventHandlers(SipClientClass2 sipClass)
{
sipClass.RegistrationStateChanged += SipClass_RegistrationStateChanged;
sipClass.InviteReceived += SipClass_InviteReceived;
sipClass.DialogCreated += SipClass_DialogCreated;
sipClass.VoiceReceived += SipClass_VoiceReceived;
sipClass.DialogClosed += SipClass_DialogClosed;
//sipClass.HangtimeEnded += SipClass_HangtimeEnded;
sipClass.ErrorOnCreatingDialog += SipClass_ErrorOnCreatingDialog;
sipClass.SipSmsReceived += SipClass_SipSmsReceived;
}
private void RemoveEventHandlers(SipClientClass2 sipClass)
{
sipClass.RegistrationStateChanged -= SipClass_RegistrationStateChanged;
sipClass.InviteReceived -= SipClass_InviteReceived;
sipClass.DialogCreated -= SipClass_DialogCreated;
sipClass.VoiceReceived -= SipClass_VoiceReceived;
sipClass.DialogClosed -= SipClass_DialogClosed;
//sipClass.HangtimeEnded -= SipClass_HangtimeEnded;
sipClass.ErrorOnCreatingDialog -= SipClass_ErrorOnCreatingDialog;
sipClass.SipSmsReceived -= SipClass_SipSmsReceived;
}
private bool RadioCanMakePrivateCallToLinx(int remoteRadioID, int linxRadioID)
{
// Check that the radio sending the request is registered to Asterisk by the gateway
int radioSipID = GetSipIDFromRadioID(remoteRadioID, false);
if (radioSipID == remoteRadioID) // the radio sip id was not found, the function returned te radio id
{
return false;
}
// Check that the linx radio id exists
bool linxExists = _linxRadioIdToLinxSipID_dict.ContainsKey(linxRadioID);
if (linxExists)
{
// Check if the radio has right in Admin to make private PTT to the linx
// ..... code that checks that .......
return true;
}
else if (linxRadioID == 0)
{
return true; // this will reset the link with Linx
}
else
return false;
}
#region SipClientClass Event Handlers
private void SipClass_RegistrationStateChanged(object sender, RegistrationStateChangedEventArgs e)
{
bool connectedToAsterisk = (e.Reason == RegistrationStatus.Registered);
SafeMobileLib.Utils.WriteLine($"Sip id's {e.RegisteredId} registration status is \"{e.Reason}\"",
connectedToAsterisk ? ConsoleColor.Green : ConsoleColor.Red);
lock (_lockerConnectedToAstersik)
{
if (this._connectedToAsterisk != connectedToAsterisk)
{
_connectedToAsterisk = connectedToAsterisk;
// fire event
ConnectionStatusChanded?.Invoke(_connectedToAsterisk);
}
//// Restart gw if error on IndependentSoft library (when Asterisk is down)
if (e.Reason == RegistrationStatus.SocketError && !_unregistering)
{
_unregistering = true;
string message = "Unregistering from Sip Server....";
SafeMobileLib.Utils.WriteLine(message, ConsoleColor.Green);
// Start a task with a delay of 5s that will unregister and register again to Sip server for the new IDs
Task.Delay(5000).ContinueWith(t =>
{
UnregisterFromSipServer();
Thread.Sleep(3000);
_unregistering = false;
string message1 = "Unregistered from Sip Server. Will start registration";
SafeMobileLib.Utils.WriteLine(message1, ConsoleColor.Green);
Thread.Sleep(3000);
bool exceptionOnRegister = false;
RegisterToSipServer(_contactsToRegister, out exceptionOnRegister);
if (exceptionOnRegister)
ShowUIMessage?.Invoke(_msgForErrorOnRegister);
});
}
}
}
private void SipClass_InviteReceived(object sender, InviteReceivedArgs e)
{
//throw new NotImplementedException();
SipClientClass2 sipClass = (SipClientClass2)sender;
int radioSipID = int.Parse(sipClass.UserName);
SafeMobileLib.Utils.WriteLine($"Sip id {e.SipIDsendingInvite} sends invite to radio with sip id {radioSipID}", ConsoleColor.Green);
if (_sipIDToRadioSipInfoDict.ContainsKey(radioSipID))
{
LinxSendsInvite?.Invoke(_sipIDToRadioSipInfoDict[radioSipID], e.SipIDsendingInvite);
}
}
private void SipClass_ErrorOnCreatingDialog(object sender, ErrorEventArgs e)
{
SipClientClass2 sipClass = (SipClientClass2)sender;
int radioSipID = int.Parse(sipClass.UserName);
SafeMobileLib.Utils.WriteLine($"Error on creating dialog when sip id {e.FromID} sent invite to {e.ToID}; Reason: {e.Reason}",
ConsoleColor.Red);
if (_sipIDToRadioSipInfoDict.ContainsKey(radioSipID))
{
bool inviteWasSentbyRadio = e.FromID == sipClass.UserName;
ErrorOnCreatingDialog?.Invoke(_sipIDToRadioSipInfoDict[radioSipID], inviteWasSentbyRadio ? e.ToID : e.FromID, inviteWasSentbyRadio);
}
}
//private void SipClass_HangtimeEnded(object sender, HangtimeEndedEventArgs e)
//{
// SipClientClass2 sipClass = (SipClientClass2)sender;
// int radioSipID = int.Parse(sipClass.UserName);
// SafeMobileLib.Utils.WriteLine($"Linx with sipID {e.SipIDinDialogWith} has ended hangtime with radio with sipID {radioSipID}",
// ConsoleColor.Magenta);
// if (_sipIDToRadioSipInfoDict.ContainsKey(radioSipID))
// {
// LinxHangtimeEnded?.Invoke(_sipIDToRadioSipInfoDict[radioSipID], e.SipIDinDialogWith);
// }
//}
private void SipClass_SipSmsReceived(object sender, SmsReceivedEventsArgs e)
{
SipClientClass2 sipClass = (SipClientClass2)sender;
int radioSipID = int.Parse(sipClass.UserName);
SafeMobileLib.Utils.WriteLine($"Sms from linx with sipID {e.SipIDinDialogWith} to radio with sipID {radioSipID}",
ConsoleColor.Green);
if (_sipIDToRadioSipInfoDict.ContainsKey(radioSipID))
{
int linxSipID = int.Parse(e.SipIDinDialogWith);
if (_linxSipIDToLinxRadioID_dict.ContainsKey(linxSipID))
{
SmsFromLinxToRadio?.Invoke(_sipIDToRadioSipInfoDict[radioSipID].ContactInfo, e.Message, _linxSipIDToLinxRadioID_dict[linxSipID]);
}
}
//SmsReceived?.Invoke()
}
private void SipClass_DialogClosed(object sender, LinxDialogClosedEventArgs e)
{
SipClientClass2 sipClass = (SipClientClass2)sender;
int radioSipID = int.Parse(sipClass.UserName);
SafeMobileLib.Utils.WriteLine($"Dialog closed between linx with sipID {e.SipIDinDialogWith} and radio with sipID {radioSipID}",
ConsoleColor.Green);
if (_sipIDToRadioSipInfoDict.ContainsKey(radioSipID))
{
DialogClosed?.Invoke(_sipIDToRadioSipInfoDict[radioSipID], e.SipIDinDialogWith, e.SipIDwhoClosed == sipClass.UserName);
}
}
private void SipClass_VoiceReceived(object sender, LinxAudioEventArgs e)
{
SipClientClass2 sipClass = (SipClientClass2)sender;
int radioSipID = int.Parse(sipClass.UserName);
if (_sipIDToRadioSipInfoDict.ContainsKey(radioSipID))
{
VoiceReceived?.Invoke(_sipIDToRadioSipInfoDict[radioSipID], e.Buffer);
}
}
private void SipClass_DialogCreated(object sender, LinxDialogCreatedEventArgs e)
{
SipClientClass2 sipClass = (SipClientClass2)sender;
int radioSipID = int.Parse(sipClass.UserName);
// get the linx sip id
string linxSipID = (e.FromID == sipClass.UserName ? e.ToID : e.FromID);
if (_sipIDToRadioSipInfoDict.ContainsKey(radioSipID))
{
DialogCreated?.Invoke(_sipIDToRadioSipInfoDict[radioSipID], linxSipID, e.FromID == sipClass.UserName);
}
}
#endregion
#endregion
#region Private methods
private int ReturnAvailableSIPport(int minSipPort, int maxSipPort)
{
int sipPort = minSipPort;
while (_setOfUnavalablePorts.Contains(sipPort) || IsPortAllreadyInUse(sipPort) && sipPort < maxSipPort)
{
sipPort++;
}
if (sipPort < maxSipPort)
return sipPort;
else
throw new ApplicationException(
string.Format("Nu gasesc port liber in range-ul {0} - {1}", minSipPort, maxSipPort));
}
private bool IsPortAllreadyInUse(int portNumber)
{
return (from p in System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()
where p.Port == portNumber
select p).Count() == 1;
}
/// <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;
}
IEnumerable<ContactLinx> GetListOfContactsToRegister(DBcontactsManager contactsManager)
{
List<ContactLinx> dbContacts = contactsManager.getContactsForSIPgw(Main.GWID);
// update the dictionary with Linx radio id to linx sip id
_linxRadioIdToLinxSipID_dict.Clear();
_linxSipIDToLinxRadioID_dict.Clear();
foreach (ContactLinx contact in dbContacts)
{
if (contact.GWtype == GatewayType.Broadband && contact.Type == ContactType.UNIT)
{
// This is a linx unit (not group)
_linxRadioIdToLinxSipID_dict.Add(contact.Id, contact.SipId);
_linxSipIDToLinxRadioID_dict.Add(contact.SipId, contact.Id);
}
}
// Select only units or groups
var contactsToRegister = from contact in dbContacts
where (contact.GWtype == GatewayType.Tier2Radio && (contact.Type == ContactType.GROUP || contact.Type == ContactType.UNIT))
select contact;
return contactsToRegister;
}
#endregion
#region Events
public delegate void LinxToRadioDel(RadioSipInfo sipInfo, string linxID);
public event LinxToRadioDel LinxSendsInvite;
public delegate void LinxToRadioDel2(RadioSipInfo sipInfo, string linxID, bool fromRadioToLinx);
public event LinxToRadioDel2 DialogCreated;
public event LinxToRadioDel2 DialogClosed;
public event LinxToRadioDel2 ErrorOnCreatingDialog;
//public event LinxToRadioDel LinxHangtimeEnded;
public delegate void VoiceReceivedDel(RadioSipInfo sipInfo, byte[] buffer);
public event VoiceReceivedDel VoiceReceived;
public delegate void ConnectionStatusChangedDel(bool connected);
public event ConnectionStatusChangedDel ConnectionStatusChanded;
public delegate void SmsFromLinxToRadioDelegate(ContactLinx radioContactInfo, string message, int linxRadioID);
public event SmsFromLinxToRadioDelegate SmsFromLinxToRadio;
public delegate void ShowUIMessageDel(string message);
public event ShowUIMessageDel ShowUIMessage;
#endregion
}
}