using System.Buffers.Binary; namespace MxNativeCodec; public enum NmxTransferMessageKind : int { Metadata = 1, ItemControl = 2, Write = 3, } public sealed record NmxTransferEnvelope( NmxTransferMessageKind MessageKind, int SourceGalaxyId, int SourcePlatformId, int LocalEngineId, int TargetGalaxyId, int TargetPlatformId, int TargetEngineId, int TimeoutMilliseconds, ReadOnlyMemory InnerBody) { public const int HeaderLength = 46; private const ushort Version = 1; private const int InnerLengthOffset = 2; private const int ReservedOffset = 6; private const int MessageKindOffset = 10; private const int SourceGalaxyOffset = 14; private const int SourcePlatformOffset = 18; private const int LocalEngineOffset = 22; private const int TargetGalaxyOffset = 26; private const int TargetPlatformOffset = 30; private const int TargetEngineOffset = 34; private const int ProtocolMarkerOffset = 38; private const int TimeoutOffset = 42; private const int ProtocolMarker = 0x0201; private const int DefaultTimeoutMilliseconds = 30000; public static NmxTransferEnvelope Parse(ReadOnlyMemory transferBody) { if (transferBody.Length < HeaderLength) { throw new ArgumentException("NMX TransferData body is too short.", nameof(transferBody)); } ReadOnlySpan span = transferBody.Span; ushort version = BinaryPrimitives.ReadUInt16LittleEndian(span[..sizeof(ushort)]); if (version != Version) { throw new ArgumentException($"Unsupported NMX TransferData version {version}.", nameof(transferBody)); } int innerLength = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(InnerLengthOffset, sizeof(int))); if (innerLength != transferBody.Length - HeaderLength) { throw new ArgumentException("NMX TransferData inner length does not match the body size.", nameof(transferBody)); } int protocolMarker = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(ProtocolMarkerOffset, sizeof(int))); if (protocolMarker != ProtocolMarker) { throw new ArgumentException($"Unsupported NMX TransferData protocol marker 0x{protocolMarker:X8}.", nameof(transferBody)); } return new NmxTransferEnvelope( (NmxTransferMessageKind)BinaryPrimitives.ReadInt32LittleEndian(span.Slice(MessageKindOffset, sizeof(int))), BinaryPrimitives.ReadInt32LittleEndian(span.Slice(SourceGalaxyOffset, sizeof(int))), BinaryPrimitives.ReadInt32LittleEndian(span.Slice(SourcePlatformOffset, sizeof(int))), BinaryPrimitives.ReadInt32LittleEndian(span.Slice(LocalEngineOffset, sizeof(int))), BinaryPrimitives.ReadInt32LittleEndian(span.Slice(TargetGalaxyOffset, sizeof(int))), BinaryPrimitives.ReadInt32LittleEndian(span.Slice(TargetPlatformOffset, sizeof(int))), BinaryPrimitives.ReadInt32LittleEndian(span.Slice(TargetEngineOffset, sizeof(int))), BinaryPrimitives.ReadInt32LittleEndian(span.Slice(TimeoutOffset, sizeof(int))), transferBody[HeaderLength..]); } public static byte[] Encode( NmxTransferMessageKind messageKind, int localEngineId, int targetGalaxyId, int targetPlatformId, int targetEngineId, ReadOnlySpan innerBody, int sourceGalaxyId = 1, int sourcePlatformId = 1, int timeoutMilliseconds = DefaultTimeoutMilliseconds) { byte[] transferBody = new byte[HeaderLength + innerBody.Length]; BinaryPrimitives.WriteUInt16LittleEndian(transferBody.AsSpan(0, sizeof(ushort)), Version); BinaryPrimitives.WriteInt32LittleEndian(transferBody.AsSpan(InnerLengthOffset, sizeof(int)), innerBody.Length); BinaryPrimitives.WriteInt32LittleEndian(transferBody.AsSpan(ReservedOffset, sizeof(int)), 0); BinaryPrimitives.WriteInt32LittleEndian(transferBody.AsSpan(MessageKindOffset, sizeof(int)), (int)messageKind); BinaryPrimitives.WriteInt32LittleEndian(transferBody.AsSpan(SourceGalaxyOffset, sizeof(int)), sourceGalaxyId); BinaryPrimitives.WriteInt32LittleEndian(transferBody.AsSpan(SourcePlatformOffset, sizeof(int)), sourcePlatformId); BinaryPrimitives.WriteInt32LittleEndian(transferBody.AsSpan(LocalEngineOffset, sizeof(int)), localEngineId); BinaryPrimitives.WriteInt32LittleEndian(transferBody.AsSpan(TargetGalaxyOffset, sizeof(int)), targetGalaxyId); BinaryPrimitives.WriteInt32LittleEndian(transferBody.AsSpan(TargetPlatformOffset, sizeof(int)), targetPlatformId); BinaryPrimitives.WriteInt32LittleEndian(transferBody.AsSpan(TargetEngineOffset, sizeof(int)), targetEngineId); BinaryPrimitives.WriteInt32LittleEndian(transferBody.AsSpan(ProtocolMarkerOffset, sizeof(int)), ProtocolMarker); BinaryPrimitives.WriteInt32LittleEndian(transferBody.AsSpan(TimeoutOffset, sizeof(int)), timeoutMilliseconds); innerBody.CopyTo(transferBody.AsSpan(HeaderLength)); return transferBody; } }