467 lines
14 KiB
C#
467 lines
14 KiB
C#
using CSNetwork.Protocols;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Net;
|
|
// using System.Reflection.PortableExecutable;
|
|
using System.Text;
|
|
using static CSNetwork.Protocols.selectrole;
|
|
|
|
namespace CSNetwork
|
|
{
|
|
public class OctetsStream : IDisposable
|
|
{
|
|
private Octets _octets;
|
|
private int _position;
|
|
private static readonly Encoding GbkEncoding = Encoding.GetEncoding(936);
|
|
private const int MaxSpare = 16384;
|
|
private int _transactionPosition;
|
|
|
|
public enum Transaction
|
|
{
|
|
Begin,
|
|
Commit,
|
|
Rollback
|
|
}
|
|
|
|
public int Length => _octets.Length;
|
|
|
|
// Position within the stream for reading/writing
|
|
public int Position
|
|
{
|
|
get => _position;
|
|
set
|
|
{
|
|
// Basic bounds check, could be more robust
|
|
if (value < 0 || value > _octets.Length)
|
|
throw new ArgumentOutOfRangeException(nameof(value), "Position is outside the bounds of the stream.");
|
|
_position = value;
|
|
}
|
|
}
|
|
|
|
// Access to the underlying Octets object
|
|
public Octets UnderlyingOctets => _octets;
|
|
|
|
// Access to the raw buffer for in-place operations (like ISecurity.Update)
|
|
public byte[] RawBuffer => _octets.RawBuffer;
|
|
|
|
public bool IsEof => _position >= _octets.Length;
|
|
|
|
public OctetsStream(Octets octets = null)
|
|
{
|
|
_octets = octets ?? new Octets();
|
|
_position = 0;
|
|
}
|
|
|
|
// Helper methods for big-endian conversion
|
|
private byte[] GetBigEndianBytes(ushort value)
|
|
{
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
return new[] { (byte)(value >> 8), (byte)value };
|
|
}
|
|
return BitConverter.GetBytes(value);
|
|
}
|
|
|
|
private byte[] GetBigEndianBytes(uint value)
|
|
{
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
return new[]
|
|
{
|
|
(byte)(value >> 24),
|
|
(byte)(value >> 16),
|
|
(byte)(value >> 8),
|
|
(byte)value
|
|
};
|
|
}
|
|
return BitConverter.GetBytes(value);
|
|
}
|
|
|
|
// Primitive type support
|
|
public void Write(byte value) => _octets.Insert(_position++, new[] { value });
|
|
|
|
public void Write(short value)
|
|
{
|
|
var bytes = GetBigEndianBytes((ushort)value);
|
|
_octets.Insert(_position, bytes);
|
|
_position += 2;
|
|
}
|
|
|
|
public void Write(ushort value)
|
|
{
|
|
var bytes = GetBigEndianBytes(value);
|
|
_octets.Insert(_position, bytes);
|
|
_position += 2;
|
|
}
|
|
|
|
public void Write(int value)
|
|
{
|
|
var bytes = GetBigEndianBytes((uint)value);
|
|
_octets.Insert(_position, bytes);
|
|
_position += 4;
|
|
}
|
|
|
|
public void Write(uint value)
|
|
{
|
|
var bytes = GetBigEndianBytes(value);
|
|
_octets.Insert(_position, bytes);
|
|
_position += 4;
|
|
}
|
|
|
|
public void Write(float value)
|
|
{
|
|
var bytes = BitConverter.GetBytes(value);
|
|
if (BitConverter.IsLittleEndian)
|
|
Array.Reverse(bytes);
|
|
_octets.Insert(_position, bytes);
|
|
_position += 4;
|
|
}
|
|
|
|
public void Write(double value)
|
|
{
|
|
var bytes = BitConverter.GetBytes(value);
|
|
if (BitConverter.IsLittleEndian)
|
|
Array.Reverse(bytes);
|
|
_octets.Insert(_position, bytes);
|
|
_position += 8;
|
|
}
|
|
|
|
// Read methods for primitive types
|
|
public byte ReadByte()
|
|
{
|
|
if (_position + 1 > _octets.Length)
|
|
throw new IndexOutOfRangeException("Attempt to read beyond the end of the stream.");
|
|
return ReadBytes(1)[0];
|
|
}
|
|
|
|
public short ReadInt16()
|
|
{
|
|
if (_position + 2 > _octets.Length)
|
|
throw new IndexOutOfRangeException("Attempt to read beyond the end of the stream.");
|
|
var bytes = ReadBytes(2);
|
|
if (BitConverter.IsLittleEndian)
|
|
Array.Reverse(bytes);
|
|
return BitConverter.ToInt16(bytes, 0);
|
|
}
|
|
|
|
public ushort ReadUInt16()
|
|
{
|
|
if (_position + 2 > _octets.Length)
|
|
throw new IndexOutOfRangeException("Attempt to read beyond the end of the stream.");
|
|
var bytes = ReadBytes(2);
|
|
if (BitConverter.IsLittleEndian)
|
|
Array.Reverse(bytes);
|
|
return BitConverter.ToUInt16(bytes, 0);
|
|
}
|
|
|
|
public int ReadInt32()
|
|
{
|
|
if (_position + 4 > _octets.Length)
|
|
throw new IndexOutOfRangeException("Attempt to read beyond the end of the stream.");
|
|
var bytes = ReadBytes(4);
|
|
if (BitConverter.IsLittleEndian)
|
|
Array.Reverse(bytes);
|
|
return BitConverter.ToInt32(bytes, 0);
|
|
}
|
|
|
|
public uint ReadUInt32()
|
|
{
|
|
if (_position + 4 > _octets.Length)
|
|
throw new IndexOutOfRangeException("Attempt to read beyond the end of the stream.");
|
|
var bytes = ReadBytes(4);
|
|
if (BitConverter.IsLittleEndian)
|
|
Array.Reverse(bytes);
|
|
return BitConverter.ToUInt32(bytes, 0);
|
|
}
|
|
|
|
public float ReadFloat()
|
|
{
|
|
if (_position + 4 > _octets.Length)
|
|
throw new IndexOutOfRangeException("Attempt to read beyond the end of the stream.");
|
|
var bytes = ReadBytes(4);
|
|
if (BitConverter.IsLittleEndian)
|
|
Array.Reverse(bytes);
|
|
return BitConverter.ToSingle(bytes, 0);
|
|
}
|
|
|
|
public double ReadDouble()
|
|
{
|
|
if (_position + 8 > _octets.Length)
|
|
throw new IndexOutOfRangeException("Attempt to read beyond the end of the stream.");
|
|
var bytes = ReadBytes(8);
|
|
if (BitConverter.IsLittleEndian)
|
|
Array.Reverse(bytes);
|
|
return BitConverter.ToDouble(bytes, 0);
|
|
}
|
|
|
|
// Compact integer support
|
|
public void WriteCompactInt(int value)
|
|
{
|
|
if (value >= 0)
|
|
{
|
|
WriteCompactUInt((uint)value);
|
|
}
|
|
else
|
|
{
|
|
uint unsignedValue = (uint)(-value);
|
|
unsignedValue = (unsignedValue << 1) | 1;
|
|
WriteCompactUInt(unsignedValue);
|
|
}
|
|
}
|
|
|
|
public int ReadCompactInt()
|
|
{
|
|
uint value = ReadCompactUInt();
|
|
if ((value & 1) == 0)
|
|
return (int)(value >> 1);
|
|
else
|
|
return -(int)(value >> 1);
|
|
}
|
|
|
|
// Transaction support
|
|
public void HandleTransaction(Transaction trans)
|
|
{
|
|
switch (trans)
|
|
{
|
|
case Transaction.Begin:
|
|
_transactionPosition = _position;
|
|
break;
|
|
case Transaction.Rollback:
|
|
_position = _transactionPosition;
|
|
break;
|
|
case Transaction.Commit:
|
|
if (_position >= MaxSpare)
|
|
{
|
|
// Optimize memory usage
|
|
var newOctets = new byte[_octets.Length - _position];
|
|
Array.Copy(_octets.ToArray(), _position, newOctets, 0, newOctets.Length);
|
|
_octets.Dispose();
|
|
_octets = new Octets(newOctets.Length);
|
|
_position = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Container support
|
|
public void WriteList<T>(IList<T> list)
|
|
where T : IMarshallable
|
|
{
|
|
WriteCompactUInt((uint)list.Count);
|
|
foreach (var item in list)
|
|
{
|
|
item.Marshal(this);
|
|
}
|
|
}
|
|
|
|
public void ReadList<T>(IList<T> list)
|
|
where T : IMarshallable, new()
|
|
{
|
|
uint count = ReadCompactUInt();
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var item = new T();
|
|
item.Unmarshal(this);
|
|
list.Add(item);
|
|
}
|
|
}
|
|
|
|
public void WriteListInt(IList<int> listInt)
|
|
{
|
|
if (listInt?.Count <= 0) return;
|
|
|
|
WriteCompactUInt((uint)listInt.Count);
|
|
foreach(var item in listInt)
|
|
{
|
|
Write(item);
|
|
}
|
|
}
|
|
|
|
public void ReadListInt(IList<int> listInt)
|
|
{
|
|
uint count = ReadCompactUInt();
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
listInt.Add(ReadInt32());
|
|
}
|
|
}
|
|
|
|
public void WriteCompactUInt(uint value)
|
|
{
|
|
if (value < 0x80)
|
|
{
|
|
_octets.Insert(_position, new[] { (byte)value });
|
|
_position++;
|
|
}
|
|
else if (value < 0x4000)
|
|
{
|
|
var bytes = GetBigEndianBytes((ushort)(value | 0x8000));
|
|
_octets.Insert(_position, bytes);
|
|
_position += 2;
|
|
}
|
|
else if (value < 0x20000000)
|
|
{
|
|
var bytes = GetBigEndianBytes(value | 0xc0000000);
|
|
_octets.Insert(_position, bytes);
|
|
_position += 4;
|
|
}
|
|
else
|
|
{
|
|
_octets.Insert(_position, new[] { (byte)0xe0 });
|
|
_octets.Insert(_position + 1, GetBigEndianBytes(value));
|
|
_position += 5;
|
|
}
|
|
}
|
|
|
|
public void GetFirstBytes()
|
|
{
|
|
IPrefixedLogger _logger = LoggerFactory.GetLogger(nameof(Protocol));
|
|
var tempBytes = ReadByteWithoutAdvancePos(41);
|
|
StringBuilder sb = new StringBuilder();
|
|
for (int i = 0; i < 41; i++)
|
|
{
|
|
sb.Append(tempBytes[i]);
|
|
sb.Append(' ');
|
|
}
|
|
_logger.Debug($"Try Decode: Protocol 10 first bytes : {sb}");
|
|
}
|
|
|
|
public uint ReadCompactUInt()
|
|
{
|
|
byte flag = ReadBytes(1)[0];
|
|
IPrefixedLogger _logger = LoggerFactory.GetLogger(nameof(Protocol));
|
|
if ((flag & 0xE0) == 0xE0)
|
|
{
|
|
var bytes = ReadBytes(4);
|
|
return BitConverter.IsLittleEndian
|
|
? ((uint)bytes[0] << 24)
|
|
| ((uint)bytes[1] << 16)
|
|
| ((uint)bytes[2] << 8)
|
|
| bytes[3]
|
|
: BitConverter.ToUInt32(bytes, 0);
|
|
}
|
|
else if ((flag & 0xE0) == 0xC0)
|
|
{
|
|
var bytes = ReadBytes(3);
|
|
uint value = 0;
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
value = ((uint)bytes[0] << 16) | ((uint)bytes[1] << 8) | bytes[2];
|
|
}
|
|
else
|
|
{
|
|
value = BitConverter.ToUInt32(bytes, 0);
|
|
}
|
|
|
|
return (value | ((uint)flag << 24)) & ~0xC0000000;
|
|
}
|
|
else if ((flag & 0xE0) == 0x80 || (flag & 0xE0) == 0xA0)
|
|
{
|
|
var bytes = ReadBytes(1);
|
|
return (uint)((bytes[0] | (flag << 8)) & ~0x8000);
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
|
|
public void WriteString(string value)
|
|
{
|
|
var bytes = GbkEncoding.GetBytes(value);
|
|
WriteCompactUInt((uint)bytes.Length);
|
|
_octets.Insert(_position, bytes);
|
|
_position += bytes.Length;
|
|
}
|
|
|
|
public string ReadString()
|
|
{
|
|
uint length = ReadCompactUInt();
|
|
var bytes = ReadBytes((int)length);
|
|
return GbkEncoding.GetString(bytes);
|
|
}
|
|
|
|
public byte[] ReadBytes(int count)
|
|
{
|
|
if (_position + count > _octets.Length)
|
|
throw new IndexOutOfRangeException("Attempt to read beyond the end of the stream.");
|
|
var result = new byte[count];
|
|
Array.Copy(_octets.ToArray(), _position, result, 0, count);
|
|
_position += count;
|
|
return result;
|
|
}
|
|
|
|
public byte[] ReadByteWithoutAdvancePos(int count)
|
|
{
|
|
if (_position + count > _octets.Length)
|
|
return new byte[count];
|
|
var result = new byte[count];
|
|
Array.Copy(_octets.ToArray(), _position, result, 0, count);
|
|
return result;
|
|
}
|
|
|
|
public byte[] ToArray() => _octets.ToArray();
|
|
|
|
public void Dispose() => _octets?.Dispose();
|
|
|
|
public void WriteBytes(byte[] data)
|
|
{
|
|
_octets.Insert(_position, data);
|
|
_position += data.Length;
|
|
}
|
|
|
|
// Add these methods for Octets support
|
|
public void Write(Octets value)
|
|
{
|
|
WriteCompactUInt((uint)value.Size);
|
|
WriteBytes(value.ToArray());
|
|
}
|
|
|
|
public Octets ReadOctets()
|
|
{
|
|
uint size = ReadCompactUInt();
|
|
byte[] data = ReadBytes((int)size);
|
|
return new Octets(data);
|
|
}
|
|
|
|
public void Write(List<Octets> list)
|
|
{
|
|
WriteCompactUInt((uint)list.Count);
|
|
foreach (var octets in list)
|
|
{
|
|
Write(octets);
|
|
}
|
|
}
|
|
|
|
public List<Octets> ReadOctetsList()
|
|
{
|
|
uint count = ReadCompactUInt();
|
|
var list = new List<Octets>();
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
list.Add(ReadOctets());
|
|
}
|
|
return list;
|
|
}
|
|
|
|
// Erase a section of the underlying buffer
|
|
public void Erase(int pos, int length)
|
|
{
|
|
_octets.Erase(pos, length);
|
|
// Adjust position if the erasure happened before the current position
|
|
if (pos < _position)
|
|
{
|
|
_position = Math.Max(pos, _position - length);
|
|
}
|
|
}
|
|
|
|
// Compact the stream by removing processed data (0 to Position)
|
|
public void Compact()
|
|
{
|
|
if (_position > 0)
|
|
{
|
|
_octets.Erase(0, _position);
|
|
_position = 0; // Reset position after compacting
|
|
}
|
|
}
|
|
}
|
|
} |