Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ jobs:
version: ${{ needs.getVersion.outputs.version }}
tag: ${{ needs.getVersion.outputs.tag }}
channel: ${{ needs.getVersion.outputs.channel }}
bypassPackageCheck: true
bypassPackageCheck: true
devToolsVersion: ${{ vars.ESSENTIALSDEVTOOLSVERSION }}
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.19.4-local</Version>
<Version>2.29.0-local</Version>
<InformationalVersion>$(Version)</InformationalVersion>
<Authors>PepperDash Technology</Authors>
<Company>PepperDash Technology</Company>
Expand Down
294 changes: 196 additions & 98 deletions src/PepperDash.Core/Logging/DebugWebsocketSink.cs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/PepperDash.Core/Web/WebApiServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public WebApiServer(string key, string name, string basePath)

if (_server == null) _server = new HttpCwsServer(BasePath);

_server.AuthenticateAllRoutes = false;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting AuthenticateAllRoutes = false disables authentication for every CWS route created by this server. That’s a high-impact security change (many endpoints expose device metadata and control actions). If the intent is to allow only specific unauthenticated routes (e.g., /login), keep global auth enabled and selectively exempt/handle auth per-route instead of turning it off globally.

Suggested change
_server.AuthenticateAllRoutes = false;
_server.AuthenticateAllRoutes = true;

Copilot uses AI. Check for mistakes.

_server.setProcessName(Key);
_server.HttpRequestHandler = new DefaultRequestHandler();

Expand Down
4 changes: 2 additions & 2 deletions src/PepperDash.Essentials.Core/Devices/DeviceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -436,14 +436,14 @@ public static void GetRoutingPorts(string s)
CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Input Ports:{2}", s, inputPorts.Count, CrestronEnvironment.NewLine);
foreach (var routingInputPort in inputPorts)
{
CrestronConsole.ConsoleCommandResponse("{0}{1}", routingInputPort.Key, CrestronEnvironment.NewLine);
CrestronConsole.ConsoleCommandResponse("key: {0} signalType: {1}{2}", routingInputPort.Key, routingInputPort.Type, CrestronEnvironment.NewLine);
}
}
if (outputPorts == null) return;
CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Output Ports:{2}", s, outputPorts.Count, CrestronEnvironment.NewLine);
foreach (var routingOutputPort in outputPorts)
{
CrestronConsole.ConsoleCommandResponse("{0}{1}", routingOutputPort.Key, CrestronEnvironment.NewLine);
CrestronConsole.ConsoleCommandResponse("key: {0} signalType: {1}{2}", routingOutputPort.Key, routingOutputPort.Type, CrestronEnvironment.NewLine);
}
}

Expand Down
284 changes: 271 additions & 13 deletions src/PepperDash.Essentials.Core/Routing/Extensions.cs

Large diffs are not rendered by default.

95 changes: 4 additions & 91 deletions src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,15 @@ public void ExecuteRoutes()
/// Releases the usage tracking for the route and optionally clears the route on the switching devices.
/// </summary>
/// <param name="clearRoute">If true, attempts to clear the route on the switching devices (e.g., set input to null/0).</param>


