using System.Buffers.Binary; using System.Text; namespace MxNativeCodec; public sealed record NmxReferenceRegistrationMessage( int ItemHandle, Guid ItemCorrelationId, string ItemDefinition, string ItemContext, bool Subscribe) { private const byte Command = 0x10; private const ushort Version = 1; private const int HeaderLength = 55; private const int ItemStringReservedLength = 8; private const int TailLength = 20; public static NmxReferenceRegistrationMessage Parse(ReadOnlySpan body) { if (body.Length < HeaderLength + ItemStringReservedLength + TailLength) { throw new ArgumentException("NMX reference-registration body is too short.", nameof(body)); } if (body[0] != Command) { throw new ArgumentException($"Unsupported reference-registration command 0x{body[0]:X2}.", nameof(body)); } ushort version = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(1, sizeof(ushort))); if (version != Version) { throw new ArgumentException($"Unsupported reference-registration version {version}.", nameof(body)); } int itemHandle = BinaryPrimitives.ReadInt32LittleEndian(body.Slice(3, sizeof(int))); var correlationId = new Guid(body.Slice(7, 16)); int offset = HeaderLength; string itemDefinition = ReadRegisteredString(body, ref offset, taggedLength: true); if (body.Slice(offset, ItemStringReservedLength).IndexOfAnyExcept((byte)0) >= 0) { throw new ArgumentException("Unexpected nonzero reference-registration item string reserved bytes.", nameof(body)); } offset += ItemStringReservedLength; string itemContext = ReadRegisteredString(body, ref offset, taggedLength: false); if (body.Length - offset != TailLength) { throw new ArgumentException($"Unexpected reference-registration tail length {body.Length - offset}.", nameof(body)); } if (body.Slice(offset, TailLength - 1).IndexOfAnyExcept((byte)0) >= 0) { throw new ArgumentException("Unexpected nonzero reference-registration tail bytes.", nameof(body)); } return new NmxReferenceRegistrationMessage( itemHandle, correlationId, itemDefinition, itemContext, body[offset + TailLength - 1] != 0); } public byte[] Encode() { byte[] itemDefinitionBytes = EncodeNullTerminatedUtf16(ItemDefinition); byte[] itemContextBytes = EncodeNullTerminatedUtf16(ItemContext); byte[] body = new byte[ HeaderLength + sizeof(int) + itemDefinitionBytes.Length + ItemStringReservedLength + sizeof(int) + itemContextBytes.Length + TailLength]; body[0] = Command; BinaryPrimitives.WriteUInt16LittleEndian(body.AsSpan(1, sizeof(ushort)), Version); BinaryPrimitives.WriteInt32LittleEndian(body.AsSpan(3, sizeof(int)), ItemHandle); ItemCorrelationId.TryWriteBytes(body.AsSpan(7, 16)); BinaryPrimitives.WriteInt16LittleEndian(body.AsSpan(23, sizeof(short)), -1); BinaryPrimitives.WriteInt32LittleEndian(body.AsSpan(27, sizeof(int)), 1); int offset = HeaderLength; WriteRegisteredString(body, ref offset, itemDefinitionBytes, taggedLength: true); offset += ItemStringReservedLength; WriteRegisteredString(body, ref offset, itemContextBytes, taggedLength: false); body[offset + TailLength - 1] = Subscribe ? (byte)1 : (byte)0; return body; } public static string ToBufferedItemDefinition(string itemDefinition) { ArgumentException.ThrowIfNullOrWhiteSpace(itemDefinition); return itemDefinition.EndsWith(".property(buffer)", StringComparison.OrdinalIgnoreCase) ? itemDefinition : itemDefinition + ".property(buffer)"; } private static string ReadRegisteredString(ReadOnlySpan body, ref int offset, bool taggedLength) { int rawLength = BinaryPrimitives.ReadInt32LittleEndian(body.Slice(offset, sizeof(int))); int byteLength = taggedLength ? rawLength & 0x00FF_FFFF : rawLength; if (taggedLength && (rawLength & unchecked((int)0xFF00_0000)) != unchecked((int)0x8100_0000)) { throw new ArgumentException("Reference-registration item definition is missing the observed 0x81 string marker."); } offset += sizeof(int); if (byteLength < 2 || byteLength % 2 != 0 || offset + byteLength > body.Length) { throw new ArgumentException($"Invalid reference-registration string byte length {byteLength}."); } string value = Encoding.Unicode.GetString(body.Slice(offset, byteLength - 2)); if (body[offset + byteLength - 2] != 0 || body[offset + byteLength - 1] != 0) { throw new ArgumentException("Reference-registration string is not null terminated."); } offset += byteLength; return value; } private static void WriteRegisteredString(byte[] body, ref int offset, byte[] value, bool taggedLength) { int rawLength = taggedLength ? value.Length | unchecked((int)0x8100_0000) : value.Length; BinaryPrimitives.WriteInt32LittleEndian(body.AsSpan(offset, sizeof(int)), rawLength); offset += sizeof(int); value.CopyTo(body.AsSpan(offset)); offset += value.Length; } private static byte[] EncodeNullTerminatedUtf16(string value) { return Encoding.Unicode.GetBytes(value + '\0'); } }