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
1 change: 1 addition & 0 deletions Interception/Certificates/CertificateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ private static async Task<X509Certificate2> LoadOrCreateServerCert(string path,

var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddDnsName("do.pishock.com");
sanBuilder.AddDnsName("ps.pishock.com");

Choose a reason for hiding this comment

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

P1 Badge Regenerate existing server certs for new ps SAN

Adding ps.pishock.com to SAN here only affects newly generated certificates; LoadOrCreateServerCert still returns any existing, unexpired interception-server.pfx, so upgraded users keep a cert that only matches do.pishock.com and TLS validation for https://ps.pishock.com will fail until they manually delete/regenerate the cert.

Useful? React with 👍 / 👎.

sanBuilder.AddIpAddress(IPAddress.Loopback);
req.CertificateExtensions.Add(sanBuilder.Build());

Expand Down
21 changes: 13 additions & 8 deletions Interception/HostsFile/HostsFileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ namespace OpenShock.Desktop.Modules.Interception.HostsFile;
public sealed class HostsFileManager
{
private const string HostsPath = @"C:\Windows\System32\drivers\etc\hosts";
private const string HostEntry = "127.0.0.1 do.pishock.com";
private const string Marker = "# OpenShock Interception";

private static readonly string[] HostEntries =
[
$"127.0.0.1 do.pishock.com {Marker}",
$"127.0.0.1 ps.pishock.com {Marker}"
];

public bool IsEnabled { get; private set; }

public async Task EnableAsync()
{
if (IsEnabled) return;
var line = $"{HostEntry} {Marker}";
await RunElevatedHostsCommand($"add \"{line}\"");
await RunElevatedHostsCommand("add");
IsEnabled = true;
await FlushDns();
}
Expand Down Expand Up @@ -44,15 +48,16 @@ public async Task DetectCurrentState()
private static async Task RunElevatedHostsCommand(string action)
{
string script;
if (action.StartsWith("add"))
if (action == "add")
{
var line = action.Substring(4).Trim();
var linesArray = string.Join(",", HostEntries.Select(e => $"'{e}'"));
script = string.Join("\n",
$"$line = {line};",
$"$lines = @({linesArray});",
$"$hostsPath = '{HostsPath}';",
"$content = Get-Content $hostsPath -Raw -ErrorAction SilentlyContinue;",
"if ($content -notmatch 'OpenShock Interception') {",
" Add-Content -Path $hostsPath -Value \"`n$line\" -NoNewline:$false",
" $toAdd = \"`n\" + ($lines -join \"`n\");",
" Add-Content -Path $hostsPath -Value $toAdd -NoNewline:$false",
Comment on lines 58 to +60

Choose a reason for hiding this comment

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

P2 Badge Add missing ps hosts entry when marker already exists

The add script appends both host entries only when no OpenShock Interception marker exists, so machines that already have the legacy single do.pishock.com entry (with the same marker) will never receive the new ps.pishock.com redirect and the /PiShock interception path will not be reached until users manually remove/re-add hosts entries.

Useful? React with 👍 / 👎.

"}");
}
else
Expand Down Expand Up @@ -104,4 +109,4 @@ private static async Task FlushDns()
// Best-effort DNS flush
}
}
}
}
6 changes: 4 additions & 2 deletions Interception/InterceptionConfig.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using OpenShock.Desktop.Modules.Interception.Server;

namespace OpenShock.Desktop.Modules.Interception;

public sealed class InterceptionConfig
{
public ushort Port { get; set; } = 443;
public bool AutoStart { get; set; } = true;
public Dictionary<string, Guid> ShareCodeMappings { get; set; } = new();
}
public Dictionary<string, ShareCodeMapping> ShareCodeMappings { get; set; } = new();
}
6 changes: 5 additions & 1 deletion Interception/InterceptionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@ public async Task StartAsync()
var cert = certManager.ServerCertificate;

var operateController = ActivatorUtilities.CreateInstance<DoWebApiController>(serviceProvider);
var psController = ActivatorUtilities.CreateInstance<PsWebApiController>(serviceProvider);
var healthController = new HealthWebApiController();

_server = new WebServer(o => o
.WithUrlPrefix($"https://*:{port}/")
.WithMode(HttpListenerMode.EmbedIO)
.WithCertificate(cert))
.WithWebApi("/api", m => m.WithController(() => operateController))
.WithWebApi("/PiShock", m => m.WithController(() => psController))
.WithWebApi("/Health", m => m.WithController(() => healthController))
;

_ = _server.RunAsync();
Expand All @@ -60,4 +64,4 @@ public async Task UpdateConfig(Action<InterceptionConfig> update)
update(Config);
await moduleConfig.Save();
}
}
}
44 changes: 25 additions & 19 deletions Interception/Server/DoWebApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public async Task Operate()
return;
}

