Record
public record HdlcFrame : System.ValueType, System.IEquatable<SharpMeter.Dlms.Framing.HdlcFrame>

Namespace: SharpMeter.Dlms.Framing

HDLC (High-level Data Link Control) frame for DLMS/COSEM communication per IEC 62056-46.

Remarks

Frame structure: [Flag 0x7E] [Format] [DestAddr] [SrcAddr] [Control] [HCS] [Information...] [FCS] [Flag 0x7E]

Inheritance

Inherits from: System.ValueType

Implements: System.IEquatable<SharpMeter.Dlms.Framing.HdlcFrame>

Properties

NameDescription
Control Gets the control field.
Destination Gets the destination address.
HasInformation Gets whether this frame carries information data.
Information Gets the information (payload) field.
Source Gets the source address.

Control

HdlcControl HdlcFrame.Control { get; init; }

Gets the control field.

Destination

HdlcAddress HdlcFrame.Destination { get; init; }

Gets the destination address.

HasInformation

bool HdlcFrame.HasInformation { get; }

Gets whether this frame carries information data.

Information

ReadOnlyMemory<byte> HdlcFrame.Information { get; init; }

Gets the information (payload) field.

Source

HdlcAddress HdlcFrame.Source { get; init; }

Gets the source address.

Methods

NameDescription
Decode(ReadOnlySpan<byte> source) static Attempts to decode an HDLC frame from raw bytes.
Encode() Encodes this HDLC frame into a byte buffer for wire transmission.

Decode(ReadOnlySpan source)

Result<HdlcFrame> HdlcFrame.Decode(ReadOnlySpan<byte> source)

Attempts to decode an HDLC frame from raw bytes.

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>The raw frame bytes (including flags).

Returns: The decoded frame, or an error.

Encode()

byte[] HdlcFrame.Encode()

Encodes this HDLC frame into a byte buffer for wire transmission.

Returns: The encoded frame bytes including opening and closing flags.

Fields

NameDescription
Flag static HDLC flag byte marking start and end of frame.
FormatType3 static Frame format type 3 (A-field format) base value.
MaxInfoLength static Maximum information field length.

Flag

byte Flag

HDLC flag byte marking start and end of frame.

FormatType3

ushort FormatType3

Frame format type 3 (A-field format) base value.

MaxInfoLength

int MaxInfoLength

Maximum information field length.

Type Relationships
classDiagram
                    style HdlcFrame fill:#f9f,stroke:#333,stroke-width:2px
                    HdlcFrame --|> ValueType : inherits
                    HdlcFrame ..|> HdlcFrame~ : implements
                
