using Sick.SafetyScanners.DataStructures; namespace Sick.SafetyScanners.DataProcessing; /// /// Merges TCP packets that may be fragmented across multiple TCP packets /// Thread-safe implementation - matches C++ logic /// public sealed class TcpPacketMerger : IDisposable { private readonly List _packetBuffers; private uint _targetSize; private bool _isComplete; private PacketBuffer? _deployedBuffer; private bool _disposed; private readonly object _lock = new(); /// /// Creates a new TCP packet merger /// public TcpPacketMerger(uint targetSize = 0) { _packetBuffers = new List(); _targetSize = targetSize; _isComplete = false; } /// /// Indicates whether the packet is complete /// public bool IsComplete { get { lock (_lock) { return _isComplete; } } } /// /// Indicates whether the buffer is empty /// public bool IsEmpty { get { lock (_lock) { return _packetBuffers.Count == 0; } } } /// /// Gets the target size /// public uint TargetSize { get { lock (_lock) { return _targetSize; } } } /// /// Sets the expected total packet length /// public void SetTargetSize(uint expectedLength) { lock (_lock) { _targetSize = expectedLength; } } /// /// Adds a TCP packet to the merger (stores reference, doesn't copy data yet) /// Returns true if the packet is complete /// public bool AddTcpPacket(PacketBuffer packet) { if (_disposed) throw new ObjectDisposedException(nameof(TcpPacketMerger)); if (packet == null) throw new ArgumentNullException(nameof(packet)); lock (_lock) { // If already complete, reset for new packet if (_isComplete) { _isComplete = false; _deployedBuffer = null; } // Calculate remaining size BEFORE adding packet (matches C++ logic) var currentSize = GetCurrentSize(); var remainingSize = _targetSize - currentSize; // Add packet reference (don't copy data yet) _packetBuffers.Add(packet); // Check if complete (remaining size should equal packet length) if (remainingSize == packet.Length) { _isComplete = true; DeployPacketIfComplete(); } return _isComplete; } } /// /// Gets the current total size of all packets /// private uint GetCurrentSize() { uint sum = 0; foreach (var packet in _packetBuffers) { sum += (uint)packet.Length; } return sum; } /// /// Deploys packet if complete (merges all packets into one buffer) /// private void DeployPacketIfComplete() { if (!_isComplete) return; DeployPacket(); } /// /// Merges all packets into a single buffer /// private void DeployPacket() { var totalSize = GetCurrentSize(); var mergedBuffer = new List((int)totalSize); // Pre-allocate capacity for better performance foreach (var packet in _packetBuffers) { var buffer = packet.GetBuffer(); mergedBuffer.AddRange(buffer.Span); } _deployedBuffer = new PacketBuffer(mergedBuffer.ToArray(), mergedBuffer.Count); _packetBuffers.Clear(); } /// /// Gets the merged packet buffer (only when complete) /// public PacketBuffer GetDeployedBuffer() { if (_disposed) throw new ObjectDisposedException(nameof(TcpPacketMerger)); lock (_lock) { if (!_isComplete) throw new InvalidOperationException("Packet is not complete yet"); if (_deployedBuffer == null) throw new InvalidOperationException("Deployed buffer is null"); // Reset for next packet _isComplete = false; var result = _deployedBuffer; _deployedBuffer = null; return result; } } /// /// Resets the merger for a new packet /// public void Reset() { lock (_lock) { _packetBuffers.Clear(); _targetSize = 0; _isComplete = false; _deployedBuffer = null; } } public void Dispose() { if (_disposed) return; lock (_lock) { _packetBuffers.Clear(); _deployedBuffer = null; _disposed = true; } } }