using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; namespace Server_Dashboard_Socket { /// /// Generic Protocol class for Json and Xml serialization /// /// JsonMessageProtocol or XmlMessageProtocol public abstract class Protocol { //Header size is always 4 private const int HeaderSize = 4; /// /// Gets the message and checks with the header if the message is valid or not /// important to defend against attacks with infinite long messages /// /// A network stream /// MessageType e.g. Json or Xml public async Task ReceiveAsync(NetworkStream networkStream) { //Gets the body length int bodyLength = await ReadHeader(networkStream).ConfigureAwait(false); //Validates the length AssertValidMessageLength(bodyLength); //Returns the body message type return await ReadBody(networkStream, bodyLength).ConfigureAwait(false); } /// /// Sends data Async /// /// Message type e.g. object or string /// Network stream /// The message /// public async Task SendAsync(NetworkStream networkStream, T message) { //encodes the message to a header and body var (header, body) = Encode(message); //Sends the header await networkStream.WriteAsync(header, 0, header.Length).ConfigureAwait(false); //Sends the body await networkStream.WriteAsync(body, 0, body.Length).ConfigureAwait(false); } /// /// Reads the header and converts it to an integer /// /// A network stream /// Header as Integer private async Task ReadHeader(NetworkStream networkStream) { byte[] headerBytes = await ReadAsync(networkStream, HeaderSize).ConfigureAwait(false); return IPAddress.HostToNetworkOrder(BitConverter.ToInt32(headerBytes)); } /// /// Reads the body and decodes it to a human readable string /// /// A network stream /// Length of the body /// Decoded body private async Task ReadBody(NetworkStream networkStream, int bodyLength) { //Reads the bytes from the stream into an array byte[] bodyBytes = await ReadAsync(networkStream, bodyLength).ConfigureAwait(false); //Decodes it and returns the string return Decode(bodyBytes); } /// /// Reads the network stream as long as something is readable /// /// A network stream /// how many bytes there are to read /// Every byte from the Stream private async Task ReadAsync(NetworkStream networkStream, int bytesToRead) { //new buffer that is as big as the content(watch out for buffer overflows) byte[] buffer = new byte[bytesToRead]; //keep account of the bytes that are already read int bytesRead = 0; //White we still have something to read while (bytesRead < bytesToRead) { //Read it from the stream var bytesReceived = await networkStream.ReadAsync(buffer, bytesRead, (bytesToRead - bytesRead)).ConfigureAwait(false); //If it happens to be 0 close the socket if (bytesReceived == 0) throw new Exception("Socket Closed"); bytesRead += bytesReceived; } return buffer; } /// /// Encode the message from human readable to bytes for the stream /// /// The message as anything e.g. object or string /// The message to be send /// The Header and Body as bytes[] protected (byte[] header, byte[] body) Encode(T message) { //Creates the body bytes var bodyBytes = EncodeBody(message); //Creates the header bytes var headerBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(bodyBytes.Length)); return (headerBytes, bodyBytes); } /// /// Decode the message with the given type, json or xml /// /// The message to decode /// Decoded message protected abstract TMessageType Decode(byte[] message); /// /// Validate the message length to combat attacks /// /// The message length protected virtual void AssertValidMessageLength(int messageLength) { //If its not 0 throw an exception if (messageLength < 1) throw new ArgumentOutOfRangeException("Invalid message length"); } /// /// Encode the message so it can be send via the network stream /// /// Message type e.g. object or string /// The message to be send /// protected abstract byte[] EncodeBody(T message); } }