using Crestron.SimplSharp; using Crestron.SimplSharp.WebScripting; using Crestron.SimplSharpPro.EthernetCommunication; using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; using Serilog.Events; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace PepperDash.Essentials.Core.Web.RequestHandlers { /// /// Represents a DebugSessionRequestHandler /// public class DebugSessionRequestHandler : WebApiBaseRequestHandler { private CTimer _portForwardTimeoutTimer; private readonly object _timerLock = new object(); /// /// Constructor /// public DebugSessionRequestHandler() : base(true) { } /// /// Gets details for a debug session /// /// protected override void HandleGet(Crestron.SimplSharp.WebScripting.HttpCwsContext context) { var routeData = context.Request.RouteData; if (routeData == null) { context.Response.StatusCode = 400; context.Response.StatusDescription = "Bad Request"; context.Response.End(); return; } try { var ip = CrestronEthernetHelper.GetEthernetParameter( CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); var port = 0; string csIp = null; if (!Debug.WebsocketSink.IsRunning) { Debug.LogMessage(LogEventLevel.Information, "Starting WS Server"); // Generate a random port within a specified range port = new Random().Next(65435, 65535); // Start the WS Server Debug.WebsocketSink.StartServerAndSetPort(port); Debug.SetWebSocketMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose); } // Attempt to get the CS LAN IP and forward the port try { var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType( EthernetAdapterType.EthernetCSAdapter); csIp = CrestronEthernetHelper.GetEthernetParameter( CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); if (port > 0) { var result = CrestronEthernetHelper.AddPortForwarding( (ushort)port, (ushort)port, csIp, CrestronEthernetHelper.ePortMapTransport.TCP); if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) { Debug.LogMessage(LogEventLevel.Warning, "Error adding port forwarding for debug websocket: {0}", result); } else { Debug.LogMessage(LogEventLevel.Information, "Port {0} forwarded to CS LAN for debug websocket", port); StartPortForwardTimeout(port, csIp); } } } catch (ArgumentException) { Debug.LogMessage(LogEventLevel.Debug, "This processor does not have a CS LAN adapter; skipping port forwarding"); } catch (Exception ex) { Debug.LogMessage(LogEventLevel.Warning, "Error automatically forwarding debug websocket port to CS LAN: {0}", ex.Message); } var url = Debug.WebsocketSink.Url; var data = new { url = Debug.WebsocketSink.Url, fallbackUrl = csIp != null ? url.Replace(csIp, ip) : null }; Debug.LogMessage(LogEventLevel.Information, "Debug Session URL: {0}", url); Debug.LogMessage(LogEventLevel.Information, "Fallback Debug Session URL: {0}", data.fallbackUrl); // Return the port number with the full url of the WS Server var res = JsonConvert.SerializeObject(data); context.Response.ContentType = "application/json"; context.Response.ContentEncoding = Encoding.UTF8; context.Response.StatusCode = 200; context.Response.StatusDescription = "OK"; context.Response.Write(res, false); context.Response.End(); } catch (Exception e) { Debug.LogMessage(LogEventLevel.Information, "Error: {0}", e); } } /// /// Stops a debug session /// /// protected override void HandlePost(HttpCwsContext context) { CancelPortForwardTimeout(); var port = Debug.WebsocketSink.Port; Debug.WebsocketSink.StopServer(); // Remove the port forwarding entry try { var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType( EthernetAdapterType.EthernetCSAdapter); var csIp = CrestronEthernetHelper.GetEthernetParameter( CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); if (port <= 0) { Debug.LogMessage(LogEventLevel.Debug, "Debug websocket port is not set; skipping port forwarding removal"); } else { var result = CrestronEthernetHelper.RemovePortForwarding( (ushort)port, (ushort)port, csIp, CrestronEthernetHelper.ePortMapTransport.TCP); if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) { Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding for debug websocket: {0}", result); } else { Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed", port); } } } catch (ArgumentException) { Debug.LogMessage(LogEventLevel.Debug, "This processor does not have a CS LAN adapter; skipping port forwarding removal"); } catch (Exception ex) { Debug.LogMessage(LogEventLevel.Warning, "Error removing debug websocket port forwarding: {0}", ex.Message); } context.Response.StatusCode = 200; context.Response.StatusDescription = "OK"; context.Response.End(); Debug.LogMessage(LogEventLevel.Information, "Websocket Debug Session Stopped"); } private void StartPortForwardTimeout(int port, string csIp) { lock (_timerLock) { _portForwardTimeoutTimer?.Dispose(); _portForwardTimeoutTimer = new CTimer(_ => { if (Debug.WebsocketSink.HasActiveConnections) { Debug.LogMessage(LogEventLevel.Debug, "Debug websocket has active connections; keeping port forward"); return; } Debug.LogMessage(LogEventLevel.Information, "No debug websocket connection within 30 seconds; removing port forward for port {0}", port); try { var result = CrestronEthernetHelper.RemovePortForwarding( (ushort)port, (ushort)port, csIp, CrestronEthernetHelper.ePortMapTransport.TCP); if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) { Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", result); } else { Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed due to timeout", port); } } catch (Exception ex) { Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", ex.Message); } }, 30000); } } /// /// Cancels the port forward timeout timer if a session is being explicitly stopped. /// private void CancelPortForwardTimeout() { lock (_timerLock) { _portForwardTimeoutTimer?.Dispose(); _portForwardTimeoutTimer = null; } } } }