📄 src/Headers/StorageHeader.cs
using System.Buffers.Binary;
using SharpDlt.Internal;

namespace SharpDlt;

/// <summary>
/// The Storage Header which precedes every DLT message stored in a DLT file.
/// </summary>
/// <param name="Seconds">Seconds since 01.01.1970 (unix time).</param>
/// <param name="Microseconds">Microseconds of the second.</param>
/// <param name="EcuId">The ECU ID.</param>
public sealed record StorageHeader(uint Seconds, int Microseconds, string EcuId)
{
    /// <summary>Length of the Storage Header in bytes.</summary>
    public const int Length = 16;

    /// <summary>The DLT pattern ("DLT" + 0x01) which starts every Storage Header.</summary>
    public static ReadOnlySpan<byte> DltPattern => "DLT\u0001"u8;

    /// <summary>Gets the point in time at which the message was stored.</summary>
    public DateTimeOffset Timestamp =>
        DateTimeOffset.FromUnixTimeSeconds(Seconds)
        + TimeSpan.FromTicks(Microseconds * (TimeSpan.TicksPerMillisecond / 1000));

    /// <summary>Parses a Storage Header from the beginning of <paramref name="data"/>.</summary>
    /// <exception cref="DltParseException">The data is too short or does not start with the DLT pattern.</exception>
    public static StorageHeader Parse(ReadOnlySpan<byte> data)
    {
        if (data.Length < Length)
        {
            throw new DltParseException(
                $"Unexpected length of the data: {data.Length} / Storage Header must be {Length} or more."
            );
        }

        if (!data[..4].SequenceEqual(DltPattern))
        {
            throw new DltParseException($"DLT pattern is not found in the data: {Convert.ToHexString(data[..4])}.");
        }

        var seconds = BinaryPrimitives.ReadUInt32LittleEndian(data[4..]);
        var microseconds = BinaryPrimitives.ReadInt32LittleEndian(data[8..]);
        var ecuId = AsciiText.Decode(data[12..16]);

        return new StorageHeader(seconds, microseconds, ecuId);
    }
}