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
| Name | Description |
|---|---|
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
| Name | Description |
|---|---|
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
| Name | Type | Description |
|---|---|---|
source | ReadOnlySpan<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
| Name | Description |
|---|---|
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
}