public void ReleaseRoutes(bool clearRoute = false)
{
foreach (var route in Routes.Where(r => r.SwitchingDevice is IRouting))
{
if (route.SwitchingDevice is IRouting switchingDevice)
{
if(clearRoute)
if (clearRoute)
{
try
{
Expand Down Expand Up @@ -137,98 +137,11 @@ public void ReleaseRoutes(bool clearRoute = false)
/// Returns a string representation of the route descriptor, including source, destination, and individual route steps.
/// </summary>
/// <returns>A string describing the route.</returns>



public override string ToString()
{
var routesText = Routes.Select(r => r.ToString()).ToArray();
return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText));
return $"Route table from {Source.Key} to {Destination.Key} for {SignalType}:\r\n {string.Join("\r\n ", routesText)}";
}
}

/*/// <summary>
/// Represents an collection of individual route steps between Source and Destination
/// </summary>
/// <summary>
/// Represents a RouteDescriptor
/// </summary>
public class RouteDescriptor<TInputSelector, TOutputSelector>
{
/// <summary>
/// Gets or sets the Destination
/// </summary>
public IRoutingInputs<TInputSelector> Destination { get; private set; }
/// <summary>
/// Gets or sets the Source
/// </summary>
public IRoutingOutputs<TOutputSelector> Source { get; private set; }
/// <summary>
/// Gets or sets the SignalType
/// </summary>
public eRoutingSignalType SignalType { get; private set; }
public List<RouteSwitchDescriptor<TInputSelector, TOutputSelector>> Routes { get; private set; }


public RouteDescriptor(IRoutingOutputs<TOutputSelector> source, IRoutingInputs<TInputSelector> destination, eRoutingSignalType signalType)
{
Destination = destination;
Source = source;
SignalType = signalType;
Routes = new List<RouteSwitchDescriptor<TInputSelector, TOutputSelector>>();
}

/// <summary>
/// ExecuteRoutes method
/// </summary>
public void ExecuteRoutes()
{
foreach (var route in Routes)
{
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString());

if (route.SwitchingDevice is IRoutingSinkWithSwitching<TInputSelector> sink)
{
sink.ExecuteSwitch(route.InputPort.Selector);
continue;
}

if (route.SwitchingDevice is IRouting switchingDevice)
{
switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType);

route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType);

Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
}
}
}

/// <summary>
/// ReleaseRoutes method
/// </summary>
public void ReleaseRoutes()
{
foreach (var route in Routes)
{
if (route.SwitchingDevice is IRouting<TInputSelector, TOutputSelector>)
{
// Pull the route from the port. Whatever is watching the output's in use tracker is
// responsible for responding appropriately.
route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);
Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
}
}
}

/// <summary>
/// ToString method
/// </summary>
/// <inheritdoc />
public override string ToString()
{
var routesText = Routes.Select(r => r.ToString()).ToArray();
return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText));
}
}*/
}
113 changes: 34 additions & 79 deletions src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using PepperDash.Core;
using Serilog.Events;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using PepperDash.Core;
using Serilog.Events;


namespace PepperDash.Essentials.Core
Expand All @@ -12,7 +12,7 @@ namespace PepperDash.Essentials.Core
public class RouteDescriptorCollection
{
/// <summary>
/// DefaultCollection static property
/// Gets the default collection of RouteDescriptors.
/// </summary>
public static RouteDescriptorCollection DefaultCollection
{
Expand All @@ -27,6 +27,11 @@ public static RouteDescriptorCollection DefaultCollection

private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();

/// <summary>
/// Gets an enumerable collection of all RouteDescriptors in this collection.
/// </summary>
public IEnumerable<RouteDescriptor> Descriptors => RouteDescriptors.AsReadOnly();

/// <summary>
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
/// destination exists already, it will not be added - in order to preserve
Expand All @@ -40,13 +45,29 @@ public void AddRouteDescriptor(RouteDescriptor descriptor)
return;
}

if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)
&& RouteDescriptors.Any(t => t.Destination == descriptor.Destination && t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key))
// Check if a route already exists with the same source, destination, input port, AND signal type
var existingRoute = RouteDescriptors.FirstOrDefault(t =>
t.Source == descriptor.Source &&
t.Destination == descriptor.Destination &&
t.SignalType == descriptor.SignalType &&
((t.InputPort == null && descriptor.InputPort == null) ||
(t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key)));

Comment on lines +48 to +55
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Route de-duplication keys on source/destination/signalType/inputPort only. With the new startup mapping iterating source.OutputPorts (and route requests supporting sourcePortKey), routes that differ only by source output port will be treated as duplicates and one will be dropped, making source-port-specific routing unreliable. Consider including the selected source output port in the RouteDescriptor model and in this uniqueness check (or remove source-port iteration if it’s not meant to be supported).

