using System.Buffers.Binary; using System.Text; namespace HSchool.Protocol; /// /// Little-endian cursor over a caller-owned buffer. Little-endian matches the /// browser's DataView calls in src/HSchool.Client/src/net/protocol.ts. /// public ref struct PacketWriter(Span buffer) { private readonly Span _buffer = buffer; private int _position = 0; public readonly int Position => _position; public void WriteByte(byte value) { EnsureRoom(sizeof(byte)); _buffer[_position] = value; _position += sizeof(byte); } public void WriteMessageType(MessageType value) => WriteByte((byte)value); public void WriteUInt16(ushort value) { EnsureRoom(sizeof(ushort)); BinaryPrimitives.WriteUInt16LittleEndian(_buffer[_position..], value); _position += sizeof(ushort); } public void WriteUInt32(uint value) { EnsureRoom(sizeof(uint)); BinaryPrimitives.WriteUInt32LittleEndian(_buffer[_position..], value); _position += sizeof(uint); } /// u16 byte length, then UTF-8. Empty string is a zero length. public void WriteString(string value) { var byteCount = Encoding.UTF8.GetByteCount(value); if (byteCount > ushort.MaxValue) { throw new ProtocolException($"String is {byteCount} bytes; u16 length cannot hold it."); } WriteUInt16((ushort)byteCount); EnsureRoom(byteCount); Encoding.UTF8.GetBytes(value, _buffer[_position..]); _position += byteCount; } public void WriteInt16(short value) { EnsureRoom(sizeof(short)); BinaryPrimitives.WriteInt16LittleEndian(_buffer[_position..], value); _position += sizeof(short); } public void WriteInt32(int value) { EnsureRoom(sizeof(int)); BinaryPrimitives.WriteInt32LittleEndian(_buffer[_position..], value); _position += sizeof(int); } public void WriteInt64(long value) { EnsureRoom(sizeof(long)); BinaryPrimitives.WriteInt64LittleEndian(_buffer[_position..], value); _position += sizeof(long); } private readonly void EnsureRoom(int bytes) { if (_position + bytes > _buffer.Length) { throw new ProtocolException( $"Buffer overflow: need {bytes} more bytes at offset {_position}, capacity is {_buffer.Length}."); } } }