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 _sipIDToRadioSipInfoDict = new Dictionary(); private bool _connectedToAsterisk = false; private object _lockerConnectedToAstersik = new object(); private volatile bool _unregistering = false; private IEnumerable _contactsToRegister; private Dictionary _motoRadioIDToSipID_dict = new Dictionary(); private Dictionary _radioGroupSipIDToListeningLINX = new Dictionary(); private Dictionary _linxRadioIdToLinxSipID_dict = new Dictionary(); private Dictionary _linxSipIDToLinxRadioID_dict = new Dictionary(); private Dictionary _radioIDtoLinxSipIDforPrivateCall_dict = new Dictionary(); private string _msgForErrorOnRegister = "Not all the radios are connected to Linx server.Check sip ports range in config file"; private HashSet _setOfUnavalablePorts = new HashSet(); 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; } /// /// Returns the sip id of a radio unit or group, /// or the received radioID if the radio is not on the list of registered radios /// /// The radio id of a radio or a radio group /// True if is id is for radio group, otherwise false /// The sip id of a radio unit or group, or the received radio id if the sip id is not found 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 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 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; } /// /// Determines the most appropriate local end point to contact the provided remote end point. /// Testing shows this method takes on average 1.6ms to return. /// /// The remote end point /// The selected local end point private static IPEndPoint BestLocalEndPoint(IPEndPoint remoteIPEndPoint) { Socket testSocket = new Socket(remoteIPEndPoint.AddressFamily, SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); testSocket.Connect(remoteIPEndPoint); return (IPEndPoint)testSocket.LocalEndPoint; } IEnumerable GetListOfContactsToRegister(DBcontactsManager contactsManager) { List 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 } }