#nullable enable
using System;
using System.IO;
using System.Text;
using System.Buffers;
namespace DeukPack.Protocol {
///
/// Binary protocol implementation with FastPacket-level optimization.
/// Unity compatible high-performance serialization.
///
public class DpBinaryProtocol : DpProtocol, IDisposable
{
private readonly Stream _stream;
private readonly bool _strictRead;
private readonly bool _strictWrite;
private readonly bool _bigEndian;
// FastPacket-level optimizations
private static readonly ArrayPool _bufferPool = ArrayPool.Shared;
private byte[]? _writeBuffer;
private int _writePosition;
private int _writeCapacity;
// Read buffer pooling
private byte[]? _readBuffer;
private int _readPosition;
private int _readLength;
private const int READ_BUFFER_SIZE = 4096;
private const int MaxBinaryLength = 1024 * 1024 * 10; // 10MB
private const int MaxElementCount = 1000000; // 1M elements
// Small buffer for primitive types (stackalloc-like optimization)
private const int SMALL_BUFFER_SIZE = 16;
private readonly byte[] _smallBuffer = new byte[SMALL_BUFFER_SIZE];
private readonly bool _directWrite;
#pragma warning disable CS0414 // assigned but never used (reserved for potential stream disposal)
private readonly bool _ownsStream;
#pragma warning restore CS0414
///
/// 기본 생성자: 내부 MemoryStream 자동 생성 (Write 후 ToBytes()로 결과 획득)
///
public DpBinaryProtocol() : this(new MemoryStream())
{
_ownsStream = true;
}
///
/// byte[] 로부터 Read용 프로토콜 생성
///
public DpBinaryProtocol(byte[] data) : this(new MemoryStream(data))
{
_ownsStream = true;
}
/// true = Binary(Apache 호환), false = LEBinary
public DpBinaryProtocol(Stream stream, bool bigEndian = true, bool strictRead = true, bool strictWrite = true, int initialBufferSize = 4096)
{
_stream = stream;
_bigEndian = bigEndian;
_strictRead = strictRead;
_strictWrite = strictWrite;
_ownsStream = false;
_directWrite = (stream is System.IO.MemoryStream);
if (!_directWrite && initialBufferSize > 0)
{
_writeBuffer = _bufferPool.Rent(initialBufferSize);
_writeCapacity = _writeBuffer.Length;
}
_writePosition = 0;
_readBuffer = _bufferPool.Rent(READ_BUFFER_SIZE);
_readPosition = 0;
_readLength = 0;
}
///
/// Write한 결과를 byte[]로 반환 (내부 MemoryStream 사용 시)
///
public byte[] ToBytes()
{
if (_stream is MemoryStream ms)
return ms.ToArray();
throw new InvalidOperationException("ToBytes() is only available when using internal MemoryStream.");
}
private void EnsureWriteBuffer(int additionalBytes)
{
if (_directWrite) return;
if (_writeBuffer == null || _writePosition + additionalBytes > _writeCapacity)
{
FlushWriteBuffer();
int newSize = Math.Max(_writePosition + additionalBytes, _writeCapacity * 2);
if (_writeBuffer != null)
{
_bufferPool.Return(_writeBuffer);
}
_writeBuffer = _bufferPool.Rent(newSize);
_writeCapacity = _writeBuffer.Length;
}
}
private void FlushWriteBuffer()
{
if (_writeBuffer != null && _writePosition > 0)
{
_stream.Write(_writeBuffer, 0, _writePosition);
_writePosition = 0;
}
}
public void Dispose()
{
FlushWriteBuffer();
if (_writeBuffer != null)
{
_bufferPool.Return(_writeBuffer);
_writeBuffer = null;
}
if (_readBuffer != null)
{
_bufferPool.Return(_readBuffer);
_readBuffer = null;
}
}
public void WriteStructBegin(DpRecord structBegin)
{
// Binary protocol doesn't write struct names
}
public void WriteStructEnd()
{
// Binary protocol doesn't write struct end markers
}
///
/// Flush all buffered writes to stream.
/// No-op when stream is MemoryStream (direct write mode).
///
public void Flush()
{
if (!_directWrite) FlushWriteBuffer();
}
public void WriteFieldBegin(DpColumn field)
{
WriteByte((byte)field.Type);
WriteI16(field.ID);
}
public void WriteFieldEnd()
{
// Binary protocol doesn't write field end markers
}
public void WriteFieldStop()
{
WriteByte((byte)DpWireType.Stop);
}
public void WriteBool(bool b)
{
WriteByte(b ? (byte)1 : (byte)0);
}
public void WriteByte(byte b)
{
if (_directWrite) { _stream.WriteByte(b); return; }
EnsureWriteBuffer(1);
_writeBuffer![_writePosition++] = b;
}
public void WriteI16(short i16)
{
if (_directWrite)
{
if (_bigEndian)
{
_smallBuffer[0] = (byte)((i16 >> 8) & 0xFF);
_smallBuffer[1] = (byte)(i16 & 0xFF);
}
else
{
_smallBuffer[0] = (byte)(i16 & 0xFF);
_smallBuffer[1] = (byte)((i16 >> 8) & 0xFF);
}
_stream.Write(_smallBuffer, 0, 2);
return;
}
EnsureWriteBuffer(2);
WriteI16Optimized(i16);
}
public void WriteI32(int i32)
{
if (_directWrite)
{
if (_bigEndian)
{
_smallBuffer[0] = (byte)((i32 >> 24) & 0xFF);
_smallBuffer[1] = (byte)((i32 >> 16) & 0xFF);
_smallBuffer[2] = (byte)((i32 >> 8) & 0xFF);
_smallBuffer[3] = (byte)(i32 & 0xFF);
}
else
{
_smallBuffer[0] = (byte)(i32 & 0xFF);
_smallBuffer[1] = (byte)((i32 >> 8) & 0xFF);
_smallBuffer[2] = (byte)((i32 >> 16) & 0xFF);
_smallBuffer[3] = (byte)((i32 >> 24) & 0xFF);
}
_stream.Write(_smallBuffer, 0, 4);
return;
}
EnsureWriteBuffer(4);
WriteI32Optimized(i32);
}
public void WriteI64(long i64)
{
if (_directWrite)
{
if (_bigEndian)
{
_smallBuffer[0] = (byte)((i64 >> 56) & 0xFF);
_smallBuffer[1] = (byte)((i64 >> 48) & 0xFF);
_smallBuffer[2] = (byte)((i64 >> 40) & 0xFF);
_smallBuffer[3] = (byte)((i64 >> 32) & 0xFF);
_smallBuffer[4] = (byte)((i64 >> 24) & 0xFF);
_smallBuffer[5] = (byte)((i64 >> 16) & 0xFF);
_smallBuffer[6] = (byte)((i64 >> 8) & 0xFF);
_smallBuffer[7] = (byte)(i64 & 0xFF);
}
else
{
_smallBuffer[0] = (byte)(i64 & 0xFF);
_smallBuffer[1] = (byte)((i64 >> 8) & 0xFF);
_smallBuffer[2] = (byte)((i64 >> 16) & 0xFF);
_smallBuffer[3] = (byte)((i64 >> 24) & 0xFF);
_smallBuffer[4] = (byte)((i64 >> 32) & 0xFF);
_smallBuffer[5] = (byte)((i64 >> 40) & 0xFF);
_smallBuffer[6] = (byte)((i64 >> 48) & 0xFF);
_smallBuffer[7] = (byte)((i64 >> 56) & 0xFF);
}
_stream.Write(_smallBuffer, 0, 8);
return;
}
EnsureWriteBuffer(8);
WriteI64Optimized(i64);
}
public void WriteDouble(double d)
{
if (_directWrite)
{
WriteI64(BitConverter.DoubleToInt64Bits(d));
return;
}
EnsureWriteBuffer(8);
WriteDoubleOptimized(d);
}
public void WriteString(string? s)
{
if (s == null)
{
WriteI32(0);
return;
}
int byteCount = Encoding.UTF8.GetByteCount(s);
WriteI32(byteCount);
if (byteCount > 0)
{
if (_directWrite)
{
byte[] bytes = Encoding.UTF8.GetBytes(s);
_stream.Write(bytes, 0, bytes.Length);
}
else
{
EnsureWriteBuffer(byteCount);
Encoding.UTF8.GetBytes(s, 0, s.Length, _writeBuffer!, _writePosition);
_writePosition += byteCount;
}
}
}
public void WriteBinary(byte[]? b)
{
if (b == null)
{
WriteI32(0);
return;
}
WriteI32(b.Length);
if (b.Length > 0)
{
if (_directWrite)
{
_stream.Write(b, 0, b.Length);
}
else
{
EnsureWriteBuffer(b.Length);
Array.Copy(b, 0, _writeBuffer!, _writePosition, b.Length);
_writePosition += b.Length;
}
}
}
private void WriteI16Optimized(short value)
{
var wb = _writeBuffer!;
if (_bigEndian)
{
wb[_writePosition] = (byte)((value >> 8) & 0xFF);
wb[_writePosition + 1] = (byte)(value & 0xFF);
}
else
{
wb[_writePosition] = (byte)(value & 0xFF);
wb[_writePosition + 1] = (byte)((value >> 8) & 0xFF);
}
_writePosition += 2;
}
private void WriteI32Optimized(int value)
{
var wb = _writeBuffer!;
if (_bigEndian)
{
wb[_writePosition] = (byte)((value >> 24) & 0xFF);
wb[_writePosition + 1] = (byte)((value >> 16) & 0xFF);
wb[_writePosition + 2] = (byte)((value >> 8) & 0xFF);
wb[_writePosition + 3] = (byte)(value & 0xFF);
}
else
{
wb[_writePosition] = (byte)(value & 0xFF);
wb[_writePosition + 1] = (byte)((value >> 8) & 0xFF);
wb[_writePosition + 2] = (byte)((value >> 16) & 0xFF);
wb[_writePosition + 3] = (byte)((value >> 24) & 0xFF);
}
_writePosition += 4;
}
private void WriteI64Optimized(long value)
{
var wb = _writeBuffer!;
if (_bigEndian)
{
wb[_writePosition] = (byte)((value >> 56) & 0xFF);
wb[_writePosition + 1] = (byte)((value >> 48) & 0xFF);
wb[_writePosition + 2] = (byte)((value >> 40) & 0xFF);
wb[_writePosition + 3] = (byte)((value >> 32) & 0xFF);
wb[_writePosition + 4] = (byte)((value >> 24) & 0xFF);
wb[_writePosition + 5] = (byte)((value >> 16) & 0xFF);
wb[_writePosition + 6] = (byte)((value >> 8) & 0xFF);
wb[_writePosition + 7] = (byte)(value & 0xFF);
}
else
{
wb[_writePosition] = (byte)(value & 0xFF);
wb[_writePosition + 1] = (byte)((value >> 8) & 0xFF);
wb[_writePosition + 2] = (byte)((value >> 16) & 0xFF);
wb[_writePosition + 3] = (byte)((value >> 24) & 0xFF);
wb[_writePosition + 4] = (byte)((value >> 32) & 0xFF);
wb[_writePosition + 5] = (byte)((value >> 40) & 0xFF);
wb[_writePosition + 6] = (byte)((value >> 48) & 0xFF);
wb[_writePosition + 7] = (byte)((value >> 56) & 0xFF);
}
_writePosition += 8;
}
private void WriteDoubleOptimized(double value)
{
// Convert double to long and write as I64
long bits = BitConverter.DoubleToInt64Bits(value);
WriteI64Optimized(bits);
}
public void WriteListBegin(DpList list)
{
WriteByte((byte)list.ElementType);
WriteI32(list.Count);
}
public void WriteListEnd()
{
// Binary protocol doesn't write list end markers
}
public void WriteSetBegin(DpSet set)
{
WriteByte((byte)set.ElementType);
WriteI32(set.Count);
}
public void WriteSetEnd()
{
// Binary protocol doesn't write set end markers
}
public void WriteMapBegin(DpDict map)
{
WriteByte((byte)map.KeyType);
WriteByte((byte)map.ValueType);
WriteI32(map.Count);
}
public void WriteMapEnd()
{
// Binary protocol doesn't write map end markers
}
public DpRecord ReadStructBegin()
{
return new DpRecord();
}
public void ReadStructEnd()
{
// Binary protocol doesn't read struct end markers
}
public DpColumn ReadFieldBegin()
{
byte type = ReadByte();
if (type == (byte)DpWireType.Stop)
{
return new DpColumn("", DpWireType.Stop, 0);
}
short id = ReadI16();
return new DpColumn("", (DpWireType)type, id);
}
public void ReadFieldEnd()
{
// Binary protocol doesn't read field end markers
}
public bool ReadBool()
{
return ReadByte() != 0;
}
private void EnsureReadBuffer(int bytesNeeded)
{
if (_readPosition + bytesNeeded <= _readLength)
return;
int remaining = _readLength - _readPosition;
var readBuf = _readBuffer ?? throw new ObjectDisposedException(nameof(DpBinaryProtocol));
if (bytesNeeded > readBuf.Length)
{
var newBuf = _bufferPool.Rent(bytesNeeded + READ_BUFFER_SIZE);
if (remaining > 0)
Array.Copy(readBuf, _readPosition, newBuf, 0, remaining);
_bufferPool.Return(readBuf);
_readBuffer = newBuf;
readBuf = newBuf;
}
else if (remaining > 0)
{
Array.Copy(readBuf, _readPosition, readBuf, 0, remaining);
}
_readPosition = 0;
_readLength = remaining;
while (_readLength < bytesNeeded)
{
int bytesRead = _stream.Read(readBuf, _readLength, readBuf.Length - _readLength);
if (bytesRead == 0)
throw new EndOfStreamException();
_readLength += bytesRead;
}
}
public byte ReadByte()
{
EnsureReadBuffer(1);
return _readBuffer![_readPosition++];
}
public short ReadI16()
{
EnsureReadBuffer(2);
return ReadI16Optimized();
}
public int ReadI32()
{
EnsureReadBuffer(4);
return ReadI32Optimized();
}
public long ReadI64()
{
EnsureReadBuffer(8);
return ReadI64Optimized();
}
public double ReadDouble()
{
// Convert from long bits
long bits = ReadI64();
return BitConverter.Int64BitsToDouble(bits);
}
public string? ReadString()
{
int length = ReadI32();
if (length == 0)
return string.Empty;
if (length < 0 || length > MaxBinaryLength)
throw new InvalidOperationException($"Invalid string length: {length}");
EnsureReadBuffer(length);
string result = Encoding.UTF8.GetString(_readBuffer!, _readPosition, length);
_readPosition += length;
return result;
}
public byte[]? ReadBinary()
{
int length = ReadI32();
if (length == 0)
return new byte[0];
if (length < 0 || length > MaxBinaryLength)
throw new InvalidOperationException($"Invalid binary length: {length}");
EnsureReadBuffer(length);
byte[] result = new byte[length];
Array.Copy(_readBuffer!, _readPosition, result, 0, length);
_readPosition += length;
return result;
}
// 엔디언: _bigEndian에 따라 BE / LE(네트워크 바이트 순서) 고정
private short ReadI16Optimized()
{
var rb = _readBuffer!;
short value = _bigEndian
? (short)((rb[_readPosition] << 8) | rb[_readPosition + 1])
: (short)(rb[_readPosition] | (rb[_readPosition + 1] << 8));
_readPosition += 2;
return value;
}
private int ReadI32Optimized()
{
var rb = _readBuffer!;
int b0 = rb[_readPosition], b1 = rb[_readPosition + 1], b2 = rb[_readPosition + 2], b3 = rb[_readPosition + 3];
_readPosition += 4;
return _bigEndian
? (b0 << 24) | (b1 << 16) | (b2 << 8) | b3
: (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
}
private long ReadI64Optimized()
{
var rb = _readBuffer!;
long b0 = rb[_readPosition], b1 = rb[_readPosition + 1], b2 = rb[_readPosition + 2], b3 = rb[_readPosition + 3],
b4 = rb[_readPosition + 4], b5 = rb[_readPosition + 5], b6 = rb[_readPosition + 6], b7 = rb[_readPosition + 7];
_readPosition += 8;
return _bigEndian
? ((long)b0 << 56) | ((long)b1 << 48) | ((long)b2 << 40) | ((long)b3 << 32) | ((long)b4 << 24) | ((long)b5 << 16) | ((long)b6 << 8) | b7
: ((long)b7 << 56) | ((long)b6 << 48) | ((long)b5 << 40) | ((long)b4 << 32) | ((long)b3 << 24) | ((long)b2 << 16) | ((long)b1 << 8) | b0;
}
public DpList ReadListBegin()
{
byte elementType = ReadByte();
int count = ReadI32();
if (count < 0 || count > MaxElementCount) throw new InvalidOperationException($"Invalid list count: {count}");
return new DpList { ElementType = (DpWireType)elementType, Count = count };
}
public void ReadListEnd()
{
// Binary protocol doesn't read list end markers
}
public DpSet ReadSetBegin()
{
byte elementType = ReadByte();
int count = ReadI32();
if (count < 0 || count > MaxElementCount) throw new InvalidOperationException($"Invalid set count: {count}");
return new DpSet { ElementType = (DpWireType)elementType, Count = count };
}
public void ReadSetEnd()
{
// Binary protocol doesn't read set end markers
}
public DpDict ReadMapBegin()
{
byte keyType = ReadByte();
byte valueType = ReadByte();
int count = ReadI32();
if (count < 0 || count > MaxElementCount) throw new InvalidOperationException($"Invalid map count: {count}");
return new DpDict { KeyType = (DpWireType)keyType, ValueType = (DpWireType)valueType, Count = count };
}
public void ReadMapEnd()
{
// Binary protocol doesn't read map end markers
}
}
}