diff --git a/MTSC-TestServer/Program.cs b/MTSC-TestServer/Program.cs
index 20e73f7..7cb9fb3 100644
--- a/MTSC-TestServer/Program.cs
+++ b/MTSC-TestServer/Program.cs
@@ -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();
}
diff --git a/MTSC/Client/Handlers/WebsocketHandler.cs b/MTSC/Client/Handlers/WebsocketHandler.cs
index 23702bf..6b570bd 100644
--- a/MTSC/Client/Handlers/WebsocketHandler.cs
+++ b/MTSC/Client/Handlers/WebsocketHandler.cs
@@ -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;
}
diff --git a/MTSC/Common/WebSockets/ClientModules/ChatModule.cs b/MTSC/Common/WebSockets/ClientModules/ChatModule.cs
index 216fa79..34a0cc7 100644
--- a/MTSC/Common/WebSockets/ClientModules/ChatModule.cs
+++ b/MTSC/Common/WebSockets/ClientModules/ChatModule.cs
@@ -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
diff --git a/MTSC/Common/WebSockets/ClientModules/IWebsocketModule.cs b/MTSC/Common/WebSockets/ClientModules/IWebsocketModule.cs
index a145173..ddfca0c 100644
--- a/MTSC/Common/WebSockets/ClientModules/IWebsocketModule.cs
+++ b/MTSC/Common/WebSockets/ClientModules/IWebsocketModule.cs
@@ -17,6 +17,6 @@ namespace MTSC.Common.WebSockets.ClientModules
/// Handler currently processing the message.
/// Array containing the message.
/// True if no other module should handle this message.
- bool HandleReceivedMessage(Client.Client client, IHandler handler, byte[] messageBytes);
+ bool HandleReceivedMessage(Client.Client client, IHandler handler, WebsocketMessage messageBytes);
}
}
diff --git a/MTSC/Common/WebSockets/ServerModules/BroadcastModule.cs b/MTSC/Common/WebSockets/ServerModules/BroadcastModule.cs
new file mode 100644
index 0000000..a921ec6
--- /dev/null
+++ b/MTSC/Common/WebSockets/ServerModules/BroadcastModule.cs
@@ -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;
+ }
+ }
+}
diff --git a/MTSC/Common/WebSockets/ServerModules/EchoModule.cs b/MTSC/Common/WebSockets/ServerModules/EchoModule.cs
index 61bab89..7b60b04 100644
--- a/MTSC/Common/WebSockets/ServerModules/EchoModule.cs
+++ b/MTSC/Common/WebSockets/ServerModules/EchoModule.cs
@@ -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
}
}
diff --git a/MTSC/Common/WebSockets/ServerModules/IWebsocketModule.cs b/MTSC/Common/WebSockets/ServerModules/IWebsocketModule.cs
index 179615f..c0709e8 100644
--- a/MTSC/Common/WebSockets/ServerModules/IWebsocketModule.cs
+++ b/MTSC/Common/WebSockets/ServerModules/IWebsocketModule.cs
@@ -8,6 +8,13 @@ namespace MTSC.Common.WebSockets.ServerModules
{
public interface IWebsocketModule
{
+ ///
+ /// Called when a connection has been initialized.
+ ///
+ /// Server object.
+ /// Handler currently processing.
+ /// Client object.
+ void ConnectionInitialized(Server.Server server, IHandler handler, ClientData client);
///
/// Handle a received message.
///
@@ -15,6 +22,13 @@ namespace MTSC.Common.WebSockets.ServerModules
/// Bytes of the message.
/// Client data.
/// True if no other module should process the message.
- bool HandleReceivedMessage(IHandler handler, ClientData client, byte[] messageBytes);
+ bool HandleReceivedMessage(Server.Server server, IHandler handler, ClientData client, WebsocketMessage receivedMessage);
+ ///
+ /// Called when a connection has been closed.
+ ///
+ /// Server object.
+ /// Handler currently processing.
+ /// Client object.
+ void ConnectionClosed(Server.Server server, IHandler handler, ClientData client);
}
}
diff --git a/MTSC/Common/WebSockets/WebsocketHelper.cs b/MTSC/Common/WebSockets/WebsocketHelper.cs
index 5c04a7f..843c3ff 100644
--- a/MTSC/Common/WebSockets/WebsocketHelper.cs
+++ b/MTSC/Common/WebSockets/WebsocketHelper.cs
@@ -18,7 +18,7 @@ namespace MTSC.Common.WebSockets
///
/// Byte array containing the received message.
/// String containing the received message.
- 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
///
/// Message to encode.
/// Byte array containing the encoded message.
- 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);
diff --git a/MTSC/Common/WebSockets/WebsocketMessage.cs b/MTSC/Common/WebSockets/WebsocketMessage.cs
new file mode 100644
index 0000000..40ba924
--- /dev/null
+++ b/MTSC/Common/WebSockets/WebsocketMessage.cs
@@ -0,0 +1,208 @@
+using MTSC.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MTSC.Common.WebSockets
+{
+ ///
+ /// Class containing the bytes of a websocket received message.
+ ///
+ public class WebsocketMessage
+ {
+ public enum Opcodes
+ {
+ Data = 1,
+ Text = 2,
+ Close = 8,
+ Ping = 9,
+ Pong = 10
+ }
+
+ byte controlByte = new byte();
+ byte[] lengthBytes;
+ byte[] data;
+ ///
+ /// FIN bit.
+ ///
+ public bool FIN { get => (controlByte & 0x80) == 0x80; set => controlByte = (byte)(value? controlByte | 0x80 : controlByte & 0x7F);}
+ ///
+ /// Frame Opcode
+ ///
+ /// Gets and sets the 4 lower bits of the first byte.
+ public Opcodes Opcode { get => (Opcodes)(controlByte & 0xF);
+ set => controlByte = (byte)((controlByte & 0xF0) | ((int)value & 0xF)); }
+ ///
+ /// Mask bit.
+ ///
+ public bool Masked { get => (lengthBytes[0] & 0x80) == 0x1; set => lengthBytes[0] = (byte)(value ? lengthBytes[0] | 0x80 : lengthBytes[0] & 0x7F); }
+ ///
+ /// Length of message.
+ ///
+ 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
+ {
+
+ }
+ }
+ }
+ ///
+ /// Mask of the message.
+ ///
+ public byte[] Mask { get; set; }
+ ///
+ /// Data of the message.
+ ///
+ public byte[] Data
+ {
+ get => data;
+ set
+ {
+ MessageLength = (ulong)value.Length;
+ data = value;
+ }
+ }
+
+ ///
+ /// Creates a new instance of websocket message containing the given message.
+ ///
+ /// Byte array containing the message bytes.
+ 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]);
+ }
+ }
+ ///
+ /// Creates a new instance of websocket message.
+ ///
+ public WebsocketMessage()
+ {
+ controlByte = new byte();
+ Mask = new byte[4];
+ data = new byte[0];
+ lengthBytes = new byte[1];
+ }
+ ///
+ /// Get the packed message bytes.
+ ///
+ /// An array containing the message.
+ 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;
+ }
+ }
+}
diff --git a/MTSC/Exceptions/InvalidWebsocketFormatException.cs b/MTSC/Exceptions/InvalidWebsocketFormatException.cs
new file mode 100644
index 0000000..066d485
--- /dev/null
+++ b/MTSC/Exceptions/InvalidWebsocketFormatException.cs
@@ -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)
+ {
+ }
+ }
+}
diff --git a/MTSC/Exceptions/NoDataException.cs b/MTSC/Exceptions/NoDataException.cs
new file mode 100644
index 0000000..6bd3348
--- /dev/null
+++ b/MTSC/Exceptions/NoDataException.cs
@@ -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)
+ {
+ }
+ }
+}
diff --git a/MTSC/Server/Handlers/WebsocketHandler.cs b/MTSC/Server/Handlers/WebsocketHandler.cs
index 408a38a..c3efd4a 100644
--- a/MTSC/Server/Handlers/WebsocketHandler.cs
+++ b/MTSC/Server/Handlers/WebsocketHandler.cs
@@ -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
/// Message to be sent to client.
public void QueueMessage(ClientData client, byte[] message)
{
- messageQueue.Enqueue(new Tuple(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(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;
}
diff --git a/index.html b/index.html
index 0e38cb9..af36998 100644
--- a/index.html
+++ b/index.html
@@ -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 @@
+
\ No newline at end of file