using System.Buffers.Binary; using System.Text; namespace HSchool.Protocol; /// Little-endian cursor over a received frame. Mirror of . public ref struct PacketReader(ReadOnlySpan buffer) { private readonly ReadOnlySpan _buffer = buffer; private int _position = 0; public readonly int Position => _position; public readonly int Remaining => _buffer.Length - _position; public byte ReadByte() { EnsureAvailable(sizeof(byte)); var value = _buffer[_position]; _position += sizeof(byte); return value; } public MessageType ReadMessageType() => (MessageType)ReadByte(); public ushort ReadUInt16() { EnsureAvailable(sizeof(ushort)); var value = BinaryPrimitives.ReadUInt16LittleEndian(_buffer[_position..]); _position += sizeof(ushort); return value; } public uint ReadUInt32() { EnsureAvailable(sizeof(uint)); var value = BinaryPrimitives.ReadUInt32LittleEndian(_buffer[_position..]); _position += sizeof(uint); return value; } /// u16 byte length, then UTF-8. Empty string is a zero length. public string ReadString() { var byteCount = ReadUInt16(); EnsureAvailable(byteCount); var value = Encoding.UTF8.GetString(_buffer.Slice(_position, byteCount)); _position += byteCount; return value; } public short ReadInt16() { EnsureAvailable(sizeof(short)); var value = BinaryPrimitives.ReadInt16LittleEndian(_buffer[_position..]); _position += sizeof(short); return value; } public int ReadInt32() { EnsureAvailable(sizeof(int)); var value = BinaryPrimitives.ReadInt32LittleEndian(_buffer[_position..]); _position += sizeof(int); return value; } public long ReadInt64() { EnsureAvailable(sizeof(long)); var value = BinaryPrimitives.ReadInt64LittleEndian(_buffer[_position..]); _position += sizeof(long); return value; } private readonly void EnsureAvailable(int bytes) { if (_position + bytes > _buffer.Length) { throw new ProtocolException( $"Truncated frame: need {bytes} bytes at offset {_position}, only {Remaining} available."); } } }