using System.Buffers.Binary;
namespace AcDream.Core.Net.Packets;
///
/// The 16-byte header that prefixes every message fragment inside a UDP
/// packet's body. A single UDP packet can contain one or more fragments,
/// and a single logical GameMessage can be split across multiple
/// fragments that share the same and
/// but differ in .
///
///
/// Layout (byte offsets):
///
/// 0 Sequence uint32 Fragment-level sequence
/// 4 Id uint32 Message id — fragments with the same Id belong
/// to the same logical GameMessage. Outbound
/// messages use the high bit set (0x80000000+).
/// 8 Count uint16 Total fragments in the logical message (1 if
/// the message fits in a single fragment)
/// 10 Size uint16 Total fragment size including this 16-byte
/// header — max 464 bytes (448 bytes of payload)
/// 12 Index uint16 0-based position of this fragment in the
/// logical message
/// 14 Queue uint16 GameMessageGroup — used by the server to
/// sequence related messages
///
///
///
/// Reimplemented from ACE's AGPL reference; see NOTICE.md.
///
public struct MessageFragmentHeader
{
public const int Size = 16;
/// Max total fragment size on the wire (including this header).
public const int MaxFragmentSize = 464;
/// Max payload bytes per fragment (= MaxFragmentSize - Size).
public const int MaxFragmentDataSize = MaxFragmentSize - Size; // 448
public uint Sequence;
public uint Id;
public ushort Count;
public ushort TotalSize; // total bytes of this fragment including header
public ushort Index;
public ushort Queue;
public readonly void Pack(Span destination)
{
if (destination.Length < Size)
throw new ArgumentException($"destination must be at least {Size} bytes", nameof(destination));
BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(0), Sequence);
BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(4), Id);
BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(8), Count);
BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(10), TotalSize);
BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(12), Index);
BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(14), Queue);
}
public static MessageFragmentHeader Unpack(ReadOnlySpan source)
{
if (source.Length < Size)
throw new ArgumentException($"source must be at least {Size} bytes", nameof(source));
return new MessageFragmentHeader
{
Sequence = BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(0)),
Id = BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(4)),
Count = BinaryPrimitives.ReadUInt16LittleEndian(source.Slice(8)),
TotalSize = BinaryPrimitives.ReadUInt16LittleEndian(source.Slice(10)),
Index = BinaryPrimitives.ReadUInt16LittleEndian(source.Slice(12)),
Queue = BinaryPrimitives.ReadUInt16LittleEndian(source.Slice(14)),
};
}
}