/* ========================================================================
* Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Opc.Ua.Bindings.Pcap.Capture;
namespace Opc.Ua.Bindings.Pcap.Frame
{
///
/// A packet record read from a libpcap file.
///
public readonly struct PcapRecord : IEquatable
{
///
/// Constructs a pcap packet record.
///
public PcapRecord(DateTimeOffset timestamp, uint linkType, int originalLength, ReadOnlyMemory data)
{
Timestamp = timestamp;
LinkType = linkType;
OriginalLength = originalLength;
Data = data;
}
///
/// Packet timestamp.
///
public DateTimeOffset Timestamp { get; }
///
/// Link type from the global header.
///
public uint LinkType { get; }
///
/// Original packet length.
///
public int OriginalLength { get; }
///
/// Captured packet bytes.
///
public ReadOnlyMemory Data { get; }
///
public bool Equals(PcapRecord other)
{
return Timestamp.Equals(other.Timestamp) &&
LinkType == other.LinkType &&
OriginalLength == other.OriginalLength &&
Data.Span.SequenceEqual(other.Data.Span);
}
///
public override bool Equals(object? obj)
{
return obj is PcapRecord other && Equals(other);
}
///
public override int GetHashCode()
{
return HashCode.Combine(Timestamp, LinkType, OriginalLength, Data.Length);
}
///
/// Equality comparison.
///
public static bool operator ==(PcapRecord left, PcapRecord right) => left.Equals(right);
///
/// Inequality comparison.
///
public static bool operator !=(PcapRecord left, PcapRecord right) => !left.Equals(right);
}
///
/// Reads packet records from a libpcap file.
///
public static class PcapFileReader
{
///
/// Maximum number of bytes allowed for a single captured packet.
/// Defends against a malicious pcap whose record header declares
/// an absurd capturedLength that would otherwise trigger
/// an when allocating the
/// payload buffer.
///
public const int MaxPacketBytes = 64 * 1024 * 1024;
///
/// Reads all packet records from the supplied libpcap file.
///
public static IAsyncEnumerable ReadAllAsync(
string filePath,
CancellationToken ct)
{
ArgumentException.ThrowIfNullOrWhiteSpace(filePath);
return ReadAllCoreAsync(filePath, ct);
}
private static async IAsyncEnumerable ReadAllCoreAsync(
string filePath,
[EnumeratorCancellation] CancellationToken ct)
{
using FileStream stream = new(
filePath,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
bufferSize: 4096,
FileOptions.Asynchronous | FileOptions.SequentialScan);
byte[] globalHeader = new byte[24];
if (!await ReadExactOrEndAsync(stream, globalHeader, ct).ConfigureAwait(false))
{
yield break;
}
uint magic = BinaryPrimitives.ReadUInt32LittleEndian(globalHeader);
bool littleEndian = magic switch
{
0xA1B2C3D4U => true,
0xD4C3B2A1U => false,
_ => throw new PcapDiagnosticsException("Invalid pcap magic number.")
};
uint linkType = ReadUInt32(globalHeader.AsSpan(20, 4), littleEndian);
byte[] recordHeader = new byte[16];
while (await ReadExactOrEndAsync(stream, recordHeader, ct).ConfigureAwait(false))
{
uint tsSec = ReadUInt32(recordHeader.AsSpan(0, 4), littleEndian);
uint tsUsec = ReadUInt32(recordHeader.AsSpan(4, 4), littleEndian);
uint capturedLength = ReadUInt32(recordHeader.AsSpan(8, 4), littleEndian);
uint originalLength = ReadUInt32(recordHeader.AsSpan(12, 4), littleEndian);
if (capturedLength > MaxPacketBytes)
{
throw new PcapDiagnosticsException(
$"Pcap record capturedLength {capturedLength} exceeds " +
$"MaxPacketBytes ({MaxPacketBytes}). The file may be " +
"malformed or hostile; refusing to allocate.");
}
long remaining = stream.Length - stream.Position;
if (capturedLength > remaining)
{
throw new PcapDiagnosticsException("Truncated pcap packet record.");
}
byte[] data;
try
{
data = new byte[(int)capturedLength];
}
catch (OutOfMemoryException ex)
{
throw new PcapDiagnosticsException(
$"Could not allocate {capturedLength}-byte capture frame buffer.",
ex);
}
if (!await ReadExactOrEndAsync(stream, data, ct).ConfigureAwait(false))
{
throw new PcapDiagnosticsException("Truncated pcap packet record.");
}
DateTimeOffset timestamp = DateTimeOffset.UnixEpoch.AddSeconds(tsSec).AddTicks(tsUsec * 10L);
yield return new PcapRecord(timestamp, linkType, checked((int)originalLength), data);
}
}
private static uint ReadUInt32(ReadOnlySpan value, bool littleEndian)
{
return littleEndian
? BinaryPrimitives.ReadUInt32LittleEndian(value)
: BinaryPrimitives.ReadUInt32BigEndian(value);
}
private static async ValueTask ReadExactOrEndAsync(Stream stream, Memory buffer, CancellationToken ct)
{
int offset = 0;
while (offset < buffer.Length)
{
int read = await stream.ReadAsync(buffer[offset..], ct).ConfigureAwait(false);
if (read == 0)
{
return offset == 0;
}
offset += read;
}
return true;
}
}
}