View Source
/// <summary>
///     HDLC (High-level Data Link Control) frame for DLMS/COSEM communication per IEC 62056-46.
/// </summary>
/// <remarks>
///     Frame structure: [Flag 0x7E] [Format] [DestAddr] [SrcAddr] [Control] [HCS] [Information...] [FCS] [Flag 0x7E]
/// </remarks>
public readonly record struct HdlcFrame
{
#region Encoding
    /// <summary>
    ///     Encodes this HDLC frame into a byte buffer for wire transmission.
    /// </summary>
    /// <returns>The encoded frame bytes including opening and closing flags.</returns>
    public byte[] Encode()
    {
        var destBytes = Destination.Encode();
        var srcBytes = Source.Encode();
        ReadOnlySpan<byte> infoSpan = Information.Span;
        // Calculate frame length (everything between flags, excluding flags)
        var frameLength = 2 + destBytes.Length + srcBytes.Length + 1 + 2; // format + addr + control + HCS
        if (infoSpan.Length > 0)
            frameLength += infoSpan.Length + 2; // info + FCS
        var format = (ushort)(FormatType3 | (frameLength & 0x07FF));
        // Build frame
        var totalLength = 1 + frameLength + 1; // opening flag + frame + closing flag
        if (infoSpan.Length > 0)
            totalLength += 0; // FCS already counted in frameLength
        var buffer = new byte[1 + 2 + destBytes.Length + srcBytes.Length + 1 + 2 + infoSpan.Length + (infoSpan.Length > 0 ? 2 : 0) + 1];
        var offset = 0;
        buffer[offset++] = Flag;
        // Format (2 bytes, big-endian)
        buffer[offset++] = (byte)(format >> 8);
        buffer[offset++] = (byte)(format & 0xFF);
        // Destination address
        destBytes.CopyTo(buffer, offset);
        offset += destBytes.Length;
        // Source address
        srcBytes.CopyTo(buffer, offset);
        offset += srcBytes.Length;
        // Control byte
        buffer[offset++] = Control.Encode();
        // HCS (Header Check Sequence) — CRC-16 over format through control
        var hcs = Crc16.Compute(buffer.AsSpan(1, offset - 1));
        buffer[offset++] = (byte)(hcs & 0xFF);
        buffer[offset++] = (byte)(hcs >> 8);
        // Information field
        if (infoSpan.Length > 0)
        {
            infoSpan.CopyTo(buffer.AsSpan(offset));
            offset += infoSpan.Length;
            // FCS (Frame Check Sequence) — CRC-16 over format through information
            var fcs = Crc16.Compute(buffer.AsSpan(1, offset - 1));
            buffer[offset++] = (byte)(fcs & 0xFF);
            buffer[offset++] = (byte)(fcs >> 8);
        }

        buffer[offset] = Flag;
        return buffer[..(offset + 1)];
    }

#endregion
#region Decoding
    /// <summary>
    ///     Attempts to decode an HDLC frame from raw bytes.
    /// </summary>
    /// <param name = "source">The raw frame bytes (including flags).</param>
    /// <returns>The decoded frame, or an error.</returns>
    public static Result<HdlcFrame> Decode(ReadOnlySpan<byte> source)
    {
        if (source.Length < 9) // minimum: flag + format(2) + addr(2) + control + HCS(2) + flag
            return PsemError.Framing($"HDLC frame too short: {source.Length} bytes");
        if (source[0] != Flag || source[^1] != Flag)
            return PsemError.Framing("HDLC frame missing flag bytes");
        var offset = 1;
        // Format field
        var format = (ushort)((source[offset] << 8) | source[offset + 1]);
        var frameLength = format & 0x07FF;
        offset += 2;
        // Destination address (variable length)
        Result<HdlcAddress> destResult = HdlcAddress.Decode(source[offset..]);
        if (destResult.IsFailure)
            return destResult.Error;
        offset += destResult.Value.EncodedLength;
        // Source address (variable length)
        Result<HdlcAddress> srcResult = HdlcAddress.Decode(source[offset..]);
        if (srcResult.IsFailure)
            return srcResult.Error;
        offset += srcResult.Value.EncodedLength;
        // Control byte
        var control = HdlcControl.Decode(source[offset++]);
        // HCS validation — CRC-16 over bytes from format through control
        ReadOnlySpan<byte> hcsData = source.Slice(1, offset - 1);
        var computedHcs = Crc16.Compute(hcsData);
        var storedHcs = (ushort)(source[offset] | (source[offset + 1] << 8));
        if (computedHcs != storedHcs)
            return PsemError.Framing($"HCS mismatch: computed 0x{computedHcs:X4}, stored 0x{storedHcs:X4}");
        offset += 2;
        // Information field (if present)
        ReadOnlyMemory<byte> info = ReadOnlyMemory<byte>.Empty;
        var infoEnd = source.Length - 3; // before FCS(2) + closing flag
        if (offset < infoEnd)
        {
            info = source[offset..infoEnd].ToArray();
            // FCS validation — CRC-16 over bytes from format through information
            ReadOnlySpan<byte> fcsData = source.Slice(1, infoEnd - 1);
            var computedFcs = Crc16.Compute(fcsData);
            var storedFcs = (ushort)(source[infoEnd] | (source[infoEnd + 1] << 8));
            if (computedFcs != storedFcs)
                return PsemError.Framing($"FCS mismatch: computed 0x{computedFcs:X4}, stored 0x{storedFcs:X4}");
        }

        return new HdlcFrame
        {
            Destination = destResult.Value,
            Source = srcResult.Value,
            Control = control,
            Information = info
        };
    }

#endregion
#region Constants
    /// <summary>HDLC flag byte marking start and end of frame.</summary>
    public const byte Flag = 0x7E;
    /// <summary>Frame format type 3 (A-field format) base value.</summary>
    public const ushort FormatType3 = 0xA000;
    /// <summary>Maximum information field length.</summary>
    public const int MaxInfoLength = 2030;
#endregion
#region Properties
    /// <summary>Gets the destination address.</summary>
    public HdlcAddress Destination { get; init; }
    /// <summary>Gets the source address.</summary>
    public HdlcAddress Source { get; init; }
    /// <summary>Gets the control field.</summary>
    public HdlcControl Control { get; init; }
    /// <summary>Gets the information (payload) field.</summary>
    public ReadOnlyMemory<byte> Information { get; init; }
    /// <summary>Gets whether this frame carries information data.</summary>
    public bool HasInformation => Information.Length > 0;
#endregion
}
Was this page helpful?