diff --git a/MTSC-TestClient/Program.cs b/MTSC-TestClient/Program.cs
index c7f7710..133745c 100644
--- a/MTSC-TestClient/Program.cs
+++ b/MTSC-TestClient/Program.cs
@@ -1,5 +1,6 @@
using MTSC.Client;
using MTSC.Client.Handlers;
+using MTSC.Common.WebSockets.ClientModules;
using MTSC.Logging;
using System;
@@ -9,15 +10,23 @@ namespace MTSC_TestClient
{
static void Main(string[] args)
{
- Client client = new Client(true);
+ Client client = new Client();
+ WebsocketHandler websocketHandler = new WebsocketHandler();
+ ChatModule chatModule = new ChatModule();
client
.SetServerAddress("127.0.0.1")
- .SetPort(555)
- .AddHandler(new EncryptionHandler())
+ .SetPort(80)
+ .AddHandler(websocketHandler.AddModule(chatModule))
+ //.AddHandler(new EncryptionHandler())
//.AddHandler(new BroadcastHandler())
.AddLogger(new ConsoleLogger())
.AddLogger(new DebugConsoleLogger())
.Connect();
+ while (true)
+ {
+ string message = Console.ReadLine();
+ chatModule.SendMessage(websocketHandler, message);
+ }
}
}
}
diff --git a/MTSC/Client/Handlers/WebsocketHandler.cs b/MTSC/Client/Handlers/WebsocketHandler.cs
new file mode 100644
index 0000000..23702bf
--- /dev/null
+++ b/MTSC/Client/Handlers/WebsocketHandler.cs
@@ -0,0 +1,138 @@
+using MTSC.Common.Http;
+using MTSC.Common.WebSockets.ClientModules;
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace MTSC.Client.Handlers
+{
+ ///
+ /// Handler implementing websocket protocol.
+ ///
+ public class WebsocketHandler : IHandler
+ {
+ private static string WebsocketHeaderAcceptKey = "Sec-WebSocket-Accept";
+ private static string WebsocketHeaderKey = "Sec-WebSocket-Key";
+ private static string WebsocketProtocolKey = "Sec-WebSocket-Protocol";
+ private static string WebsocketProtocolVersionKey = "Sec-WebSocket-Version";
+ private static string GlobalUniqueIdentifier = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ private static SHA1 sha1Provider = SHA1.Create();
+ private enum SocketState
+ {
+ Initial,
+ Handshaking,
+ Established,
+ Closed
+ }
+
+ #region Fields
+ SocketState state = SocketState.Initial;
+ Queue messageQueue = new Queue();
+ List websocketModules = new List();
+ string expectedguid = string.Empty;
+ #endregion
+ #region Properties
+ public string WebsocketURI { get; set; }
+ #endregion
+ #region Constructors
+ public WebsocketHandler()
+ {
+ WebsocketURI = "/";
+ }
+ #endregion
+ #region Public Methods
+ ///
+ /// Add a module to the websocket handler.
+ ///
+ /// Module to be added.
+ /// This handler object.
+ public WebsocketHandler AddModule(IWebsocketModule websocketModule)
+ {
+ websocketModules.Add(websocketModule);
+ return this;
+ }
+ ///
+ /// Add a message to the queue to be sent.
+ ///
+ /// Message to be sent.
+ public void QueueMessage(byte[] message)
+ {
+ messageQueue.Enqueue(message);
+ }
+ #endregion
+ #region Handler Implementation
+ void IHandler.Disconnected(Client client)
+ {
+
+ }
+
+ bool IHandler.HandleReceivedMessage(Client client, Message message)
+ {
+ if(state == SocketState.Handshaking)
+ {
+ HttpMessage response = new HttpMessage();
+ response.ParseResponse(message.MessageBytes);
+ if(response.StatusCode == HttpMessage.StatusCodes.SwitchingProtocols &&
+ response["Upgrade"] == "websocket" &&
+ response[HttpMessage.GeneralHeadersEnum.Connection].ToLower() == "upgrade" &&
+ response[WebsocketHeaderAcceptKey].Trim() == expectedguid)
+ {
+ state = SocketState.Established;
+ }
+ return true;
+ }
+ else if(state == SocketState.Established)
+ {
+ foreach(IWebsocketModule websocketModule in websocketModules)
+ {
+ if(websocketModule.HandleReceivedMessage(client, this, message.MessageBytes))
+ {
+ break;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ bool IHandler.HandleSendMessage(Client client, ref Message message)
+ {
+ return false;
+ }
+
+ bool IHandler.InitializeConnection(Client client)
+ {
+ state = SocketState.Handshaking;
+ string handshakeGuid = Guid.NewGuid().ToString();
+ string handshakeKey = handshakeGuid+ GlobalUniqueIdentifier;
+ expectedguid = Convert.ToBase64String(sha1Provider.ComputeHash(Encoding.UTF8.GetBytes(handshakeKey)));
+ HttpMessage beginRequest = new HttpMessage();
+ beginRequest.Method = HttpMessage.MethodEnum.Get;
+ beginRequest.RequestURI = WebsocketURI;
+ beginRequest[HttpMessage.RequestHeadersEnum.Host] = client.Address;
+ beginRequest[HttpMessage.GeneralHeadersEnum.Connection] = "Upgrade";
+ beginRequest[WebsocketHeaderKey] = handshakeGuid;
+ beginRequest["Origin"] = client.Address;
+ beginRequest[WebsocketProtocolKey] = "chat";
+ beginRequest[WebsocketProtocolVersionKey] = "13";
+ client.QueueMessage(beginRequest.GetRequest());
+ return true;
+ }
+
+ bool IHandler.PreHandleReceivedMessage(Client client, ref Message message)
+ {
+ return false;
+ }
+
+ void IHandler.Tick(Client client)
+ {
+ while(messageQueue.Count > 0)
+ {
+ byte[] message = messageQueue.Dequeue();
+ client.QueueMessage(message);
+ }
+ }
+ #endregion
+ }
+}
diff --git a/MTSC/Common/Http/HttpMessage.cs b/MTSC/Common/Http/HttpMessage.cs
index 424e823..2cd6a28 100644
--- a/MTSC/Common/Http/HttpMessage.cs
+++ b/MTSC/Common/Http/HttpMessage.cs
@@ -222,7 +222,7 @@ namespace MTSC.Common.Http
public byte[] GetRequest()
{
StringBuilder requestString = new StringBuilder();
- requestString.Append(Method.ToString()).Append(SP).Append(RequestURI.ToString()).Append(SP).Append(HTTPVER).Append(CRLF);
+ requestString.Append(methods[(int)Method]).Append(SP).Append(RequestURI.ToString()).Append(SP).Append(HTTPVER).Append(CRLF);
foreach(KeyValuePair header in headers)
{
requestString.Append(header.Key).Append(':').Append(SP).Append(header.Value).Append(CRLF);
@@ -525,7 +525,7 @@ namespace MTSC.Common.Http
#region Private Methods
private MethodEnum GetMethod(string methodString)
{
- int index = Array.IndexOf(methods, methodString);
+ int index = Array.IndexOf(methods, methodString.ToUpper());
return (MethodEnum)index;
}
diff --git a/MTSC/Common/WebSockets/ClientModules/ChatModule.cs b/MTSC/Common/WebSockets/ClientModules/ChatModule.cs
new file mode 100644
index 0000000..cec2a5b
--- /dev/null
+++ b/MTSC/Common/WebSockets/ClientModules/ChatModule.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using MTSC.Client.Handlers;
+
+namespace MTSC.Common.WebSockets.ClientModules
+{
+ public class ChatModule : IWebsocketModule
+ {
+ #region Public Methods
+ public void SendMessage(WebsocketHandler websocketHandler, string message)
+ {
+ byte[] encodedMessage = WebsocketHelper.EncodeMessage(message);
+ websocketHandler.QueueMessage(encodedMessage);
+ }
+ #endregion
+ #region Interface Implementation
+ bool IWebsocketModule.HandleReceivedMessage(Client.Client client, IHandler handler, byte[] messageBytes)
+ {
+ string receivedMessage = WebsocketHelper.DecodeMessage(messageBytes);
+ client.Log(">" + receivedMessage);
+ return false;
+ }
+ #endregion
+ }
+}
diff --git a/MTSC/Common/WebSockets/ClientModules/IWebsocketModule.cs b/MTSC/Common/WebSockets/ClientModules/IWebsocketModule.cs
new file mode 100644
index 0000000..a145173
--- /dev/null
+++ b/MTSC/Common/WebSockets/ClientModules/IWebsocketModule.cs
@@ -0,0 +1,22 @@
+using MTSC.Client.Handlers;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MTSC.Common.WebSockets.ClientModules
+{
+ ///
+ /// Interface for websocket modules.
+ ///
+ public interface IWebsocketModule
+ {
+ ///
+ /// Handle received message.
+ ///
+ /// Client object.
+ /// 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);
+ }
+}
diff --git a/MTSC/Common/WebSockets/ServerModules/EchoModule.cs b/MTSC/Common/WebSockets/ServerModules/EchoModule.cs
index 7c5b792..61bab89 100644
--- a/MTSC/Common/WebSockets/ServerModules/EchoModule.cs
+++ b/MTSC/Common/WebSockets/ServerModules/EchoModule.cs
@@ -12,93 +12,14 @@ namespace MTSC.Common.WebSockets.ServerModules
#region Public Methods
public void SendMessage(WebsocketHandler handler, ClientData client, string message)
{
- byte[] encodedMessage = EncodeMessage(message);
+ byte[] encodedMessage = WebsocketHelper.EncodeMessage(message);
handler.QueueMessage(client, encodedMessage);
}
#endregion
- #region Private Methods
- private string DecodeMessage(byte[] bytes)
- {
- byte secondByte = bytes[1];
- int dataLength = secondByte & 127;
- int indexFirstMask = 2;
- if (dataLength == 126)
- indexFirstMask = 4;
- else if (dataLength == 127)
- indexFirstMask = 10;
-
- IEnumerable keys = bytes.Skip(indexFirstMask).Take(4);
- int indexFirstDataByte = indexFirstMask + 4;
-
- byte[] decoded = new byte[bytes.Length - indexFirstDataByte];
- for (int i = indexFirstDataByte, j = 0; i < bytes.Length; i++, j++)
- {
- decoded[j] = (byte)(bytes[i] ^ keys.ElementAt(j % 4));
- }
-
- return Encoding.UTF8.GetString(decoded, 0, decoded.Length);
- }
- private static byte[] EncodeMessage(string message)
- {
- byte[] response;
- byte[] bytesRaw = Encoding.UTF8.GetBytes(message);
- byte[] frame = new byte[10];
-
- int indexStartRawData = -1;
- int length = bytesRaw.Length;
-
- frame[0] = (byte)129;
- if (length <= 125)
- {
- frame[1] = (byte)length;
- indexStartRawData = 2;
- }
- else if (length >= 126 && length <= 65535)
- {
- frame[1] = (byte)126;
- frame[2] = (byte)((length >> 8) & 255);
- frame[3] = (byte)(length & 255);
- indexStartRawData = 4;
- }
- else
- {
- frame[1] = (byte)127;
- frame[2] = (byte)((length >> 56) & 255);
- frame[3] = (byte)((length >> 48) & 255);
- frame[4] = (byte)((length >> 40) & 255);
- frame[5] = (byte)((length >> 32) & 255);
- frame[6] = (byte)((length >> 24) & 255);
- frame[7] = (byte)((length >> 16) & 255);
- frame[8] = (byte)((length >> 8) & 255);
- frame[9] = (byte)(length & 255);
-
- indexStartRawData = 10;
- }
-
- response = new byte[indexStartRawData + length];
-
- int i, reponseIdx = 0;
-
- //Add the frame bytes to the reponse
- for (i = 0; i < indexStartRawData; i++)
- {
- response[reponseIdx] = frame[i];
- reponseIdx++;
- }
-
- //Add the data bytes to the response
- for (i = 0; i < length; i++)
- {
- response[reponseIdx] = bytesRaw[i];
- reponseIdx++;
- }
- return response;
- }
- #endregion
#region Interface Implementation
bool IWebsocketModule.HandleReceivedMessage(IHandler handler, ClientData client, byte[] messageBytes)
{
- string receivedMessage = DecodeMessage(messageBytes);
+ string receivedMessage = WebsocketHelper.DecodeMessage(messageBytes);
SendMessage((WebsocketHandler)handler, client, receivedMessage);
return false;
}
diff --git a/MTSC/Common/WebSockets/WebsocketHelper.cs b/MTSC/Common/WebSockets/WebsocketHelper.cs
new file mode 100644
index 0000000..7b192c6
--- /dev/null
+++ b/MTSC/Common/WebSockets/WebsocketHelper.cs
@@ -0,0 +1,116 @@
+using MTSC.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MTSC.Common.WebSockets
+{
+ ///
+ /// Class with websocket helper functions.
+ ///
+ public class WebsocketHelper
+ {
+ ///
+ /// Decode text websocket message.
+ ///
+ /// Byte array containing the received message.
+ /// String containing the received message.
+ public static string DecodeMessage(byte[] bytes)
+ {
+ if(bytes[0] != 129)
+ {
+ throw new InvalidFirstByteException();
+ }
+ int startIndex = 1;
+ int length = 0;
+ if(bytes[1] <= 125)
+ {
+ startIndex = 2;
+ length = bytes[1];
+ }
+ else if(bytes[1] == 126)
+ {
+ startIndex = 4;
+ length = bytes[2] << 8;
+ length += bytes[3];
+ }
+ else if(bytes[1] == 127)
+ {
+ startIndex = 10;
+ length = bytes[2] << 56;
+ length += bytes[3] << 48;
+ length += bytes[4] << 40;
+ length += bytes[5] << 32;
+ length += bytes[6] << 24;
+ length += bytes[7] << 16;
+ length += bytes[8] << 8;
+ length += bytes[9];
+ }
+ byte[] message = new byte[length];
+ Array.Copy(bytes, startIndex, message, 0, length);
+ return Encoding.UTF8.GetString(message);
+ }
+ ///
+ /// Encode text websocket message.
+ ///
+ /// Message to encode.
+ /// Byte array containing the encoded message.
+ public static byte[] EncodeMessage(string message)
+ {
+ byte[] response;
+ byte[] bytesRaw = Encoding.UTF8.GetBytes(message);
+ byte[] frame = new byte[10];
+
+ int indexStartRawData = -1;
+ int length = bytesRaw.Length;
+
+ frame[0] = (byte)129;
+ if (length <= 125)
+ {
+ frame[1] = (byte)length;
+ indexStartRawData = 2;
+ }
+ else if (length >= 126 && length <= 65535)
+ {
+ frame[1] = (byte)126;
+ frame[2] = (byte)((length >> 8) & 255);
+ frame[3] = (byte)(length & 255);
+ indexStartRawData = 4;
+ }
+ else
+ {
+ frame[1] = (byte)127;
+ frame[2] = (byte)((length >> 56) & 255);
+ frame[3] = (byte)((length >> 48) & 255);
+ frame[4] = (byte)((length >> 40) & 255);
+ frame[5] = (byte)((length >> 32) & 255);
+ frame[6] = (byte)((length >> 24) & 255);
+ frame[7] = (byte)((length >> 16) & 255);
+ frame[8] = (byte)((length >> 8) & 255);
+ frame[9] = (byte)(length & 255);
+
+ indexStartRawData = 10;
+ }
+
+ response = new byte[indexStartRawData + length];
+
+ int i, reponseIdx = 0;
+
+ //Add the frame bytes to the reponse
+ for (i = 0; i < indexStartRawData; i++)
+ {
+ response[reponseIdx] = frame[i];
+ reponseIdx++;
+ }
+
+ //Add the data bytes to the response
+ for (i = 0; i < length; i++)
+ {
+ response[reponseIdx] = bytesRaw[i];
+ reponseIdx++;
+ }
+ return response;
+ }
+ }
+}
diff --git a/MTSC/Exceptions/InvalidFirstByteException.cs b/MTSC/Exceptions/InvalidFirstByteException.cs
new file mode 100644
index 0000000..d61cf8f
--- /dev/null
+++ b/MTSC/Exceptions/InvalidFirstByteException.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace MTSC.Exceptions
+{
+ ///
+ /// Exception in case of first invalid byte for websocket communication.
+ ///
+ public class InvalidFirstByteException : Exception
+ {
+ public InvalidFirstByteException()
+ {
+ }
+
+ public InvalidFirstByteException(string message) : base(message)
+ {
+ }
+
+ public InvalidFirstByteException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected InvalidFirstByteException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/MTSC/MTSC.csproj b/MTSC/MTSC.csproj
index 161319f..de0a40e 100644
--- a/MTSC/MTSC.csproj
+++ b/MTSC/MTSC.csproj
@@ -15,8 +15,4 @@
AnyCPU;x64
-
-
-
-
diff --git a/MTSC/Server/Handlers/WebsocketHandler.cs b/MTSC/Server/Handlers/WebsocketHandler.cs
index 0b7940e..fe03096 100644
--- a/MTSC/Server/Handlers/WebsocketHandler.cs
+++ b/MTSC/Server/Handlers/WebsocketHandler.cs
@@ -23,10 +23,11 @@ namespace MTSC.Server.Handlers
Established,
Closed
}
+ #region Fields
ConcurrentDictionary webSockets = new ConcurrentDictionary();
ConcurrentQueue> messageQueue = new ConcurrentQueue>();
List websocketModules = new List();
-
+ #endregion
#region Public Methods
///
/// Add a webSocket module onto the server.
@@ -100,6 +101,7 @@ namespace MTSC.Server.Handlers
break;
}
}
+ return true;
}
return false;
}