Class Static
public static class AxdrEncoder

Namespace: SharpMeter.Dlms.Codec

Encodes and decodes DLMS data values using A-XDR (Adapted External Data Representation) per IEC 62056.

Methods

NameDescription
DecodeBoolean(ReadOnlySpan<byte> source) static Decodes a boolean value from A-XDR data.
DecodeDateTime(ReadOnlySpan<byte> source) static Decodes a COSEM date-time (12 bytes) from A-XDR data.
DecodeDoubleLong(ReadOnlySpan<byte> source) static Decodes a signed 32-bit integer from A-XDR data (big-endian).
DecodeDoubleLongUnsigned(ReadOnlySpan<byte> source) static Decodes an unsigned 32-bit integer from A-XDR data (big-endian).
DecodeInteger(ReadOnlySpan<byte> source) static Decodes a signed 8-bit integer from A-XDR data.
DecodeLength(ReadOnlySpan<byte> source, int bytesConsumed) static Decodes a BER-encoded length from the given span.
DecodeLong(ReadOnlySpan<byte> source) static Decodes a signed 16-bit integer from A-XDR data (big-endian).
DecodeLong64Unsigned(ReadOnlySpan<byte> source) static Decodes an unsigned 64-bit integer from A-XDR data (big-endian).
DecodeLongUnsigned(ReadOnlySpan<byte> source) static Decodes an unsigned 16-bit integer from A-XDR data (big-endian).
DecodeOctetString(ReadOnlySpan<byte> source, int bytesConsumed) static Decodes an octet string from A-XDR data.
DecodeUnsigned(ReadOnlySpan<byte> source) static Decodes an unsigned 8-bit integer from A-XDR data.
DecodeVisibleString(ReadOnlySpan<byte> source, int bytesConsumed) static Decodes a visible (ASCII) string from A-XDR data.
EncodeBoolean(bool value) static Encodes a boolean value.
EncodeDoubleLong(int value) static Encodes a signed 32-bit integer (big-endian).
EncodeDoubleLongUnsigned(uint value) static Encodes an unsigned 32-bit integer (big-endian).
EncodeEnum(byte value) static Encodes an enum value (unsigned 8-bit).
EncodeInteger(sbyte value) static Encodes a signed 8-bit integer.
EncodeLength(int length) static Encodes a length value using ASN.1 BER length encoding.
EncodeLong(short value) static Encodes a signed 16-bit integer (big-endian).
EncodeLongUnsigned(ushort value) static Encodes an unsigned 16-bit integer (big-endian).
EncodeNull() static Encodes null data (no value).
EncodeOctetString(ReadOnlySpan<byte> value) static Encodes an octet string (byte array).
EncodeUnsigned(byte value) static Encodes an unsigned 8-bit integer.
EncodeVisibleString(string value) static Encodes a visible (ASCII) string.
ReadDataType(ReadOnlySpan<byte> source) static Decodes the data type tag from the given span.

DecodeBoolean(ReadOnlySpan source)

bool AxdrEncoder.DecodeBoolean(ReadOnlySpan<byte> source)

Decodes a boolean value from A-XDR data.

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>Source starting after the type tag.

Returns: The decoded boolean value.

DecodeDateTime(ReadOnlySpan source)

DateTime AxdrEncoder.DecodeDateTime(ReadOnlySpan<byte> source)

Decodes a COSEM date-time (12 bytes) from A-XDR data.

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>Source starting after the type tag (must be at least 12 bytes).

Returns: The decoded DateTime value in UTC.

DecodeDoubleLong(ReadOnlySpan source)

int AxdrEncoder.DecodeDoubleLong(ReadOnlySpan<byte> source)

Decodes a signed 32-bit integer from A-XDR data (big-endian).

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>Source starting after the type tag.

Returns: The decoded value.

DecodeDoubleLongUnsigned(ReadOnlySpan source)

uint AxdrEncoder.DecodeDoubleLongUnsigned(ReadOnlySpan<byte> source)

Decodes an unsigned 32-bit integer from A-XDR data (big-endian).