if (!_service.Config.ShareCodeMappings.TryGetValue(request.Code, out var shockerId))
if (!_service.Config.ShareCodeMappings.TryGetValue(request.Code, out var mapping))
{
_logger.LogError("Share code not mapped to any shocker: {Code}", request.Code);
HttpContext.Response.StatusCode = 404;
Expand All @@ -65,6 +65,15 @@ await HttpContext.SendStringAsync("Share code not mapped to any shocker", "text/
return;
}

if (mapping.ShockerIds.Count == 0)
{
_logger.LogError("Share code has no shockers configured: {Code}", request.Code);
HttpContext.Response.StatusCode = 404;
await HttpContext.SendStringAsync("Share code has no shockers configured", "text/plain",
Encoding.UTF8);
return;
}

var controlType = request.Op switch
{
0 => ControlType.Shock,
Expand All @@ -73,30 +82,27 @@ await HttpContext.SendStringAsync("Share code not mapped to any shocker", "text/
_ => ControlType.Vibrate
};

var durationMs = (ushort)Math.Clamp(request.Duration * 1000, 300, 30000);
var intensity = (byte)Math.Clamp(request.Intensity, 1, 100);
var durationMs = (ushort)Math.Clamp(request.Duration * 1000, mapping.MinDuration * 1000, mapping.MaxDuration * 1000);
var intensity = (byte)Math.Clamp(request.Intensity, mapping.MinIntensity, mapping.MaxIntensity);
Comment on lines +85 to +86

Choose a reason for hiding this comment

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

P2 Badge Validate min/max mapping bounds before request clamping

These clamps assume Min* <= Max*, but the new mapping form stores each field independently and does not enforce ordering, so a configuration like MinIntensity > MaxIntensity or MinDuration > MaxDuration will make Math.Clamp throw ArgumentException and fail apioperate requests at runtime.

Useful? React with 👍 / 👎.


if (request.Intensity <= 0) controlType = ControlType.Stop;

var controls = new[]
var controls = mapping.ShockerIds.Select(id => new ShockerControl
{
new ShockerControl
{
Id = shockerId,
Type = controlType,
Intensity = intensity,
Duration = durationMs
}
};
Id = id,
Type = controlType,
Intensity = intensity,
Duration = durationMs
}).ToArray();

var customName = request.Name ?? request.Username ?? "PiShock Interception";

try
{
await _openShockControl.Control(controls, customName);
_logger.LogInformation(
"PiShock Do API: control command: {ControlType} {Intensity}% for {Duration}s on shocker {ShockerId} by {Name}",
controlType, intensity, durationMs / 1000.0, shockerId, customName);
"PiShock Do API: control command: {ControlType} {Intensity}% for {Duration}s on {ShockerCount} shocker(s) by {Name}",
controlType, intensity, durationMs / 1000.0, controls.Length, customName);
await HttpContext.SendStringAsync(
JsonSerializer.Serialize(new { success = true, message = "Operation Succeeded." }),
"application/json", Encoding.UTF8);
Expand Down Expand Up @@ -137,7 +143,7 @@ public async Task GetShockerInfo()
return;
}

if (!_service.Config.ShareCodeMappings.TryGetValue(request.Code, out var shockerId))
if (!_service.Config.ShareCodeMappings.TryGetValue(request.Code, out var mapping))
{
_logger.LogError("Share code not mapped to any shocker: {Code}", request.Code);
HttpContext.Response.StatusCode = 404;
Expand All @@ -147,15 +153,15 @@ public async Task GetShockerInfo()

var info = new
{
clientId = shockerId,
clientId = mapping.ShockerIds.FirstOrDefault(),
name = $"Shocker ({request.Code})",
maxIntensity = 100,
maxDuration = 15,
maxIntensity = (int)mapping.MaxIntensity,
maxDuration = (int)mapping.MaxDuration,
online = true
};

await HttpContext.SendStringAsync(
JsonSerializer.Serialize(info),
"application/json", Encoding.UTF8);
}
}
}
23 changes: 23 additions & 0 deletions Interception/Server/HealthWebApiController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Text;
using EmbedIO;
using EmbedIO.Routing;
using EmbedIO.WebApi;

namespace OpenShock.Desktop.Modules.Interception.Server;

public sealed class HealthWebApiController : WebApiController
{
[Route(HttpVerbs.Get, "/Check")]
public async Task Check()
{
HttpContext.Response.StatusCode = 200;
await HttpContext.SendStringAsync("OK", "text/plain", Encoding.UTF8);
}

[Route(HttpVerbs.Get, "/Server")]
public Task Server()
{
HttpContext.Response.StatusCode = 204;
return Task.CompletedTask;
}
}
Loading
Loading