Building a robust, SSL, CRC-Verified server/client solution in the .NET Framework with C#

Quite a lengthy post here with a lot of code in the hope that my experience of building an integrity-checking SSL (text-only for now) communication system will be of use to somebody else.

The way the system I have designed works is thus:

  1. Server sends banner
  2. Client sends login
  3. Server verifies and then either client or server is free to send commands with sequence numbers

The conditions are that the system must verify every single line of text sent via some kind of CRC (using MD5 here) and disconnect gracefully, raising an event to tell the host application so, if there is a problem.

First of all we need to define some kind of protocol between the client and server. Here's what I came up with for a skeleton.

ProtocolText.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Shared
{
    /// <summary>
    /// The protocol definition
    /// </summary>
    public static class ProtocolText
    {
        static string _banner = "AUTH";

        /// <summary>
        /// The banner text of the protocol
        /// </summary>
        public static string BANNER
        {
            get { return _banner; }
            set { _banner = value; }
        }

        static string _positive = "YES";

        /// <summary>
        /// The positive response text of the protocol
        /// </summary>
        public static string Positive
        {
            get { return ProtocolText._positive; }
            set { ProtocolText._positive = value; }
        }

        static string _negative = "NO";

        /// <summary>
        /// The negative response text of the protocol
        /// </summary>
        public static string Negative
        {
            get { return ProtocolText._negative; }
            set { ProtocolText._negative = value; }
        }

        static string _LOGINPrefix = "LOGIN";

        /// <summary>
        /// The prefix to the login command of the protocol
        /// </summary>
        public static string LOGINPrefix
        {
            get { return ProtocolText._LOGINPrefix; }
            set { ProtocolText._LOGINPrefix = value; }
        }

        static string _QUIT = "GOODBYE";

        /// <summary>
        /// The quit command text of the protocol
        /// </summary>
        public static string QUIT
        {
            get { return ProtocolText._QUIT; }
            set { ProtocolText._QUIT = value; }
        }
    }
}

Next up, some form of wrapping "commands" inside an API. These are actually just text, but putting them into objects has obvious usability implications.

aCommand.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Shared.Commands
{
    /// <summary>
    /// The type of this command
    /// </summary>
    public enum CommandType
    {
        BANNER,
        AUTH,
        QUIT,
        PREPAREINDEX
    }

    /// <summary>
    /// The response level from the command
    /// </summary>
    public enum Response
    {
        TERMINAL,
        ERROR,
        WARNING,
        INFORMATION,
        SUCCESS
    }

    /// <summary>
    /// An abstract class for implementing commands
    /// </summary>
    public abstract class aCommand
    {
        long _sequenceNumber = 0;
        string[] _commandText = null;
        bool _hasCommandsToSend = false;
        string _information = string.Empty;
        List<string> _response = new List<string>();

        /// <summary>
        /// When Response.INFORMATION is issued, this property will contain
        /// the information specified in the response
        /// </summary>
        public string Information
        {
            get { return _information; }
        }

        /// <summary>
        /// The sequence number of this message
        /// </summary>
        public long SequenceNumber
        {
            get
            {
                return _sequenceNumber;
            }
            set
            {
                _sequenceNumber = value;
            }
        }

        /// <summary>
        /// The command text of this message
        /// </summary>
        public string[] CommandText
        {
            get
            {
                return _commandText;
            }
        }

        /// <summary>
        /// Whether there is fresh command text to send
        /// </summary>
        public bool HasCommandsToSend
        {
            get
            {
                return _hasCommandsToSend;
            }
            set
            {
                _hasCommandsToSend = value;
            }
        }

        /// <summary>
        /// The text of the response received so far
        /// </summary>
        public List<string> ResponseText
        {
            get { return _response; }
        }

        /// <summary>
        /// A method for inheritors to set new command text
        /// </summary>
        /// <param name="commands">The command text to send</param>
        protected void setCommandText(string[] commands)
        {
            _hasCommandsToSend = true;
            _commandText = commands;
        }

        /// <summary>
        /// A method for inheritors to set the Information property
        /// </summary>
        /// <param name="info">The text that the Information property should be set to</param>
        protected void setInformation(string info)
        {
            _information = info;
        }

        /// <summary>
        /// Append text to the response of this client
        /// </summary>
        /// <param name="msg">The text to append</param>
        public void AddResponse(string msg)
        {
            _response.Add(msg);
        }

        /// <summary>
        /// The type of this command
        /// </summary>
        public abstract CommandType CommandType { get; }

        /// <summary>
        /// Called by aClient whenever a response block is complete
        /// </summary>
        /// <returns>A Response object indicating the status of this operation</returns>
        public abstract Response ResponseDone();
    }
}