DecodeInteger(ReadOnlySpan source)

sbyte AxdrEncoder.DecodeInteger(ReadOnlySpan<byte> source)

Decodes a signed 8-bit integer from A-XDR data.

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>Source starting after the type tag.

Returns: The decoded value.

DecodeLength(ReadOnlySpan source, int bytesConsumed)

int AxdrEncoder.DecodeLength(ReadOnlySpan<byte> source, out int bytesConsumed)

Decodes a BER-encoded length from the given span.

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>The source data.
bytesConsumedout intThe number of bytes consumed by the length encoding.

Returns: The decoded length value.

DecodeLong(ReadOnlySpan source)

short AxdrEncoder.DecodeLong(ReadOnlySpan<byte> source)

Decodes a signed 16-bit integer from A-XDR data (big-endian).

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>Source starting after the type tag.

Returns: The decoded value.

DecodeLong64Unsigned(ReadOnlySpan source)

ulong AxdrEncoder.DecodeLong64Unsigned(ReadOnlySpan<byte> source)

Decodes an unsigned 64-bit integer from A-XDR data (big-endian).

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>Source starting after the type tag.

Returns: The decoded value.

DecodeLongUnsigned(ReadOnlySpan source)

ushort AxdrEncoder.DecodeLongUnsigned(ReadOnlySpan<byte> source)

Decodes an unsigned 16-bit integer from A-XDR data (big-endian).

DecodeOctetString(ReadOnlySpan source, int bytesConsumed)

byte[] AxdrEncoder.DecodeOctetString(ReadOnlySpan<byte> source, out int bytesConsumed)

Decodes an octet string from A-XDR data.

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>Source starting after the type tag.
bytesConsumedout intTotal bytes consumed including length prefix.

Returns: The decoded byte array.

DecodeUnsigned(ReadOnlySpan source)

byte AxdrEncoder.DecodeUnsigned(ReadOnlySpan<byte> source)

Decodes an unsigned 8-bit integer from A-XDR data.

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>Source starting after the type tag.

Returns: The decoded value.

DecodeVisibleString(ReadOnlySpan source, int bytesConsumed)

string AxdrEncoder.DecodeVisibleString(ReadOnlySpan<byte> source, out int bytesConsumed)

Decodes a visible (ASCII) string from A-XDR data.

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>Source starting after the type tag.
bytesConsumedout intTotal bytes consumed including length prefix.

Returns: The decoded string.

EncodeBoolean(bool value)

byte[] AxdrEncoder.EncodeBoolean(bool value)

Encodes a boolean value.

EncodeDoubleLong(int value)

byte[] AxdrEncoder.EncodeDoubleLong(int value)

Encodes a signed 32-bit integer (big-endian).

EncodeDoubleLongUnsigned(uint value)

byte[] AxdrEncoder.EncodeDoubleLongUnsigned(uint value)

Encodes an unsigned 32-bit integer (big-endian).

EncodeEnum(byte value)

byte[] AxdrEncoder.EncodeEnum(byte value)

Encodes an enum value (unsigned 8-bit).

EncodeInteger(sbyte value)

byte[] AxdrEncoder.EncodeInteger(sbyte value)

Encodes a signed 8-bit integer.

EncodeLength(int length)

byte[] AxdrEncoder.EncodeLength(int length)

Encodes a length value using ASN.1 BER length encoding.

Parameters

NameTypeDescription
lengthintThe length to encode.

Returns: The encoded length bytes.

EncodeLong(short value)

byte[] AxdrEncoder.EncodeLong(short value)

Encodes a signed 16-bit integer (big-endian).

EncodeLongUnsigned(ushort value)

byte[] AxdrEncoder.EncodeLongUnsigned(ushort value)

Encodes an unsigned 16-bit integer (big-endian).

EncodeNull()

byte[] AxdrEncoder.EncodeNull()

Encodes null data (no value).

EncodeOctetString(ReadOnlySpan value)

byte[] AxdrEncoder.EncodeOctetString(ReadOnlySpan<byte> value)

