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;
}
}
}