mirror of
https://github.com/islehorse/HISP.git
synced 2025-04-21 20:25:51 +12:00
Add Multiple transports system, begin work on WebSockets
This commit is contained in:
parent
e869a23463
commit
e74f66a439
22 changed files with 3752 additions and 3301 deletions
101
HorseIsleServer/LibHISP/Server/Network/Hybrid.cs
Normal file
101
HorseIsleServer/LibHISP/Server/Network/Hybrid.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace HISP.Server.Network
|
||||
{
|
||||
public class Hybrid : Transport
|
||||
{
|
||||
|
||||
Transport actualTransport = null;
|
||||
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if(actualTransport == null)
|
||||
return "TransportDeterminer";
|
||||
else
|
||||
return actualTransport.Name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override bool Disconnected
|
||||
{
|
||||
get
|
||||
{
|
||||
if (actualTransport == null)
|
||||
return base.Disconnected;
|
||||
else
|
||||
return actualTransport.Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
public override string Ip
|
||||
{
|
||||
get
|
||||
{
|
||||
if (actualTransport == null)
|
||||
return base.Ip;
|
||||
else
|
||||
return actualTransport.Ip;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void Disconnect()
|
||||
{
|
||||
if (actualTransport == null)
|
||||
{
|
||||
base.Disconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
actualTransport.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ProcessReceivedPackets(int available, byte[] buffer)
|
||||
{
|
||||
for (int i = 0; i < available; i++)
|
||||
base.currentPacket.Add(buffer[i]);
|
||||
|
||||
if (currentPacket.Count >= 3)
|
||||
{
|
||||
if (ConfigReader.EnableWebSocket && WebSocket.IsStartOfHandshake(currentPacket.ToArray()))
|
||||
{
|
||||
Logger.InfoPrint(this.Ip + " Switching to WebSocket");
|
||||
actualTransport = new WebSocket();
|
||||
|
||||
actualTransport.passObjects(this.socket, this.onReceiveCallback, this.onDisconnectCallback);
|
||||
actualTransport.ProcessReceivedPackets(available, buffer);
|
||||
actualTransport.Accept(base.socket, base.onReceiveCallback, base.onDisconnectCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.InfoPrint(this.Ip + " Switching to XmlSocket");
|
||||
actualTransport = new XmlSocket();
|
||||
|
||||
actualTransport.passObjects(this.socket, this.onReceiveCallback, this.onDisconnectCallback);
|
||||
actualTransport.ProcessReceivedPackets(available, buffer);
|
||||
actualTransport.Accept(base.socket, base.onReceiveCallback, base.onDisconnectCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override void receivePackets(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
if (!base.checkForError(e))
|
||||
ProcessReceivedPackets(e.BytesTransferred, e.Buffer);
|
||||
}
|
||||
|
||||
public override void Send(byte[] data)
|
||||
{
|
||||
if(actualTransport == null)
|
||||
base.Send(data);
|
||||
else
|
||||
actualTransport.Send(data);
|
||||
}
|
||||
}
|
||||
}
|
16
HorseIsleServer/LibHISP/Server/Network/ITransport.cs
Normal file
16
HorseIsleServer/LibHISP/Server/Network/ITransport.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace HISP.Server.Network
|
||||
{
|
||||
public interface ITransport
|
||||
{
|
||||
public string Name { get; }
|
||||
public bool Disconnected { get; }
|
||||
public string Ip { get; }
|
||||
|
||||
public void Accept(Socket socket, Action<byte[]> onReceive, Action onDisconnect);
|
||||
public void Send(byte[] data);
|
||||
public void Disconnect();
|
||||
}
|
||||
}
|
128
HorseIsleServer/LibHISP/Server/Network/Transport.cs
Normal file
128
HorseIsleServer/LibHISP/Server/Network/Transport.cs
Normal file
|
@ -0,0 +1,128 @@
|
|||
using HISP.Util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
|
||||
|
||||
namespace HISP.Server.Network
|
||||
{
|
||||
public abstract class Transport : ITransport
|
||||
{
|
||||
internal Socket socket;
|
||||
internal string remoteIp;
|
||||
|
||||
internal Action<byte[]> onReceiveCallback;
|
||||
internal Action onDisconnectCallback;
|
||||
|
||||
internal List<byte> currentPacket = new List<byte>();
|
||||
internal byte[] workBuffer = new byte[0xFFFF];
|
||||
|
||||
internal bool isDisconnecting = false;
|
||||
|
||||
public abstract void ProcessReceivedPackets(int available, byte[] buffer);
|
||||
public abstract string Name { get; }
|
||||
|
||||
internal virtual bool checkForError(SocketAsyncEventArgs e)
|
||||
{
|
||||
if (isDisconnecting || socket == null || e.BytesTransferred <= 0 || !socket.Connected || e.SocketError != SocketError.Success)
|
||||
{
|
||||
Disconnect();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
internal virtual void receivePackets(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (!checkForError(e))
|
||||
ProcessReceivedPackets(e.BytesTransferred, e.Buffer);
|
||||
else
|
||||
break;
|
||||
|
||||
} while (!socket.ReceiveAsync(e));
|
||||
|
||||
}
|
||||
|
||||
public virtual string Ip
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.remoteIp;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Disconnected
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.isDisconnecting;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void passObjects(Socket socket, Action<byte[]> onReceive, Action onDisconnect)
|
||||
{
|
||||
socket.SendTimeout = 10 * 1000; // 10sec
|
||||
socket.ReceiveTimeout = 10 * 1000; // 10sec
|
||||
|
||||
this.socket = socket;
|
||||
this.onReceiveCallback = onReceive;
|
||||
this.onDisconnectCallback = onDisconnect;
|
||||
this.remoteIp = Helper.GetIp(socket.RemoteEndPoint);
|
||||
}
|
||||
|
||||
public virtual void Accept(Socket socket, Action<byte[]> onReceive, Action onDisconnect)
|
||||
{
|
||||
passObjects(socket, onReceive, onDisconnect);
|
||||
|
||||
SocketAsyncEventArgs e = new SocketAsyncEventArgs();
|
||||
e.Completed += receivePackets;
|
||||
e.SetBuffer(workBuffer, 0, workBuffer.Length);
|
||||
if (!socket.ReceiveAsync(e))
|
||||
receivePackets(null, e);
|
||||
}
|
||||
|
||||
public virtual void Disconnect()
|
||||
{
|
||||
if (this.isDisconnecting)
|
||||
return;
|
||||
this.isDisconnecting = true;
|
||||
|
||||
// Close Socket
|
||||
if (socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
socket.Disconnect(false);
|
||||
socket.Dispose();
|
||||
socket = null;
|
||||
|
||||
}
|
||||
catch (SocketException e) { }
|
||||
catch (ObjectDisposedException e) { };
|
||||
}
|
||||
|
||||
onDisconnectCallback();
|
||||
}
|
||||
|
||||
public virtual void Send(byte[] data)
|
||||
{
|
||||
if (Disconnected) return;
|
||||
if (data == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
socket.Send(data);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
if (!Disconnected)
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
196
HorseIsleServer/LibHISP/Server/Network/WebSocket.cs
Normal file
196
HorseIsleServer/LibHISP/Server/Network/WebSocket.cs
Normal file
|
@ -0,0 +1,196 @@
|
|||
using HISP.Security;
|
||||
using HISP.Util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace HISP.Server.Network
|
||||
{
|
||||
public class WebSocket : Transport
|
||||
{
|
||||
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 WEBASSEMBLY_LENGTH_INT16 = 0x7E;
|
||||
private const byte WEBASSEMBLY_LENGTH_INT64 = 0x7F;
|
||||
|
||||
private List<byte> currentMessage = new List<byte>();
|
||||
|
||||
private string secWebsocketKey = null;
|
||||
private bool handshakeDone = false;
|
||||
|
||||
private Dictionary<string, string> parseHttpHeaders(string httpResponse)
|
||||
{
|
||||
Dictionary<string, string> httpHeaders = new Dictionary<string, string>();
|
||||
string[] parts = httpResponse.Replace("\r", "").Split("\n");
|
||||
foreach (string part in parts)
|
||||
{
|
||||
if (part.StartsWith("GET")) continue;
|
||||
|
||||
if (part.Contains(":"))
|
||||
{
|
||||
string[] keyValuePairs = part.Split(":");
|
||||
if (keyValuePairs.Length >= 2)
|
||||
httpHeaders.Add(keyValuePairs[0].Trim().ToLower(), keyValuePairs[1].Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
private string deriveWebsocketSecKey(string webSocketKey)
|
||||
{
|
||||
byte[] derivedKey = Authentication.Sha1Digest(Encoding.UTF8.GetBytes(webSocketKey.Trim() + WEBSOCKET_SEED.Trim()));
|
||||
return Convert.ToBase64String(derivedKey);
|
||||
}
|
||||
private byte[] createHandshakeResponse(string secWebsocketKey)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(String.Join("\r\n", new string[] {
|
||||
"HTTP/1.1 101 Switching Protocols",
|
||||
"Connection: Upgrade",
|
||||
"Upgrade: websocket",
|
||||
"Sec-WebSocket-Accept: " + secWebsocketKey,
|
||||
"",
|
||||
""
|
||||
}));
|
||||
}
|
||||
|
||||
private byte[] parseHandshake(string handshakeResponse)
|
||||
{
|
||||
Dictionary<string, string> headers = parseHttpHeaders(handshakeResponse);
|
||||
|
||||
string webSocketKey = null;
|
||||
headers.TryGetValue("sec-websocket-key", out webSocketKey);
|
||||
|
||||
if (webSocketKey != null)
|
||||
{
|
||||
string secWebsocketKey = deriveWebsocketSecKey(webSocketKey);
|
||||
return createHandshakeResponse(secWebsocketKey);
|
||||
}
|
||||
|
||||
return createHandshakeResponse("");
|
||||
}
|
||||
|
||||
public static bool IsStartOfHandshake(byte[] data)
|
||||
{
|
||||
return Helper.ByteArrayStartsWith(data, Encoding.UTF8.GetBytes("GET"));
|
||||
}
|
||||
|
||||
public static bool IsEndOfHandshake(byte[] data)
|
||||
{
|
||||
return Helper.ByteArrayEndsWith(data, Encoding.UTF8.GetBytes("\r\n\r\n"));
|
||||
}
|
||||
|
||||
public override void ProcessReceivedPackets(int available, byte[] buffer)
|
||||
{
|
||||
for (int i = 0; i < available; i++)
|
||||
currentPacket.Add(buffer[i]);
|
||||
byte[] webAsmMsg = currentPacket.ToArray();
|
||||
|
||||
if (!handshakeDone)
|
||||
{
|
||||
if (IsStartOfHandshake(webAsmMsg) && IsEndOfHandshake(webAsmMsg))
|
||||
{
|
||||
string httpHandshake = Encoding.UTF8.GetString(webAsmMsg);
|
||||
byte[] handshakeResponse = parseHandshake(httpHandshake);
|
||||
base.Send(handshakeResponse);
|
||||
|
||||
currentPacket.Clear();
|
||||
handshakeDone = true;
|
||||
}
|
||||
}
|
||||
if (currentPacket.Count >= 2)
|
||||
{
|
||||
bool finished = (currentPacket[0] & 0b10000000) != 0;
|
||||
int opcode = (currentPacket[0] & 0b00001111);
|
||||
|
||||
bool mask = (currentPacket[1] & 0b10000000) != 0;
|
||||
UInt64 messageLength = Convert.ToUInt64(currentPacket[1] & 0b01111111);
|
||||
|
||||
int offset = 2;
|
||||
|
||||
if (messageLength == WEBASSEMBLY_LENGTH_INT16)
|
||||
{
|
||||
if(currentPacket.Count >= offset + 2)
|
||||
{
|
||||
byte[] uint16Bytes = new byte[2];
|
||||
Array.ConstrainedCopy(webAsmMsg, offset, uint16Bytes, 0, uint16Bytes.Length);
|
||||
uint16Bytes = uint16Bytes.Reverse().ToArray();
|
||||
messageLength = BitConverter.ToUInt16(uint16Bytes);
|
||||
|
||||
offset += uint16Bytes.Length;
|
||||
}
|
||||
}
|
||||
else if (messageLength == WEBASSEMBLY_LENGTH_INT64)
|
||||
{
|
||||
if (currentPacket.Count >= offset + 8)
|
||||
{
|
||||
byte[] uint64Bytes = new byte[8];
|
||||
Array.ConstrainedCopy(webAsmMsg, offset, uint64Bytes, 0, uint64Bytes.Length);
|
||||
uint64Bytes = uint64Bytes.Reverse().ToArray();
|
||||
messageLength = BitConverter.ToUInt64(uint64Bytes);
|
||||
|
||||
offset += uint64Bytes.Length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (mask)
|
||||
{
|
||||
switch (opcode)
|
||||
{
|
||||
case WEBASSEMBLY_TEXT:
|
||||
|
||||
if (currentPacket.LongCount() >= (offset + 4))
|
||||
{
|
||||
byte[] unmaskKey = new byte[4];
|
||||
Array.ConstrainedCopy(buffer, offset, unmaskKey, 0, unmaskKey.Length);
|
||||
offset += unmaskKey.Length;
|
||||
|
||||
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());
|
||||
currentMessage.Clear();
|
||||
currentPacket.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return "WebSocket";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override void Send(byte[] data)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
38
HorseIsleServer/LibHISP/Server/Network/XmlSocket.cs
Normal file
38
HorseIsleServer/LibHISP/Server/Network/XmlSocket.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using HISP.Security;
|
||||
using HISP.Util;
|
||||
using System.Text;
|
||||
|
||||
namespace HISP.Server.Network
|
||||
{
|
||||
public class XmlSocket : Transport
|
||||
{
|
||||
public override void ProcessReceivedPackets(int available, byte[] buffer)
|
||||
{
|
||||
// In XmlSocket Packets are terminates by 0x00 so we have to read until we receive that terminator
|
||||
for (int i = 0; i < available; i++)
|
||||
{
|
||||
currentPacket.Add(buffer[i]);
|
||||
if (buffer[i] == PacketBuilder.PACKET_TERMINATOR) // Read until \0...
|
||||
{
|
||||
onReceiveCallback(currentPacket.ToArray());
|
||||
currentPacket.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle XMLSocket Policy File
|
||||
if (Helper.ByteArrayStartsWith(buffer, Encoding.UTF8.GetBytes("<policy-file-request/>")))
|
||||
{
|
||||
this.Send(CrossDomainPolicy.GetPolicyFile());
|
||||
}
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return "XmlSocket";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue