2020-04-16 15:02:34 +02:00
|
|
|
module server.types;
|
|
|
|
|
2020-04-16 16:35:07 +02:00
|
|
|
import utils.debugging : debugPrint;
|
|
|
|
import std.conv : to;
|
2020-04-16 17:50:10 +02:00
|
|
|
import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress, SocketFlags;
|
2020-04-16 17:05:56 +02:00
|
|
|
import core.thread : Thread;
|
2020-04-16 17:50:10 +02:00
|
|
|
import std.stdio : writeln;
|
2020-04-16 19:16:38 +02:00
|
|
|
import std.json : JSONValue, parseJSON, JSONException, JSONType;
|
2020-04-16 22:56:06 +02:00
|
|
|
import std.string;
|
2020-04-16 16:35:07 +02:00
|
|
|
|
2020-04-16 15:02:34 +02:00
|
|
|
public class BesterServer
|
|
|
|
{
|
2020-04-16 16:35:07 +02:00
|
|
|
|
2020-04-16 16:39:31 +02:00
|
|
|
/* The server's socket */
|
|
|
|
private Socket serverSocket;
|
|
|
|
|
2020-04-16 16:35:07 +02:00
|
|
|
this(string bindAddress, ushort listenPort)
|
|
|
|
{
|
|
|
|
debugPrint("Binding to address: " ~ bindAddress ~ " and port " ~ to!(string)(listenPort));
|
2020-04-16 16:39:31 +02:00
|
|
|
initialize(bindAddress, listenPort);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void initialize(string bindAddress, ushort listenPort)
|
|
|
|
{
|
|
|
|
/* Create a socket */
|
2020-04-16 16:41:56 +02:00
|
|
|
serverSocket = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
|
2020-04-16 16:43:00 +02:00
|
|
|
serverSocket.bind(parseAddress(bindAddress, listenPort));
|
2020-04-16 16:39:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Start listen loop */
|
|
|
|
public void run()
|
|
|
|
{
|
2020-04-16 16:43:00 +02:00
|
|
|
serverSocket.listen(1); /* TODO: This value */
|
2020-04-16 16:39:31 +02:00
|
|
|
debugPrint("Server listen loop started");
|
2020-04-16 17:05:56 +02:00
|
|
|
while(true)
|
|
|
|
{
|
|
|
|
/* Wait for an incoming connection */
|
|
|
|
Socket clientConnection = serverSocket.accept();
|
|
|
|
|
2020-04-16 17:50:10 +02:00
|
|
|
/* Create a new client connection handler and start its thread */
|
2020-04-16 23:25:22 +02:00
|
|
|
BesterConnection besterConnection = new BesterConnection(clientConnection, this);
|
2020-04-16 17:50:10 +02:00
|
|
|
besterConnection.start();
|
2020-04-16 17:05:56 +02:00
|
|
|
}
|
|
|
|
}
|
2020-04-16 23:25:22 +02:00
|
|
|
|
|
|
|
/* Authenticate the user */
|
|
|
|
public bool authenticate(string username, string password)
|
|
|
|
{
|
|
|
|
/* TODO: Implement me */
|
|
|
|
return true;
|
|
|
|
}
|
2020-04-16 17:05:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private class BesterConnection : Thread
|
|
|
|
{
|
|
|
|
|
|
|
|
/* The socket to the client */
|
|
|
|
private Socket clientConnection;
|
|
|
|
|
2020-04-16 23:25:22 +02:00
|
|
|
/* The server backend */
|
|
|
|
private BesterServer server;
|
|
|
|
|
|
|
|
this(Socket clientConnection, BesterServer server)
|
2020-04-16 17:05:56 +02:00
|
|
|
{
|
|
|
|
/* Save socket and set thread worker function pointer */
|
|
|
|
super(&run);
|
2020-04-16 17:50:10 +02:00
|
|
|
this.clientConnection = clientConnection;
|
2020-04-16 23:25:22 +02:00
|
|
|
this.server = server;
|
2020-04-16 17:50:10 +02:00
|
|
|
|
|
|
|
debugPrint("New client handler spawned for " ~ clientConnection.remoteAddress().toAddrString());
|
2020-04-16 17:05:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Read/send loop */
|
|
|
|
private void run()
|
|
|
|
{
|
2020-04-16 17:50:10 +02:00
|
|
|
/* Receive buffer */
|
|
|
|
byte[] buffer;
|
|
|
|
|
2020-04-16 16:39:31 +02:00
|
|
|
while(true)
|
|
|
|
{
|
2020-04-16 17:50:10 +02:00
|
|
|
/* Make the dynamic array's size 4 */
|
|
|
|
buffer.length = 4;
|
|
|
|
|
2020-04-16 18:15:47 +02:00
|
|
|
/* Read the first 4 bytes (retrieve message size) */
|
|
|
|
long bytesReceived = clientConnection.receive(buffer);
|
|
|
|
writeln("PreambleWait: Bytes received: ", cast(ulong)bytesReceived);
|
2020-04-16 17:50:10 +02:00
|
|
|
|
|
|
|
/* Make sure exactly 4 bytes were received */
|
|
|
|
if (bytesReceived != 4)
|
|
|
|
{
|
|
|
|
/* If we don't get exactly 4 bytes, drop the client */
|
|
|
|
debugPrint("Did not get exactly 4 bytes for preamble, disconnecting client...");
|
|
|
|
clientConnection.close();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the message length */
|
|
|
|
int messageLength = *(cast(int*)buffer.ptr);
|
2020-04-16 18:15:47 +02:00
|
|
|
writeln("Message length: ", cast(uint)messageLength);
|
2020-04-16 17:50:10 +02:00
|
|
|
|
2020-04-16 18:15:47 +02:00
|
|
|
/* TODO: Testing locally ain't good as stuff arrives way too fast, although not as fast as I can type */
|
|
|
|
/* What must happen is a loop to loop and wait for data */
|
2020-04-16 18:26:53 +02:00
|
|
|
|
|
|
|
/* Full message buffer */
|
|
|
|
byte[] messageBuffer;
|
|
|
|
|
|
|
|
|
2020-04-16 18:30:47 +02:00
|
|
|
/* TODO: Add timeout if we haven't received a message in a certain amount of time */
|
|
|
|
|
2020-04-16 18:26:53 +02:00
|
|
|
uint currentByte = 0;
|
|
|
|
while(currentByte < cast(uint)messageLength)
|
|
|
|
{
|
|
|
|
/* Receive 20 bytes (at most) at a time */
|
|
|
|
byte[20] messageBufferPartial;
|
|
|
|
bytesReceived = clientConnection.receive(messageBufferPartial);
|
|
|
|
|
|
|
|
/* Append the received bytes to the FULL message buffer */
|
|
|
|
messageBuffer ~= messageBufferPartial[0..bytesReceived];
|
|
|
|
|
2020-04-16 18:52:28 +02:00
|
|
|
/* TODO: Bug when over send, we must not allow this */
|
|
|
|
|
2020-04-16 18:26:53 +02:00
|
|
|
/* Increment counter of received bytes */
|
|
|
|
currentByte += bytesReceived;
|
2020-04-16 18:30:47 +02:00
|
|
|
writeln("Received ", currentByte, "/", cast(uint)messageLength, " bytes");
|
2020-04-16 18:26:53 +02:00
|
|
|
}
|
|
|
|
|
2020-04-16 18:46:26 +02:00
|
|
|
/* Process the message */
|
|
|
|
processMessage(messageBuffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-17 00:07:41 +02:00
|
|
|
/* TODO: Pass in type and just payload or what */
|
|
|
|
private bool dispatch(JSONValue message)
|
|
|
|
{
|
|
|
|
|
|
|
|
/* TODO: Set return value */
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-16 18:46:26 +02:00
|
|
|
/* Process the received message */
|
|
|
|
private void processMessage(byte[] messageBuffer)
|
|
|
|
{
|
|
|
|
/* The message as a JSONValue struct */
|
|
|
|
JSONValue jsonMessage;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
/* Convert message to JSON */
|
|
|
|
jsonMessage = parseJSON(cast(string)messageBuffer);
|
|
|
|
writeln("JSON received: ", jsonMessage);
|
2020-04-16 19:16:38 +02:00
|
|
|
|
|
|
|
/* Make sure we have a JSON object */
|
|
|
|
if(jsonMessage.type == JSONType.object)
|
|
|
|
{
|
|
|
|
/* As per spec, look for the "besterHeader" */
|
|
|
|
JSONValue besterHeader;
|
|
|
|
|
|
|
|
/* TODO: Check for out of bounds here */
|
2020-04-16 22:15:24 +02:00
|
|
|
besterHeader = jsonMessage["header"];
|
2020-04-16 19:16:38 +02:00
|
|
|
|
|
|
|
/* Check if it is a JSON object */
|
|
|
|
if(besterHeader.type == JSONType.object)
|
|
|
|
{
|
|
|
|
/* TODO: Add further checks here */
|
2020-04-16 19:28:36 +02:00
|
|
|
|
2020-04-16 22:56:06 +02:00
|
|
|
|
2020-04-17 00:07:41 +02:00
|
|
|
/* TODO: Bounds check */
|
|
|
|
JSONValue payloadType;
|
|
|
|
|
|
|
|
payloadType = jsonMessage["type"];
|
|
|
|
|
|
|
|
/* The payload type must be a string */
|
|
|
|
if(payloadType.type == JSONType.string)
|
|
|
|
{
|
|
|
|
/* TODO: Move everything into this block */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-04-16 22:56:06 +02:00
|
|
|
/* The header must contain a scope block */
|
|
|
|
JSONValue scopeBlock;
|
|
|
|
|
|
|
|
/* TODO: Add bounds check */
|
|
|
|
scopeBlock = besterHeader["scope"];
|
|
|
|
|
|
|
|
/* Make sure the type of the JSON value is string */
|
|
|
|
if(scopeBlock.type == JSONType.string)
|
|
|
|
{
|
|
|
|
/* Get the scope */
|
|
|
|
string scopeString = scopeBlock.str;
|
|
|
|
|
|
|
|
/* If the message is for client<->server */
|
|
|
|
if(cmp(scopeString, "client"))
|
|
|
|
{
|
2020-04-16 22:58:10 +02:00
|
|
|
debugPrint("Scope: client<->server");
|
2020-04-16 23:04:08 +02:00
|
|
|
|
|
|
|
/* The header must contain a authentication JSON object */
|
|
|
|
JSONValue authenticationBlock;
|
|
|
|
|
|
|
|
/* TODO: Check for out of bounds here */
|
|
|
|
authenticationBlock = besterHeader["authentication"];
|
|
|
|
|
|
|
|
/* TODO: Bounds check for both below */
|
|
|
|
JSONValue username, password;
|
|
|
|
username = authenticationBlock["username"];
|
|
|
|
password = authenticationBlock["password"];
|
2020-04-17 00:07:41 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
2020-04-16 23:04:08 +02:00
|
|
|
|
|
|
|
if(username.type == JSONType.string && password.type == JSONType.string)
|
|
|
|
{
|
|
|
|
/* TODO: Now do some stuff */
|
2020-04-16 23:25:22 +02:00
|
|
|
|
|
|
|
/* TODO: Authenticate the user */
|
|
|
|
string usernameString = username.str;
|
|
|
|
string passwordString = password.str;
|
|
|
|
bool isAuthenticated = server.authenticate(usernameString, passwordString);
|
|
|
|
|
|
|
|
if(isAuthenticated)
|
|
|
|
{
|
|
|
|
debugPrint("Authenticated");
|
|
|
|
|
2020-04-17 00:07:41 +02:00
|
|
|
/* TODO: Dispatch to the correct message handler */
|
|
|
|
dispatch(jsonMessage);
|
2020-04-16 23:25:22 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* TODO: Add error handling here */
|
|
|
|
debugPrint("Authentication failure");
|
|
|
|
}
|
2020-04-16 23:04:08 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* TODO: Add error handling here */
|
|
|
|
debugPrint("Username or password is not a JSON string");
|
|
|
|
}
|
2020-04-16 22:56:06 +02:00
|
|
|
}
|
|
|
|
/* If the message is for server<->server */
|
|
|
|
else if(cmp(scopeString, "server"))
|
|
|
|
{
|
2020-04-16 22:58:10 +02:00
|
|
|
debugPrint("Scope: server<->server");
|
2020-04-16 22:56:06 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-16 22:58:10 +02:00
|
|
|
/* TODO: Error handling */
|
|
|
|
debugPrint("Unknown scope provided");
|
2020-04-16 22:56:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* TODO: Handle error */
|
|
|
|
debugPrint("Scope block JSON value not a string");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-16 23:04:08 +02:00
|
|
|
|
2020-04-16 19:16:38 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* TODO: Add error handling here */
|
2020-04-16 19:28:36 +02:00
|
|
|
debugPrint("Header received was not a JSON object");
|
2020-04-16 19:16:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* TODO: Add error here */
|
|
|
|
debugPrint("Did not receive a JSON object");
|
|
|
|
}
|
2020-04-16 18:46:26 +02:00
|
|
|
}
|
|
|
|
catch(JSONException exception)
|
|
|
|
{
|
|
|
|
/* TODO: Implement this */
|
2020-04-16 19:16:38 +02:00
|
|
|
debugPrint("Error parsing the received JSON message");
|
2020-04-16 16:39:31 +02:00
|
|
|
}
|
2020-04-16 18:46:26 +02:00
|
|
|
|
2020-04-16 16:35:07 +02:00
|
|
|
}
|
2020-04-16 17:05:56 +02:00
|
|
|
|
2020-04-16 15:02:34 +02:00
|
|
|
|
|
|
|
}
|