📄 src/Headers/ExtendedHeader.cs
using SharpDlt.Internal;

namespace SharpDlt;

/// <summary>
/// The Extended Header of a DLT message.
/// </summary>
/// <param name="Verbose">If set, the payload carries a description of the transmitted data.</param>
/// <param name="MessageType">Type of the transmitted DLT message.</param>
/// <param name="MessageTypeInfo">Raw Message Type Info, its meaning depends on <paramref name="MessageType"/>.</param>
/// <param name="NumberOfArguments">Number of arguments in the message payload.</param>
/// <param name="ApplicationId">ID of the application.</param>
/// <param name="ContextId">Unique ID of the logging / tracing context.</param>
public sealed record ExtendedHeader(
    bool Verbose,
    DltMessageType MessageType,
    byte MessageTypeInfo,
    byte NumberOfArguments,
    string ApplicationId,
    string ContextId
)
{
    /// <summary>Length of the Extended Header in bytes.</summary>
    public const int Length = 10;

    private const byte VerboseMask = 0b0000_0001;
    private const byte MessageTypeMask = 0b0000_1110;
    private const byte MessageTypeInfoMask = 0b1111_0000;
    private const int MessageTypeShift = 1;
    private const int MessageTypeInfoShift = 4;

    /// <summary>Gets the Message Type Info interpreted as log info.</summary>
    public DltLogInfo LogInfo => (DltLogInfo)MessageTypeInfo;

    /// <summary>Gets the Message Type Info interpreted as application trace info.</summary>
    public DltTraceInfo TraceInfo => (DltTraceInfo)MessageTypeInfo;

    /// <summary>Gets the Message Type Info interpreted as network trace (bus) info.</summary>
    public DltBusInfo BusInfo => (DltBusInfo)MessageTypeInfo;

    /// <summary>Gets the Message Type Info interpreted as control info.</summary>
    public DltControlInfo ControlInfo => (DltControlInfo)MessageTypeInfo;

    /// <summary>Gets the Message Info (MSIN) byte.</summary>
    public byte MessageInfo
    {
        get
        {
            var msin = Verbose ? VerboseMask : 0;
            msin |= ((int)MessageType << MessageTypeShift) & MessageTypeMask;
            msin |= (MessageTypeInfo << MessageTypeInfoShift) & MessageTypeInfoMask;
            return (byte)msin;
        }
    }

    /// <summary>Parses an Extended Header from the beginning of <paramref name="data"/>.</summary>
    /// <exception cref="DltParseException">The data is too short.</exception>
    public static ExtendedHeader Parse(ReadOnlySpan<byte> data)
    {
        if (data.Length < Length)
        {
            throw new DltParseException(
                $"Unexpected length of the data: {data.Length} / Extended Header must be {Length} or more."
            );
        }

        var messageInfo = data[0];
        var verbose = (messageInfo & VerboseMask) != 0;
        var messageType = (DltMessageType)((messageInfo & MessageTypeMask) >> MessageTypeShift);
        var messageTypeInfo = (byte)((messageInfo & MessageTypeInfoMask) >> MessageTypeInfoShift);
        var numberOfArguments = data[1];
        var applicationId = AsciiText.Decode(data.Slice(2, 4));
        var contextId = AsciiText.Decode(data.Slice(6, 4));

        return new ExtendedHeader(verbose, messageType, messageTypeInfo, numberOfArguments, applicationId, contextId);
    }
}