Class
Static
public static class TableDeserializer
Namespace: SharpMeter.Core.Tables
Deserializes raw PSEM table bytes into ParsedTable using a TableDefinition.
Methods
| Name | Description |
|---|---|
Deserialize(TableDefinition definition, ReadOnlyMemory<byte> data) static |
Parses raw table bytes using the provided table definition. |
Deserialize(TableDefinition definition, ReadOnlyMemory data)
Result<ParsedTable> TableDeserializer.Deserialize(TableDefinition definition, ReadOnlyMemory<byte> data)
Parses raw table bytes using the provided table definition.
Parameters
| Name | Type | Description |
|---|---|---|
definition | SharpMeter.Core.Tables.TableDefinition | The table structure definition. |
data | ReadOnlyMemory<byte> | The raw table bytes. |
Returns: A fully parsed table with named fields, or an error.
View Source
/// <summary>
/// Deserializes raw PSEM table bytes into <see cref = "ParsedTable"/> using a <see cref = "TableDefinition"/>.
/// </summary>
public static class TableDeserializer
{
#region Public Methods
/// <summary>
/// Parses raw table bytes using the provided table definition.
/// </summary>
/// <param name = "definition">The table structure definition.</param>
/// <param name = "data">The raw table bytes.</param>
/// <returns>A fully parsed table with named fields, or an error.</returns>
public static Result<ParsedTable> Deserialize(TableDefinition definition, ReadOnlyMemory<byte> data)
{
ArgumentNullException.ThrowIfNull(definition);
if (definition.ExpectedSize > 0 && data.Length < definition.ExpectedSize)
return PsemError.Framing($"Table {definition.DisplayName} expected {definition.ExpectedSize} bytes, got {data.Length}");
ImmutableArray<ParsedTableField>.Builder fields = ImmutableArray.CreateBuilder<ParsedTableField>(definition.Fields.Length);
foreach (TableFieldDefinition fieldDef in definition.Fields)
{
if (fieldDef.Offset + fieldDef.Size > data.Length)
break; // Partial table — stop parsing
ReadOnlyMemory<byte> rawBytes = data.Slice(fieldDef.Offset, fieldDef.Size);
ParsedTableField parsed = ParseField(fieldDef, rawBytes);
fields.Add(parsed);
}
return new ParsedTable
{
Definition = definition,
RawData = data,
Fields = fields.ToImmutable()
};
}
#endregion
#region Field Parsing
private static ParsedTableField ParseField(TableFieldDefinition def, ReadOnlyMemory<byte> rawBytes)
{
ReadOnlySpan<byte> span = rawBytes.Span;
var(displayValue, numericValue) = def.Type switch
{
FieldType.Ascii => (Encoding.ASCII.GetString(span).TrimEnd('\0', ' '), -1),
FieldType.String => (Encoding.UTF8.GetString(span).TrimEnd('\0'), -1),
FieldType.Hex => (Convert.ToHexString(span), -1),
FieldType.UInt8 when span.Length >= 1 => (span[0].ToString(CultureInfo.InvariantCulture), span[0]),
FieldType.UInt16 when span.Length >= 2 => ParseUInt16(span),
FieldType.UInt32 when span.Length >= 4 => ParseUInt32(span),
FieldType.Int8 when span.Length >= 1 => (((sbyte)span[0]).ToString(CultureInfo.InvariantCulture), (sbyte)span[0]),
FieldType.Int16 when span.Length >= 2 => ParseInt16(span),
FieldType.Int32 when span.Length >= 4 => ParseInt32(span),
FieldType.Boolean when span.Length >= 1 => (span[0] != 0 ? "True" : "False", span[0] != 0 ? 1L : 0L),
FieldType.Enum when span.Length >= 1 => ParseEnum(def, span[0]),
FieldType.BitArray => ParseBitArray(span),
FieldType.BitField => ("(bit field)", -1),
FieldType.Bcd => ParseBcd(span),
FieldType.DateTime => ParseDateTime(span),
_ => (Convert.ToHexString(span), -1)};
ImmutableArray<ParsedBitField> bitFields = def.Type == FieldType.BitField ? ParseBitFields(def, span) : ImmutableArray<ParsedBitField>.Empty;
return new ParsedTableField
{
Definition = def,
RawBytes = rawBytes,
DisplayValue = displayValue,
NumericValue = numericValue,
BitFields = bitFields
};
}
private static (string Display, long Numeric) ParseUInt16(ReadOnlySpan<byte> span)
{
var value = (ushort)((span[0] << 8) | span[1]);
return (value.ToString(CultureInfo.InvariantCulture), value);
}
private static (string Display, long Numeric) ParseUInt32(ReadOnlySpan<byte> span)
{
var value = (uint)((span[0] << 24) | (span[1] << 16) | (span[2] << 8) | span[3]);
return (value.ToString(CultureInfo.InvariantCulture), value);
}
private static (string Display, long Numeric) ParseInt16(ReadOnlySpan<byte> span)
{
var value = (short)((span[0] << 8) | span[1]);
return (value.ToString(CultureInfo.InvariantCulture), value);
}
private static (string Display, long Numeric) ParseInt32(ReadOnlySpan<byte> span)
{
var value = (span[0] << 24) | (span[1] << 16) | (span[2] << 8) | span[3];
return (value.ToString(CultureInfo.InvariantCulture), value);
}
private static (string Display, long Numeric) ParseEnum(TableFieldDefinition def, byte value)
{
if (def.EnumValues.TryGetValue(value, out var name))
return ($"{name} ({value})", value);
return (value.ToString(CultureInfo.InvariantCulture), value);
}
private static (string Display, long Numeric) ParseBitArray(ReadOnlySpan<byte> span)
{
var sb = new StringBuilder();
for (var byteIdx = 0; byteIdx < span.Length; byteIdx++)
for (var bit = 0; bit < 8; bit++)
if ((span[byteIdx] & (1 << bit)) != 0)
{
if (sb.Length > 0)
sb.Append(", ");
sb.Append(byteIdx * 8 + bit);
}
return (sb.Length > 0 ? sb.ToString() : "(none)", -1);
}
private static (string Display, long Numeric) ParseBcd(ReadOnlySpan<byte> span)
{
var sb = new StringBuilder(span.Length * 2);
foreach (var b in span)
{
sb.Append((char)('0' + (b >> 4)));
sb.Append((char)('0' + (b & 0x0F)));
}
return (sb.ToString(), -1);
}
private static (string Display, long Numeric) ParseDateTime(ReadOnlySpan<byte> span)
{
if (span.Length < 6)
return (Convert.ToHexString(span), -1);
var year = 2000 + span[0];
return ($"{year:D4}-{span[1]:D2}-{span[2]:D2} {span[3]:D2}:{span[4]:D2}:{span[5]:D2}", -1);
}
private static ImmutableArray<ParsedBitField> ParseBitFields(TableFieldDefinition def, ReadOnlySpan<byte> span)
{
if (def.BitFields.IsDefaultOrEmpty)
return[];
// Convert bytes to a single integer value for bit extraction
long rawValue = 0;
for (var i = 0; i < Math.Min(span.Length, 8); i++)
rawValue |= (long)span[i] << (i * 8);
ImmutableArray<ParsedBitField>.Builder builder = ImmutableArray.CreateBuilder<ParsedBitField>(def.BitFields.Length);
foreach (BitFieldDefinition bitDef in def.BitFields)
{
var mask = (1L << bitDef.BitCount) - 1;
var value = (int)((rawValue >> bitDef.StartBit) & mask);
string display;
if (bitDef.EnumValues is not null && bitDef.EnumValues.TryGetValue(value, out var enumName))
display = $"{enumName} ({value})";
else if (bitDef.BitCount == 1)
display = value != 0 ? "True" : "False";
else
display = value.ToString(CultureInfo.InvariantCulture);
builder.Add(new ParsedBitField(bitDef.Name, value, display));
}
return builder.ToImmutable();
}
#endregion
}