From f9c09fcabac8dcbf42edfeb894bf6893b449f8ed Mon Sep 17 00:00:00 2001 From: Alex Macocian Date: Thu, 25 Jul 2019 16:01:02 +0300 Subject: [PATCH] Implementing standard websocket packet. --- MTSC-TestServer/Program.cs | 2 +- MTSC/Client/Handlers/WebsocketHandler.cs | 4 +- .../WebSockets/ClientModules/ChatModule.cs | 8 +- .../ClientModules/IWebsocketModule.cs | 2 +- .../ServerModules/BroadcastModule.cs | 31 +++ .../WebSockets/ServerModules/EchoModule.cs | 17 +- .../ServerModules/IWebsocketModule.cs | 16 +- MTSC/Common/WebSockets/WebsocketHelper.cs | 4 +- MTSC/Common/WebSockets/WebsocketMessage.cs | 208 ++++++++++++++++++ .../InvalidWebsocketFormatException.cs | 26 +++ MTSC/Exceptions/NoDataException.cs | 26 +++ MTSC/Server/Handlers/WebsocketHandler.cs | 14 +- index.html | 5 + 13 files changed, 346 insertions(+), 17 deletions(-) create mode 100644 MTSC/Common/WebSockets/ServerModules/BroadcastModule.cs create mode 100644 MTSC/Common/WebSockets/WebsocketMessage.cs create mode 100644 MTSC/Exceptions/InvalidWebsocketFormatException.cs create mode 100644 MTSC/Exceptions/NoDataException.cs 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