diff --git a/source/app.d b/source/app.d index f7061b5..0c598a3 100644 --- a/source/app.d +++ b/source/app.d @@ -1,140 +1,5 @@ module besterd; -import server.server : BesterServer; -import std.conv : to; -import std.socket : SocketOSException, parseAddress, UnixAddress; -import utils.debugging : dprint; -import std.stdio : File, writeln; -import std.json : parseJSON, JSONValue; -import listeners.listener : BesterListener, BesterListenerException; -import listeners.types : TCP4Listener, TCP6Listener, UNIXListener; -import std.file : exists; - void main(string[] args) { - /* Make sure we have atleast two arguments */ - if(args.length >= 2) - { - /* Get the second argument for configuration file path */ - string configFilePath = args[1]; - - /* Start thhe server if the file exists, else show an error */ - if(exists(configFilePath)) - { - startServer(args[1]); - } - else - { - writeln("Provided server configuration file \"" ~ configFilePath ~ "\" does not exist"); - } - } - - /* TODO: Add usage check and arguments before this */ - - dprint("Main finished, remaining threads are keeping this open if it hangs"); - - /* TODO: Install signal handler so a shutdown can trigger shutdown */ -} - -private JSONValue getConfig(string configurationFilePath) -{ - /* TODO: Open the file here */ - File configFile; - configFile.open(configurationFilePath); - - /* The file buffer */ - byte[] fileBuffer; - - /* Allocate the buffer to be the size of the file */ - fileBuffer.length = configFile.size(); - - /* Read the content of the file */ - /* TODO: Error handling ErrnoException */ - fileBuffer = configFile.rawRead(fileBuffer); - configFile.close(); - - JSONValue config; - - /* TODO: JSON error checking */ - config = parseJSON(cast(string)fileBuffer); - - return config; -} - -private BesterListener[] getListeners(BesterServer server, JSONValue networkBlock) -{ - BesterListener[] listeners; - - /* TODO: Error handling and get keys and clean up for formality */ - - try - { - /* Look for IPv4 TCP block */ - JSONValue inet4TCPBlock = networkBlock["tcp4"]; - dprint("<<< IPv4 TCP Block >>>\n" ~ inet4TCPBlock.toPrettyString()); - string inet4Address = inet4TCPBlock["address"].str(); - ushort inet4Port = to!(ushort)(inet4TCPBlock["port"].str()); - TCP4Listener tcp4Listener = new TCP4Listener(server, parseAddress(inet4Address, inet4Port)); - listeners ~= tcp4Listener; - - /* Look for IPv6 TCP block */ - JSONValue inet6TCPBlock = networkBlock["tcp6"]; - dprint("<<< IPv6 TCP Block >>>\n" ~ inet6TCPBlock.toPrettyString()); - string inet6Address = inet6TCPBlock["address"].str(); - ushort inet6Port = to!(ushort)(inet6TCPBlock["port"].str()); - TCP6Listener tcp6Listener = new TCP6Listener(server, parseAddress(inet6Address, inet6Port)); - listeners ~= tcp6Listener; - - /* Look for UNIX Domain block */ - JSONValue unixDomainBlock = networkBlock["unix"]; - dprint("<<< UNIX Domain Block >>>\n" ~ unixDomainBlock.toPrettyString()); - string unixAddress = unixDomainBlock["address"].str(); - // UNIXListener unixListener = new UNIXListener(server, new UnixAddress(unixAddress)); - // listeners ~= unixListener; - } - catch(BesterListenerException e) - { - - } - - - return listeners; -} - -private void startServer(string configurationFilePath) -{ - /* The server configuration */ - JSONValue serverConfiguration = getConfig(configurationFilePath); - dprint("<<< Bester.d configuration >>>\n" ~ serverConfiguration.toPrettyString()); - - try - { - /* The server */ - BesterServer server = null; - - /* TODO: Bounds anc type checking */ - - /* Get the network block */ - JSONValue networkBlock = serverConfiguration["network"]; - - /* Create the Bester server */ - server = new BesterServer(serverConfiguration); - - /* TODO: Get keys */ - BesterListener[] listeners = getListeners(server, networkBlock); - - for(ulong i = 0; i < listeners.length; i++) - { - /* Add listener */ - server.addListener(listeners[i]); - } - - /* Start running the server (starts the listeners) */ - server.run(); - } - catch(SocketOSException exception) - { - dprint("Error binding: " ~ exception.toString()); - } - -} +} \ No newline at end of file diff --git a/source/base/net.d b/source/base/net.d deleted file mode 100644 index 08b7420..0000000 --- a/source/base/net.d +++ /dev/null @@ -1,12 +0,0 @@ -module base.net; - -import base.types; -import std.socket : Socket; - -public class NetworkException : BesterException -{ - this(Socket socketResponsible) - { - super("Socket error with: " ~ socketResponsible.remoteAddress().toAddrString()); - } -} \ No newline at end of file diff --git a/source/base/types.d b/source/base/types.d deleted file mode 100644 index 7b1d8d0..0000000 --- a/source/base/types.d +++ /dev/null @@ -1,10 +0,0 @@ -module base.types; - -public class BesterException : Exception -{ - this(string message) - { - /* TODO: Implement me */ - super(message); - } -} \ No newline at end of file diff --git a/source/connection/connection.d b/source/connection/connection.d deleted file mode 100644 index dc1e90c..0000000 --- a/source/connection/connection.d +++ /dev/null @@ -1,535 +0,0 @@ -module connection.connection; - -import utils.debugging : debugPrint; /* TODO: Stephen */ -import std.conv : to; -import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress, SocketFlags, Address; -import core.thread : Thread; -import std.stdio : writeln, File; -import std.json : JSONValue, parseJSON, JSONException, JSONType, toJSON; -import std.string : cmp; -import handlers.handler : MessageHandler; -import server.server : BesterServer; -import handlers.response : ResponseError, HandlerResponse; -import utils.message : receiveMessage, sendMessage; -import base.net : NetworkException; -import base.types : BesterException; - -public final class BesterConnection : Thread -{ - - /* The socket to the client */ - private Socket clientConnection; - - /* The server backend */ - public BesterServer server; - - /* The client's credentials */ - private string username; - private string password; - - /* If the connection is active */ - private bool isActive = true; - - /* The connection scope */ - public enum Scope - { - CLIENT, - SERVER, - UNKNOWN - } - - /* The type of this connection */ - private Scope connectionType = Scope.UNKNOWN; - - /* Get the type of the connection */ - public Scope getType() - { - return connectionType; - } - - /* Get the socket */ - public Socket getSocket() - { - return clientConnection; - } - - this(Socket clientConnection, BesterServer server) - { - /* Save socket and set thread worker function pointer */ - super(&run); - this.clientConnection = clientConnection; - this.server = server; - - debugPrint("New client handler spawned for " ~ clientConnection.remoteAddress().toAddrString()); - } - - /** - * Shutdown the BesterConnection by stopping - * the read-write loop and closing the socket. - */ - public void shutdown() - { - /* TODO: Send message posssibly, think about this for listeners and informers (etc.) too */ - isActive = false; - } - - override public string toString() - { - return username ~ "@" ~ clientConnection.remoteAddress().toAddrString(); - } - - /** - * Returns an array of the username and password. - */ - public string[] getCredentials() - { - return [username, password]; - } - - /* Read/send loop */ - private void run() - { - debugPrint("<<< Begin read/send loop >>>"); - while(isActive) - { - /* Received JSON message */ - JSONValue receivedMessage; - - /* Attempt to receive a message */ - try - { - /* Receive a message */ - receiveMessage(clientConnection, receivedMessage); - - /** - * If the message was received successfully then - * process the message. */ - processMessage(receivedMessage); - - /* Check if this is a server connection, if so, end the connection */ - if(connectionType == Scope.SERVER) - { - debugPrint("Server connection done, closing BesterConnection."); - shutdown(); - } - } - catch(BesterException exception) - { - debugPrint("Error in read/write loop: " ~ exception.toString()); - break; - } - } - debugPrint("<<< End read/send loop >>>"); - - /* Close the socket */ - clientConnection.close(); - - /* TODO: Remove myself from the connections array */ - } - - /** - * Destructor for BesterConnection - * - * Upon the client disconnecting, the only reference to - * this object should be through the `connections` array - * in the instance of BesterServer but because that will - * be removed in the end of the `run` function call. - * - * And because the thread ends thereafter, there will be - * no reference there either. - * - * Only then will this function be called by the garbage- - * collector, this will provide the remaining clean ups. - */ - ~this() - { - debugPrint("Destructor for \"" ~ this.toString() ~ "\" running..."); - - /* Close the socket to the client */ - clientConnection.close(); - debugPrint("Closed socket to client"); - - debugPrint("Destructor finished"); - } - - /* TODO: Comment [], rename [] */ - - /** - * Dispatches the message to the correct message handler. - * - * Returns `true` on success or partial success, `false` - * on fatal protocol error. - */ - private bool dispatchMessage(Scope scopeField, JSONValue payloadBlock) - { - /* The payload type */ - string payloadType; - - /* The payload data */ - JSONValue payloadData; - - /* The payload tag */ - string payloadTag; - - /* Attempt to parse protocol-critical fields */ - try - { - /* Get the payload type */ - payloadType = payloadBlock["type"].str; - debugPrint("Payload type is \"" ~ payloadType ~ "\""); - - /* Get the payload data */ - payloadData = payloadBlock["data"]; - - /* Get the payload tag */ - payloadTag = payloadBlock["id"].str(); - } - catch(JSONException e) - { - debugPrint("Fatal error when processing packet, missing fields"); - return false; - } - - /* Lookup the payloadType handler */ - MessageHandler chosenHandler = server.findHandler(payloadType); - - /* Check if it is a dummy type */ - if(cmp(payloadType, "dummy") == 0) - { - /* Construct a dummy response */ - JSONValue dummyMessage; - - /* Construct a header block */ - JSONValue headerBlock; - headerBlock["status"] = "0"; - - /* Attach the header block */ - dummyMessage["header"] = headerBlock; - - /* Construct the payload block */ - JSONValue dummyPayloadBlock; - dummyPayloadBlock["data"] = null; - dummyPayloadBlock["type"] = payloadType; - dummyPayloadBlock["id"] = payloadTag; - - /* Attach the payload block */ - dummyMessage["payload"] = dummyPayloadBlock; - - try - { - /* Send the message */ - sendMessage(clientConnection, dummyMessage); - } - catch(NetworkException e) - { - debugPrint("Error sending status message, fatal closing connection"); - /* TODO: We should deactivate the connection when this happens */ - return false; - } - } - /* Check if the payload is a built-in command */ - else if(cmp(payloadType, "builtin") == 0) - { - /* TODO: Implement me */ - debugPrint("Built-in payload type"); - - /** - * Built-in commands follow the structure of - * "command" : {"type" : "cmdType", "command" : ...} - */ - JSONValue commandBlock = payloadData["command"]; - string commandType = commandBlock["type"].str; - //JSONValue command = commandBlock["args"]; - - /* If the command is `close` */ - if(cmp(commandType, "close") == 0) - { - debugPrint("Closing socket..."); - isActive = false; - - // sendStatus(0, JSONValue()); - } - else - { - debugPrint("Invalid built-in command type"); - /* TODO: Generate error response */ - // dispatchStatus = false; - - /* TODO: Send a response as the "builtin" message handler */ - } - } - /* If an external handler is found (i.e. not a built-in command) */ - else if(chosenHandler) - { - /* TODO: Implement me */ - debugPrint("Chosen handler for payload type \"" ~ payloadType ~ "\" is " ~ chosenHandler.getPluginName()); - - try - { - /* Provide the handler the message and let it process it and send us a reply */ - HandlerResponse handlerResponse = chosenHandler.handleMessage(payloadData); - - /* TODO: Continue here, we will make all error handling do on construction as to make this all more compact */ - debugPrint("<<< Message Handler [" ~ chosenHandler.getPluginName() ~ "] response >>>\n\n" ~ handlerResponse.toString()); - - /* Execute the message handler's command (as per its reply) and pass in the tag */ - handlerResponse.execute(this, payloadTag); - } - catch(ResponseError e) - { - /* In the case of an error with the message handler, send an error to the client/server */ - - /* TODO: Clean up comments */ - - /* Send error message to client */ - sendStatusReport(StatusType.FAILURE, payloadTag); - } - /* TODO: Be more specific with errors and reporting in the future */ - catch(Exception e) - { - /* TODO: Remove me */ - debugPrint("fhjhfsdjhfdjhgsdkjh UUUUH:" ~e.toString()); - - /* Send error message to client */ - sendStatusReport(StatusType.FAILURE, payloadTag); - } - - debugPrint("Handler section done (for client)"); - } - /* If no message handler for the specified type could be found */ - else - { - /* TODO: Implement error handling */ - debugPrint("No handler available for payload type \"" ~ payloadType ~ "\""); - - /* Send error message to client */ - sendStatusReport(StatusType.FAILURE, payloadTag); - } - - return true; - } - - /** - * Type of the status report. - * Either 0 (for success) or 1 (for failure). - */ - public enum StatusType - { - SUCCESS, - FAILURE - } - - /** - * Send a status report for the message with id - * `id` of type `StatusType`. - */ - public void sendStatusReport(StatusType statusType, string id) - { - /* Construct the response */ - JSONValue statusMessage; - - /* Construct the header block */ - JSONValue headerBlock; - headerBlock["status"] = statusType == 0 ? "good" : "bad"; - headerBlock["messageType"] = "statusReport"; - - /* Attach the header block */ - statusMessage["header"] = headerBlock; - - /* Create the payload block */ - JSONValue payloadBlock; - payloadBlock["id"] = id; - - /* Attach the payload block */ - statusMessage["payload"] = payloadBlock; - - try - { - /* Send the message */ - sendMessage(clientConnection, statusMessage); - } - catch(NetworkException e) - { - debugPrint("Error sending status message"); - } - } - - /** - * Given the headerBlock, this returns the requested scope - * of the connection. - */ - private Scope getConnectionScope(JSONValue headerBlock) - { - /* TODO: Type checking and bounds checking */ - - /* Get the scope block */ - JSONValue scopeBlock = headerBlock["scope"]; - string scopeString = scopeBlock.str(); - - if(cmp(scopeString, "client") == 0) - { - return Scope.CLIENT; - } - else if(cmp(scopeString, "server") == 0) - { - return Scope.SERVER; - } - - return Scope.UNKNOWN; - } - - - /** - * Sends an error message on fatal error. - * Used before client shutdown on such - * an error. - */ - private void sendFatalMessage() - { - /* TODO: Implement me */ - } - - /* Process the received message */ - private void processMessage(JSONValue jsonMessage) - { - /* Attempt to convert the message to JSON */ - try - { - /* Convert message to JSON */ - debugPrint("<<< Received JSON >>>\n\n" ~ jsonMessage.toPrettyString()); - - /* Get the header */ - JSONValue headerBlock = jsonMessage["header"]; - - /** - * Check to see if this connection is currently "untyped". - * - * If it is then we set the type. - */ - if(connectionType == Scope.UNKNOWN) - { - /* Get the scope of the message */ - Scope scopeField = getConnectionScope(headerBlock); - - /* TODO: Authenticate if client, else do ntohing for server */ - - /* Decide what action to take depending on the scope */ - if(scopeField == Scope.UNKNOWN) - { - /* If the host-provided `scope` field was invalid */ - debugPrint("Host provided scope was UNKNOWN"); - - /* TODO: Send message back about an invalid scope */ - - /* Send fatal message */ - sendFatalMessage(); - - /* Stop the read/write loop */ - shutdown(); - return; - } - else if(scopeField == Scope.CLIENT) - { - /** - * If the host-provided `scope` field is `Scope.CLIENT` - * then we must attempt authentication, if it fails - * send the client a message back and then close the - * connection. - */ - debugPrint("Client scope enabled"); - - - bool authenticationStatus; - - /* The `authentication` block */ - JSONValue authenticationBlock = headerBlock["authentication"]; - - /* Get the username and password */ - string username = authenticationBlock["username"].str(), password = authenticationBlock["password"].str(); - - /* Attempt authentication */ - authenticationStatus = server.authenticate(username, password); - - /* Check if the authentication was successful or not */ - if(authenticationStatus) - { - /** - * If the authentication was successful then store the - * client's credentials. - */ - this.username = username; - this.password = password; - - /* Send error message to client */ - // sendStatus(5, JSONValue()); - - /* TODO: Send authentication success */ - sendStatusReport(StatusType.SUCCESS, "auth_special"); - } - /* If authentication failed due to malformed message or incorrect details */ - else - { - /** - * If the authentication was unsuccessful then send a - * message to the client stating so and close the connection. - */ - debugPrint("Authenticating the user failed, sending error and closing connection."); - - /* Send fatal message */ - sendFatalMessage(); - - /* Stop the read/write loop */ - shutdown(); - return; - } - } - else if(scopeField == Scope.SERVER) - { - debugPrint("Server scope enabled"); - } - - /* Set the connection type to `scopeField` */ - connectionType = scopeField; - - if(connectionType == Scope.CLIENT) - { - return; - } - } - - /* Get the `payload` block */ - JSONValue payloadBlock = jsonMessage["payload"]; - debugPrint("<<< Payload is >>>\n\n" ~ payloadBlock.toPrettyString()); - - /** - * Dispatch the message. If a fatal failure is - * detected then the connection will be shutdown. - */ - if(dispatchMessage(connectionType, payloadBlock)) - { - debugPrint("Dispatch succeeded"); - } - else - { - debugPrint("Dispatch failed, deactivating connection..."); - - /* Send fatal message */ - sendFatalMessage(); - - /* Shutdown the connection */ - shutdown(); - } - } - /* If the attempt to convert the message to JSON fails */ - catch(JSONException exception) - { - debugPrint("Fatal format error, deactivating connection..."); - - /* Send fatal message */ - sendFatalMessage(); - - /* Shutdown the connection */ - shutdown(); - } - } -} \ No newline at end of file diff --git a/source/handlers/commands.d b/source/handlers/commands.d deleted file mode 100644 index 466debe..0000000 --- a/source/handlers/commands.d +++ /dev/null @@ -1,11 +0,0 @@ -module handlers.commands; - -import std.json : JSONValue; - -public class Command -{ - JSONValue execute() - { - return JSONValue(); - } -} \ No newline at end of file diff --git a/source/handlers/handler.d b/source/handlers/handler.d deleted file mode 100644 index be81899..0000000 --- a/source/handlers/handler.d +++ /dev/null @@ -1,217 +0,0 @@ -module handlers.handler; - -import std.stdio : writeln; -import std.socket : Socket, AddressFamily, parseAddress, SocketType, SocketOSException, UnixAddress; -import std.json : JSONValue, JSONType; -import utils.debugging : debugPrint; -import handlers.response; -import base.net; -import utils.message : sendMessage, receiveMessage; -import server.server : BesterServer; -import std.process : spawnProcess, Pid; - -public final class MessageHandler -{ - /* The path to the message handler executable */ - private string executablePath; - - /* The UNIX domain socket */ - private Socket domainSocket; - - /* The pluginName/type */ - private string pluginName; - - /* The UNIX domain socket path */ - public string socketPath; - - /* The BesterServer being used */ - public BesterServer server; - - /* The PID of the process */ - private Pid pid; - - public Socket getSocket() - { - return domainSocket; - } - - this(BesterServer server, string executablePath, string socketPath, string pluginName) - { - /* Set the plugin name */ - this.pluginName = pluginName; - - /* Set the socket path */ - this.socketPath = socketPath; - - /* Set the server this handler is associated with */ - this.server = server; - - /* Start the message handler */ - startHandlerExecutable(); - } - - private void startHandlerExecutable() - { - // pid = spawnProcess(executablePath); - } - - public string getPluginName() - { - return pluginName; - } - - public Socket getNewSocket() - { - /* Create the UNIX domain socket */ - Socket domainSocket = new Socket(AddressFamily.UNIX, SocketType.STREAM); - - /* Connect to the socket at the socket path */ - domainSocket.connect(new UnixAddress(socketPath)); - - return domainSocket; - } - - private static string[] getAvailableTypes(JSONValue handlerBlock) - { - /* Available types as strings */ - string[] availableTypesStrings; - - /* Get the available handlers */ - JSONValue availableTypes; - - /* TODO: Bounds check */ - availableTypes = handlerBlock["availableTypes"]; - - /* Make sure it is an array */ - if(availableTypes.type == JSONType.array) - { - /* Get the array of available types */ - JSONValue[] availableTypesArray = availableTypes.array; - - for(uint i = 0; i < availableTypesArray.length; i++) - { - /* Make sure that it is a string */ - if(availableTypesArray[i].type == JSONType.string) - { - /* Add the type handler to the list of available types */ - availableTypesStrings ~= availableTypesArray[i].str; - debugPrint("Module wanted: " ~ availableTypesArray[i].str); - } - else - { - /* TODO: Error handling here */ - debugPrint("Available type not of type JSON string"); - } - } - } - else - { - /* TODO: Error handling */ - } - - return availableTypesStrings; - } - - private static string[2] getConfigurationArray(string pluginName, JSONValue typeMapBlock) - { - /* The configuration string */ - string[2] configurationString; - - /* The module block */ - JSONValue moduleBlock; - - /* TODO: Bounds check */ - moduleBlock = typeMapBlock[pluginName]; - - /* Module block mst be of tpe JSON object */ - if(moduleBlock.type == JSONType.object) - { - /* TODO: Set the executable path */ - configurationString[0] = moduleBlock["handlerBinary"].str; - - /* TODO: Set the UNIX domain socket path */ - configurationString[1] = moduleBlock["unixDomainSocketPath"].str; - } - else - { - /* TODO: Error handling */ - } - - return configurationString; - } - - /* TODO: Implement me */ - public static MessageHandler[] constructHandlers(BesterServer server, JSONValue handlerBlock) - { - /* List of loaded message handlers */ - MessageHandler[] handlers; - - /* TODO: Throwing error from inside this function */ - string[] availableTypes = getAvailableTypes(handlerBlock); - - for(uint i = 0; i < availableTypes.length; i++) - { - /* Load module */ - string pluginName = availableTypes[i]; - debugPrint("Loading module \"" ~ pluginName ~ "\"..."); - - try - { - JSONValue typeMap; - - /* TODO: Bounds check */ - typeMap = handlerBlock["typeMap"]; - - string[2] configuration = getConfigurationArray(pluginName, typeMap); - debugPrint("Module executable at: \"" ~ configuration[0] ~ "\""); - debugPrint("Module socket path at: \"" ~ configuration[1] ~ "\""); - MessageHandler constructedMessageHandler = new MessageHandler(server, configuration[0], configuration[1], pluginName); - handlers ~= constructedMessageHandler; - debugPrint("Module \"" ~ pluginName ~ "\" loaded"); - } - catch(SocketOSException exception) - { - debugPrint("Error whilst loading module \"" ~ pluginName ~ "\": " ~ exception.toString()); - } - } - - return handlers; - } - - /** - * Sends the payload to the designated message handler and gets - * the response message from the handler and returns it. - */ - public HandlerResponse handleMessage(JSONValue payload) - { - /* TODO: If unix sock is down, this just hangs, we should see if the socket file exists first */ - /* Handler's UNIX domain socket */ - Socket handlerSocket = getNewSocket(); - - /* Send the payload to the message handler */ - debugPrint("Sending payload over to handler for \"" ~ getPluginName() ~ "\"."); - sendMessage(handlerSocket, payload); - - /* Get the payload sent from the message handler in response */ - debugPrint("Waiting for response from handler for \"" ~ getPluginName() ~ "\"."); - JSONValue response; - - try - { - receiveMessage(handlerSocket, response); - } - catch(NetworkException exception) - { - /* TODO: Implement error handling here and send a repsonse back (Issue: https://github.com/besterprotocol/besterd/issues/10) */ - debugPrint("Error communicating with message handler"); - } - finally - { - /* Always close the socket */ - handlerSocket.close(); - debugPrint("Closed UNIX domain socket to handler"); - } - - return new HandlerResponse(server, this, response); - } -} \ No newline at end of file diff --git a/source/handlers/response.d b/source/handlers/response.d deleted file mode 100644 index d44d438..0000000 --- a/source/handlers/response.d +++ /dev/null @@ -1,354 +0,0 @@ -module handlers.response; - -import std.json : JSONValue, JSONException, parseJSON, toJSON; -import std.conv : to; -import utils.debugging : debugPrint; -import std.string : cmp; -import std.stdio : writeln; -import connection.connection; -import base.types; -import std.socket : Socket, SocketOSException, AddressFamily, SocketType, ProtocolType, parseAddress; -import utils.message : receiveMessage, sendMessage; -import handlers.handler; -import std.string : split; -import server.server : BesterServer; -import handlers.commands : Command; - -/** -* The type of the command the message handler wants -* us to run -*/ -private enum CommandType : ubyte -{ - /* Simple message flow (always end point) */ - SEND_CLIENTS, SEND_SERVERS, SEND_HANDLER, - - /* Unknown command */ - UNKNOWN -} - -/** -* Represents a response message from a handler. -*/ -public final class HandlerResponse -{ - /** - * The message-handler's response. - */ - private JSONValue messageResponse; - - /* The command to be executed */ - private CommandType commandType; - - /* The handler that caused such a response to be illicited */ - private MessageHandler handler; - - /* The associated server */ - private BesterServer server; - - /** - * Constructs a new `HandlerResponse` object that represents the - * message handler's response message and the execution of it. - */ - this(BesterServer server, MessageHandler handler, JSONValue messageResponse) - { - /* Set the message-handler's response message */ - this.messageResponse = messageResponse; - - /* Set the handler who caused this reponse to occur */ - this.handler = handler; - - /* Set the server associated with this message handler */ - this.server = server; - - /* Attempt parsing the message and error checking it */ - parse(messageResponse); - } - - private void parse(JSONValue handlerResponse) - { - /** - * Handles the response sent back to the server from the - * message handler. - */ - - /* Get the status */ - ulong statusCode; - - /* Error? */ - bool error; - - /* TODO: Bounds checking, type checking */ - try - { - /* Get the header block */ - JSONValue headerBlock = handlerResponse["header"]; - - /* Get the status */ - statusCode = to!(ulong)(headerBlock["status"].str()); - debugPrint("Status code: " ~ to!(string)(statusCode)); - - /* If the status is 0, then it is all fine */ - if(statusCode == 0) - { - debugPrint("Status is fine, the handler ran correctly"); - - /* The command block */ - JSONValue commandBlock = headerBlock["command"]; - - /** - * Get the command that the message handler wants the - * server to run. - */ - string serverCommand = commandBlock["type"].str; - debugPrint("Handler->Server command: \"" ~ serverCommand ~ "\""); - - /* Check the command to be run */ - if(cmp(serverCommand, "sendClients") == 0) - { - /* Set the command type to SEND_CLIENTS */ - commandType = CommandType.SEND_CLIENTS; - - /* TODO: Error check and do accesses JSON that would be done in `.execute` */ - } - else if(cmp(serverCommand, "sendServers") == 0) - { - /* Set the command type to SEND_SERVERS */ - commandType = CommandType.SEND_SERVERS; - - /* TODO: Error check and do accesses JSON that would be done in `.execute` */ - } - else if(cmp(serverCommand, "sendHandler") == 0) - { - /* Set the command type to SEND_HAANDLER */ - commandType = CommandType.SEND_HANDLER; - - /* TODO: Error check and do accesses JSON that would be done in `.execute` */ - } - else - { - /* TODO: Error handling */ - debugPrint("The message handler is using an invalid command"); - } - } - else - { - /* If the message handler returned a response in error */ - debugPrint("Message handler returned an error code: " ~ to!(string)(statusCode)); - error = true; - } - } - catch(JSONException exception) - { - debugPrint("<<< There was an error handling the response message >>>\n\n" ~ exception.toString()); - error = true; - } - - /** - * If an error was envountered anyway down the processing of the - * message-handler then raise a `ResponseError` exception. - */ - if(error) - { - throw new ResponseError(messageResponse, statusCode); - } - } - - /** - * Executes the command. Either `sendClients`, `sendServers` - * or `sendHandler`. - */ - public void execute(BesterConnection originalRequester, string messageID) - { - /* If the command is SEND_CLIENTS */ - if(commandType == CommandType.SEND_CLIENTS) - { - /* Get the list of clients to send to */ - string[] clients; - JSONValue[] clientList = messageResponse["header"]["command"]["data"].array(); - for(ulong i = 0; i < clientList.length; i++) - { - clients ~= clientList[i].str(); - } - - debugPrint("Users wanting to send to: " ~ to!(string)(clients)); - - /* Find the users that are wanting to be sent to */ - BesterConnection[] connectionList = originalRequester.server.getClients(clients); - //debugPrint("Users matched online on server: " ~ to!(string)(connectionList)); - - /* The fully response message to send back */ - JSONValue clientPayload; - - /* Set the header of the response */ - JSONValue headerBlock; - headerBlock["messageType"] = "receivedMessage"; - clientPayload["header"] = headerBlock; - - /* Set the payload of the response */ - JSONValue payloadBlock; - payloadBlock["data"] = messageResponse["data"]; - payloadBlock["type"] = handler.getPluginName(); - clientPayload["payload"] = payloadBlock; - - - /** - * Loop through each BesterConnection in connectionList and - * send the message-handler payload response message to each - * of them. - */ - bool allSuccess = true; - for(ulong i = 0; i < connectionList.length; i++) - { - /* Get the conneciton */ - BesterConnection clientConnection = connectionList[i]; - - try - { - /* Get the client's socket */ - Socket clientSocket = clientConnection.getSocket(); - //debugPrint("IsAlive?: " ~ to!(string)(clientSocket.isAlive())); - - /* Send the message to the client */ - debugPrint("Sending handler's response to client \"" ~ clientConnection.toString() ~ "\"..."); - sendMessage(clientSocket, clientPayload); - debugPrint("Sending handler's response to client \"" ~ clientConnection.toString() ~ "\"... [sent]"); - } - catch(SocketOSException exception) - { - /** - * If there was an error sending to the client, this can happen - * if the client has disconnected but hasn't yet been removed from - * the connections array and hence we try to send on a dead socket - * or get the remoteAddress on a dead socket, which causes a - * SocketOSException to be called. - */ - debugPrint("Attempted interacting with dead socket"); - allSuccess = false; - } - } - - debugPrint("SEND_CLIENTS: Completed run"); - - /** - * Send a status report here. - */ - originalRequester.sendStatusReport(cast(BesterConnection.StatusType)!allSuccess, messageID); - } - else if (commandType == CommandType.SEND_SERVERS) - { - /* Get the list of servers to send to */ - string[] servers; - JSONValue[] serverList = messageResponse["header"]["command"]["data"].array(); - for(ulong i = 0; i < serverList.length; i++) - { - servers ~= serverList[i].str(); - } - - /* TODO: Implement me */ - writeln("Servers wanting to send to ", servers); - - - /* The fully response message to send back */ - JSONValue serverPayload; - - /* Set the header of the response */ - JSONValue headerBlock; - headerBlock["scope"] = "server"; - serverPayload["header"] = headerBlock; - - /* Set the payload of the response */ - JSONValue payloadBlock; - payloadBlock["data"] = messageResponse["data"]; - payloadBlock["type"] = handler.getPluginName(); - serverPayload["payload"] = payloadBlock; - - - /* Attempt connecting to each server and sending the payload */ - bool allSuccess = true; - for(ulong i = 0; i < servers.length; i++) - { - /* Get the current server address and port */ - string serverString = servers[i]; - string host = serverString.split(":")[0]; - ushort port = to!(ushort)(serverString.split(":")[1]); - - try - { - Socket serverConnection = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP); - - /* Connect to the server */ - debugPrint("Connecting to server \"" ~ serverConnection.toString() ~ "\"..."); - serverConnection.connect(parseAddress(host, port)); - debugPrint("Connecting to server \"" ~ serverConnection.toString() ~ "\"... [connected]"); - - /* Send the payload */ - debugPrint("Sending handler's response to server \"" ~ serverConnection.toString() ~ "\"..."); - sendMessage(serverConnection, serverPayload); - debugPrint("Sending handler's response to server \"" ~ serverConnection.toString() ~ "\"... [sent]"); - - /* Close the connection to the server */ - serverConnection.close(); - debugPrint("Closed connection to server \"" ~ serverConnection.toString() ~ "\""); - } - catch(Exception e) - { - /* TODO: Be more specific with the above exception type */ - debugPrint("Error whilst sending payload to server: " ~ e.toString()); - allSuccess = false; - } - } - - debugPrint("SEND_SERVERS: Completed run"); - - /** - * Send a status report here. - */ - originalRequester.sendStatusReport(cast(BesterConnection.StatusType)!allSuccess, messageID); - } - else if (commandType == CommandType.SEND_HANDLER) - { - /* Name of the handler to send the message to */ - string handler = messageResponse["header"]["command"]["data"].str(); - debugPrint("Handler to forward to: " ~ handler); - - /* Lookup the payloadType handler */ - MessageHandler chosenHandler = server.findHandler(handler); - - /* Send the data to the message handler */ - HandlerResponse handlerResponse = chosenHandler.handleMessage(messageResponse["data"]); - - /* Execute the code (this here, recursive) */ - handlerResponse.execute(originalRequester, messageID); - - debugPrint("SEND_HANDLER: Completed run"); - } - else - { - /* TODO: */ - } - } - - override public string toString() - { - return messageResponse.toPrettyString(); - } -} - -/** -* Represents an error in handling the response. -*/ -public final class ResponseError : BesterException -{ - - /* */ - - /* The status code that resulted in the response handling error */ - private ulong statusCode; - - this(JSONValue messageResponse, ulong statusCode) - { - /* TODO: Set message afterwards again */ - super(""); - } -} \ No newline at end of file diff --git a/source/listeners/listener.d b/source/listeners/listener.d deleted file mode 100644 index d2bd6c3..0000000 --- a/source/listeners/listener.d +++ /dev/null @@ -1,101 +0,0 @@ -module listeners.listener; - -import utils.debugging : debugPrint; -import std.conv : to; -import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress, SocketFlags, Address; -import core.thread : Thread; -import std.stdio : writeln, File; -import std.json : JSONValue, parseJSON, JSONException, JSONType, toJSON; -import std.string : cmp; -import handlers.handler; -import server.server; -import connection.connection; -import base.types : BesterException; - -/** -* Represents a server listener which is a method -* by which conections to the server (client or server) -* can be made. -*/ -public class BesterListener : Thread -{ - /* The associated BesterServer */ - private BesterServer server; - - /* The server's socket */ - private Socket serverSocket; - - /* Whether or not the listener is active */ - private bool active = true; - - /* the address of this listener */ - protected Address address; - - this(BesterServer besterServer) - { - /* Set the function address to be called as the worker function */ - super(&run); - - /* Set this listener's BesterServer */ - this.server = besterServer; - } - - /** - * Set the server socket. - */ - public void setServerSocket(Socket serverSocket) - { - /* Set the server socket */ - this.serverSocket = serverSocket; - - /* Set the address */ - address = serverSocket.localAddress(); - } - - /** - * Get the server socket. - */ - public Socket getServerSocket() - { - return serverSocket; - } - - /** - * Start listen loop. - */ - public void run() - { - serverSocket.listen(1); /* TODO: This value */ - debugPrint("Server listen loop started"); - - /* Loop receive and dispatch connections whilst active */ - while(active) - { - /* Wait for an incoming connection */ - Socket clientConnection = serverSocket.accept(); - - /* Create a new client connection handler and start its thread */ - BesterConnection besterConnection = new BesterConnection(clientConnection, server); - besterConnection.start(); - - /* Add this client to the list of connected clients */ - server.addConnection(besterConnection); - } - - /* Close the socket */ - serverSocket.close(); - } - - public void shutdown() - { - active = false; - } -} - -public final class BesterListenerException : BesterException -{ - this(BesterListener e) - { - super("Could not bind to: " ~ e.toString()); - } -} diff --git a/source/listeners/types.d b/source/listeners/types.d deleted file mode 100644 index 1d17cb0..0000000 --- a/source/listeners/types.d +++ /dev/null @@ -1,105 +0,0 @@ -module listeners.types; - -import listeners.listener : BesterListener, BesterListenerException; -import server.server : BesterServer; -import std.socket : Socket, Address, AddressFamily, SocketType, SocketException; - -/** -* Represents a stream socket listener over UNIX -* domain sockets. -*/ -public final class UNIXListener : BesterListener -{ - this(BesterServer besterServer, Address address) - { - super(besterServer); - try - { - setServerSocket(setupUNIXSocket(address)); - } - catch(SocketException e) - { - throw new BesterListenerException(this); - } - } - - private Socket setupUNIXSocket(Address address) - { - Socket unixSocket = new Socket(AddressFamily.UNIX, SocketType.STREAM); - unixSocket.bind(address); - return unixSocket; - } - - override public string toString() - { - string address = "unix://"~super.address.toAddrString(); - return address; - } -} - -/** -* Represents a stream socket listener over TCP -* on IPv4. -*/ -public final class TCP4Listener : BesterListener -{ - this(BesterServer besterServer, Address address) - { - super(besterServer); - try - { - setServerSocket(setupTCP4Socket(address)); - } - catch(SocketException e) - { - throw new BesterListenerException(this); - } - } - - private Socket setupTCP4Socket(Address address) - { - Socket tcp4Socket = new Socket(AddressFamily.INET, SocketType.STREAM); - tcp4Socket.bind(address); - return tcp4Socket; - } - - override public string toString() - { - string address = "tcp4://"~super.address.toAddrString()~":"~super.address.toPortString(); - return address; - } -} - -/** -* Represents a stream socket listener over TCP -* on IPv6. -*/ -public final class TCP6Listener : BesterListener -{ - this(BesterServer besterServer, Address address) - { - super(besterServer); - try - { - setServerSocket(setupTCP6Socket(address)); - } - catch(SocketException e) - { - throw new BesterListenerException(this); - } - } - - private Socket setupTCP6Socket(Address address) - { - Socket tcp6Socket = new Socket(AddressFamily.INET6, SocketType.STREAM); - tcp6Socket.bind(address); - return tcp6Socket; - } - - override public string toString() - { - string address = "tcp6://"~super.address.toAddrString()~":"~super.address.toPortString(); - return address; - } -} - diff --git a/source/server/accounts/base.d b/source/server/accounts/base.d deleted file mode 100644 index a44e06d..0000000 --- a/source/server/accounts/base.d +++ /dev/null @@ -1,23 +0,0 @@ -module server.accounts.base; - -/** -* This represents the accounts management system of -* the server. It is only an abstract class. -*/ -public abstract class BesterDataStore -{ - /** - * Creates a new account with the given `username` and - * `password`. - */ - public abstract void createAccount(string username, string password); - - /** - * Check if the user, `username`, exists in the database. - */ - public abstract bool userExists(string username); - - public abstract bool authenticate(string username, string password); - - public abstract void shutdown(); -} \ No newline at end of file diff --git a/source/server/accounts/redis.d b/source/server/accounts/redis.d deleted file mode 100644 index c69960b..0000000 --- a/source/server/accounts/redis.d +++ /dev/null @@ -1,118 +0,0 @@ -module server.accounts.redis; - -import vibe.vibe; -import server.accounts.base : BesterDataStore; -import utils.debugging : debugPrint; - -/** -* This represents a Redis datastore for the Bester -* server's account management system. -*/ -public final class RedisDataStore : BesterDataStore -{ - - /** - * Redis client. - */ - private RedisClient redisClient; - - /** - * Redis database with the account information - */ - private RedisDatabase redisDatabase; - - this(string address, ushort port) - { - /* Opens a connection to the redis server */ - initializeRedis(address, port); - } - - private void initializeRedis(string address, ushort port) - { - redisClient = new RedisClient(address, port); - redisDatabase = redisClient.getDatabase(0); - // createAccount("deavmi", "poes"); - - } - - override public bool userExists(string username) - { - /* TODO: Implement me */ - return redisDatabase.exists(username); - // return true; - } - - override public bool authenticate(string username, string password) - { - debugPrint(redisClient.info()); - debugPrint(redisDatabase.keys("*")); - /* Check if a key exists with the `username` */ - bool accountExists = redisDatabase.exists(username); - debugPrint(accountExists); - if(accountExists) - { - /** - * Check within the key if the subkey and value pair exists. - * `(username) [password: ], ...` - */ - if(redisDatabase.hexists(username, "password")) - { - /* Get the password sub-field */ - string passwordDB = redisDatabase.hget(username, "password"); - if(cmp(password, passwordDB) == 0) - { - return true; - } - else - { - return false; - } - } - else - { - /* TODO: Raise exception for missing password sub-key */ - } - } - else - { - /* TODO: Raise exception for non-existent account */ - } - - /* TODO: Remove */ - return false; - } - - override public void createAccount(string username, string password) - { - /* TODO: Implement me */ - - /* Check if a key exists with the `username` */ - bool accountExists = redisDatabase.exists(username); - - if(!accountExists) - { - /** - * Create the new account. - * This involves creating a new key named `username` - * with a field named `"password"` matching to the value - * of `password`. - */ - redisDatabase.hset(username, "password", password); - } - else - { - /* TODO: Raise exception for an already existing account */ - } - } - - public void f() - { - - } - - override public void shutdown() - { - /* TODO: Should we shutdown the server? */ - redisClient.shutdown(); - } -} \ No newline at end of file diff --git a/source/server/informer/client.d b/source/server/informer/client.d deleted file mode 100644 index 11aafb8..0000000 --- a/source/server/informer/client.d +++ /dev/null @@ -1,217 +0,0 @@ -module server.informer.client; - -import core.thread : Thread; -import server.server : BesterServer; -import std.socket; -import utils.message : receiveMessage, sendMessage; -import std.json; -import utils.debugging; -import std.string; -import std.conv : to; -import connection.connection : BesterConnection; - -/** -* Represents a handler's connection to the -* Bester informer socket, runs as a seperate -* thread with a read, dispatch, write loop -* to handle commands and responses to the handler -* from the server. -*/ -public final class BesterInformerClient : Thread -{ - /* The associated `BesterServer` */ - private BesterServer server; - - /* The socket to the handler */ - private Socket handlerSocket; - - /* If the connection is still active or not */ - private bool active = true; - - /** - * Constructs a new `BesterInformerClient` with the - * associated BesterServer, `server`, and handler - * socket, `handlerSocket`. - */ - this(BesterServer server, Socket handlerSocket) - { - super(&worker); - this.server = server; - this.handlerSocket = handlerSocket; - } - - /** - * Run's the command specified in `commandBlock` and sets the - * response in the variable pointed to by `result`. - */ - private bool runCommand(JSONValue commandBlock, ref JSONValue result) - { - try - { - /* Get the command type */ - string commandType = commandBlock["type"].str(); - debugPrint("CommandType: " ~ commandType); - - /* Check if the command if `listClients` */ - if(cmp(commandType, "listClients") == 0) - { - /* Create a JSON list of strings */ - result = listClients(server); - } - /* Check if the command is `isClient` */ - else if(cmp(commandType, "isClient") == 0) - { - /* The username to match */ - string username = commandBlock["data"].str(); - result = isClient(server, username); - } - /* Check if the command is `serverInfo` */ - else if(cmp(commandType, "serverInfo") == 0) - { - result = getServerInfo(server); - } - /* Check if the command is `quit` */ - else if (cmp(commandType, "quit") == 0) - { - /* Set the connection to inactive */ - active = false; - result = null; /* TODO: JSOn default value */ - } - /* If the command is invalid */ - else - { - result = null; - return false; - } - - debugPrint(result.toPrettyString()); - return true; - } - catch(JSONException e) - { - return false; - } - } - - private void worker() - { - /* TODO: Implement me */ - while(active) - { - /* Receive a message */ - JSONValue handlerCommand; - receiveMessage(handlerSocket, handlerCommand); - - /* The response to send */ - JSONValue handlerResponse; - - /* Respose from `runCommand` */ - JSONValue runCommandData; - - /* Attempt to get the JSON */ - try - { - /* Get the command type */ - string commandType = handlerCommand["command"]["type"].str(); - debugPrint("Command: " ~ commandType); - - /* Dispatch to the correct command and return a status */ - bool commandStatus = runCommand(handlerCommand["command"], runCommandData); - - /* Set the data */ - handlerResponse["data"] = runCommandData; - - /* Set the `status` field to `"0"` */ - handlerResponse["status"] = commandStatus ? "0" : "1"; - } - catch(JSONException e) - { - /* Set the `status` field to `"1"` */ - handlerResponse["status"] = "1"; - } - - /* Send the response to the handler */ - sendMessage(handlerSocket, handlerResponse); - } - - /* Close the socket */ - handlerSocket.close(); - } - - /** - * Shutdown the informer client. - */ - public void shutdown() - { - active = false; - } - - /** - * This functions returns `string[]` where each element - * contains the username of the locally connected client. - */ - public static string[] listClients(BesterServer server) - { - string[] clientList; - - for(ulong i = 0; i < server.clients.length; i++) - { - /* Make sure only to add client connections */ - BesterConnection connection = server.clients[i]; - if(connection.getType() == BesterConnection.Scope.CLIENT) - { - clientList ~= [connection.getCredentials()[0]]; - } - } - - return clientList; - } - - /** - * This function returns `true` if the provided username - * matches a locally connected client, `false` otherwise. - */ - public static bool isClient(BesterServer server, string username) - { - for(ulong i = 0; i < server.clients.length; i++) - { - /* Make sure only to match client connections */ - BesterConnection connection = server.clients[i]; - if(connection.getType() == BesterConnection.Scope.CLIENT && cmp(connection.getCredentials[0], username)) - { - return true; - } - } - - return false; - } - - /** - * This function returns server information. - */ - public static JSONValue getServerInfo(BesterServer server) - { - /* Server information */ - JSONValue serverInfo; - - /* Create the `listeners` block */ - JSONValue listenersBlock; - - for(ulong i = 0; i < server.listeners.length; i++) - { - JSONValue listener; - listener["address"] = server.listeners[i].toString(); - listenersBlock["listener"~to!(string)(i)] = listener; - } - - - /* TODO: Load additional information from `server.conf`'s `admin[info]` block */ - - /* TODO: Use as is number, no string */ - serverInfo["clientCount"] = to!(string)(server.clients.length); - serverInfo["adminInfo"] = server.getAdminInfo(); - serverInfo["listeners"] = listenersBlock; - - return serverInfo; - } -} \ No newline at end of file diff --git a/source/server/informer/informer.d b/source/server/informer/informer.d deleted file mode 100644 index a96fdce..0000000 --- a/source/server/informer/informer.d +++ /dev/null @@ -1,83 +0,0 @@ -module server.informer.informer; - -import core.thread : Thread; -import server.server : BesterServer; -import std.socket; -import server.informer.client : BesterInformerClient; - -/** -* The `BesterInformer` allows handlers to query (out-of-band) -* information regarding this bester server. -* -* This is useful when the message handler requires additional -* information before it sends its final message response. -*/ -public final class BesterInformer : Thread -{ - /* The associated `BesterServer` */ - private BesterServer server; - - /* Informer socket */ - private Socket informerSocket; - - /* Whether or not the informer server is active */ - private bool active = true; - - /* Connected clients */ - private BesterInformerClient[] informerClients; - - /** - * Constructs a new `BesterInformer` object - * which will accept incoming connections - * (upon calling `.start`) from handlers, - * over a UNIX domain socket, requesting - * server information. - */ - this(BesterServer server) - { - /* Set the worker function */ - super(&worker); - - /* Setup the socket */ - informerSocket = new Socket(AddressFamily.UNIX, SocketType.STREAM); - informerSocket.bind(new UnixAddress("bInformer")); - informerSocket.listen(1); /* TODO: Value */ - - /* Set the associated BesterServer */ - this.server = server; - } - - /** - * Client send-receive loop. - * Accepts incoming connections to the informer - * and dispatches them to a worker thread. - */ - private void worker() - { - while(active) - { - /* Accept the queued incoming connection */ - Socket handlerSocket = informerSocket.accept(); - - /** - * Create a new worker for the informer client, adds it - * to the client queue and dispatches its worker thread. - */ - BesterInformerClient newInformer = new BesterInformerClient(server, handlerSocket); - informerClients ~= newInformer; - newInformer.start(); - } - - /* Close the informer socket */ - informerSocket.close(); - } - - public void shutdown() - { - for(ulong i = 0; i < informerClients.length; i++) - { - informerClients[i].shutdown(); - } - } - -} \ No newline at end of file diff --git a/source/server/server.d b/source/server/server.d deleted file mode 100644 index 7530380..0000000 --- a/source/server/server.d +++ /dev/null @@ -1,356 +0,0 @@ -module server.server; - -import utils.debugging : debugPrint; -import std.conv : to; -import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress; -import core.thread : Thread; -import core.sync.mutex; -import std.stdio : writeln, File; -import std.json : JSONValue, parseJSON, JSONException, JSONType, toJSON; -import std.string : cmp, strip; -import handlers.handler : MessageHandler; -import listeners.listener : BesterListener; -import connection.connection : BesterConnection; -import server.informer.informer : BesterInformer; -import server.accounts.base : BesterDataStore; -import server.accounts.redis : RedisDataStore; - -/** -* Represents an instance of a Bester server. -*/ -public final class BesterServer -{ - /** - * Array of message handlers attached to - * this server. - */ - public MessageHandler[] handlers; - - /** - * The server's socket. - */ - private Socket serverSocket; - /* TODO: The above to be replaced */ - - /** - * Socket listeners for incoming connections. - */ - public BesterListener[] listeners; - - /** - * Connected clients. - */ - public BesterConnection[] clients; - private Mutex clientsMutex; - - /** - * The informer server. - */ - private BesterInformer informer; - - /** - * Admin information regarding this server. - */ - private JSONValue adminInfo; - - /** - * The datastore for the account information. - */ - private BesterDataStore dataStore; - - /** - * Returns a list of BesterConnection objects that - * match the usernames provided. - * - * @param usernames Array of strings of usernames to match to - */ - public BesterConnection[] getClients(string[] usernames) - { - /* List of authenticated users matching `usernames` */ - BesterConnection[] matchedUsers; - - //debugPrint("All clients (ever connected): " ~ to!(string)(clients)); - - /* Search through the provided usernames */ - for(ulong i = 0; i < usernames.length; i++) - { - for(ulong k = 0; k < clients.length; k++) - { - /* The potentially-matched user */ - BesterConnection potentialMatch = clients[k]; - - /* Check if the user is authenticated */ - if(potentialMatch.getType() == BesterConnection.Scope.CLIENT && cmp(potentialMatch.getCredentials()[0], usernames[i]) == 0) - { - matchedUsers ~= potentialMatch; - } - } - } - - return matchedUsers; - } - - /** - * Adds a new Connection, `connection`, to the server. - */ - public void addConnection(BesterConnection connection) - { - /** - * Lock the mutex so that only one listener thread - * may access the array at a time. - */ - clientsMutex.lock(); - - /** - * Append the connection to the array - */ - clients ~= connection; - - /** - * Release the mutex so other listeners can now append - * to the array. - */ - clientsMutex.unlock(); - } - - /* TODO: Add more thread sfaety here and abroad */ - - /** - * Adds a listener, `listener`, to this server's - * listener set. - */ - public void addListener(BesterListener listener) - { - this.listeners ~= listener; - } - - /** - * Constructs a new BesterServer with the given - * JSON configuration. - */ - this(JSONValue config) - { - /* TODO: Bounds check and JSON type check */ - //debugPrint("Setting up socket..."); - //setupServerSocket(config["network"]); - - /* TODO: Bounds check and JSON type check */ - debugPrint("Setting up message handlers..."); - setupHandlers(config["handlers"]); - setupDatabase(config["database"]); - - /* Initialize the `clients` array mutex */ - clientsMutex = new Mutex(); - } - - /* TODO: Add comment, implement me */ - private void setupDatabase(JSONValue databaseBlock) - { - /* Get the type */ - string dbType = databaseBlock["type"].str(); - - if(cmp(dbType, "redis") == 0) - { - /* get the redis block */ - JSONValue redisBlock = databaseBlock["redis"]; - - /* Get information */ - string address = redisBlock["address"].str(); - ushort port = to!(ushort)(redisBlock["port"].str()); - - /* Create the redis datastore */ - // dataStore = new RedisDataStore(address, port); - // dataStore.createAccount("bruh","fdgdg"); - // writeln("brdfsfdhjk: ", dataStore.userExists("bruh")); - // writeln("brfdddhjk: ", dataStore.userExists("brsdfuh")); - // writeln("brfddhjk: ", dataStore.userExists("bradsfuh")); - // writeln("brfddhjk: ", dataStore.userExists("brasuh")); - // writeln("brfdhdgfgsfdsgfdgfdsjk: ", dataStore.userExists("brdfsauh")); - // writeln("brfdhjk: ", dataStore.userExists("brfdsasuh")); - // writeln("brfdhjk: ", dataStore.userExists("brasuh")); - // writeln("brfdhjk: ", dataStore.userExists("brsauh")); - // writeln("brfdhjk: ", dataStore.userExists("brsaasuh")); - // writeln("brfdhjk: ", dataStore.userExists("brusaasfh")); - // writeln("fhdjfhjdf"); - // dataStore.authenticate("dd","dd"); - - } - } - - /** - * Given JSON, `handlerBlock`, this will setup the - * relevant message handlers. - */ - private void setupHandlers(JSONValue handlerBlock) - { - /* TODO: Implement me */ - debugPrint("Constructing message handlers..."); - handlers = MessageHandler.constructHandlers(this, handlerBlock); - } - - /** - * Setup the server socket. - */ - private void setupServerSocket(JSONValue networkBlock) - { - string bindAddress; - ushort listenPort; - - JSONValue jsonAddress, jsonPort; - - debugPrint(networkBlock); - - /* TODO: Bounds check */ - jsonAddress = networkBlock["address"]; - jsonPort = networkBlock["port"]; - - bindAddress = jsonAddress.str; - listenPort = cast(ushort)jsonPort.integer; - - debugPrint("Binding to address: " ~ bindAddress ~ " and port " ~ to!(string)(listenPort)); - - /* Create a socket */ - serverSocket = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP); - serverSocket.bind(parseAddress(bindAddress, listenPort)); - } - - /** - * Starts all the listeners. - */ - private void startListeners() - { - for(ulong i = 0; i < listeners.length; i++) - { - debugPrint("Starting listener \"" ~ listeners[i].toString() ~ "\"..."); - listeners[i].start(); - } - } - - /** - * Starts the BesterInformer. - */ - private void startInformer() - { - informer = new BesterInformer(this); - informer.start(); - } - - /** - * Start listen loop. - */ - public void run() - { - /* Start the listeners */ - startListeners(); - - /* Start the informer */ - startInformer(); - } - - /** - * Authenticate the user. - */ - public bool authenticate(string username, string password) - { - /* TODO: Implement me */ - debugPrint("Attempting to authenticate:\n\nUsername: " ~ username ~ "\nPassword: " ~ password); - - /* If the authentication went through */ - bool authed = true; - - /* Strip the username of whitespace (TODO: Should we?) */ - username = strip(username); - - /* Make sure username and password are not empty */ - if(cmp(username, "") != 0 && cmp(password, "") != 0) - { - /* TODO: Fix me */ - //authed = dataStore.authenticate(username, password); - } - else - { - authed = false; - } - - debugPrint("Auth" ~ to!(string)(authed)); - - return authed; - } - - /** - * Returns the MessageHandler object of the requested type. - */ - public MessageHandler findHandler(string payloadType) - { - /* The found MessageHandler */ - MessageHandler foundHandler; - - for(uint i = 0; i < handlers.length; i++) - { - if(cmp(handlers[i].getPluginName(), payloadType) == 0) - { - foundHandler = handlers[i]; - break; - } - } - return foundHandler; - } - - public static bool isBuiltInCommand(string command) - { - /* Whether or not `payloadType` is a built-in command */ - bool isBuiltIn = true; - - - return isBuiltIn; - } - - /** - * Stops the server. - */ - private void shutdown() - { - /* Stop the informer service */ - informer.shutdown(); - - /* Shutdown all the listeners */ - shutdownListeners(); - - /* Shutdown all the clients */ - shutdownClients(); - - /* Shutdown the datastore */ - dataStore.shutdown(); - } - - /** - * Loops through the list of `BesterListener`s and - * shuts each of them down. - */ - private void shutdownListeners() - { - /* Shutdown all the listeners */ - for(ulong i = 0; i < listeners.length; i++) - { - listeners[i].shutdown(); - } - } - - /** - * Loops through the list of `BesterConnection`s and - * shuts each of them down. - */ - private void shutdownClients() - { - /* Shutdown all the clients */ - for(ulong i = 0; i < clients.length; i++) - { - clients[i].shutdown(); - } - } - - public JSONValue getAdminInfo() - { - return adminInfo; - } -} \ No newline at end of file diff --git a/source/utils/debugger.d b/source/utils/debugger.d deleted file mode 100644 index f171695..0000000 --- a/source/utils/debugger.d +++ /dev/null @@ -1,16 +0,0 @@ -module utils.debugging; - -import std.stdio : writeln; -import std.process : environment; -import std.conv : to; - -alias dprint = debugPrint; - -void debugPrint(MessageType)(MessageType message) -{ - /* Check if the environment variable for `B_DEBUG` exists */ - if(!(environment.get("B_DEBUG") is null)) - { - writeln("[DEBUG] " ~ to!(string)(message)); - } -} \ No newline at end of file diff --git a/source/utils/message.d b/source/utils/message.d deleted file mode 100644 index 6e89ab1..0000000 --- a/source/utils/message.d +++ /dev/null @@ -1,46 +0,0 @@ -module utils.message; - -import std.socket : Socket, SocketFlags; -import std.json : JSONValue, parseJSON, toJSON; -import utils.debugging : debugPrint; -import std.stdio : writeln; -import base.net : NetworkException; -import bmessage : bformatreceiveMessage = receiveMessage, bformatsendMessage = sendMessage; - -/** - * Generalized socket receive function which will read into the - * variable pointed to by `receiveMessage` by reading from the - * socket `originator`. - */ -public void receiveMessage(Socket originator, ref JSONValue receiveMessage) -{ - /* The received bytes */ - byte[] receivedBytes; - - if(!bformatreceiveMessage(originator, receivedBytes)) - { - throw new NetworkException(originator); - } - - receiveMessage = parseJSON(cast(string)receivedBytes); -} - -/** - * Generalized socket send function which will send the JSON - * encoded message, `jsonMessage`, over to the client at the - * other end of the socket, `recipient`. - * - * It gets the length of `jsonMessage` and encodes a 4 byte - * message header in little-endian containing the message's - * length. - */ -public void sendMessage(Socket recipient, JSONValue jsonMessage) -{ - if(!bformatsendMessage(recipient, cast(byte[])toJSON(jsonMessage))) - { - throw new NetworkException(recipient); - } -} - - -/* TODO: Hash message: Next-gen implementation */ \ No newline at end of file