Also in our shared library (this is referenced by both the client and the server) we need the CRC checker.

CRC.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace Shared
{
    /// <summary>
    /// A class to provide CRC functions
    /// </summary>
    public class CRC
    {
        static MD5CryptoServiceProvider cSP = new MD5CryptoServiceProvider();

        /// <summary>
        /// Compute the CRC of a message
        /// </summary>
        /// <param name="message">The message text</param>
        /// <returns>The CRC</returns>
        public static string ComputeCRC(string message)
        {   
            return BitConverter.ToString(cSP.ComputeHash(System.Text.ASCIIEncoding.ASCII.GetBytes(message)));
        }

        /// <summary>
        /// Compute the CRC of a stream
        /// </summary>
        /// <param name="stream">The stream (probably a file)</param>
        /// <returns>The CRC</returns>
        public static string ComputeCRC(Stream stream)
        {
            return BitConverter.ToString(cSP.ComputeHash(stream));
        }
    }
}

Now, the workhorse itself, the actual client. This is responsible for threading, SSL initiation (to some extent), CRC checking and passing message responses to the correct places.

aClient.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.IO;
using System.Threading;
using System.Text.RegularExpressions;
using System.Diagnostics;
using Shared.Commands;

namespace Shared
{
    /// <summary>
    /// A list of client events that inheritors can raise
    /// </summary>
    public enum ClientEvents
    {
        OnAuthFailure,
        OnAuthSuccess
    }

    /// <summary>
    /// An abstract class for implementing SSL, CRC verified communication
    /// between clients
    /// </summary>
    public abstract class aClient
    {
        TcpClient _client = null;
        SslStream _ssl = null;
        StreamReader _reader = null;
        StreamWriter _writer = null;
        bool _shutdown = false;
        bool _isConnecting = false;
        string _server = string.Empty;
        int _sequenceNumber = 0;
        int _connectionAttempts = 0;
        List<aCommand> _commandQueue = new List<aCommand>();
        ManualResetEvent _hasMessages = new ManualResetEvent(false);
        ManualResetEvent _connectingWait = new ManualResetEvent(false);
        Regex msgMatcher = new Regex(@"^(\d+)\s(.+)\sCRC(.+)$");

        public delegate void ClientEvent(aClient client);
        public event ClientEvent OnAuthFailure;
        public event ClientEvent OnAuthSuccess;
        public event ClientEvent OnShutdown;

        protected State _state = State.Not_Connected;

        public abstract void initSSL(SslStream ssl, string hostname);
        public abstract void connectionInit();
        public abstract void processResponse(Response response, aCommand command);
        public abstract void processNewCommand(string commandText, long sequenceNumber);

        /// <summary>
        /// States of the client
        /// </summary>
        protected enum State
        {
            Not_Connected,
            Connected,
            Authenticated
        }

        /// <summary>
        /// Creates a new instance of the aClient
        /// </summary>
        /// <param name="client">The underlying TcpClient</param>
        /// <param name="hostname">The hostname</param>
        public aClient(TcpClient client, string hostname)
        {
            _client = client;
            _server = hostname;
        }

        /// <summary>
        /// Instructs the aClient to begin processing messages
        /// </summary>
        /// <remarks>This is a non-blocking operation</remarks>
        public void Start()
        {
            ThreadStart ts = new ThreadStart(messageLoop);
            Thread t = new Thread(ts);
            t.Start();
        }

        /// <summary>
        /// Raise a client event
        /// </summary>
        /// <param name="eventType">The type of event to rai