feat: bootstrap suitelink tag client codecs
This commit is contained in:
84
src/SuiteLink.Client/Protocol/SuiteLinkSubscriptionCodec.cs
Normal file
84
src/SuiteLink.Client/Protocol/SuiteLinkSubscriptionCodec.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
namespace SuiteLink.Client.Protocol;
|
||||
|
||||
public static class SuiteLinkSubscriptionCodec
|
||||
{
|
||||
public const ushort AdviseMessageType = 0x8010;
|
||||
public const ushort AdviseAckMessageType = 0x0003;
|
||||
public const ushort UnadviseMessageType = 0x8004;
|
||||
|
||||
public static byte[] EncodeAdvise(string itemName)
|
||||
{
|
||||
// Convenience overload for initial bring-up scenarios.
|
||||
return EncodeAdvise(0, itemName);
|
||||
}
|
||||
|
||||
public static byte[] EncodeAdvise(uint tagId, string itemName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(itemName))
|
||||
{
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(itemName));
|
||||
}
|
||||
|
||||
var itemNameBytes = SuiteLinkEncoding.EncodeLengthPrefixedUtf16(itemName);
|
||||
var payload = new byte[sizeof(uint) + itemNameBytes.Length];
|
||||
SuiteLinkEncoding.WriteUInt32LittleEndian(payload.AsSpan(0, sizeof(uint)), tagId);
|
||||
itemNameBytes.CopyTo(payload, sizeof(uint));
|
||||
|
||||
return SuiteLinkFrameWriter.WriteFrame(AdviseMessageType, payload);
|
||||
}
|
||||
|
||||
public static byte[] EncodeUnadvise(uint tagId)
|
||||
{
|
||||
Span<byte> payload = stackalloc byte[sizeof(uint)];
|
||||
SuiteLinkEncoding.WriteUInt32LittleEndian(payload, tagId);
|
||||
return SuiteLinkFrameWriter.WriteFrame(UnadviseMessageType, payload);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<AdviseAck> DecodeAdviseAckMany(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
var frame = SuiteLinkFrameReader.ParseFrame(bytes);
|
||||
if (frame.MessageType != AdviseAckMessageType)
|
||||
{
|
||||
throw new FormatException(
|
||||
$"Unexpected advise ack message type 0x{frame.MessageType:x4}; expected 0x{AdviseAckMessageType:x4}.");
|
||||
}
|
||||
|
||||
var payload = frame.Payload.Span;
|
||||
if (payload.Length == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
if (payload.Length % 5 != 0)
|
||||
{
|
||||
throw new FormatException(
|
||||
$"Unexpected advise ack payload length {payload.Length}; payload must be a multiple of 5 bytes.");
|
||||
}
|
||||
|
||||
var itemCount = payload.Length / 5;
|
||||
var items = new List<AdviseAck>(itemCount);
|
||||
var offset = 0;
|
||||
while (offset < payload.Length)
|
||||
{
|
||||
var tagId = SuiteLinkEncoding.ReadUInt32LittleEndian(payload.Slice(offset, 4));
|
||||
items.Add(new AdviseAck(tagId));
|
||||
offset += 5;
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public static AdviseAck DecodeAdviseAck(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
var items = DecodeAdviseAckMany(bytes);
|
||||
if (items.Count != 1)
|
||||
{
|
||||
throw new FormatException(
|
||||
$"Expected a single advise ack item but decoded {items.Count} items.");
|
||||
}
|
||||
|
||||
return items[0];
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct AdviseAck(uint TagId);
|
||||
Reference in New Issue
Block a user