From f658fdf3631be6fe9fed69c684ff4e0818dd93b2 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 21 May 2026 16:21:03 -0600 Subject: [PATCH 1/4] fix: update routing logic and enhance logging in Extensions and MockVC classes closes #1423 --- .../Routing/Extensions.cs | 20 ++++++++++++------- .../VideoCodec/MockVC/MockVC.cs | 2 +- .../VideoCodec/VideoCodecBase.cs | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index 631ebd99..be533b70 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -249,7 +249,7 @@ namespace PepperDash.Essentials.Core } // otherwise, audioVideo needs to be handled as two steps. - Debug.LogDebug(destination, "Attempting to build source route from {destinationKey} to {sourceKey} of type {type}", source.Key, signalType); + Debug.LogDebug(destination, "Attempting to build source route from {destinationKey} to {sourceKey} of type {type}", destination.Key, source.Key, signalType); RouteDescriptor audioRouteDescriptor; @@ -374,24 +374,28 @@ namespace PepperDash.Essentials.Core IndexTieLines(); } - var sinks = DeviceManager.AllDevices.OfType().Where(d => !(d is IRoutingInputsOutputs)); - var sources = DeviceManager.AllDevices.OfType().Where(d => !(d is IRoutingInputsOutputs)); + var sinks = DeviceManager.AllDevices.OfType(); + var sources = DeviceManager.AllDevices.OfType(); - foreach (var sink in sinks) + foreach (var sink in sinks.Where(d => !(d is IRoutingInputsOutputs))) { - foreach (var source in sources) + foreach (var source in sources.Where(d => !(d is IRoutingInputsOutputs))) { foreach (var inputPort in sink.InputPorts) { foreach (var outputPort in source.OutputPorts) { - var (audioOrSingleRoute, videoRoute) = sink.GetRouteToSource(source, inputPort.Type, inputPort, outputPort); + var (audioOrSingleRoute, videoRoute) = sink.GetRouteToSource(source, outputPort.Type, inputPort, outputPort); if (audioOrSingleRoute == null && videoRoute == null) { continue; } + Debug.LogVerbose("AudioOrSingleRoute Found: {audioRoute}", audioOrSingleRoute); + + Debug.LogVerbose("VideoRoute Found: {videoRoute}", videoRoute); + if (audioOrSingleRoute != null) { // Only add routes that have actual switching steps @@ -646,6 +650,8 @@ namespace PepperDash.Essentials.Core // Only the ones that are routing devices var midpointTieLines = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); + Debug.LogVerbose(destination, "Found {tieLineCount} tie lines to walk for {destinationKey}", midpointTieLines.Count(), destination.Key); + //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration if (alreadyCheckedDevices == null) alreadyCheckedDevices = new List(); @@ -685,7 +691,7 @@ namespace PepperDash.Essentials.Core if (goodInputPort == null) { - Debug.LogVerbose(destination, "No route found to {0}", source.Key); + Debug.LogVerbose(destination, "No route found to {0} from destination {1} for type {2}", source.Key, destination.Key, signalType); // Cache this as an impossible route _impossibleRoutes.TryAdd(routeKey, 0); diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs index e9f7908c..bdefa09d 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs @@ -21,7 +21,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec /// /// Represents a MockVC /// - public class MockVC : VideoCodecBase, IRoutingSource, IHasCallHistory, IHasScheduleAwareness, IHasCallFavorites, IHasDirectory, IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets + public class MockVC : VideoCodecBase, IRoutingSource, IHasCallHistory, IHasScheduleAwareness, IHasCallFavorites, IHasDirectory, IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets, IRoutingInputs { /// /// Gets or sets the PropertiesConfig diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs index b0b5ef55..04c7e64d 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs @@ -26,7 +26,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec /// /// Base class for video codec devices /// - public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutputs, + public abstract class VideoCodecBase : ReconfigurableDevice, IUsageTracking, IHasDialer, IHasContentSharing, ICodecAudio, iVideoCodecInfo, IBridgeAdvanced, IHasStandbyMode { private const int XSigEncoding = 28591; From abef1d095f624b8bb6aa526e755e0d7e86a7170e Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Fri, 22 May 2026 09:48:12 -0600 Subject: [PATCH 2/4] fix: prevent processing of null or empty midpoint keys in RoutingFeedbackManager --- .../Routing/RoutingFeedbackManager.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs index 16822ff2..7fe41c5c 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs @@ -66,6 +66,9 @@ namespace PepperDash.Essentials.Core.Routing foreach (var midpointKey in upstreamMidpoints) { + if (string.IsNullOrEmpty(midpointKey)) + continue; + if (!midpointToSinksMap.ContainsKey(midpointKey)) midpointToSinksMap[midpointKey] = new HashSet(); @@ -115,7 +118,8 @@ namespace PepperDash.Essentials.Core.Routing if (tieLine.SourcePort.ParentDevice is IRoutingWithFeedback midpoint) { - midpoints.Add(midpoint.Key); + if (!string.IsNullOrEmpty(midpoint.Key)) + midpoints.Add(midpoint.Key); // Find upstream TieLines connected to this midpoint's inputs var midpointInputs = (midpoint as IRoutingInputs)?.InputPorts; @@ -244,6 +248,9 @@ namespace PepperDash.Essentials.Core.Routing var upstreamMidpoints = GetUpstreamMidpoints(sink); foreach (var midpointKey in upstreamMidpoints) { + if (string.IsNullOrEmpty(midpointKey)) + continue; + if (!midpointToSinksMap.ContainsKey(midpointKey)) midpointToSinksMap[midpointKey] = new HashSet(); From 77c700c5659740a3f8c0f0d2b13ea6c673919da2 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 2 Jun 2026 11:25:48 -0600 Subject: [PATCH 3/4] fix: add escape handling for byte arrays and strings in DeviceJsonApi --- .../Devices/DeviceJsonApi.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs b/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs index c4ec2ca9..c01d5e81 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs @@ -7,6 +7,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace PepperDash.Essentials.Core @@ -176,7 +177,20 @@ namespace PepperDash.Essentials.Core { if (!conversionType.IsEnum) { - return Convert.ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture); + if (conversionType == typeof(byte[]) && value is string byteString) + { + var unescaped = UnescapeString(byteString); + return System.Text.Encoding.GetEncoding(28591).GetBytes(unescaped); + } + + var converted = Convert.ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture); + + if (conversionType == typeof(string) && converted is string s) + { + return UnescapeString(s); + } + + return converted; } var stringValue = Convert.ToString(value); @@ -189,6 +203,32 @@ namespace PepperDash.Essentials.Core return Enum.Parse(conversionType, stringValue, true); } + /// + /// Processes escape sequences in a string, converting sequences like \r, \n, \t, \xHH + /// to their corresponding non-printable ASCII characters. + /// + private static string UnescapeString(string input) + { + if (string.IsNullOrEmpty(input)) + return input; + + return Regex.Replace(input, @"\\(r|n|t|\\|x[0-9A-Fa-f]{2})", match => + { + var seq = match.Groups[1].Value; + switch (seq) + { + case "r": return "\r"; + case "n": return "\n"; + case "t": return "\t"; + case "\\": return "\\"; + default: + // \xHH hex escape + var hex = seq.Substring(1); + return ((char)Convert.ToInt32(hex, 16)).ToString(); + } + }); + } + /// /// Gets the properties on a device /// From 1ce86dab291814938b18d524fd41e6c47b28e4b0 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 2 Jun 2026 14:10:33 -0600 Subject: [PATCH 4/4] fix: add escape handling for port forwarding in DebugSessionRequestHandler --- .../DebugSessionRequestHandler.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs index 01e38834..56c983ae 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs @@ -57,6 +57,36 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers // Start the WS Server Debug.WebsocketSink.StartServerAndSetPort(port); Debug.SetWebSocketMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose); + + // Attempt to forward the port to the CS LAN + try + { + var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType( + EthernetAdapterType.EthernetCSAdapter); + var csIp = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); + + 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); + } + } + 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; @@ -90,8 +120,40 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers /// protected override void HandlePost(HttpCwsContext context) { + 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); + + 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();