using System.Buffers.Binary; namespace MxNativeCodec; public enum NmxItemControlCommand : byte { Advise = 0x1f, AdviseSupervisory = 0x1f, UnAdvise = 0x21, } public sealed record NmxItemControlMessage( NmxItemControlCommand Command, Guid ItemCorrelationId, ushort ObjectId, ushort ObjectSignature, short PrimitiveId, short AttributeId, short PropertyId, ushort AttributeSignature, short AttributeIndex, uint Tail) { private const ushort Version = 1; private const int HeaderLength = 3; private const int GuidLength = 16; private const int AdviseExtraLength = 2; private const int PayloadLength = 18; public static int GetEncodedLength(NmxItemControlCommand command) { return HeaderLength + GuidLength + (command == NmxItemControlCommand.AdviseSupervisory ? AdviseExtraLength : 0) + PayloadLength; } public static NmxItemControlMessage Parse(ReadOnlySpan body) { if (body.Length < HeaderLength + GuidLength + PayloadLength) { throw new ArgumentException("NMX item-control body is too short.", nameof(body)); } var command = (NmxItemControlCommand)body[0]; if (command is not (NmxItemControlCommand.AdviseSupervisory or NmxItemControlCommand.UnAdvise)) { throw new ArgumentException($"Unsupported item-control command 0x{body[0]:X2}.", nameof(body)); } ushort version = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(1, sizeof(ushort))); if (version != Version) { throw new ArgumentException($"Unsupported item-control version {version}.", nameof(body)); } int expectedLength = GetEncodedLength(command); if (body.Length != expectedLength) { throw new ArgumentException($"Unexpected item-control body length {body.Length}; expected {expectedLength}.", nameof(body)); } int offset = HeaderLength; var itemCorrelationId = new Guid(body.Slice(offset, GuidLength)); offset += GuidLength; if (command == NmxItemControlCommand.AdviseSupervisory) { offset += AdviseExtraLength; } return new NmxItemControlMessage( command, itemCorrelationId, BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(offset, sizeof(ushort))), BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(offset + 2, sizeof(ushort))), BinaryPrimitives.ReadInt16LittleEndian(body.Slice(offset + 4, sizeof(short))), BinaryPrimitives.ReadInt16LittleEndian(body.Slice(offset + 6, sizeof(short))), BinaryPrimitives.ReadInt16LittleEndian(body.Slice(offset + 8, sizeof(short))), BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(offset + 10, sizeof(ushort))), BinaryPrimitives.ReadInt16LittleEndian(body.Slice(offset + 12, sizeof(short))), BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(offset + 14, sizeof(uint)))); } public static NmxItemControlMessage FromReferenceHandle( NmxItemControlCommand command, Guid itemCorrelationId, MxReferenceHandle referenceHandle, uint tail = 3) { return new NmxItemControlMessage( command, itemCorrelationId, referenceHandle.ObjectId, referenceHandle.ObjectSignature, referenceHandle.PrimitiveId, referenceHandle.AttributeId, referenceHandle.PropertyId, referenceHandle.AttributeSignature, referenceHandle.AttributeIndex, tail); } public MxReferenceHandle ToReferenceHandle( byte galaxyId = 1, ushort platformId = 1, ushort engineId = 1) { return new MxReferenceHandle( galaxyId, platformId, engineId, ObjectId, ObjectSignature, PrimitiveId, AttributeId, PropertyId, AttributeSignature, AttributeIndex); } public byte[] Encode() { byte[] body = new byte[GetEncodedLength(Command)]; body[0] = (byte)Command; BinaryPrimitives.WriteUInt16LittleEndian(body.AsSpan(1, sizeof(ushort)), Version); int offset = HeaderLength; ItemCorrelationId.TryWriteBytes(body.AsSpan(offset, GuidLength)); offset += GuidLength; if (Command == NmxItemControlCommand.AdviseSupervisory) { offset += AdviseExtraLength; } BinaryPrimitives.WriteUInt16LittleEndian(body.AsSpan(offset, sizeof(ushort)), ObjectId); BinaryPrimitives.WriteUInt16LittleEndian(body.AsSpan(offset + 2, sizeof(ushort)), ObjectSignature); BinaryPrimitives.WriteInt16LittleEndian(body.AsSpan(offset + 4, sizeof(short)), PrimitiveId); BinaryPrimitives.WriteInt16LittleEndian(body.AsSpan(offset + 6, sizeof(short)), AttributeId); BinaryPrimitives.WriteInt16LittleEndian(body.AsSpan(offset + 8, sizeof(short)), PropertyId); BinaryPrimitives.WriteUInt16LittleEndian(body.AsSpan(offset + 10, sizeof(ushort)), AttributeSignature); BinaryPrimitives.WriteInt16LittleEndian(body.AsSpan(offset + 12, sizeof(short)), AttributeIndex); BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(offset + 14, sizeof(uint)), Tail); return body; } public NmxItemControlMessage ToUnAdvise() { return this with { Command = NmxItemControlCommand.UnAdvise }; } public NmxItemControlMessage ToAdviseSupervisory() { return this with { Command = NmxItemControlCommand.AdviseSupervisory }; } }