355 lines
15 KiB
C#
355 lines
15 KiB
C#
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; }
|
|
}
|
|
}
|
|
}
|