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
{
2025-10-17 12:06:36 +01:00
public const string JSONLoc = "C:\\Users\\BradleyBorn\\OneDrive - MAV Systems Ltd\\Desktop\\AiQ_GUI\\AiQ_GUI\\FakeCamera\\" ;
2025-09-02 15:32:24 +01:00
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" ] ;
2025-10-29 16:17:59 +00:00
2025-09-02 15:32:24 +01:00
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" ;
2025-10-17 12:06:36 +01:00
//MainForm.Instance.AddToActionsList($"VISCA Command Received: {context.Request.QueryString["commandHex"]}");
if ( ! RegexCache . VISCAAPIRegex ( ) . IsMatch ( context . Request . QueryString [ "commandHex" ] ) )
2025-09-02 15:32:24 +01:00
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 ; }
}
}
}