From b9e3949b141828aacf59e92975f0a1a2b16aa24c Mon Sep 17 00:00:00 2001
From: Li
Date: Thu, 17 Nov 2022 02:52:30 +1300
Subject: [PATCH] Improve websocket code.
---
.../HISPd/Properties/AssemblyInfo.cs | 4 +-
.../HISPd/Resources/DEBIAN/control | 2 +-
.../LibHISP/Properties/AssemblyInfo.cs | 4 +-
HorseIsleServer/LibHISP/Server/GameClient.cs | 31 +--
HorseIsleServer/LibHISP/Server/GameServer.cs | 2 +-
.../LibHISP/Server/Network/Transport.cs | 4 +-
.../LibHISP/Server/Network/WebSocket.cs | 214 +++++++++++++-----
.../MPN00BS/Properties/AssemblyInfo.cs | 4 +-
8 files changed, 186 insertions(+), 79 deletions(-)
diff --git a/HorseIsleServer/HISPd/Properties/AssemblyInfo.cs b/HorseIsleServer/HISPd/Properties/AssemblyInfo.cs
index 6c9b07c..f70575a 100755
--- a/HorseIsleServer/HISPd/Properties/AssemblyInfo.cs
+++ b/HorseIsleServer/HISPd/Properties/AssemblyInfo.cs
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.8.38.0")]
-[assembly: AssemblyFileVersion("1.8.38.0")]
+[assembly: AssemblyVersion("1.8.39.0")]
+[assembly: AssemblyFileVersion("1.8.39.0")]
diff --git a/HorseIsleServer/HISPd/Resources/DEBIAN/control b/HorseIsleServer/HISPd/Resources/DEBIAN/control
index 8291173..03bb4ab 100755
--- a/HorseIsleServer/HISPd/Resources/DEBIAN/control
+++ b/HorseIsleServer/HISPd/Resources/DEBIAN/control
@@ -1,5 +1,5 @@
Package: hisp
-Version: 1.8.38
+Version: 1.8.39
Depends: coreutils,systemd,mariadb-server,libsqlite3-dev,zlib1g-dev,libicu-dev,libkrb5-dev
Maintainer: Li
Homepage: https://islehorse.com
diff --git a/HorseIsleServer/LibHISP/Properties/AssemblyInfo.cs b/HorseIsleServer/LibHISP/Properties/AssemblyInfo.cs
index 3df89e9..2982524 100755
--- a/HorseIsleServer/LibHISP/Properties/AssemblyInfo.cs
+++ b/HorseIsleServer/LibHISP/Properties/AssemblyInfo.cs
@@ -30,8 +30,8 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
-[assembly: AssemblyVersion("1.8.38.0")]
-[assembly: AssemblyFileVersion("1.8.38.0")]
+[assembly: AssemblyVersion("1.8.39.0")]
+[assembly: AssemblyFileVersion("1.8.39.0")]
diff --git a/HorseIsleServer/LibHISP/Server/GameClient.cs b/HorseIsleServer/LibHISP/Server/GameClient.cs
index d60475d..d5ac067 100755
--- a/HorseIsleServer/LibHISP/Server/GameClient.cs
+++ b/HorseIsleServer/LibHISP/Server/GameClient.cs
@@ -109,29 +109,21 @@ namespace HISP.Server
}
public static void CreateClient(object sender, SocketAsyncEventArgs e)
{
-#if !DEBUG
- try
+ do
{
-#endif
- do
- {
- Socket clientSocket = e.AcceptSocket;
+ Socket clientSocket = e.AcceptSocket;
- if (GameServer.ServerSocket == null)
- return;
- if (clientSocket == null)
- continue;
- if (clientSocket.RemoteEndPoint == null)
- continue;
+ if (GameServer.ServerSocket == null)
+ return;
+ if (clientSocket == null)
+ continue;
+ if (clientSocket.RemoteEndPoint == null)
+ continue;
- connectedClients.Add(new GameClient(clientSocket));
- e.AcceptSocket = null;
+ connectedClients.Add(new GameClient(clientSocket));
+ e.AcceptSocket = null;
- } while (!GameServer.ServerSocket.AcceptAsync(e));
-#if !DEBUG
- }
- catch (ObjectDisposedException ex) { Logger.ErrorPrint("Server shutdown due to " + ex.Message); } // server shutdown
-#endif
+ } while (!GameServer.ServerSocket.AcceptAsync(e));
}
private void timeoutTimerTick(object state)
{
@@ -394,6 +386,7 @@ namespace HISP.Server
if (packet.Length < 1)
{
Logger.ErrorPrint("Received an invalid packet (size: "+packet.Length+")");
+ return;
}
byte identifier = packet[0];
diff --git a/HorseIsleServer/LibHISP/Server/GameServer.cs b/HorseIsleServer/LibHISP/Server/GameServer.cs
index f9ebeb7..4c329a4 100755
--- a/HorseIsleServer/LibHISP/Server/GameServer.cs
+++ b/HorseIsleServer/LibHISP/Server/GameServer.cs
@@ -8264,7 +8264,7 @@ namespace HISP.Server
IPAddress hostIP = IPAddress.Parse(ConfigReader.BindIP);
IPEndPoint ep = new IPEndPoint(hostIP, ConfigReader.Port);
ServerSocket.Bind(ep);
- ServerSocket.Listen(0x7fffffff);
+ ServerSocket.Listen(0xFFFF);
gameTimer = new Timer(new TimerCallback(onGameTick), null, gameTickSpeed, gameTickSpeed);
minuteTimer = new Timer(new TimerCallback(onMinuteTick), null, oneMinute, oneMinute);
Logger.InfoPrint("Binding to ip: " + ConfigReader.BindIP + " On port: " + ConfigReader.Port.ToString());
diff --git a/HorseIsleServer/LibHISP/Server/Network/Transport.cs b/HorseIsleServer/LibHISP/Server/Network/Transport.cs
index ea71363..d2e064d 100644
--- a/HorseIsleServer/LibHISP/Server/Network/Transport.cs
+++ b/HorseIsleServer/LibHISP/Server/Network/Transport.cs
@@ -2,7 +2,7 @@
using System;
using System.Collections.Generic;
using System.Net.Sockets;
-
+using System.Threading;
namespace HISP.Server.Network
{
@@ -43,6 +43,8 @@ namespace HISP.Server.Network
else
break;
+ Thread.Sleep(1000 * 3);
+
} while (!socket.ReceiveAsync(e));
}
diff --git a/HorseIsleServer/LibHISP/Server/Network/WebSocket.cs b/HorseIsleServer/LibHISP/Server/Network/WebSocket.cs
index 09c0808..a2fc1bc 100644
--- a/HorseIsleServer/LibHISP/Server/Network/WebSocket.cs
+++ b/HorseIsleServer/LibHISP/Server/Network/WebSocket.cs
@@ -1,4 +1,6 @@
-using HISP.Security;
+#define WEBSOCKET_DEBUG
+
+using HISP.Security;
using HISP.Util;
using System;
using System.Collections.Generic;
@@ -11,16 +13,25 @@ namespace HISP.Server.Network
{
private const string WEBSOCKET_SEED = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- private const byte WEBASSEMBLY_CONTINUE = 0x0;
- private const byte WEBASSEMBLY_TEXT = 0x1;
+ private const byte WEBSOCKET_CONTINUE = 0x0;
+ private const byte WEBSOCKET_TEXT = 0x1;
+ private const byte WEBSOCKET_BINARY = 0x2;
- private const byte WEBASSEMBLY_LENGTH_INT16 = 0x7E;
- private const byte WEBASSEMBLY_LENGTH_INT64 = 0x7F;
+ private const byte WEBSOCKET_LENGTH_INT16 = 0x7E;
+ private const byte WEBSOCKET_LENGTH_INT64 = 0x7F;
private List currentMessage = new List();
- private string secWebsocketKey = null;
+ private int lastOpcode;
+ private Int64 expectedLength = -1;
private bool handshakeDone = false;
+ private void webSocketLog(string msg)
+ {
+#if WEBSOCKET_DEBUG
+ foreach(string str in msg.Replace("\r", "").Split("\n"))
+ Logger.InfoPrint("[WEBSOCKET] " + str);
+#endif
+ }
private Dictionary parseHttpHeaders(string httpResponse)
{
@@ -52,18 +63,21 @@ namespace HISP.Server.Network
}
private byte[] createHandshakeResponse(string secWebsocketKey)
{
- return Encoding.UTF8.GetBytes(String.Join("\r\n", new string[] {
+ string msg = String.Join("\r\n", new string[] {
"HTTP/1.1 101 Switching Protocols",
"Connection: Upgrade",
"Upgrade: websocket",
"Sec-WebSocket-Accept: " + secWebsocketKey,
"",
""
- }));
+ });
+ webSocketLog(msg);
+ return Encoding.UTF8.GetBytes(msg);
}
private byte[] parseHandshake(string handshakeResponse)
{
+ webSocketLog(handshakeResponse);
Dictionary headers = parseHttpHeaders(handshakeResponse);
string webSocketKey = null;
@@ -90,15 +104,36 @@ namespace HISP.Server.Network
public override void ProcessReceivedPackets(int available, byte[] buffer)
{
- for (int i = 0; i < available; i++)
- currentPacket.Add(buffer[i]);
- byte[] webAsmMsg = currentPacket.ToArray();
+ Int64 received = 0;
+ // add to current packet
+ // if current packet is less than size of an expected incoming message
+ // then keep receiving until full message received.
+
+ Int64 curPacketLength = currentPacket.LongCount();
+ for (received = 0; received < available; received++)
+ {
+ currentPacket.Add(buffer[received]);
+ curPacketLength++;
+
+ // i use a <0 value to say there is no expected length
+ // check if entire packet received
+ if (expectedLength > 0 && (curPacketLength >= expectedLength))
+ break;
+ }
+
+ if (expectedLength > 0 && (currentPacket.LongCount() < expectedLength))
+ return;
+ else
+ expectedLength = -1;
+
+ byte[] webSocketMsg = currentPacket.ToArray();
if (!handshakeDone)
{
- if (IsStartOfHandshake(webAsmMsg) && IsEndOfHandshake(webAsmMsg))
+
+ if (IsStartOfHandshake(webSocketMsg) && IsEndOfHandshake(webSocketMsg))
{
- string httpHandshake = Encoding.UTF8.GetString(webAsmMsg);
+ string httpHandshake = Encoding.UTF8.GetString(webSocketMsg);
byte[] handshakeResponse = parseHandshake(httpHandshake);
base.Send(handshakeResponse);
@@ -106,76 +141,153 @@ namespace HISP.Server.Network
handshakeDone = true;
}
}
- if (currentPacket.Count >= 2)
+ else if(currentPacket.Count > 2) // else, begin parsing websocket message
{
- bool finished = (currentPacket[0] & 0b10000000) != 0;
- int opcode = (currentPacket[0] & 0b00001111);
- bool mask = (currentPacket[1] & 0b10000000) != 0;
- UInt64 messageLength = Convert.ToUInt64(currentPacket[1] & 0b01111111);
+ byte[] unmaskKey = new byte[4];
+
+ bool finished = (webSocketMsg[0] & 0b10000000) != 0;
+
+ bool rsv1 = (webSocketMsg[0] & 0b01000000) != 0;
+ bool rsv2 = (webSocketMsg[0] & 0b00100000) != 0;
+ bool rsv3 = (webSocketMsg[0] & 0b00010000) != 0;
+
+ int opcode = (webSocketMsg[0] & 0b00001111);
+
+ bool mask = (webSocketMsg[1] & 0b10000000) != 0;
+ Int64 messageLength = Convert.ToInt64(webSocketMsg[1] & 0b01111111);
int offset = 2;
- if (messageLength == WEBASSEMBLY_LENGTH_INT16)
+
+ if (messageLength == WEBSOCKET_LENGTH_INT16)
{
- if(currentPacket.Count >= offset + 2)
+ if (webSocketMsg.LongLength >= offset + 2)
{
byte[] uint16Bytes = new byte[2];
- Array.ConstrainedCopy(webAsmMsg, offset, uint16Bytes, 0, uint16Bytes.Length);
+ Array.ConstrainedCopy(webSocketMsg, offset, uint16Bytes, 0, uint16Bytes.Length);
uint16Bytes = uint16Bytes.Reverse().ToArray();
messageLength = BitConverter.ToUInt16(uint16Bytes);
offset += uint16Bytes.Length;
}
}
- else if (messageLength == WEBASSEMBLY_LENGTH_INT64)
+ else if (messageLength == WEBSOCKET_LENGTH_INT64)
{
- if (currentPacket.Count >= offset + 8)
+ if (webSocketMsg.LongLength >= offset + 8)
{
- byte[] uint64Bytes = new byte[8];
- Array.ConstrainedCopy(webAsmMsg, offset, uint64Bytes, 0, uint64Bytes.Length);
- uint64Bytes = uint64Bytes.Reverse().ToArray();
- messageLength = BitConverter.ToUInt64(uint64Bytes);
+ byte[] int64Bytes = new byte[8];
+ Array.ConstrainedCopy(webSocketMsg, offset, int64Bytes, 0, int64Bytes.Length);
+ int64Bytes = int64Bytes.Reverse().ToArray();
+ messageLength = BitConverter.ToInt64(int64Bytes);
- offset += uint64Bytes.Length;
+ offset += int64Bytes.Length;
}
}
-
if (mask)
{
- switch (opcode)
+ Array.ConstrainedCopy(webSocketMsg, offset, unmaskKey, 0, unmaskKey.Length);
+ offset += unmaskKey.Length;
+ }
+
+ // Handle tcp fragmentation
+
+ Int64 actualLength = (webSocketMsg.LongLength - offset);
+
+ // check if full message received, if not then set expected length
+ // and return, thus entering the loop at the beginning
+ if (actualLength < messageLength)
+ {
+ expectedLength = messageLength + offset; // set expected length and return
+ webSocketLog("Partial websocket frame received, expected size: " + messageLength + " got size: " + actualLength);
+ return;
+ }
+ else
+ {
+ expectedLength = -1;
+ currentPacket.Clear();
+ }
+
+ // dont care about extensions
+ if (rsv1 || rsv2 || rsv3) return;
+
+ webSocketLog("Finished: " + finished + "\nRsv1: " + rsv1 + "\nRsv2: " + rsv2 + "\nRsv3: " + rsv3 + "\nOpcode: " + opcode + "\nMask: " + mask + "\nMesssageLength: " + messageLength);
+
+ // websocket packet fully received, begin processing
+
+ // allow for continuing
+ if (opcode == WEBSOCKET_CONTINUE)
+ lastOpcode = opcode;
+ else
+ opcode = lastOpcode;
+
+ switch (opcode)
+ {
+ case WEBSOCKET_BINARY: // writing this almost killed me, because im non-binary :(
+ case WEBSOCKET_TEXT:
+ for (Int64 i = 0; i < messageLength; i++)
+ currentMessage.Add(mask ? Convert.ToByte(webSocketMsg[offset + i] ^ unmaskKey[i % unmaskKey.Length]) : Convert.ToByte(webSocketMsg[offset + i]));
+ break;
+ }
+
+ if (finished)
+ {
+ if(currentMessage.LongCount() > 0)
{
- case WEBASSEMBLY_TEXT:
+ byte[] message = currentMessage.ToArray();
- if (currentPacket.LongCount() >= (offset + 4))
- {
- byte[] unmaskKey = new byte[4];
- Array.ConstrainedCopy(buffer, offset, unmaskKey, 0, unmaskKey.Length);
- offset += unmaskKey.Length;
+ if(opcode == WEBSOCKET_TEXT)
+ webSocketLog(Encoding.UTF8.GetString(message));
- for (int i = 0; i < (currentPacket.Count - offset); i++)
- {
- currentMessage.Add(Convert.ToByte(currentPacket[offset+ i] ^ unmaskKey[i % unmaskKey.Length]));
- }
-
- currentPacket.Clear();
- }
- break;
- }
-
- if (finished)
- {
- onReceiveCallback(currentMessage.ToArray());
+ onReceiveCallback(message);
currentMessage.Clear();
- currentPacket.Clear();
}
}
+
+ // is there another frame after this one?
+ if(actualLength > messageLength)
+ {
+ Int64 left = (actualLength - messageLength);
+ Int64 totalSent = left;
+
+ Int64 loc = messageLength + offset;
+
+ while(totalSent > 0)
+ {
+ int total = buffer.Length;
+ if (totalSent < total)
+ total = Convert.ToInt32(totalSent);
+
+ for (int i = 0; i < total; i++)
+ buffer[i] = webSocketMsg[loc + i];
+
+ ProcessReceivedPackets(total, buffer);
+
+ totalSent -= total;
+ loc += total;
+ }
+
+ }
}
-
+
+ // parse remaining data after end.
+
+ if (received < available)
+ {
+ int left = Convert.ToInt32(available - received);
+ byte[] leftData = new byte[left];
+ Array.ConstrainedCopy(buffer, available, leftData, 0, left);
+ Array.Copy(leftData, buffer, leftData.Length);
+
+ ProcessReceivedPackets(available, buffer);
+ }
+
}
+
+
public override string Name
{
get
diff --git a/HorseIsleServer/MPN00BS/Properties/AssemblyInfo.cs b/HorseIsleServer/MPN00BS/Properties/AssemblyInfo.cs
index 0f160ea..5419dd1 100755
--- a/HorseIsleServer/MPN00BS/Properties/AssemblyInfo.cs
+++ b/HorseIsleServer/MPN00BS/Properties/AssemblyInfo.cs
@@ -30,8 +30,8 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
-[assembly: AssemblyVersion("1.8.38.0")]
-[assembly: AssemblyFileVersion("1.8.38.0")]
+[assembly: AssemblyVersion("1.8.39.0")]
+[assembly: AssemblyFileVersion("1.8.39.0")]