diff --git a/MTSC.UnitTests/E2ETests.cs b/MTSC.UnitTests/E2ETests.cs index af8a579..0670d1f 100644 --- a/MTSC.UnitTests/E2ETests.cs +++ b/MTSC.UnitTests/E2ETests.cs @@ -34,7 +34,10 @@ namespace MTSC.UnitTests [ClassInitialize] public static void InitializeServer(TestContext testContext) { + ServicePointManager.ServerCertificateValidationCallback = (_, __, ___, ____) => true; + Server = new ServerSide.Server(800) + .WithCertificate(new X509Certificate2("myCert.cer")) .AddHandler(new WebsocketRoutingHandler() .AddRoute("echo", new EchoWebsocketModule() .WithReceiveTemplateProvider((message) => UTF8Encoding.UTF8.GetString(message.Data)) @@ -49,6 +52,7 @@ namespace MTSC.UnitTests .AddRoute(HttpMessage.HttpMethods.Get, "", new Http200Module()) .AddRoute(HttpMessage.HttpMethods.Get, "query", new TestQueryModule()) .AddRoute(HttpMessage.HttpMethods.Get, "echo", new EchoModule()) + .AddRoute(HttpMessage.HttpMethods.Post, "echo", new EchoModule()) .AddRoute(HttpMessage.HttpMethods.Get, "long-running", new LongRunningModule()) .WithFragmentsExpirationTime(TimeSpan.FromMilliseconds(500)) .WithMaximumSize(300)) @@ -63,11 +67,11 @@ namespace MTSC.UnitTests public async Task ServerRespondsDuringLongRunningTask() { HttpClient httpClient = new HttpClient(); - httpClient.BaseAddress = new Uri("http://localhost:800"); + httpClient.BaseAddress = new Uri("https://localhost:800"); var longRunningTask = httpClient.GetAsync("long-running"); int responses = 0; HttpClient client2 = new HttpClient(); - client2.BaseAddress = new Uri("http://localhost:800"); + client2.BaseAddress = new Uri("https://localhost:800"); while (!longRunningTask.IsCompleted) { var echoResponse = await client2.GetAsync("echo"); @@ -82,7 +86,7 @@ namespace MTSC.UnitTests public void HelloWorldHTTP() { HttpClient httpClient = new HttpClient(); - httpClient.BaseAddress = new Uri("http://localhost:800"); + httpClient.BaseAddress = new Uri("https://localhost:800"); var result = httpClient.GetAsync("").Result; Assert.AreEqual(result.StatusCode, System.Net.HttpStatusCode.OK); } @@ -96,6 +100,7 @@ namespace MTSC.UnitTests client.SetServerAddress("127.0.0.1") .SetPort(800) .AddHandler(notifyHandler) + .WithSsl(true) .Connect(); HttpRequest request = new HttpRequest(); @@ -120,6 +125,8 @@ namespace MTSC.UnitTests } } HttpResponse response = HttpResponse.FromBytes(receivedMessage); + //Trim the null bytes from encryption/decryption + response.BodyString = response.BodyString.Trim('\0'); Assert.AreEqual(response.StatusCode, HttpMessage.StatusCodes.OK); Assert.AreEqual(response.BodyString, "Brought a message to you my guy!"); } @@ -133,6 +140,7 @@ namespace MTSC.UnitTests client.SetServerAddress("127.0.0.1") .SetPort(800) .AddHandler(notifyHandler) + .WithSsl(true) .Connect(); HttpRequest request = new HttpRequest(); @@ -168,6 +176,7 @@ namespace MTSC.UnitTests client.SetServerAddress("127.0.0.1") .SetPort(800) .AddHandler(notifyHandler) + .WithSsl(true) .Connect(); HttpRequest request = new HttpRequest(); @@ -199,7 +208,7 @@ namespace MTSC.UnitTests [TestMethod] public void GetWithQueryHttp() { - var builder = new UriBuilder("http://localhost:800/query"); + var builder = new UriBuilder("https://localhost:800/query"); var query = HttpUtility.ParseQueryString(builder.Query); query["key1"] = "value1"; query["key2"] = "value2"; @@ -217,7 +226,7 @@ namespace MTSC.UnitTests { byte[] bytes = new byte[100]; ClientWebSocket client = new ClientWebSocket(); - client.ConnectAsync(new Uri("ws://localhost:800/echo"), CancellationToken.None).Wait(); + client.ConnectAsync(new Uri("wss://localhost:800/echo"), CancellationToken.None).Wait(); client.SendAsync(ASCIIEncoding.ASCII.GetBytes("Hello world!"), WebSocketMessageType.Text, true, CancellationToken.None).Wait(); client.ReceiveAsync(bytes, CancellationToken.None).Wait(); var resultString = ASCIIEncoding.ASCII.GetString(bytes, 0, 12); @@ -228,7 +237,7 @@ namespace MTSC.UnitTests public void HTTPStressTest() { var httpClient = new HttpClient(); - httpClient.BaseAddress = new Uri("http://localhost:800"); + httpClient.BaseAddress = new Uri("https://localhost:800"); for(int i = 0; i < stressIterations; i++) { var startTime = DateTime.Now; diff --git a/MTSC.UnitTests/MTSC.UnitTests.csproj b/MTSC.UnitTests/MTSC.UnitTests.csproj index 4aa9bee..fd8ca1b 100644 --- a/MTSC.UnitTests/MTSC.UnitTests.csproj +++ b/MTSC.UnitTests/MTSC.UnitTests.csproj @@ -18,6 +18,9 @@ + + PreserveNewest + PreserveNewest diff --git a/MTSC.UnitTests/myCert.cer b/MTSC.UnitTests/myCert.cer new file mode 100644 index 0000000..caf8e2c Binary files /dev/null and b/MTSC.UnitTests/myCert.cer differ diff --git a/MTSC/ClientSide/Client.cs b/MTSC/ClientSide/Client.cs index 0c5becf..69f830a 100644 --- a/MTSC/ClientSide/Client.cs +++ b/MTSC/ClientSide/Client.cs @@ -51,6 +51,11 @@ namespace MTSC.Client } #endregion #region Public Methods + public Client WithSsl(bool ssl) + { + this.useSsl = ssl; + return this; + } /// /// Add a message to the message queue. /// diff --git a/MTSC/Common/Http/PartialHttpRequest.cs b/MTSC/Common/Http/PartialHttpRequest.cs index f2cd60d..0441af3 100644 --- a/MTSC/Common/Http/PartialHttpRequest.cs +++ b/MTSC/Common/Http/PartialHttpRequest.cs @@ -407,10 +407,6 @@ namespace MTSC.Common.Http this.Body = ms.ReadRemainingBytes(); } } - /* - * Trim all trailing null characters left over from SSL encryption. - */ - this.BodyString = this.BodyString.Trim('\0'); Complete = true; return; } diff --git a/MTSC/MTSC.csproj b/MTSC/MTSC.csproj index 7fe2672..1b0dd60 100644 --- a/MTSC/MTSC.csproj +++ b/MTSC/MTSC.csproj @@ -2,15 +2,15 @@ Library - netcoreapp2.1;net48;netstandard2.0;netcoreapp3.0 + netcoreapp2.1;net48;netstandard2.0;netcoreapp3.0;netcoreapp3.1 - 2.4.1 + 2.4.2 Alexandru-Victor Macocian MTSC Modular TCP Server and Client - 0.2.4.1 - 0.2.4.1 + 0.2.4.2 + 0.2.4.2 true AnyCPU;x64 https://github.com/AlexMacocian/MTSC diff --git a/MTSC/ServerSide/ClientData.cs b/MTSC/ServerSide/ClientData.cs index c9c5c89..5d78b57 100644 --- a/MTSC/ServerSide/ClientData.cs +++ b/MTSC/ServerSide/ClientData.cs @@ -1,4 +1,5 @@ -using System; +using MTSC.Common; +using System; using System.Net.Security; using System.Net.Sockets; @@ -7,8 +8,10 @@ namespace MTSC.ServerSide /// /// Structure containing client information. /// - public class ClientData : IDisposable, IActiveClient + public class ClientData : IDisposable, IActiveClient, IQueueHolder { + private ProducerConsumerQueue messageQueue = new ProducerConsumerQueue(); + public TcpClient TcpClient; /// /// Latest datetime when a message has been received from the client @@ -18,6 +21,9 @@ namespace MTSC.ServerSide /// Latest datetime when a message has been received or sent to the client /// public DateTime LastActivityTime { get; private set; } = DateTime.Now; + + IConsumerQueue IQueueHolder.ConsumerQueue => messageQueue; + public bool ToBeRemoved = false; public SslStream SslStream = null; public ResourceDictionary Resources = new ResourceDictionary(); @@ -37,6 +43,21 @@ namespace MTSC.ServerSide LastActivityTime = DateTime.Now; } + void IQueueHolder.Enqueue(Message value) + { + (messageQueue as IProducerQueue).Enqueue(value); + } + + Message IQueueHolder.Dequeue() + { + return (messageQueue as IConsumerQueue).Dequeue(); + } + + bool IQueueHolder.TryDequeue(out Message Value) + { + return (messageQueue as IConsumerQueue).TryDequeue(out Value); + } + #region IDisposable Support private bool disposedValue = false; // To detect redundant calls diff --git a/MTSC/ServerSide/Handlers/HttpHandler.cs b/MTSC/ServerSide/Handlers/HttpHandler.cs index 1b5d17b..4b611d9 100644 --- a/MTSC/ServerSide/Handlers/HttpHandler.cs +++ b/MTSC/ServerSide/Handlers/HttpHandler.cs @@ -96,30 +96,31 @@ namespace MTSC.ServerSide.Handlers byte[] messageBytes = null; try { + var trimmedMessageBytes = message.MessageBytes.TrimTrailingNullBytes(); if (fragmentedMessages.ContainsKey(client)) { byte[] previousBytes = fragmentedMessages[client].Item1; - if(previousBytes.Length + message.MessageBytes.Length > MaximumRequestSize) + if(previousBytes.Length + trimmedMessageBytes.Length > MaximumRequestSize) { // Discard the message if it is too big - server.LogDebug($"Discarded message. Message size [{previousBytes.Length + message.MessageBytes.Length}] > [{MaximumRequestSize}]"); + server.LogDebug($"Discarded message. Message size [{previousBytes.Length + trimmedMessageBytes.Length}] > [{MaximumRequestSize}]"); fragmentedMessages.TryRemove(client, out _); return false; } - byte[] repackagingBuffer = new byte[previousBytes.Length + message.MessageBytes.Length]; + byte[] repackagingBuffer = new byte[previousBytes.Length + trimmedMessageBytes.Length]; Array.Copy(previousBytes, 0, repackagingBuffer, 0, previousBytes.Length); - Array.Copy(message.MessageBytes, 0, repackagingBuffer, previousBytes.Length, message.MessageBytes.Length); + Array.Copy(trimmedMessageBytes, 0, repackagingBuffer, previousBytes.Length, trimmedMessageBytes.Length); messageBytes = repackagingBuffer; } else { - if(message.MessageBytes.Length > MaximumRequestSize) + if(trimmedMessageBytes.Length > MaximumRequestSize) { // Discard the message if it is too big server.LogDebug($"Discarded message. Message size [{message.MessageBytes.Length}] > [{MaximumRequestSize}]"); return false; } - messageBytes = message.MessageBytes; + messageBytes = trimmedMessageBytes; } messageBytes = messageBytes.TrimTrailingNullBytes(); var partialRequest = PartialHttpRequest.FromBytes(messageBytes); diff --git a/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs b/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs index 67e45aa..4ca1491 100644 --- a/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs +++ b/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs @@ -93,32 +93,32 @@ namespace MTSC.ServerSide.Handlers byte[] messageBytes = null; try { + var trimmedMessageBytes = message.MessageBytes.TrimTrailingNullBytes(); if (fragmentedMessages.ContainsKey(client)) { byte[] previousBytes = fragmentedMessages[client].Item1; - if (previousBytes.Length + message.MessageBytes.Length > MaximumRequestSize) + if (previousBytes.Length + trimmedMessageBytes.Length > MaximumRequestSize) { // Discard the message if it is too big - server.LogDebug($"Discarded message. Message size [{previousBytes.Length + message.MessageBytes.Length}] > [{MaximumRequestSize}]"); + server.LogDebug($"Discarded message. Message size [{previousBytes.Length + trimmedMessageBytes.Length}] > [{MaximumRequestSize}]"); fragmentedMessages.TryRemove(client, out _); return false; } byte[] repackagingBuffer = new byte[previousBytes.Length + message.MessageBytes.Length]; Array.Copy(previousBytes, 0, repackagingBuffer, 0, previousBytes.Length); - Array.Copy(message.MessageBytes, 0, repackagingBuffer, previousBytes.Length, message.MessageBytes.Length); + Array.Copy(trimmedMessageBytes, 0, repackagingBuffer, previousBytes.Length, trimmedMessageBytes.Length); messageBytes = repackagingBuffer; } else { - if (message.MessageBytes.Length > MaximumRequestSize) + if (trimmedMessageBytes.Length > MaximumRequestSize) { // Discard the message if it is too big - server.LogDebug($"Discarded message. Message size [{message.MessageBytes.Length}] > [{MaximumRequestSize}]"); + server.LogDebug($"Discarded message. Message size [{trimmedMessageBytes.Length}] > [{MaximumRequestSize}]"); return false; } - messageBytes = message.MessageBytes; + messageBytes = trimmedMessageBytes; } - messageBytes = messageBytes.TrimTrailingNullBytes(); var partialRequest = PartialHttpRequest.FromBytes(messageBytes); if (partialRequest.Complete) request = partialRequest.ToRequest(); diff --git a/MTSC/ServerSide/IQueueHolder.cs b/MTSC/ServerSide/IQueueHolder.cs new file mode 100644 index 0000000..ea1ae88 --- /dev/null +++ b/MTSC/ServerSide/IQueueHolder.cs @@ -0,0 +1,12 @@ +using MTSC.Common; + +namespace MTSC.ServerSide +{ + interface IQueueHolder + { + IConsumerQueue ConsumerQueue { get; } + void Enqueue(T value); + T Dequeue(); + bool TryDequeue(out T Value); + } +} diff --git a/MTSC/ServerSide/Schedulers/FireTasksAndForgetScheduler.cs b/MTSC/ServerSide/Schedulers/FireTasksAndForgetScheduler.cs index dd80ab0..c44e706 100644 --- a/MTSC/ServerSide/Schedulers/FireTasksAndForgetScheduler.cs +++ b/MTSC/ServerSide/Schedulers/FireTasksAndForgetScheduler.cs @@ -1,17 +1,18 @@ using MTSC.Common; using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace MTSC.ServerSide.Schedulers { public class FireTasksAndForgetScheduler : IScheduler { - public void ScheduleHandling(IConsumerQueue<(ClientData, Message)> inQueue, Action messageHandlingProcedure) + void IScheduler.ScheduleHandling(List<(ClientData, IConsumerQueue)> clientsQueues, Action> messageHandlingProcedure) { - while(inQueue.TryDequeue(out var tuple)) + foreach(var tuple in clientsQueues) { - (var client, var message) = tuple; - Task.Run(() => { messageHandlingProcedure.Invoke(client, message); }); + (var client, var messageQueue) = tuple; + Task.Run(() => messageHandlingProcedure.Invoke(client, messageQueue)); } } } diff --git a/MTSC/ServerSide/Schedulers/IScheduler.cs b/MTSC/ServerSide/Schedulers/IScheduler.cs index 03d24d3..dedb5e0 100644 --- a/MTSC/ServerSide/Schedulers/IScheduler.cs +++ b/MTSC/ServerSide/Schedulers/IScheduler.cs @@ -1,10 +1,11 @@ using MTSC.Common; using System; +using System.Collections.Generic; namespace MTSC.ServerSide.Schedulers { public interface IScheduler { - void ScheduleHandling(IConsumerQueue<(ClientData, Message)> inQueue, Action messageHandlingProcedure); + void ScheduleHandling(List<(ClientData, IConsumerQueue)> clientsQueues, Action> messageHandlingProcedure); } } diff --git a/MTSC/ServerSide/Schedulers/ParallelScheduler.cs b/MTSC/ServerSide/Schedulers/ParallelScheduler.cs index c14fbab..c627b46 100644 --- a/MTSC/ServerSide/Schedulers/ParallelScheduler.cs +++ b/MTSC/ServerSide/Schedulers/ParallelScheduler.cs @@ -7,16 +7,15 @@ namespace MTSC.ServerSide.Schedulers { public class ParallelScheduler : IScheduler { - void IScheduler.ScheduleHandling(IConsumerQueue<(ClientData, Message)> inQueue, Action messageHandlingProcedure) + void IScheduler.ScheduleHandling(List<(ClientData, IConsumerQueue)> clientsQueues, Action> messageHandlingProcedure) { List actionList = new List(); - while(inQueue.TryDequeue(out var tuple)) + foreach(var tuple in clientsQueues) { - (var client, var message) = tuple; - actionList.Add(new Action(() => { messageHandlingProcedure.Invoke(client, message); })); + (var client, var messageQueue) = tuple; + actionList.Add(new Action(() => messageHandlingProcedure.Invoke(client, messageQueue))); } - Parallel.Invoke(actionList.ToArray()); } } } diff --git a/MTSC/ServerSide/Schedulers/SequentialProcessingScheduler.cs b/MTSC/ServerSide/Schedulers/SequentialProcessingScheduler.cs index 90f2732..c898215 100644 --- a/MTSC/ServerSide/Schedulers/SequentialProcessingScheduler.cs +++ b/MTSC/ServerSide/Schedulers/SequentialProcessingScheduler.cs @@ -1,17 +1,18 @@ using MTSC.Common; using System; using System.Collections.Concurrent; +using System.Collections.Generic; namespace MTSC.ServerSide.Schedulers { public class SequentialProcessingScheduler : IScheduler { - void IScheduler.ScheduleHandling(IConsumerQueue<(ClientData, Message)> inQueue, Action messageHandlingProcedure) + void IScheduler.ScheduleHandling(List<(ClientData, IConsumerQueue)> clientsQueues, Action> messageHandlingProcedure) { - while (inQueue.TryDequeue(out var tuple)) + foreach(var tuple in clientsQueues) { - (var client, var message) = tuple; - messageHandlingProcedure.Invoke(client, message); + (var client, var messageQueue) = tuple; + messageHandlingProcedure.Invoke(client, messageQueue); } } } diff --git a/MTSC/ServerSide/Schedulers/TaskAwaiterScheduler.cs b/MTSC/ServerSide/Schedulers/TaskAwaiterScheduler.cs index e6d3d0a..f534f3a 100644 --- a/MTSC/ServerSide/Schedulers/TaskAwaiterScheduler.cs +++ b/MTSC/ServerSide/Schedulers/TaskAwaiterScheduler.cs @@ -7,13 +7,13 @@ namespace MTSC.ServerSide.Schedulers { public class TaskAwaiterScheduler : IScheduler { - void IScheduler.ScheduleHandling(IConsumerQueue<(ClientData, Message)> inQueue, Action messageHandlingProcedure) + void IScheduler.ScheduleHandling(List<(ClientData, IConsumerQueue)> clientsQueues, Action> messageHandlingProcedure) { List tasks = new List(); - while(inQueue.TryDequeue(out var tuple)) + foreach(var tuple in clientsQueues) { - (var client, var message) = tuple; - tasks.Add(Task.Run(() => messageHandlingProcedure.Invoke(client, message))); + (var client, var messageQueue) = tuple; + tasks.Add(Task.Run(() => messageHandlingProcedure.Invoke(client, messageQueue))); } Task.WaitAll(tasks.ToArray()); } diff --git a/MTSC/ServerSide/Server.cs b/MTSC/ServerSide/Server.cs index d4cc1b9..d63e325 100644 --- a/MTSC/ServerSide/Server.cs +++ b/MTSC/ServerSide/Server.cs @@ -8,6 +8,7 @@ using MTSC.ServerSide.UsageMonitors; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Security; using System.Net.Sockets; @@ -33,15 +34,12 @@ namespace MTSC.ServerSide List loggers = new List(); List exceptionHandlers = new List(); List serverUsageMonitors = new List(); - ProducerConsumerQueue<(ClientData, Message)> messageInQueue = new ProducerConsumerQueue<(ClientData, Message)>(); ProducerConsumerQueue<(ClientData, byte[])> messageOutQueue = new ProducerConsumerQueue<(ClientData, byte[])>(); #endregion #region Private Properties private IConsumerQueue _ConsumerClientQueue { get => addQueue; } private IProducerQueue _ProducerClientQueue { get => addQueue; } - private IConsumerQueue<(ClientData, Message)> _ConsumerMessageInQueue { get => messageInQueue; } private IConsumerQueue<(ClientData, byte[])> _ConsumerMessageOutQueue { get => messageOutQueue; } - private IProducerQueue<(ClientData, Message)> _ProducerMessageInQueue { get => messageInQueue; } #endregion #region Public Properties /// @@ -444,7 +442,11 @@ namespace MTSC.ServerSide * Call the scheduler to handle all received messages and distribute them to the handlers */ - Scheduler.ScheduleHandling(this._ConsumerMessageInQueue, HandleClientMessage); + Scheduler.ScheduleHandling( + clients + .Select(client => (client, (client as IQueueHolder).ConsumerQueue)) + .ToList(), + HandleClientMessages); /* * Iterate through all the handlers, running periodic operations. @@ -617,7 +619,7 @@ namespace MTSC.ServerSide (client as IActiveClient).UpdateLastReceivedMessage(); LogDebug("Received message from " + client.TcpClient.Client.RemoteEndPoint.ToString() + "\nMessage length: " + message.MessageLength); - this._ProducerMessageInQueue.Enqueue((client, message)); + (client as IQueueHolder).Enqueue(message); } } catch(Exception e) @@ -633,6 +635,14 @@ namespace MTSC.ServerSide } } + private void HandleClientMessages(ClientData client, IConsumerQueue messages) + { + while(messages.TryDequeue(out var message)) + { + HandleClientMessage(client, message); + } + } + private void HandleClientMessage(ClientData client, Message message) { foreach (IHandler handler in handlers)