Encodes an octet string (byte array).

EncodeUnsigned(byte value)

byte[] AxdrEncoder.EncodeUnsigned(byte value)

Encodes an unsigned 8-bit integer.

EncodeVisibleString(string value)

byte[] AxdrEncoder.EncodeVisibleString(string value)

Encodes a visible (ASCII) string.

ReadDataType(ReadOnlySpan source)

DlmsDataType AxdrEncoder.ReadDataType(ReadOnlySpan<byte> source)

Decodes the data type tag from the given span.

Parameters

NameTypeDescription
sourceReadOnlySpan<byte>The source data starting with a type tag.

Returns: The DLMS data type.

View Source
/// <summary>
///     Encodes and decodes DLMS data values using A-XDR (Adapted External Data Representation) per IEC 62056.
/// </summary>
public static class AxdrEncoder
{
#region Encoding
    /// <summary>Encodes a boolean value.</summary>
    public static byte[] EncodeBoolean(bool value) => [(byte)DlmsDataType.Boolean, value ? (byte)0xFF : (byte)0x00];
    /// <summary>Encodes an unsigned 8-bit integer.</summary>
    public static byte[] EncodeUnsigned(byte value) => [(byte)DlmsDataType.Unsigned, value];
    /// <summary>Encodes a signed 8-bit integer.</summary>
    public static byte[] EncodeInteger(sbyte value) => [(byte)DlmsDataType.Integer, (byte)value];
    /// <summary>Encodes an unsigned 16-bit integer (big-endian).</summary>
    public static byte[] EncodeLongUnsigned(ushort value) => [(byte)DlmsDataType.LongUnsigned, (byte)(value >> 8), (byte)(value & 0xFF)];
    /// <summary>Encodes a signed 16-bit integer (big-endian).</summary>
    public static byte[] EncodeLong(short value) => [(byte)DlmsDataType.Long, (byte)(value >> 8), (byte)(value & 0xFF)];
    /// <summary>Encodes an unsigned 32-bit integer (big-endian).</summary>
    public static byte[] EncodeDoubleLongUnsigned(uint value) => [(byte)DlmsDataType.DoubleLongUnsigned, (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)(value & 0xFF)];
    /// <summary>Encodes a signed 32-bit integer (big-endian).</summary>
    public static byte[] EncodeDoubleLong(int value) => [(byte)DlmsDataType.DoubleLong, (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)(value & 0xFF)];
    /// <summary>Encodes an octet string (byte array).</summary>
    public static byte[] EncodeOctetString(ReadOnlySpan<byte> value)
    {
        var result = new byte[1 + EncodeLength(value.Length).Length + value.Length];
        var offset = 0;
        result[offset++] = (byte)DlmsDataType.OctetString;
        var lenBytes = EncodeLength(value.Length);
        lenBytes.CopyTo(result, offset);
        offset += lenBytes.Length;
        value.CopyTo(result.AsSpan(offset));
        return result;
    }

    /// <summary>Encodes a visible (ASCII) string.</summary>
    public static byte[] EncodeVisibleString(string value)
    {
        var bytes = Encoding.ASCII.GetBytes(value);
        var result = new byte[1 + EncodeLength(bytes.Length).Length + bytes.Length];
        var offset = 0;
        result[offset++] = (byte)DlmsDataType.VisibleString;
        var lenBytes = EncodeLength(bytes.Length);
        lenBytes.CopyTo(result, offset);
        offset += lenBytes.Length;
        bytes.CopyTo(result, offset);
        return result;
    }

    /// <summary>Encodes null data (no value).</summary>
    public static byte[] EncodeNull() => [(byte)DlmsDataType.NullData];
    /// <summary>Encodes an enum value (unsigned 8-bit).</summary>
    public static byte[] EncodeEnum(byte value) => [(byte)DlmsDataType.Enum, value];
#endregion
#region Length Encoding
    /// <summary>
    ///     Encodes a length value using ASN.1 BER length encoding.
    /// </summary>
    /// <param name = "length">The length to encode.</param>
    /// <returns>The encoded length bytes.</returns>
    public static byte[] EncodeLength(int length)
    {
        if (length < 0x80)
            return[(byte)length];
        if (length <= 0xFF)
            return[0x81, (byte)length];
        return[0x82, (byte)(length >> 8), (byte)(length & 0xFF)];
    }

