using Google.Protobuf; using MikuSB.Enums.Packet; using MikuSB.Util; using System.Buffers.Binary; using System.Net.Sockets; namespace MikuSB.TcpSharp { public class PacketCodec { private const int HeaderSize4Byte = 4; private const int HeaderSize2Byte = 2; private const int MaxPacketLength = 1024 * 1024; private const ushort ClientMagic = 0x011F; private const int ControlPacketSize = 35; private static readonly Logger Logger = new("PacketCodec"); public PacketCodec() { } public async Task ReadPacketAsync( Stream stream, CancellationToken cancellationToken = default) { try { var lengthBuffer = new byte[HeaderSize4Byte]; if (!await ReadExactAsync(stream, lengthBuffer, cancellationToken)) { Logger.Debug("Connection closed before packet header"); return null; } var framing = DetectFraming(lengthBuffer); switch (framing) { case PacketFraming.Control: return await HandleControlPacket(stream, cancellationToken); case PacketFraming.TwoByteBigEndianLength: return await HandleTwoBytePacket(stream, lengthBuffer, cancellationToken); case PacketFraming.FourByteLittleEndianLength: return await HandleFourBytePacket(stream, lengthBuffer, cancellationToken); default: return await HandleUnknownPacket(stream, lengthBuffer, cancellationToken); } } catch (OperationCanceledException) { Logger.Debug("Packet read cancelled"); return null; } catch (Exception ex) { Logger.Error($"Error reading packet {ex}"); return null; } } public byte[] Encode(ushort packetId, byte[] payload, PacketFraming framing = PacketFraming.FourByteLittleEndianLength) { return framing switch { PacketFraming.TwoByteBigEndianLength => EncodeTwoByteFrame(packetId, payload), PacketFraming.FourByteLittleEndianLength => EncodeFourByteFrame(packetId, payload), _ => EncodeFourByteFrame(packetId, payload) }; } public byte[] EncodeRaw(ushort packetId, byte[] payload, PacketFraming framing = PacketFraming.FourByteLittleEndianLength) { return framing switch { PacketFraming.TwoByteBigEndianLength => EncodeTwoByteFrame(packetId, payload), PacketFraming.FourByteLittleEndianLength => EncodeFourByteFrame(packetId, payload), _ => EncodeFourByteFrame(packetId, payload) }; } #region Private Methods private PacketFraming DetectFraming(byte[] header) { var firstTwoBytes = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(0, 2)); var nextTwoBytes = BinaryPrimitives.ReadUInt16LittleEndian(header.AsSpan(2, 2)); if (firstTwoBytes == ClientMagic && nextTwoBytes == 0) return PacketFraming.Control; if (firstTwoBytes == ClientMagic && IsValidPacketId(nextTwoBytes)) return PacketFraming.TwoByteBigEndianLength; if (IsValidTwoByteHeader(firstTwoBytes, (ushort)nextTwoBytes)) return PacketFraming.TwoByteBigEndianLength; return PacketFraming.FourByteLittleEndianLength; } private async Task HandleControlPacket(Stream stream, CancellationToken cancellationToken) { var controlData = new byte[ControlPacketSize]; if (!await ReadExactAsync(stream, controlData, cancellationToken)) { Logger.Debug("Connection closed during control packet read"); return null; } Logger.Debug("Control packet received"); return new BasePacket(0) { Framing = PacketFraming.Control, Body = Array.Empty() }; } private async Task HandleTwoBytePacket( Stream stream, byte[] header, CancellationToken cancellationToken) { var packetId = BinaryPrimitives.ReadUInt16LittleEndian(header.AsSpan(2, 2)); var wrapper = new byte[ControlPacketSize]; if (!await ReadExactAsync(stream, wrapper, cancellationToken)) { Logger.Debug($"Connection closed during wrapper read for packet {packetId}"); return null; } var payloadLength = BinaryPrimitives.ReadUInt16LittleEndian(wrapper.AsSpan(6, 2)); var payload = await ReadPayloadAsync(stream, payloadLength, cancellationToken); if (payload == null) return null; //Logger.Debug($"Packet received (2-byte framing): ID={packetId}, PayloadSize={payload.Length}"); return new BasePacket(packetId) { Framing = PacketFraming.TwoByteBigEndianLength, Body = payload }; } private async Task HandleFourBytePacket( Stream stream, byte[] header, CancellationToken cancellationToken) { var length = BinaryPrimitives.ReadUInt32LittleEndian(header); if (length < 2 || length > MaxPacketLength) { Logger.Warn($"Invalid packet length: {length}"); return null; } var frame = new byte[length]; if (!await ReadExactAsync(stream, frame, cancellationToken)) { Logger.Debug("Connection closed during packet body read"); return null; } var packetId = BinaryPrimitives.ReadUInt16LittleEndian(frame.AsSpan(0, 2)); var payload = frame[2..]; //Logger.Debug($"Packet received (4-byte framing): ID={packetId}, PayloadSize={payload.Length}"); return new BasePacket(packetId) { Framing = PacketFraming.FourByteLittleEndianLength, Body = payload }; } private async Task HandleUnknownPacket( Stream stream, byte[] header, CancellationToken cancellationToken) { var extraData = await ReadAvailableBytesAsync(stream, cancellationToken); var combinedData = new byte[header.Length + extraData.Length]; header.CopyTo(combinedData, 0); extraData.CopyTo(combinedData, header.Length); Logger.Warn($"Unknown packet format detected, captured {combinedData.Length} bytes"); return new BasePacket(0) { Framing = PacketFraming.Unknown, Body = combinedData }; } private async Task ReadPayloadAsync( Stream stream, int length, CancellationToken cancellationToken) { if (length <= 0) return Array.Empty(); if (length > MaxPacketLength) { Logger.Warn($"Payload too large: {length}"); return null; } var payload = new byte[length]; if (!await ReadExactAsync(stream, payload, cancellationToken)) return null; return payload; } private byte[] EncodeTwoByteFrame(ushort packetId, byte[] payload) { var wrappedPayload = WrapPayload(payload); var buffer = new byte[HeaderSize4Byte + wrappedPayload.Length]; BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), ClientMagic); BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsSpan(2, 2), packetId); wrappedPayload.CopyTo(buffer.AsSpan(HeaderSize4Byte)); return buffer; } private byte[] EncodeFourByteFrame(ushort packetId, byte[] payload) { var buffer = new byte[HeaderSize4Byte + HeaderSize2Byte + payload.Length]; BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsSpan(0, 4), (uint)(HeaderSize2Byte + payload.Length)); BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsSpan(4, 2), packetId); payload.CopyTo(buffer.AsSpan(HeaderSize4Byte + HeaderSize2Byte)); return buffer; } private byte[] WrapPayload(byte[] payload) { const int wrapperHeaderSize = 35; var wrapped = new byte[wrapperHeaderSize + payload.Length]; BinaryPrimitives.WriteUInt16LittleEndian(wrapped.AsSpan(6, 2), (ushort)payload.Length); wrapped[11] = 1; payload.CopyTo(wrapped.AsSpan(wrapperHeaderSize)); return wrapped; } private static async Task ReadExactAsync( Stream stream, byte[] buffer, CancellationToken cancellationToken) { var offset = 0; while (offset < buffer.Length) { var read = await stream.ReadAsync(buffer.AsMemory(offset), cancellationToken); if (read == 0) return false; offset += read; } return true; } private static async Task ReadAvailableBytesAsync( Stream stream, CancellationToken cancellationToken) { if (stream is not NetworkStream networkStream || !networkStream.DataAvailable) return Array.Empty(); using var ms = new MemoryStream(); var buffer = new byte[4096]; while (networkStream.DataAvailable && ms.Length < 16384) { var read = await networkStream.ReadAsync(buffer, cancellationToken); if (read <= 0) break; ms.Write(buffer, 0, read); } return ms.ToArray(); } private static bool IsValidTwoByteHeader(int firstTwoBytes, ushort packetId) { return firstTwoBytes >= 2 && firstTwoBytes <= ushort.MaxValue && IsValidPacketId(packetId); } private static bool IsValidPacketId(ushort packetId) { return packetId != 0; } #endregion } }