using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Comal.Classes; using InABox.Client.IPC; using InABox.Clients; using InABox.Core; using InABox.DigitalMatter; using PRSServer.Engines; namespace PRSServer { internal class Device { public Guid ID { get; set; } public DateTime TimeStamp { get; set; } public CoreExpression? BatteryFormula { get; set; } public Device(Guid id, DateTime timeStamp, CoreExpression? batteryFormula) { ID = id; TimeStamp = timeStamp; BatteryFormula = batteryFormula; } /// /// Should return a percentage /// /// public double CalculateBatteryLevel(double batteryValue) { if (BatteryFormula != null) return BatteryFormula.Evaluate(new Dictionary { { nameof(GPSBatteryFormulaModel.BatteryLevel), batteryValue } }); return batteryValue; } } /// /// TCPServer is the Server class. When "StartServer" method is called /// this Server object tries to connect to a IP Address specified on a port /// configured. Then the server start listening for client socket requests. /// As soon as a requestcomes in from any client then a Client Socket /// Listening thread will be started. That thread is responsible for client /// communication. /// internal class GPSEngine : Engine { /// /// Default Constants. /// public static IPAddress DEFAULT_SERVER = IPAddress.Any; public static int DEFAULT_PORT = 7999; public static IPEndPoint DEFAULT_IP_END_POINT = new(DEFAULT_SERVER, DEFAULT_PORT); private readonly CancellationTokenSource cts = new(); private readonly ConcurrentDictionary m_devices = new(); private Task m_purgingThread; private DateTime m_RefreshDate = DateTime.MinValue; /// /// Local Variables Declaration. /// private TcpListener m_server; private Task m_serverThread; private ArrayList m_socketListenersList; private bool m_stopPurging; private bool m_stopServer; private readonly ConcurrentQueue> m_updates = new(); private Task m_updatetask; public GPSEngine() { Init(DEFAULT_IP_END_POINT); } public GPSEngine(IPAddress serverIP) { Init(new IPEndPoint(serverIP, DEFAULT_PORT)); } public GPSEngine(int port) { Init(new IPEndPoint(DEFAULT_SERVER, port)); } public GPSEngine(IPAddress serverIP, int port) { Init(new IPEndPoint(serverIP, port)); } public GPSEngine(IPEndPoint ipNport) { Init(ipNport); } ~GPSEngine() { Stop(); } public override void Configure(Server server) { base.Configure(server); } /// /// Init method that create a server (TCP Listener) Object based on the /// IP Address and Port information that is passed in. /// /// private void Init(IPEndPoint ipNport) { try { m_server = new TcpListener(ipNport); } catch (Exception e) { m_server = null; } m_updatetask = Task.Run(() => { while (m_server != null && !cts.IsCancellationRequested) if (m_updates.Any()) if (m_updates.TryDequeue(out var tuple)) { Logger.Send(LogType.Information, "", string.Format("Updating Server ({0}): {1} - {2}", m_updates.Count, tuple.Item1.DeviceID, tuple.Item2)); new Client().Save(tuple.Item1, tuple.Item2, (_, __) => { }); Thread.Sleep(Properties.UpdateTimer); } }, cts.Token); } private static byte[] StringToByteArray(string hex) { var NumberChars = hex.Length; var bytes = new byte[NumberChars / 2]; for (var i = 0; i < NumberChars; i += 2) bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); return bytes; } private bool CheckConnection() { if (ClientFactory.UserGuid == Guid.Empty) { // Wait for server connection while (!Client.Ping()) { Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds..."); Task.Delay(30_000).Wait(); Logger.Send(LogType.Information, "", "Retrying connection..."); } ClientFactory.SetBypass(); } if (DateTime.Now - m_RefreshDate > new TimeSpan(0, 5, 0)) { Logger.Send(LogType.Information, "", "Refreshing Tracker Cache"); var table = new Client().Query( null, new Columns(x => x.ID, x => x.DeviceID, x => x.Type.BatteryFormula) ); m_RefreshDate = DateTime.Now; Logger.Send(LogType.Information, "", string.Format("- Tracker Cache: {0} devices", table.Rows.Count)); m_devices.Clear(); foreach (var row in table.Rows) { var formula = row.Get(x => x.Type.BatteryFormula); var expression = string.IsNullOrWhiteSpace(formula) ? null : new CoreExpression(formula); m_devices[row.Get(x => x.DeviceID)] = new Device(row.Get(x => x.ID), DateTime.MinValue, expression); } } return true; } public void StartSigfoxListener() { if(Properties.SigfoxListenPort == 0) { Logger.Send(LogType.Information, "", "No Sigfox listen port specified\n"); return; } var listener = new Listener(new SigfoxHandlerProperties(m_devices)); listener.InitPort((ushort)Properties.SigfoxListenPort); Logger.Send(LogType.Information, "", "Starting Sigfox Listener on port " + Properties.SigfoxListenPort); listener.Start(); } /// /// Method that starts TCP/IP Server. /// public override void Run() { if (string.IsNullOrWhiteSpace(Properties.Server)) { Logger.Send(LogType.Error, "", "Server is blank!"); return; } Logger.Send(LogType.Information, "", "Registering Classes"); CoreUtils.RegisterClasses(); ComalUtils.RegisterClasses(); ClientFactory.SetClientType(typeof(PipeIPCClient<>), Platform.GPSEngine, Version, DatabaseServerProperties.GetPipeName(Properties.Server)); CheckConnection(); DMFactory.Initialise(Properties.DumpFormat, Properties.DumpFile); if (m_server != null) { // Create a ArrayList for storing SocketListeners before // starting the server. m_socketListenersList = new ArrayList(); // Start the Server and start the thread to listen client // requests. m_server.Start(); m_serverThread = Task.Run(ServerThreadStart, cts.Token); // Create a low priority thread that checks and deletes client // SocktConnection objcts that are marked for deletion. m_purgingThread = Task.Run(PurgingThreadStart, cts.Token); } StartSigfoxListener(); } /// /// Method that stops the TCP/IP Server. /// public override void Stop() { if (m_server != null) { // It is important to Stop the server first before doing // any cleanup. If not so, clients might being added as // server is running, but supporting data structures // (such as m_socketListenersList) are cleared. This might // cause exceptions. cts.Cancel(); // Stop the TCP/IP Server. m_stopServer = true; m_server.Stop(); // Wait for one second for the the thread to stop. //m_serverThread.Join(1000); // If still alive; Get rid of the thread. //if (m_serverThread.IsAlive) //{ // m_serverThread.Abort(); //} m_serverThread = null; m_stopPurging = true; //m_purgingThread.Join(1000); //if (m_purgingThread.IsAlive) //{ // m_purgingThread.Abort(); //} //m_purgingThread = null; // Free Server Object. m_server = null; while (m_updates.Any()) if (m_updates.TryDequeue(out var tuple)) { Logger.Send(LogType.Information, "", string.Format("Updating Server ({0}): {1} - {2}", m_updates.Count, tuple.Item1.DeviceID, tuple.Item2)); new Client().Save(tuple.Item1, tuple.Item2, (_, __) => { }); Thread.Sleep(2000); } // Stop All clients. StopAllSocketListers(); } } /// /// Method that stops all clients and clears the list. /// private void StopAllSocketListers() { foreach (GPSListener socketListener in m_socketListenersList) socketListener.StopSocketListener(); // Remove all elements from the list. m_socketListenersList.Clear(); m_socketListenersList = null; } /// /// TCP/IP Server Thread that is listening for clients. /// private void ServerThreadStart() { // Client Socket variable; Socket clientSocket = null; GPSListener socketListener = null; while (!m_stopServer) try { if (!CheckConnection()) return; // Wait for any client requests and if there is any // request from any client accept it (Wait indefinitely). clientSocket = m_server.AcceptSocket(); // Create a SocketListener object for the client. socketListener = new GPSListener(clientSocket, m_devices, m_updates); // Add the socket listener to an array list in a thread // safe fashon. //Monitor.Enter(m_socketListenersList); lock (m_socketListenersList) { m_socketListenersList.Add(socketListener); } //Monitor.Exit(m_socketListenersList); // Start a communicating with the client in a different // thread. socketListener.StartSocketListener(); } catch (SocketException se) { m_stopServer = true; } } /// /// Thread method for purging Client Listeneres that are marked for /// deletion (i.e. clients with socket connection closed). This thead /// is a low priority thread and sleeps for 10 seconds and then check /// for any client SocketConnection obects which are obselete and /// marked for deletion. /// private void PurgingThreadStart() { while (!m_stopPurging) { var deleteList = new ArrayList(); // Check for any clients SocketListeners that are to be // deleted and put them in a separate list in a thread sage // fashon. //Monitor.Enter(m_socketListenersList); lock (m_socketListenersList) { foreach (GPSListener socketListener in m_socketListenersList) if (socketListener.IsMarkedForDeletion()) { deleteList.Add(socketListener); socketListener.StopSocketListener(); } // Delete all the client SocketConnection ojects which are // in marked for deletion and are in the delete list. for (var i = 0; i < deleteList.Count; ++i) m_socketListenersList.Remove(deleteList[i]); } //Monitor.Exit(m_socketListenersList); deleteList = null; Thread.Sleep(10000); } } } }