    /// <summary>
    ///     Decodes a BER-encoded length from the given span.
    /// </summary>
    /// <param name = "source">The source data.</param>
    /// <param name = "bytesConsumed">The number of bytes consumed by the length encoding.</param>
    /// <returns>The decoded length value.</returns>
    public static int DecodeLength(ReadOnlySpan<byte> source, out int bytesConsumed)
    {
        if (source.Length < 1)
        {
            bytesConsumed = 0;
            return 0;
        }

        if (source[0] < 0x80)
        {
            bytesConsumed = 1;
            return source[0];
        }

        var numBytes = source[0] & 0x7F;
        bytesConsumed = 1 + numBytes;
        return numBytes switch
        {
            1 when source.Length >= 2 => source[1],
            2 when source.Length >= 3 => (source[1] << 8) | source[2],
            3 when source.Length >= 4 => (source[1] << 16) | (source[2] << 8) | source[3],
            _ => 0
        };
    }

#endregion
#region Decoding
    /// <summary>
    ///     Decodes the data type tag from the given span.
    /// </summary>
    /// <param name = "source">The source data starting with a type tag.</param>
    /// <returns>The DLMS data type.</returns>
    public static DlmsDataType ReadDataType(ReadOnlySpan<byte> source) => source.Length > 0 ? (DlmsDataType)source[0] : DlmsDataType.NullData;
    /// <summary>
    ///     Decodes an unsigned 8-bit integer from A-XDR data.
    /// </summary>
    /// <param name = "source">Source starting after the type tag.</param>
    /// <returns>The decoded value.</returns>
    public static byte DecodeUnsigned(ReadOnlySpan<byte> source) => source.Length > 0 ? source[0] : (byte)0;
    /// <summary>
    ///     Decodes an unsigned 16-bit integer from A-XDR data (big-endian).
    /// </summary>
    public static ushort DecodeLongUnsigned(ReadOnlySpan<byte> source) => source.Length >= 2 ? (ushort)((source[0] << 8) | source[1]) : (ushort)0;
    /// <summary>
    ///     Decodes an unsigned 32-bit integer from A-XDR data (big-endian).
    /// </summary>
    public static uint DecodeDoubleLongUnsigned(ReadOnlySpan<byte> source) => source.Length >= 4 ? (uint)((source[0] << 24) | (source[1] << 16) | (source[2] << 8) | source[3]) : 0u;
    /// <summary>
    ///     Decodes an octet string from A-XDR data.
    /// </summary>
    /// <param name = "source">Source starting after the type tag.</param>
    /// <param name = "bytesConsumed">Total bytes consumed including length prefix.</param>
    /// <returns>The decoded byte array.</returns>
    public static byte[] DecodeOctetString(ReadOnlySpan<byte> source, out int bytesConsumed)
    {
        var length = DecodeLength(source, out var lenBytes);
        bytesConsumed = lenBytes + length;
        if (source.Length < bytesConsumed)
            throw new ArgumentException("Truncated octet string data");
        return source.Slice(lenBytes, length).ToArray();
    }