Copilot uses AI. Check for mistakes.
if (existingRoute != null)
{
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
"Route to [{0}] already exists in global routes table", descriptor?.Source?.Key);
Debug.LogMessage(LogEventLevel.Information, descriptor.Destination,
"Route from {0} to {1}:{2} ({3}) already exists in this collection",
descriptor?.Source?.Key,
descriptor?.Destination?.Key,
descriptor?.InputPort?.Key ?? "auto",
descriptor?.SignalType);
return;
}
Debug.LogMessage(LogEventLevel.Verbose, "Adding route descriptor: {0} -> {1}:{2} ({3})",
descriptor?.Source?.Key,
descriptor?.Destination?.Key,
descriptor?.InputPort?.Key ?? "auto",
descriptor?.SignalType);
RouteDescriptors.Add(descriptor);
}

Expand All @@ -61,11 +82,11 @@ public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destinati
}

/// <summary>
/// Gets the RouteDescriptor for a destination and input port key. Returns null if no matching RouteDescriptor exists.
/// Gets the route descriptor for a specific destination and input port
/// </summary>
/// <param name="destination"></param>
/// <param name="inputPortKey"></param>
/// <returns></returns>
/// <param name="destination">The destination device</param>
/// <param name="inputPortKey">The input port key</param>
/// <returns>The matching RouteDescriptor or null if not found</returns>
public RouteDescriptor GetRouteDescriptorForDestinationAndInputPort(IRoutingInputs destination, string inputPortKey)
{
Debug.LogMessage(LogEventLevel.Information, "Getting route descriptor for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
Expand All @@ -82,7 +103,7 @@ public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination, string
{
Debug.LogMessage(LogEventLevel.Information, "Removing route descriptor for '{destination}':'{inputPortKey}'", destination.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);

var descr = string.IsNullOrEmpty(inputPortKey)
var descr = string.IsNullOrEmpty(inputPortKey)
? GetRouteDescriptorForDestination(destination)
: GetRouteDescriptorForDestinationAndInputPort(destination, inputPortKey);
if (descr != null)
Expand All @@ -93,70 +114,4 @@ public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination, string
return descr;
}
}

/*/// <summary>
/// A collection of RouteDescriptors - typically the static DefaultCollection is used
/// </summary>
/// <summary>
/// Represents a RouteDescriptorCollection
/// </summary>
public class RouteDescriptorCollection<TInputSelector, TOutputSelector>
{
public static RouteDescriptorCollection<TInputSelector, TOutputSelector> DefaultCollection
{
get
{
if (_DefaultCollection == null)
_DefaultCollection = new RouteDescriptorCollection<TInputSelector, TOutputSelector>();
return _DefaultCollection;
}
}
private static RouteDescriptorCollection<TInputSelector, TOutputSelector> _DefaultCollection;

private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();

/// <summary>
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
/// destination exists already, it will not be added - in order to preserve
/// proper route releasing.
/// </summary>
/// <param name="descriptor"></param>
/// <summary>
/// AddRouteDescriptor method
/// </summary>
public void AddRouteDescriptor(RouteDescriptor descriptor)
{
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination))
{
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
"Route to [{0}] already exists in global routes table", descriptor.Source.Key);
return;
}
RouteDescriptors.Add(descriptor);
}

/// <summary>
/// Gets the RouteDescriptor for a destination
/// </summary>
/// <returns>null if no RouteDescriptor for a destination exists</returns>
/// <summary>
/// GetRouteDescriptorForDestination method
/// </summary>
public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs<TInputSelector> destination)
{
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
}

/// <summary>
/// Returns the RouteDescriptor for a given destination AND removes it from collection.
/// Returns null if no route with the provided destination exists.
/// </summary>
public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs<TInputSelector> destination)
{
var descr = GetRouteDescriptorForDestination(destination);
if (descr != null)
RouteDescriptors.Remove(descr);
return descr;
}
}*/
}
Loading
Loading