Files
AiQ_GUI/FakeCamera/FakeCamera.cs

355 lines
15 KiB
C#
Raw Normal View History

2025-09-02 15:32:24 +01:00
using Newtonsoft.Json;
using System.Net;
using System.Text;
namespace AiQ_GUI
{
public enum CAMTYPE
{
GOOD,
BAD,
BIZARRE
}
public class FakeCamera
{
public const string JSONLoc = "C:\\Users\\BradleyBorn\\OneDrive - MAV Systems Ltd\\Desktop\\AIQ_GUI_TEST\\FakeCameraGood\\";
public static bool Snapshot = false;
private HttpListener listener;
public FakeCamera(int port = 8080)
{
listener = new HttpListener();
listener.Prefixes.Add($"http://*:{port}/");
listener.Prefixes.Add("http://localhost:8080/");
}
public async Task StartAsync(CAMTYPE camType)
{
try
{
listener.Start();
MainForm.Instance.AddToActionsList("Started server successfully");
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Failed to start server: {ex.Message}");
return;
}
while (true)
{
HttpListenerContext? context = await listener.GetContextAsync();
_ = HandleRequestAsync(context, camType);
}
}
public void Stop()
{
if (listener.IsListening)
{
listener.Stop();
listener.Close();
}
}
public static bool ValidateCredentials(string username, string password, CAMTYPE camType)
{
string filePath = camType switch
{
CAMTYPE.GOOD => $"{JSONLoc}Versions.json",
CAMTYPE.BAD => $"{JSONLoc}Versions_Bad.json",
CAMTYPE.BIZARRE => $"{JSONLoc}Versions_Bizarre.json",
_ => throw new ArgumentOutOfRangeException(nameof(camType), "Invalid camera type")
};
string versJson = File.ReadAllText(filePath);
Versions vers = JsonConvert.DeserializeObject<Versions>(versJson);
TimeSpan t = DateTime.UtcNow - new DateTime(1970, 1, 1);
int secondsSinceEpoch = (int)t.TotalSeconds;
string PASS = Lics.GeneratePassword(vers.MAC, vers.version, secondsSinceEpoch);
return username == "developer" && password == PASS;
}
private static async Task HTTPReplySetup(HttpListenerContext context, string ResponseText, int StatusCode, string ContentType)
{
try
{
context.Response.StatusCode = StatusCode;
context.Response.ContentType = ContentType;
byte[] responseBytes = Encoding.UTF8.GetBytes(ResponseText);
context.Response.ContentLength64 = responseBytes.Length;
await context.Response.OutputStream.WriteAsync(responseBytes);
}
finally
{
context.Response.OutputStream.Close();
}
}
private static async Task HTTPReplySetup(HttpListenerContext context, byte[] content, int statusCode, string contentType)
{
context.Response.StatusCode = statusCode;
context.Response.ContentType = contentType;
context.Response.ContentLength64 = content.Length;
await context.Response.OutputStream.WriteAsync(content);
await context.Response.OutputStream.FlushAsync();
context.Response.OutputStream.Close();
}
private static async Task HandleRequestAsync(HttpListenerContext context, CAMTYPE camType)
{
string path = context.Request.Url?.AbsolutePath ?? "/";
string Method = context.Request.HttpMethod;
string headers = context.Request.Headers.ToString();
string Content = context.Request.InputStream?.ToString() ?? "";
// Define endpoints that do NOT require authentication
string[] publicEndpoints = ["/api/versions", "/api/diagnostics"];
bool requiresAuth = !publicEndpoints.Contains(path);
if (requiresAuth)
{
string authHeader = context.Request.Headers["Authorization"];
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Basic "))
{
context.Response.AddHeader("WWW-Authenticate", "Basic realm=\"FakeCamera\"");
await HTTPReplySetup(context, "", 401, "text/plain");
return;
}
try
{
string encodedCredentials = authHeader.Substring("Basic ".Length).Trim();
string decodedCredentials = Encoding.UTF8.GetString(Convert.FromBase64String(encodedCredentials));
string[] parts = decodedCredentials.Split(':', 2);
if (parts.Length != 2)
{
throw new Exception("Invalid Basic auth format. Expected username:password.");
}
string username = parts[0];
string password = parts[1];
if (!ValidateCredentials(username, password, camType))
{
throw new Exception("Invalid credentials");
}
}
catch
{
context.Response.AddHeader("WWW-Authenticate", "Basic realm=\"FakeCamera\"");
await HTTPReplySetup(context, "", 401, "text/plain");
return;
}
}
try
{
if (Method == "GET")
{
if (path.Equals("/Infrared/led-controls")) // Works
{
string[] allowedPowers = ["LOW", "MID", "HIGH", "SAFE", "OFF"];
string power = context.Request.QueryString["power"]?.ToUpper();
if (allowedPowers.Contains(power))
await HTTPReplySetup(context, "Power levels set successfully", 200, "text/plain");
else
await HTTPReplySetup(context, "Power level not accepted", 400, "text/plain");
}
else if (path.Equals("/api/versions"))
{
string versionsJson = File.ReadAllText(camType switch
{
CAMTYPE.GOOD => $"{JSONLoc}Versions.json",
CAMTYPE.BAD => $"{JSONLoc}Versions_Bad.json",
CAMTYPE.BIZARRE => $"{JSONLoc}Versions_Bizarre.json",
_ => throw new ArgumentOutOfRangeException()
});
Versions vers = JsonConvert.DeserializeObject<Versions>(versionsJson);
vers.timeStamp = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
versionsJson = JsonConvert.SerializeObject(vers);
await HTTPReplySetup(context, versionsJson, 200, "application/json");
}
else if (path.Equals("/api/diagnostics"))
{
string filePath = camType switch
{
CAMTYPE.GOOD => $"{JSONLoc}Diagnostics.json",
CAMTYPE.BAD => $"{JSONLoc}Diagnostics_Bad.json",
CAMTYPE.BIZARRE => $"{JSONLoc}Diagnostics_Bizarre.json",
_ => throw new ArgumentOutOfRangeException()
};
string diagsJson = File.ReadAllText(filePath);
if (camType == CAMTYPE.GOOD)
{
Diags diags = JsonConvert.DeserializeObject<Diags>(diagsJson);
diags.timeStamp = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
diagsJson = JsonConvert.SerializeObject(diags);
}
await HTTPReplySetup(context, diagsJson, 200, "application/json");
}
else if (path.Equals("/api/zoomLock")) // Fixed
{
string enable = context.Request.QueryString["enable"];
if (!string.IsNullOrEmpty(enable) && enable.Equals("true", StringComparison.OrdinalIgnoreCase)) // FIXED Use http://localhost:8080/api/zoomLock?enable=true
{
await HTTPReplySetup(context, "Zoom lock enabled.", 200, "text/plain");
}
else if (!string.IsNullOrEmpty(enable) && enable.Equals("false", StringComparison.OrdinalIgnoreCase)) // FIXED Use http://localhost:8080/api/zoomLock?enable=false
{
await HTTPReplySetup(context, "Zoom lock disabled.", 200, "text/plain");
}
else
{
await HTTPReplySetup(context, "Missing or invalid 'enable' parameter.", 400, "text/plain");
}
}
else if (path.Equals("/Infrared-camera-factory-reset") || path.Equals("/Colour-camera-factory-reset")) // WORKS
{
await HTTPReplySetup(context, "Factory reset OK.", 200, "text/plain");
}
else if (path.Equals("/Infrared-camera-control") || path.Equals("/Colour-camera-control")) // Fixed
{
string viscaReply = "9041FF9051FF";
if (!RegexCache.VISCARegex().IsMatch(context.Request.QueryString["commandHex"]))
viscaReply = "9060FF";
await HTTPReplySetup(context, viscaReply, 200, "text/plain");
}
else if (path.Equals("/SightingCreator-plate-positions")) //Works
{
string trimJson = File.ReadAllText($"{JSONLoc}Trim.json");
await HTTPReplySetup(context, trimJson, 200, "application/json");
}
else if (path.Equals("/Infrared-snapshot")) // Fixed
{
string jpegPath;
if (!Snapshot)
{
jpegPath = $"{JSONLoc}IR_Open_image.jpg"; // Light Image
Snapshot = true;
}
else
{
jpegPath = $"{JSONLoc}IR_Tight_image.jpg"; // Dark Image
}
byte[] jpegBytes = File.ReadAllBytes(jpegPath);
await HTTPReplySetup(context, jpegBytes, 200, "image/jpeg");
}
else if (path.Equals("/Colour-snapshot")) // Fixed
{
byte[] jpegBytes = File.ReadAllBytes($"{JSONLoc}OV_image.jpg");
await HTTPReplySetup(context, jpegBytes, 200, "image/jpeg");
}
else if (path.Equals("/api/fetch-config"))
{
ConfigObject config = JsonConvert.DeserializeObject<ConfigObject>(Content);
if (config.Id == "GLOBAL--NetworkConfig")
{
// TODO - Return success message?
}
else
{
await HTTPReplySetup(context, "ID not valid.", 200, "text/plain");
}
}
else if (path.Equals("/api/RaptorOCR-auto-license")) // Works
{
string vaxtorJson = "{ \"protectionKeyId\":\"123456789012345678\" }";
await HTTPReplySetup(context, vaxtorJson, 200, "application/json");
}
else
{
await HTTPReplySetup(context, $"GET request for {path} not supported", 400, "text/plain");
}
}
else if (Method == "POST")
{
if (path.Equals("/api/update-config"))
{
ConfigObject fo = JsonConvert.DeserializeObject<ConfigObject>(Content);
if (fo.Id == "GLOBAL--NetworkConfig" || fo.Id == "SightingCreator" || fo.Id == "RaptorOCR")
{
await HTTPReplySetup(context, "Update successful.", 200, "text/plain");
}
else if (fo.Id == "Internal Config")
{
string SerialNumber = "";
string ModelNumber = "";
fo.Fields.ForEach(field =>
{
if (field.Property == "propSerialNumber")
SerialNumber = field.Value;
else if (field.Property == "propMavModelNumber")
ModelNumber = field.Value;
});
// Update JSON with new SerialNumber and ModelNumber beofre sending back to the client
using StreamReader r = new($"{JSONLoc}InternalConfig.json");
string InternalConfig_JSON = r.ReadToEnd();
InternalConfig_JSON = InternalConfig_JSON.Replace("SSSSSS", SerialNumber).Replace("MMMMMM", ModelNumber);
HTTPReplySetup(context, InternalConfig_JSON, 200, "application/json");
}
else
{
await HTTPReplySetup(context, "ID not valid.", 200, "text/plain");
}
}
else
{
await HTTPReplySetup(context, $"POST request for {path} not supported.", 400, "text/plain");
}
}
else
{
await HTTPReplySetup(context, "Bad Request.", 400, "text/plain");
}
}
catch (Exception ex)
{
await HTTPReplySetup(context, $"Server error: {ex.Message}", 500, "text/plain");
MainForm.Instance.AddToActionsList($"Server error handling {Method} {path}: {ex.Message}");
}
}
public class ConfigObject
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("fields")]
public List<Field> Fields { get; set; }
}
public class Field
{
[JsonProperty("property")]
public string Property { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
}
}
}