Implementing standard websocket packet.

This commit is contained in:
2019-07-25 16:01:02 +03:00
parent dfaade7b37
commit f9c09fcaba
13 changed files with 346 additions and 17 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ namespace MTSC_TestServer
.AddLogger(new DebugConsoleLogger())
.AddExceptionHandler(new ExceptionConsoleLogger())
//.AddHandler(new BroadcastHandler())
.AddHandler(new WebsocketHandler().AddWebsocketHandler(new EchoModule()))
.AddHandler(new WebsocketHandler().AddWebsocketHandler(new BroadcastModule()))
.AddHandler(new HttpHandler().AddHttpModule(new HelloWorldModule()))
.Run();
}
+3 -1
View File
@@ -1,4 +1,5 @@
using MTSC.Common.Http;
using MTSC.Common.WebSockets;
using MTSC.Common.WebSockets.ClientModules;
using System;
using System.Collections.Generic;
@@ -84,9 +85,10 @@ namespace MTSC.Client.Handlers
}
else if(state == SocketState.Established)
{
WebsocketMessage receivedMessage = new WebsocketMessage(message.MessageBytes);
foreach(IWebsocketModule websocketModule in websocketModules)
{
if(websocketModule.HandleReceivedMessage(client, this, message.MessageBytes))
if(websocketModule.HandleReceivedMessage(client, this, receivedMessage))
{
break;
}
@@ -10,15 +10,15 @@ namespace MTSC.Common.WebSockets.ClientModules
#region Public Methods
public void SendMessage(WebsocketHandler websocketHandler, string message)
{
byte[] encodedMessage = WebsocketHelper.EncodeMessage(message, true);
byte[] encodedMessage = WebsocketHelper.EncodeTextMessage(message, true);
websocketHandler.QueueMessage(encodedMessage);
}
#endregion
#region Interface Implementation
bool IWebsocketModule.HandleReceivedMessage(Client.Client client, IHandler handler, byte[] messageBytes)
bool IWebsocketModule.HandleReceivedMessage(Client.Client client, IHandler handler, WebsocketMessage receivedMessage)
{
string receivedMessage = WebsocketHelper.DecodeMessage(messageBytes);
client.Log(">" + receivedMessage);
string messageString = Encoding.UTF8.GetString(receivedMessage.Data);
client.Log(">" + messageString);
return false;
}
#endregion
@@ -17,6 +17,6 @@ namespace MTSC.Common.WebSockets.ClientModules
/// <param name="handler">Handler currently processing the message.</param>
/// <param name="messageBytes">Array containing the message.</param>
/// <returns>True if no other module should handle this message.</returns>
bool HandleReceivedMessage(Client.Client client, IHandler handler, byte[] messageBytes);
bool HandleReceivedMessage(Client.Client client, IHandler handler, WebsocketMessage messageBytes);
}
}
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Text;
using MTSC.Server;
using MTSC.Server.Handlers;
namespace MTSC.Common.WebSockets.ServerModules
{
public class BroadcastModule : IWebsocketModule
{
void IWebsocketModule.ConnectionClosed(Server.Server server, IHandler handler, ClientData client)
{
throw new NotImplementedException();
}
void IWebsocketModule.ConnectionInitialized(Server.Server server, IHandler handler, ClientData client)
{
throw new NotImplementedException();
}
bool IWebsocketModule.HandleReceivedMessage(Server.Server server, IHandler handler, ClientData client, WebsocketMessage receivedMessage)
{
receivedMessage.Masked = false;
foreach(ClientData otherClient in server.Clients)
{
(handler as WebsocketHandler).QueueMessage(otherClient, receivedMessage.Data);
}
return false;
}
}
}
@@ -10,19 +10,28 @@ namespace MTSC.Common.WebSockets.ServerModules
public class EchoModule : IWebsocketModule
{
#region Public Methods
public void SendMessage(WebsocketHandler handler, ClientData client, string message)
public void SendMessage(WebsocketHandler handler, ClientData client, WebsocketMessage message)
{
byte[] encodedMessage = WebsocketHelper.EncodeMessage(message);
byte[] encodedMessage = message.GetMessageBytes();
handler.QueueMessage(client, encodedMessage);
}
#endregion
#region Interface Implementation
bool IWebsocketModule.HandleReceivedMessage(IHandler handler, ClientData client, byte[] messageBytes)
bool IWebsocketModule.HandleReceivedMessage(Server.Server server, IHandler handler, ClientData client, WebsocketMessage receivedMessage)
{
string receivedMessage = WebsocketHelper.DecodeMessage(messageBytes);
SendMessage((WebsocketHandler)handler, client, receivedMessage);
return false;
}
void IWebsocketModule.ConnectionClosed(Server.Server server, IHandler handler, ClientData client)
{
throw new NotImplementedException();
}
void IWebsocketModule.ConnectionInitialized(Server.Server server, IHandler handler, ClientData client)
{
throw new NotImplementedException();
}
#endregion
}
}
@@ -8,6 +8,13 @@ namespace MTSC.Common.WebSockets.ServerModules
{
public interface IWebsocketModule
{
/// <summary>
/// Called when a connection has been initialized.
/// </summary>
/// <param name="server">Server object.</param>
/// <param name="handler">Handler currently processing.</param>
/// <param name="client">Client object.</param>
void ConnectionInitialized(Server.Server server, IHandler handler, ClientData client);
/// <summary>
/// Handle a received message.
/// </summary>
@@ -15,6 +22,13 @@ namespace MTSC.Common.WebSockets.ServerModules
/// <param name="messageBytes">Bytes of the message.</param>
/// <param name="client">Client data.</param>
/// <returns>True if no other module should process the message.</returns>
bool HandleReceivedMessage(IHandler handler, ClientData client, byte[] messageBytes);
bool HandleReceivedMessage(Server.Server server, IHandler handler, ClientData client, WebsocketMessage receivedMessage);
/// <summary>
/// Called when a connection has been closed.
/// </summary>
/// <param name="server">Server object.</param>
/// <param name="handler">Handler currently processing.</param>
/// <param name="client">Client object.</param>
void ConnectionClosed(Server.Server server, IHandler handler, ClientData client);
}
}
+2 -2
View File
@@ -18,7 +18,7 @@ namespace MTSC.Common.WebSockets
/// </summary>
/// <param name="bytes">Byte array containing the received message.</param>
/// <returns>String containing the received message.</returns>
public static string DecodeMessage(byte[] bytes)
public static string DecodeTextMessage(byte[] bytes)
{
byte b = bytes[1];
int dataLength = 0;
@@ -72,7 +72,7 @@ namespace MTSC.Common.WebSockets
/// </summary>
/// <param name="message">Message to encode.</param>
/// <returns>Byte array containing the encoded message.</returns>
public static byte[] EncodeMessage(string message, bool masked = false)
public static byte[] EncodeTextMessage(string message, bool masked = false)
{
byte[] response;
byte[] bytesRaw = Encoding.UTF8.GetBytes(message);
+208
View File
@@ -0,0 +1,208 @@
using MTSC.Exceptions;
using System;
using System.Collections.Generic;
using System.Text;
namespace MTSC.Common.WebSockets
{
/// <summary>
/// Class containing the bytes of a websocket received message.
/// </summary>
public class WebsocketMessage
{
public enum Opcodes
{
Data = 1,
Text = 2,
Close = 8,
Ping = 9,
Pong = 10
}
byte controlByte = new byte();
byte[] lengthBytes;
byte[] data;
/// <summary>
/// FIN bit.
/// </summary>
public bool FIN { get => (controlByte & 0x80) == 0x80; set => controlByte = (byte)(value? controlByte | 0x80 : controlByte & 0x7F);}
/// <summary>
/// Frame Opcode
/// </summary>
/// <remarks>Gets and sets the 4 lower bits of the first byte.</remarks>
public Opcodes Opcode { get => (Opcodes)(controlByte & 0xF);
set => controlByte = (byte)((controlByte & 0xF0) | ((int)value & 0xF)); }
/// <summary>
/// Mask bit.
/// </summary>
public bool Masked { get => (lengthBytes[0] & 0x80) == 0x1; set => lengthBytes[0] = (byte)(value ? lengthBytes[0] | 0x80 : lengthBytes[0] & 0x7F); }
/// <summary>
/// Length of message.
/// </summary>
public ulong MessageLength
{
get
{
if ((lengthBytes[0] & 0x7F) <= 125)
{
return (ulong)(lengthBytes[0] & 0x7F);
}
else if ((lengthBytes[0] & 0x7F) == 126)
{
return (ulong)((lengthBytes[2] << 8) + lengthBytes[1]);
}
else if ((lengthBytes[0] & 0x7F) == 127)
{
return (ulong)((lengthBytes[8] << 56) + (lengthBytes[7] << 48) + (lengthBytes[6] << 40) + (lengthBytes[5] << 32) +
(lengthBytes[4] << 24) + (lengthBytes[3] << 16) + (lengthBytes[2] << 8) + lengthBytes[1]);
}
else
{
return 0;
}
}
set
{
if(value <= 125)
{
if(lengthBytes.Length != 1)
{
byte[] newLengthBytes = new byte[1];
newLengthBytes[0] = lengthBytes[0];
lengthBytes = newLengthBytes;
}
lengthBytes[0] = (byte)((lengthBytes[0] & 0x80) | ((byte)value & 0x7F));
}
else if(value <= UInt16.MaxValue)
{
if (lengthBytes.Length != 3)
{
byte[] newLengthBytes = new byte[3];
newLengthBytes[0] = (byte)(lengthBytes[0] & 0x80);
newLengthBytes[0] += 126;
lengthBytes = newLengthBytes;
}
for(int i = 1; i < 3; i++)
{
lengthBytes[i] = (byte)(value & 0xFF);
value >>= 8;
}
}
else if(value <= UInt64.MaxValue)
{
if (lengthBytes.Length != 8)
{
byte[] newLengthBytes = new byte[9];
newLengthBytes[0] = (byte)(lengthBytes[0] & 0x80);
newLengthBytes[0] += 127;
lengthBytes = newLengthBytes;
}
for (int i = 1; i < 9; i++)
{
lengthBytes[i] = (byte)(value & 0xFF);
value >>= 8;
}
}
else
{
}
}
}
/// <summary>
/// Mask of the message.
/// </summary>
public byte[] Mask { get; set; }
/// <summary>
/// Data of the message.
/// </summary>
public byte[] Data
{
get => data;
set
{
MessageLength = (ulong)value.Length;
data = value;
}
}
/// <summary>
/// Creates a new instance of websocket message containing the given message.
/// </summary>
/// <param name="messageBytes">Byte array containing the message bytes.</param>
public WebsocketMessage(byte[] messageBytes)
{
controlByte = messageBytes[0];
Mask = new byte[4];
ulong dataLength = 0;
int dataIndex = 0;
if ((messageBytes[1] & 0x7F) <= 125)
{
lengthBytes = new byte[1];
lengthBytes[0] = messageBytes[1];
dataLength = (ulong)(messageBytes[1] & 0x7F);
dataIndex = 2;
}
else if ((messageBytes[1] & 0x7F) == 126)
{
dataLength = (ulong)((messageBytes[3] << 8) + messageBytes[2]);
lengthBytes = new byte[3];
Array.Copy(messageBytes, 1, lengthBytes, 0, 3);
dataIndex = 4;
}
else if ((messageBytes[1] & 0x7F) == 127)
{
dataLength = (ulong)((messageBytes[9] << 56) + (messageBytes[8] << 48) + (messageBytes[7] << 40) + (messageBytes[6] << 32) +
(messageBytes[5] << 24) + (messageBytes[4] << 16) + (messageBytes[3] << 8) + messageBytes[2]);
dataIndex = 10;
lengthBytes = new byte[9];
Array.Copy(messageBytes, 1, lengthBytes, 0, 9);
}
else
{
lengthBytes = new byte[0];
throw new InvalidWebsocketFormatException("Length formatting is wrong");
}
if ((messageBytes[1] & 0x80) > 0)
{
Array.Copy(messageBytes, dataIndex, Mask, 0, 4);
dataIndex += 4;
}
data = new byte[messageBytes.Length - dataIndex];
for(ulong i = 0; i < dataLength; i++)
{
data[i] = (byte)(messageBytes[(ulong)dataIndex + i] ^ Mask[i % 4]);
}
}
/// <summary>
/// Creates a new instance of websocket message.
/// </summary>
public WebsocketMessage()
{
controlByte = new byte();
Mask = new byte[4];
data = new byte[0];
lengthBytes = new byte[1];
}
/// <summary>
/// Get the packed message bytes.
/// </summary>
/// <returns>An array containing the message.</returns>
public byte[] GetMessageBytes()
{
if(data == null)
{
throw new NoDataException("There is no data in the message");
}
byte[] messageBytes = new byte[1 + lengthBytes.Length + (Masked ? 4 : 0) + data.Length];
messageBytes[0] = controlByte;
Array.Copy(lengthBytes, 0, messageBytes, 1, lengthBytes.Length);
if (Masked)
{
Array.Copy(Mask, 0, messageBytes, lengthBytes.Length, Mask.Length);
}
Array.Copy(data, 0, messageBytes, lengthBytes.Length + (Masked ? 4 : 0), data.Length);
return messageBytes;
}
}
}
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
namespace MTSC.Exceptions
{
public class InvalidWebsocketFormatException : Exception
{
public InvalidWebsocketFormatException()
{
}
public InvalidWebsocketFormatException(string message) : base(message)
{
}
public InvalidWebsocketFormatException(string message, Exception innerException) : base(message, innerException)
{
}
protected InvalidWebsocketFormatException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
+26
View File
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
namespace MTSC.Exceptions
{
public class NoDataException : Exception
{
public NoDataException()
{
}
public NoDataException(string message) : base(message)
{
}
public NoDataException(string message, Exception innerException) : base(message, innerException)
{
}
protected NoDataException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
+11 -3
View File
@@ -1,4 +1,5 @@
using MTSC.Common.Http;
using MTSC.Common.WebSockets;
using MTSC.Common.WebSockets.ServerModules;
using System;
using System.Collections.Concurrent;
@@ -16,6 +17,7 @@ namespace MTSC.Server.Handlers
private static string WebsocketProtocolVersionKey = "Sec-WebSocket-Version";
private static string GlobalUniqueIdentifier = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private static SHA1 sha1Provider = SHA1.Create();
private static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
private enum SocketState
{
Initial,
@@ -45,7 +47,12 @@ namespace MTSC.Server.Handlers
/// <param name="message">Message to be sent to client.</param>
public void QueueMessage(ClientData client, byte[] message)
{
messageQueue.Enqueue(new Tuple<ClientData, byte[]>(client, message));
WebsocketMessage sendMessage = new WebsocketMessage();
sendMessage.Data = message;
sendMessage.FIN = true;
rng.GetBytes(sendMessage.Mask);
sendMessage.Opcode = WebsocketMessage.Opcodes.Text;
messageQueue.Enqueue(new Tuple<ClientData, byte[]>(client, sendMessage.GetMessageBytes()));
}
#endregion
#region Handler Implementation
@@ -95,9 +102,10 @@ namespace MTSC.Server.Handlers
}
else if(webSockets[client] == SocketState.Established)
{
foreach(IWebsocketModule websocketModule in websocketModules)
WebsocketMessage receivedMessage = new WebsocketMessage(message.MessageBytes);
foreach (IWebsocketModule websocketModule in websocketModules)
{
if(websocketModule.HandleReceivedMessage(this, client, message.MessageBytes))
if(websocketModule.HandleReceivedMessage(server, this, client, receivedMessage))
{
break;
}
+5
View File
@@ -61,6 +61,10 @@
var message = document.getElementById("textarea").value;
doSend(message);
}
function closeSocket(){
websocket.close();
}
window.addEventListener("load", init, false);
@@ -70,5 +74,6 @@
<textarea id="textarea" rows="4" cols="50">Write here messages.</textarea>
<button type="button" onclick="sendMessage()">Send!</button>
<button type="button" onclick="closeSocket()">Close connection!</button>
<div id="output"></div>