Files
suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkSubscriptionCodec.cs
2026-03-16 14:43:31 -04:00

85 lines
2.7 KiB
C#

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);