using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace Mirror.SimpleWeb
{
public static class MessageProcessor
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static byte FirstLengthByte(byte[] buffer) => (byte)(buffer[1] & 0b0111_1111);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool NeedToReadShortLength(byte[] buffer)
{
byte lenByte = FirstLengthByte(buffer);
return lenByte == Constants.UshortPayloadLength;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool NeedToReadLongLength(byte[] buffer)
{
byte lenByte = FirstLengthByte(buffer);
return lenByte == Constants.UlongPayloadLength;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetOpcode(byte[] buffer)
{
return buffer[0] & 0b0000_1111;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetPayloadLength(byte[] buffer)
{
byte lenByte = FirstLengthByte(buffer);
return GetMessageLength(buffer, 0, lenByte);
}
///
/// Has full message been sent
///
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Finished(byte[] buffer)
{
return (buffer[0] & 0b1000_0000) != 0;
}
public static void ValidateHeader(byte[] buffer, int maxLength, bool expectMask, bool opCodeContinuation = false)
{
bool finished = Finished(buffer);
bool hasMask = (buffer[1] & 0b1000_0000) != 0; // true from clients, false from server, "All messages from the client to the server have this bit set"
int opcode = buffer[0] & 0b0000_1111; // expecting 1 - text message
byte lenByte = FirstLengthByte(buffer);
ThrowIfMaskNotExpected(hasMask, expectMask);
ThrowIfBadOpCode(opcode, finished, opCodeContinuation);
int msglen = GetMessageLength(buffer, 0, lenByte);
ThrowIfLengthZero(msglen);
ThrowIfMsgLengthTooLong(msglen, maxLength);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ToggleMask(byte[] src, int sourceOffset, int messageLength, byte[] maskBuffer, int maskOffset)
{
ToggleMask(src, sourceOffset, src, sourceOffset, messageLength, maskBuffer, maskOffset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ToggleMask(byte[] src, int sourceOffset, ArrayBuffer dst, int messageLength, byte[] maskBuffer, int maskOffset)
{
ToggleMask(src, sourceOffset, dst.array, 0, messageLength, maskBuffer, maskOffset);
dst.count = messageLength;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ToggleMask(byte[] src, int srcOffset, byte[] dst, int dstOffset, int messageLength, byte[] maskBuffer, int maskOffset)
{
for (int i = 0; i < messageLength; i++)
{
byte maskByte = maskBuffer[maskOffset + i % Constants.MaskSize];
dst[dstOffset + i] = (byte)(src[srcOffset + i] ^ maskByte);
}
}
///
static int GetMessageLength(byte[] buffer, int offset, byte lenByte)
{
if (lenByte == Constants.UshortPayloadLength)
{
// header is 2 bytes
ushort value = 0;
value |= (ushort)(buffer[offset + 2] << 8);
value |= buffer[offset + 3];
return value;
}
else if (lenByte == Constants.UlongPayloadLength)
{
// header is 8 bytes
ulong value = 0;
value |= ((ulong)buffer[offset + 2] << 56);
value |= ((ulong)buffer[offset + 3] << 48);
value |= ((ulong)buffer[offset + 4] << 40);
value |= ((ulong)buffer[offset + 5] << 32);
value |= ((ulong)buffer[offset + 6] << 24);
value |= ((ulong)buffer[offset + 7] << 16);
value |= ((ulong)buffer[offset + 8] << 8);
value |= ((ulong)buffer[offset + 9] << 0);
if (value > int.MaxValue)
{
throw new NotSupportedException($"Can't receive payloads larger that int.max: {int.MaxValue}");
}
return (int)value;
}
else // is less than 126
{
// header is 2 bytes long
return lenByte;
}
}
///
static void ThrowIfMaskNotExpected(bool hasMask, bool expectMask)
{
if (hasMask != expectMask)
{
throw new InvalidDataException($"Message expected mask to be {expectMask} but was {hasMask}");
}
}
///
static void ThrowIfBadOpCode(int opcode, bool finished, bool opCodeContinuation)
{
// 0 = continuation
// 2 = binary
// 8 = close
// do we expect Continuation?
if (opCodeContinuation)
{
// good it was Continuation
if (opcode == 0)
return;
// bad, wasn't Continuation
throw new InvalidDataException("Expected opcode to be Continuation");
}
else if (!finished)
{
// fragmented message, should be binary
if (opcode == 2)
return;
throw new InvalidDataException("Expected opcode to be binary");
}
else
{
// normal message, should be binary or close
if (opcode == 2 || opcode == 8)
return;
throw new InvalidDataException("Expected opcode to be binary or close");
}
}
///
static void ThrowIfLengthZero(int msglen)
{
if (msglen == 0)
{
throw new InvalidDataException("Message length was zero");
}
}
///
/// need to check this so that data from previous buffer isn't used
///
public static void ThrowIfMsgLengthTooLong(int msglen, int maxLength)
{
if (msglen > maxLength)
{
throw new InvalidDataException("Message length is greater than max length");
}
}
}
}