#nullable enable
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
namespace DeukPack.Protocol
{
///
/// DeukPack Pack Protocol for C# (Tagged LE binary)
/// Ported from Java implementation.
///
public class DpPackProtocol : DpProtocol
{
private readonly Stream? _input;
private readonly Stream? _output;
private readonly Stack _fieldCounts = new Stack();
private const int MaxBinaryLength = 1024 * 1024 * 10; // 10MB
private const int MaxElementCount = 1000000; // 1M
public enum PackTag : byte
{
Null = 0, False = 1, True = 2, Int32 = 3, Int64 = 4, Double = 5, String = 6, Binary = 7, Array = 8, Map = 9, Object = 10
}
public DpPackProtocol(Stream stream)
{
if (stream.CanRead) _input = stream;
if (stream.CanWrite) _output = stream;
}
public DpPackProtocol(Stream input, Stream output)
{
_input = input;
_output = output;
}
private void WriteTag(PackTag tag)
{
_output?.WriteByte((byte)tag);
}
private byte ReadRawByte()
{
if (_input == null) throw new InvalidOperationException("Input stream is null");
int b = _input.ReadByte();
if (b == -1) throw new EndOfStreamException();
return (byte)b;
}
private void WriteRawI32(int v)
{
if (_output == null) return;
_output.WriteByte((byte)(v & 0xFF));
_output.WriteByte((byte)((v >> 8) & 0xFF));
_output.WriteByte((byte)((v >> 16) & 0xFF));
_output.WriteByte((byte)((v >> 24) & 0xFF));
}
private int ReadRawI32()
{
int b1 = ReadRawByte();
int b2 = ReadRawByte();
int b3 = ReadRawByte();
int b4 = ReadRawByte();
int v = (b1 & 0xFF) | ((b2 & 0xFF) << 8) | ((b3 & 0xFF) << 16) | ((b4 & 0xFF) << 24);
// Console.WriteLine($"ReadRawI32: {v}");
return v;
}
private void WriteRawI64(long v)
{
if (_output == null) return;
for (int i = 0; i < 8; i++)
{
_output.WriteByte((byte)(v & 0xFF));
v >>= 8;
}
}
private long ReadRawI64()
{
long v = 0;
for (int i = 0; i < 8; i++)
{
v |= ((long)(ReadRawByte() & 0xFF)) << (8 * i);
}
return v;
}
public void WriteStructBegin(DpRecord record)
{
WriteTag(PackTag.Object);
WriteRawI32(record.Count);
}
public void WriteStructEnd() { }
public void WriteFieldBegin(DpColumn column)
{
WriteRawString(column.Name);
}
public void WriteFieldEnd() { }
public void WriteFieldStop() { }
public void WriteMapBegin(DpDict dict)
{
WriteTag(PackTag.Map);
WriteRawI32(dict.Count);
}
public void WriteMapEnd() { }
public void WriteListBegin(DpList list)
{
WriteTag(PackTag.Array);
WriteRawI32(list.Count);
}
public void WriteListEnd() { }
public void WriteSetBegin(DpSet set)
{
WriteTag(PackTag.Array);
WriteRawI32(set.Count);
}
public void WriteSetEnd() { }
public void WriteBool(bool value)
{
WriteTag(value ? PackTag.True : PackTag.False);
}
public void WriteByte(byte value)
{
WriteTag(PackTag.Int32);
WriteRawI32(value);
}
public void WriteI16(short value)
{
WriteTag(PackTag.Int32);
WriteRawI32(value);
}
public void WriteI32(int value)
{
WriteTag(PackTag.Int32);
WriteRawI32(value);
}
public void WriteI64(long value)
{
WriteTag(PackTag.Int64);
WriteRawI64(value);
}
public void WriteDouble(double value)
{
WriteTag(PackTag.Double);
WriteRawI64(BitConverter.DoubleToInt64Bits(value));
}
public void WriteString(string? value)
{
if (value == null)
{
WriteTag(PackTag.Null);
return;
}
WriteTag(PackTag.String);
WriteRawString(value);
}
private void WriteRawString(string v)
{
byte[] bytes = Encoding.UTF8.GetBytes(v);
WriteRawI32(bytes.Length);
_output?.Write(bytes, 0, bytes.Length);
}
public void WriteBinary(byte[]? value)
{
if (value == null)
{
WriteTag(PackTag.Null);
return;
}
WriteTag(PackTag.Binary);
WriteRawI32(value.Length);
_output?.Write(value, 0, value.Length);
}
public DpRecord ReadStructBegin()
{
PackTag tag = (PackTag)ReadRawByte();
if (tag != PackTag.Object) throw new Exception("Expected Object tag");
int count = ReadRawI32();
if (count < 0 || count > MaxElementCount) throw new InvalidOperationException($"Invalid struct field count: {count}");
_fieldCounts.Push(count);
return new DpRecord();
}
public void ReadStructEnd() { }
public DpColumn ReadFieldBegin()
{
if (_fieldCounts.Count == 0) return new DpColumn("", DpWireType.Stop, 0);
int count = _fieldCounts.Peek();
if (count <= 0)
{
_fieldCounts.Pop();
return new DpColumn("", DpWireType.Stop, 0);
}
_fieldCounts.Pop();
_fieldCounts.Push(count - 1);
return new DpColumn(ReadRawString(), DpWireType.Void, 0);
}
private string ReadRawString()
{
int length = ReadRawI32();
if (length < 0 || length > MaxBinaryLength) throw new InvalidOperationException($"Invalid string length: {length}");
byte[] bytes = new byte[length];
ReadAll(bytes);
return Encoding.UTF8.GetString(bytes);
}
private void ReadAll(byte[] b)
{
int off = 0;
while (off < b.Length)
{
int count = _input?.Read(b, off, b.Length - off) ?? 0;
if (count <= 0) throw new EndOfStreamException();
off += count;
}
}
public void ReadFieldEnd() { }
public DpDict ReadMapBegin()
{
if ((PackTag)ReadRawByte() != PackTag.Map) throw new Exception("Expected Map tag");
int count = ReadRawI32();
if (count < 0 || count > MaxElementCount) throw new InvalidOperationException($"Invalid map count: {count}");
return new DpDict { KeyType = DpWireType.Stop, ValueType = DpWireType.Stop, Count = count };
}
public void ReadMapEnd() { }
public DpList ReadListBegin()
{
if ((PackTag)ReadRawByte() != PackTag.Array) throw new Exception("Expected Array tag");
int count = ReadRawI32();
if (count < 0 || count > MaxElementCount) throw new InvalidOperationException($"Invalid list count: {count}");
return new DpList { ElementType = DpWireType.Stop, Count = count };
}
public void ReadListEnd() { }
public DpSet ReadSetBegin()
{
if ((PackTag)ReadRawByte() != PackTag.Array) throw new Exception("Expected Array tag");
int count = ReadRawI32();
if (count < 0 || count > MaxElementCount) throw new InvalidOperationException($"Invalid set count: {count}");
return new DpSet { ElementType = DpWireType.Stop, Count = count };
}
public void ReadSetEnd() { }
public bool ReadBool()
{
PackTag tag = (PackTag)ReadRawByte();
return tag == PackTag.True;
}
public byte ReadByte() => (byte)ReadI32();
public short ReadI16() => (short)ReadI32();
public int ReadI32()
{
PackTag tag = (PackTag)ReadRawByte();
if (tag == PackTag.Int32) return ReadRawI32();
if (tag == PackTag.Int64) return (int)ReadRawI64();
throw new Exception("Expected Int tag");
}
public long ReadI64()
{
PackTag tag = (PackTag)ReadRawByte();
if (tag == PackTag.Int32) return (long)ReadRawI32();
if (tag == PackTag.Int64) return ReadRawI64();
throw new Exception("Expected Int tag");
}
public double ReadDouble()
{
PackTag tag = (PackTag)ReadRawByte();
if (tag == PackTag.Double) return BitConverter.Int64BitsToDouble(ReadRawI64());
if (tag == PackTag.Int32) return (double)ReadRawI32();
return 0;
}
public string? ReadString()
{
PackTag tag = (PackTag)ReadRawByte();
if (tag == PackTag.Null) return null;
if (tag != PackTag.String) throw new Exception("Expected String tag");
return ReadRawString();
}
public byte[]? ReadBinary()
{
PackTag tag = (PackTag)ReadRawByte();
if (tag == PackTag.Null) return null;
if (tag != PackTag.Binary) throw new Exception("Expected Binary tag");
int length = ReadRawI32();
if (length < 0 || length > MaxBinaryLength) throw new InvalidOperationException($"Invalid binary length: {length}");
byte[] bytes = new byte[length];
ReadAll(bytes);
return bytes;
}
}
}