135 lines
5.9 KiB
C#
135 lines
5.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Server_Dashboard_Socket {
|
|
|
|
/// <summary>
|
|
/// Generic Protocol class for Json and Xml serialization
|
|
/// </summary>
|
|
/// <typeparam name="TMessageType">JsonMessageProtocol or XmlMessageProtocol</typeparam>
|
|
public abstract class Protocol<TMessageType> {
|
|
|
|
//Header size is always 4
|
|
private const int HeaderSize = 4;
|
|
|
|
/// <summary>
|
|
/// Gets the message and checks with the header if the message is valid or not
|
|
/// important to defend against attacks with infinite long messages
|
|
/// </summary>
|
|
/// <param name="networkStream">A network stream</param>
|
|
/// <returns>MessageType e.g. Json or Xml</returns>
|
|
public async Task<TMessageType> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends data Async
|
|
/// </summary>
|
|
/// <typeparam name="T">Message type e.g. object or string</typeparam>
|
|
/// <param name="networkStream">Network stream</param>
|
|
/// <param name="message">The message</param>
|
|
/// <returns></returns>
|
|
public async Task SendAsync<T>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the header and converts it to an integer
|
|
/// </summary>
|
|
/// <param name="networkStream">A network stream</param>
|
|
/// <returns>Header as Integer</returns>
|
|
private async Task<int> ReadHeader(NetworkStream networkStream) {
|
|
byte[] headerBytes = await ReadAsync(networkStream, HeaderSize).ConfigureAwait(false);
|
|
return IPAddress.HostToNetworkOrder(BitConverter.ToInt32(headerBytes));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the body and decodes it to a human readable string
|
|
/// </summary>
|
|
/// <param name="networkStream">A network stream</param>
|
|
/// <param name="bodyLength">Length of the body</param>
|
|
/// <returns>Decoded body</returns>
|
|
private async Task<TMessageType> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the network stream as long as something is readable
|
|
/// </summary>
|
|
/// <param name="networkStream">A network stream</param>
|
|
/// <param name="bytesToRead">how many bytes there are to read</param>
|
|
/// <returns>Every byte from the Stream</returns>
|
|
private async Task<byte[]> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encode the message from human readable to bytes for the stream
|
|
/// </summary>
|
|
/// <typeparam name="T">The message as anything e.g. object or string</typeparam>
|
|
/// <param name="message">The message to be send</param>
|
|
/// <returns>The Header and Body as bytes[]</returns>
|
|
protected (byte[] header, byte[] body) Encode<T>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decode the message with the given type, json or xml
|
|
/// </summary>
|
|
/// <param name="message">The message to decode</param>
|
|
/// <returns>Decoded message</returns>
|
|
protected abstract TMessageType Decode(byte[] message);
|
|
|
|
/// <summary>
|
|
/// Validate the message length to combat attacks
|
|
/// </summary>
|
|
/// <param name="messageLength">The message length</param>
|
|
protected virtual void AssertValidMessageLength(int messageLength) {
|
|
//If its not 0 throw an exception
|
|
if (messageLength < 1)
|
|
throw new ArgumentOutOfRangeException("Invalid message length");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encode the message so it can be send via the network stream
|
|
/// </summary>
|
|
/// <typeparam name="T">Message type e.g. object or string</typeparam>
|
|
/// <param name="message">The message to be send</param>
|
|
/// <returns></returns>
|
|
protected abstract byte[] EncodeBody<T>(T message);
|
|
}
|
|
} |