    /// <summary>
    ///     Decodes a boolean value from A-XDR data.
    /// </summary>
    /// <param name = "source">Source starting after the type tag.</param>
    /// <returns>The decoded boolean value.</returns>
    public static bool DecodeBoolean(ReadOnlySpan<byte> source) => source.Length > 0 && source[0] != 0x00;
    /// <summary>
    ///     Decodes a signed 8-bit integer from A-XDR data.
    /// </summary>
    /// <param name = "source">Source starting after the type tag.</param>
    /// <returns>The decoded value.</returns>
    public static sbyte DecodeInteger(ReadOnlySpan<byte> source) => source.Length > 0 ? (sbyte)source[0] : (sbyte)0;
    /// <summary>
    ///     Decodes a signed 16-bit integer from A-XDR data (big-endian).
    /// </summary>
    /// <param name = "source">Source starting after the type tag.</param>
    /// <returns>The decoded value.</returns>
    public static short DecodeLong(ReadOnlySpan<byte> source) => source.Length >= 2 ? (short)((source[0] << 8) | source[1]) : (short)0;
    /// <summary>
    ///     Decodes a signed 32-bit integer from A-XDR data (big-endian).
    /// </summary>
    /// <param name = "source">Source starting after the type tag.</param>
    /// <returns>The decoded value.</returns>
    public static int DecodeDoubleLong(ReadOnlySpan<byte> source) => source.Length >= 4 ? (source[0] << 24) | (source[1] << 16) | (source[2] << 8) | source[3] : 0;
    /// <summary>
    ///     Decodes an unsigned 64-bit integer from A-XDR data (big-endian).
    /// </summary>
    /// <param name = "source">Source starting after the type tag.</param>
    /// <returns>The decoded value.</returns>
    public static ulong DecodeLong64Unsigned(ReadOnlySpan<byte> source) => source.Length >= 8 ? ((ulong)source[0] << 56) | ((ulong)source[1] << 48) | ((ulong)source[2] << 40) | ((ulong)source[3] << 32) | ((ulong)source[4] << 24) | ((ulong)source[5] << 16) | ((ulong)source[6] << 8) | source[7] : 0UL;
    /// <summary>
    ///     Decodes a visible (ASCII) string from A-XDR data.
    /// </summary>
    /// <param name = "source">Source starting after the type tag.</param>
    /// <param name = "bytesConsumed">Total bytes consumed including length prefix.</param>
    /// <returns>The decoded string.</returns>
    public static string DecodeVisibleString(ReadOnlySpan<byte> source, out int bytesConsumed)
    {
        var length = DecodeLength(source, out var lenBytes);
        bytesConsumed = lenBytes + length;
        if (source.Length < bytesConsumed)
            throw new ArgumentException("Truncated visible string data");
        return Encoding.ASCII.GetString(source.Slice(lenBytes, length));
    }

    /// <summary>
    ///     Decodes a COSEM date-time (12 bytes) from A-XDR data.
    /// </summary>
    /// <remarks>
    ///     COSEM date-time structure: year(2) month(1) day(1) dow(1) hour(1) min(1) sec(1) hundredths(1) deviation(2)
    ///     status(1).
    ///     Total: 12 bytes.
    /// </remarks>
    /// <param name = "source">Source starting after the type tag (must be at least 12 bytes).</param>
    /// <returns>The decoded DateTime value in UTC.</returns>
    public static DateTime DecodeDateTime(ReadOnlySpan<byte> source)
    {
        if (source.Length < 12)
            throw new ArgumentException("COSEM date-time requires 12 bytes");
        var year = (ushort)((source[0] << 8) | source[1]);
        var month = source[2];
        var day = source[3];
        // source[4] = day of week (ignored)
        var hour = source[5];
        var minute = source[6];
        var second = source[7];
        var hundredths = source[8];
        var deviation = (short)((source[9] << 8) | source[10]);
        // source[11] = clock status (ignored)
        // Handle "not specified" wildcard values
        if (year == 0xFFFF)
            year = 2000;
        if (month == 0xFF)
            month = 1;
        if (day == 0xFF)
            day = 1;
        if (hour == 0xFF)
            hour = 0;
        if (minute == 0xFF)
            minute = 0;
        if (second == 0xFF)
            second = 0;
        if (hundredths == 0xFF)
            hundredths = 0;
        var dt = new DateTime(year, month, day, hour, minute, second, hundredths * 10, DateTimeKind.Utc);
        // Apply deviation (minutes from UTC) if specified
        if (deviation != unchecked((short)0x8000))
            dt = dt.AddMinutes(-deviation);
        return dt;
    }
#endregion
}
Was this page helpful?