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 payload = stackalloc byte[sizeof(uint)]; SuiteLinkEncoding.WriteUInt32LittleEndian(payload, tagId); return SuiteLinkFrameWriter.WriteFrame(UnadviseMessageType, payload); } public static IReadOnlyList DecodeAdviseAckMany(ReadOnlySpan 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(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 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);