using GeoVLog.Core.Crypto;
using GeoVLog.Core.Hdf5;
using GeoVLog.Core.Manifests;
using GeoVLog.Core.Models;
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.ServiceProcess;
using System.Threading;
namespace GeoVLogSvc
{
///
/// Windows Service for GeoVLog. Handles sensor logging to HDF5 and manifest encryption on shutdown.
/// Supports graceful cleanup via PreShutdown and normal stop.
///
[SupportedOSPlatform("windows")]
public sealed class GeoVLogService : ServiceBase
{
private H5LogQueueWorker? _h5Worker;
private FlightManifest? _manifest;
private string? _manifestPath;
private byte[]? _aesKey;
private IFlightManifestSerializer? _serializer;
private bool _parsingEnabled = false;
private readonly object _cleanupLock = new();
private bool _hasCleanedUp = false;
private bool _shutdownInitiated = false;
private const string EventSourceName = "GeoVLogSvc";
private const string EventLogName = "Application";
///
/// Initializes a new instance of the GeoVLogService. Sets up PreShutdown handling and registers EventLog source.
///
public GeoVLogService()
{
this.ServiceName = EventSourceName;
this.CanStop = true;
this.CanShutdown = true;
this.AutoLog = true;
// Enable PreShutdown via reflection (not exposed directly)
FieldInfo? cmdField = typeof(ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic);
if (cmdField != null)
{
int currentFlags = (int)cmdField.GetValue(this)!;
cmdField.SetValue(this, currentFlags | 0x100); // 0x100 = SERVICE_ACCEPT_PRESHUTDOWN
}
// Ensure EventLog source exists
try
{
if (!EventLog.SourceExists(EventSourceName))
{
EventLog.CreateEventSource(new EventSourceCreationData(EventSourceName, EventLogName));
}
}
catch (Exception ex)
{
Console.WriteLine($"[GeoVLog] WARNING: Unable to register EventLog source: {ex.Message}");
}
}
///
/// Starts the service when running as a console application.
///
/// Command line arguments.
public void StartConsole(string[] args) => OnStart(args);
///
/// Stops the service when running as a console application.
///
public void StopConsole() => OnStop();
///
/// Starts the service and initializes logging infrastructure.
///
/// Service arguments (unused)
protected override void OnStart(string[] args)
{
try
{
string logDir = Path.Combine(AppContext.BaseDirectory, "Logs");
Directory.CreateDirectory(logDir);
_manifest = new FlightManifest
{
FlightStartUtc = DateTime.UtcNow
};
_manifestPath = Path.Combine(logDir, "manifest.bin");
_aesKey = RandomNumberGenerator.GetBytes(AesGcmHelper.KeySize);
_serializer = new FlightManifestProtoSerializer();
var flushOptions = new H5LogFlushOptions
{
EnableAutoFlush = true,
FlushInterval = TimeSpan.FromSeconds(1)
};
_h5Worker = new H5LogQueueWorker(logDir, _manifest, _manifestPath, _aesKey, _serializer, flushOptions);
_h5Worker.RegisterSensor(SensorId.Gps, "/Sensors/GPS", enableParse: false, schema: SensorSchema.Gps);
_h5Worker.RegisterSensor(SensorId.Imu, "/Sensors/IMU", enableParse: false, schema: SensorSchema.Imu);
_h5Worker.RegisterSensor(SensorId.Mag1, "/Sensors/MAG1", enableParse: false, schema: SensorSchema.Mag);
_h5Worker.RegisterSensor(SensorId.Mag2, "/Sensors/MAG2", enableParse: false, schema: SensorSchema.Mag);
_h5Worker.RegisterSensor(SensorId.Vlf, "/Sensors/VLF", enableParse: false, schema: SensorSchema.Vlf);
_h5Worker.RegisterSensor(SensorId.RadarAltimeter, "/Sensors/RadarAlt", enableParse: false, schema: SensorSchema.RadarAlt);
EventLog.WriteEntry(EventSourceName, "GeoVLogService started. Logging active.", EventLogEntryType.Information);
}
catch (Exception ex)
{
EventLog.WriteEntry(EventSourceName, $"GeoVLogService OnStart error: {ex}", EventLogEntryType.Error);
throw;
}
}
///
/// Called when service is stopped by SCM or manually. Triggers cleanup.
///
protected override void OnStop()
{
string reason = _shutdownInitiated ? "system shutdown" : "manual stop";
EventLog.WriteEntry(EventSourceName, $"GeoVLogService stopping due to {reason}.", EventLogEntryType.Information);
PerformCleanup();
}
///
/// Called during system shutdown. Ensures cleanup before termination.
///
protected override void OnShutdown()
{
EventLog.WriteEntry(EventSourceName, "GeoVLogService received system shutdown (OnShutdown).", EventLogEntryType.Information);
PerformCleanup();
base.OnShutdown();
}
///
/// Handles PreShutdown command (code 15). Ensures early cleanup during shutdown.
///
/// The SCM command code
protected override void OnCustomCommand(int command)
{
if (command == 15) // SERVICE_CONTROL_PRESHUTDOWN
{
_shutdownInitiated = true;
EventLog.WriteEntry(EventSourceName, "PreShutdown event received.", EventLogEntryType.Information);
PerformCleanup();
try { Stop(); } catch (Exception ex) { EventLog.WriteEntry(EventSourceName, $"Error during Stop(): {ex}", EventLogEntryType.Error); }
return;
}
base.OnCustomCommand(command);
}
///
/// Enable or disable parsing of sensor messages at runtime.
///
public void SetParsingEnabled(bool enable)
{
_parsingEnabled = enable;
_h5Worker?.SetParsingEnabledForAll(enable);
}
///
/// Flushes HDF5 log, writes and encrypts manifest, clears sensitive data. Runs once.
///
private void PerformCleanup()
{
lock (_cleanupLock)
{
if (_hasCleanedUp) return;
_hasCleanedUp = true;
}
if (_h5Worker != null)
{
try { _h5Worker.Dispose(); EventLog.WriteEntry(EventSourceName, "HDF5 worker disposed.", EventLogEntryType.Information); }
catch (Exception ex) { EventLog.WriteEntry(EventSourceName, $"Error disposing HDF5: {ex}", EventLogEntryType.Error); }
}
if (_manifest != null && _aesKey != null && _manifestPath != null && _serializer != null)
{
try
{
_manifest.FlightEndUtc = DateTime.UtcNow;
ManifestIO.WriteAsync(_manifest, _aesKey, _manifestPath, _serializer).GetAwaiter().GetResult();
EventLog.WriteEntry(EventSourceName, $"Manifest saved and encrypted to {_manifestPath}.", EventLogEntryType.Information);
}
catch (Exception ex)
{
EventLog.WriteEntry(EventSourceName, $"Error writing manifest: {ex}", EventLogEntryType.Error);
}
}
if (_aesKey != null)
{
Array.Clear(_aesKey, 0, _aesKey.Length);
}
}
///
/// Releases any remaining resources.
///
/// True if managed resources should be disposed.
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
}
}