1 module connection.connection; 2 3 import utils.debugging : debugPrint; 4 import std.conv : to; 5 import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress, SocketFlags, Address; 6 import core.thread : Thread; 7 import std.stdio : writeln, File; 8 import std.json : JSONValue, parseJSON, JSONException, JSONType, toJSON; 9 import std.string : cmp; 10 import handlers.handler; 11 import listeners.listener; 12 import server.server; 13 import handlers.response; 14 import connection.message; 15 16 public final class BesterConnection : Thread 17 { 18 19 /* The socket to the client */ 20 private Socket clientConnection; 21 22 /* The server backend */ 23 public BesterServer server; 24 25 /* The client's credentials */ 26 private string username; 27 private string password; 28 29 /* The connection scope */ 30 public enum Scope 31 { 32 CLIENT, 33 SERVER, 34 UNKNOWN 35 } 36 37 /* The type of this connection */ 38 private Scope connectionType = Scope.UNKNOWN; 39 40 public Scope getType() 41 { 42 return connectionType; 43 } 44 45 this(Socket clientConnection, BesterServer server) 46 { 47 /* Save socket and set thread worker function pointer */ 48 super(&run); 49 this.clientConnection = clientConnection; 50 this.server = server; 51 52 debugPrint("New client handler spawned for " ~ clientConnection.remoteAddress().toAddrString()); 53 } 54 55 override public string toString() 56 { 57 return clientConnection.remoteAddress().toAddrString(); 58 } 59 60 public string[] getCredentials() 61 { 62 return [username, password]; 63 } 64 65 /* Read/send loop */ 66 private void run() 67 { 68 while(true) 69 { 70 /* Received JSON message */ 71 JSONValue receivedMessage; 72 73 /* Receive a message */ 74 receiveMessage(clientConnection, receivedMessage); 75 76 /* Process the message */ 77 processMessage(receivedMessage); 78 } 79 } 80 81 /** 82 * Sends the payload to the designated message handler and gets 83 * the response message from the handler and returns it. 84 */ 85 private JSONValue handlerRun(MessageHandler chosenHandler, JSONValue payload) 86 { 87 /* Handler's UNIX domain socket */ 88 Socket handlerSocket = chosenHandler.getNewSocket(); 89 90 /* Send the payload to the message handler */ 91 debugPrint("Sending payload over to handler for \"" ~ chosenHandler.getPluginName() ~ "\"."); 92 sendMessage(handlerSocket, payload); 93 94 /* Get the payload sent from the message handler in response */ 95 debugPrint("Waiting for response from handler for \"" ~ chosenHandler.getPluginName() ~ "\"."); 96 JSONValue response; 97 receiveMessage(handlerSocket, response); 98 99 return response; 100 } 101 102 103 104 /* TODO: Version 2 of message dispatcher */ 105 private bool dispatchMessage(Scope scopeField, JSONValue payloadBlock) 106 { 107 /* Status of dispatch */ 108 bool dispatchStatus = true; 109 110 /* TODO: Bounds checking, type checking */ 111 112 /* Get the payload type */ 113 string payloadType = payloadBlock["type"].str; 114 debugPrint("Payload type is \"" ~ payloadType ~ "\""); 115 116 /* Get the payload data */ 117 JSONValue payloadData = payloadBlock["data"]; 118 119 /* Lookup the payloadType handler */ 120 MessageHandler chosenHandler = server.findHandler(payloadType); 121 122 /* Check if the payload is a built-in command */ 123 if(cmp(payloadType, "builtin") == 0) 124 { 125 /* TODO: Implement me */ 126 debugPrint("Built-in payload type"); 127 128 /** 129 * Built-in commands follow the structure of 130 * "command" : {"type" : "cmdType", "command" : ...} 131 */ 132 JSONValue commandBlock = payloadData["command"]; 133 string commandType = commandBlock["type"].str; 134 JSONValue command = commandBlock["args"]; 135 136 /* If the command is `close` */ 137 if(cmp(commandType, "close") == 0) 138 { 139 debugPrint("Closing socket..."); 140 this.clientConnection.close(); 141 } 142 else 143 { 144 debugPrint("Invalid built-in command type"); 145 /* TODO: Generate error response */ 146 } 147 } 148 /* If an external handler is found (i.e. not a built-in command) */ 149 else if(chosenHandler) 150 { 151 /* TODO: Implement me */ 152 debugPrint("Chosen handler for payload type \"" ~ payloadType ~ "\" is " ~ chosenHandler.getPluginName()); 153 154 try 155 { 156 /* TODO: Collect return value */ 157 HandlerResponse handlerResponse = new HandlerResponse(handlerRun(chosenHandler, payloadData)); 158 159 /* TODO: Continue here, we will make all error handling do on construction as to make this all more compact */ 160 debugPrint("<<< Message Handler [" ~ chosenHandler.getPluginName() ~ "] response >>>\n\n" ~ handlerResponse.toString()); 161 162 /* Execute the message handler's command */ 163 handlerResponse.execute(this); 164 } 165 catch(ResponseError e) 166 { 167 /* In the case of an error with the message handler, send an error to the client/server */ 168 169 /* TODO: Send error here */ 170 } 171 172 173 /* TODO: Handle response */ 174 } 175 else 176 { 177 /* TODO: Implement error handling */ 178 debugPrint("No handler available for payload type \"" ~ payloadType ~ "\""); 179 } 180 181 return dispatchStatus; 182 } 183 184 185 /** 186 * Given the headerBlock, this returns the requested scope 187 * of the connection. 188 */ 189 private Scope getConnectionScope(JSONValue headerBlock) 190 { 191 /* TODO: Type checking and bounds checking */ 192 193 /* Get the scope block */ 194 JSONValue scopeBlock = headerBlock["scope"]; 195 string scopeString = scopeBlock.str(); 196 197 if(cmp(scopeString, "client") == 0) 198 { 199 return Scope.CLIENT; 200 } 201 else if(cmp(scopeString, "server") == 0) 202 { 203 return Scope.SERVER; 204 } 205 206 return Scope.UNKNOWN; 207 } 208 209 /* Process the received message */ 210 private void processMessage(JSONValue jsonMessage) 211 { 212 213 /* Attempt to convert the message to JSON */ 214 try 215 { 216 /* Convert message to JSON */ 217 debugPrint("<<< Received JSON >>>\n\n" ~ jsonMessage.toPrettyString()); 218 219 /* TODO: Bounds checking, type checking */ 220 221 /* Get the header */ 222 JSONValue headerBlock = jsonMessage["header"]; 223 224 225 226 /** 227 * Check to see if this connection is currently "untyped". 228 * 229 * If it is then we set the type. 230 */ 231 if(connectionType == Scope.UNKNOWN) 232 { 233 /* Get the scope of the message */ 234 Scope scopeField = getConnectionScope(headerBlock); 235 236 /* TODO: Authenticate if client, else do ntohing for server */ 237 238 /* Decide what action to take depending on the scope */ 239 if(scopeField == Scope.UNKNOWN) 240 { 241 /* If the host-provided `scope` field was invalid */ 242 debugPrint("Host provided scope was UNKNOWN"); 243 244 /* TODO: Send message back about an invalid scope */ 245 246 /* Close the connection */ 247 clientConnection.close(); 248 } 249 else if(scopeField == Scope.CLIENT) 250 { 251 /** 252 * If the host-provided `scope` field is `Scope.CLIENT` 253 * then we must attempt authentication, if it fails 254 * send the client a message back and then close the 255 * connection. 256 */ 257 258 /* Get the authentication block */ 259 JSONValue authenticationBlock = headerBlock["authentication"]; 260 261 /* Get the username and password */ 262 string username = authenticationBlock["username"].str(), password = authenticationBlock["password"].str(); 263 264 /* Attempt authentication */ 265 bool authenticationStatus = server.authenticate(username, password); 266 267 /* Check if the authentication was successful or not */ 268 if(authenticationStatus) 269 { 270 /** 271 * If the authentication was successful then store the 272 * client's credentials. 273 */ 274 this.username = username; 275 this.password = password; 276 } 277 else 278 { 279 /** 280 * If the authentication was unsuccessful then send a 281 * message to the client stating so and close the connection. 282 */ 283 debugPrint("Authenticating the user failed, sending error and closing connection."); 284 285 /* TODO : Send error message to client */ 286 287 /* Close the connection */ 288 clientConnection.close(); 289 } 290 } 291 292 293 /* Set the connection type to `scopeField` */ 294 connectionType = scopeField; 295 } 296 else 297 { 298 /* TODO: Implement worker here */ 299 } 300 301 302 /* Get the payload block */ 303 JSONValue payloadBlock = jsonMessage["payload"]; 304 debugPrint("<<< Payload is >>>\n\n" ~ payloadBlock.toPrettyString()); 305 306 307 /* Dispatch the message */ 308 bool dispatchStatus = dispatchMessage(connectionType, payloadBlock); 309 310 if(dispatchStatus) 311 { 312 debugPrint("Dispatch succeeded"); 313 } 314 else 315 { 316 /* TODO: Error handling */ 317 debugPrint("Dispatching failed..."); 318 } 319 } 320 /* If thr attempt to convert the message to JSON fails */ 321 catch(JSONException exception) 322 { 323 debugPrint("<<< There was an error whilst parsing the JSON message >>>\n\n"~exception.toString()); 324 } 325 326 /* TODO: Return value */ 327 328 } 329 330 331 }