1 module connection.connection; 2 3 import utils.debugging : debugPrint; /* TODO: Stephen */ 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 : MessageHandler; 11 import server.server : BesterServer; 12 import handlers.response : ResponseError, HandlerResponse; 13 import utils.message : receiveMessage, sendMessage; 14 import base.net : NetworkException; 15 import base.types : BesterException; 16 17 public final class BesterConnection : Thread 18 { 19 20 /* The socket to the client */ 21 private Socket clientConnection; 22 23 /* The server backend */ 24 public BesterServer server; 25 26 /* The client's credentials */ 27 private string username; 28 private string password; 29 30 /* If the connection is active */ 31 private bool isActive = true; 32 33 /* The connection scope */ 34 public enum Scope 35 { 36 CLIENT, 37 SERVER, 38 UNKNOWN 39 } 40 41 /* The type of this connection */ 42 private Scope connectionType = Scope.UNKNOWN; 43 44 /* Get the type of the connection */ 45 public Scope getType() 46 { 47 return connectionType; 48 } 49 50 /* Get the socket */ 51 public Socket getSocket() 52 { 53 return clientConnection; 54 } 55 56 this(Socket clientConnection, BesterServer server) 57 { 58 /* Save socket and set thread worker function pointer */ 59 super(&run); 60 this.clientConnection = clientConnection; 61 this.server = server; 62 63 debugPrint("New client handler spawned for " ~ clientConnection.remoteAddress().toAddrString()); 64 } 65 66 /** 67 * Shutdown the BesterConnection by stopping 68 * the read-write loop and closing the socket. 69 */ 70 public void shutdown() 71 { 72 /* TODO: Send message posssibly, think about this for listeners and informers (etc.) too */ 73 isActive = false; 74 } 75 76 override public string toString() 77 { 78 return username ~ "@" ~ clientConnection.remoteAddress().toAddrString(); 79 } 80 81 /** 82 * Returns an array of the username and password. 83 */ 84 public string[] getCredentials() 85 { 86 return [username, password]; 87 } 88 89 /* Read/send loop */ 90 private void run() 91 { 92 debugPrint("<<< Begin read/send loop >>>"); 93 while(isActive) 94 { 95 /* Received JSON message */ 96 JSONValue receivedMessage; 97 98 /* Attempt to receive a message */ 99 try 100 { 101 /* Receive a message */ 102 receiveMessage(clientConnection, receivedMessage); 103 104 /** 105 * If the message was received successfully then 106 * process the message. */ 107 processMessage(receivedMessage); 108 109 /* Check if this is a server connection, if so, end the connection */ 110 if(connectionType == Scope.SERVER) 111 { 112 debugPrint("Server connection done, closing BesterConnection."); 113 shutdown(); 114 } 115 } 116 catch(BesterException exception) 117 { 118 debugPrint("Error in read/write loop: " ~ exception.toString()); 119 break; 120 } 121 } 122 debugPrint("<<< End read/send loop >>>"); 123 124 /* Close the socket */ 125 clientConnection.close(); 126 127 /* TODO: Remove myself from the connections array */ 128 } 129 130 /** 131 * Destructor for BesterConnection 132 * 133 * Upon the client disconnecting, the only reference to 134 * this object should be through the `connections` array 135 * in the instance of BesterServer but because that will 136 * be removed in the end of the `run` function call. 137 * 138 * And because the thread ends thereafter, there will be 139 * no reference there either. 140 * 141 * Only then will this function be called by the garbage- 142 * collector, this will provide the remaining clean ups. 143 */ 144 ~this() 145 { 146 debugPrint("Destructor for \"" ~ this.toString() ~ "\" running..."); 147 148 /* Close the socket to the client */ 149 clientConnection.close(); 150 debugPrint("Closed socket to client"); 151 152 debugPrint("Destructor finished"); 153 } 154 155 /* TODO: Comment [], rename [] */ 156 157 /** 158 * Dispatches the message to the correct message handler. 159 * 160 * Returns `true` on success or partial success, `false` 161 * on fatal protocol error. 162 */ 163 private bool dispatchMessage(Scope scopeField, JSONValue payloadBlock) 164 { 165 /* The payload type */ 166 string payloadType; 167 168 /* The payload data */ 169 JSONValue payloadData; 170 171 /* The payload tag */ 172 string payloadTag; 173 174 /* Attempt to parse protocol-critical fields */ 175 try 176 { 177 /* Get the payload type */ 178 payloadType = payloadBlock["type"].str; 179 debugPrint("Payload type is \"" ~ payloadType ~ "\""); 180 181 /* Get the payload data */ 182 payloadData = payloadBlock["data"]; 183 184 /* Get the payload tag */ 185 payloadTag = payloadBlock["id"].str(); 186 } 187 catch(JSONException e) 188 { 189 debugPrint("Fatal error when processing packet, missing fields"); 190 return false; 191 } 192 193 /* Lookup the payloadType handler */ 194 MessageHandler chosenHandler = server.findHandler(payloadType); 195 196 /* Check if it is a dummy type */ 197 if(cmp(payloadType, "dummy") == 0) 198 { 199 /* Construct a dummy response */ 200 JSONValue dummyMessage; 201 202 /* Construct a header block */ 203 JSONValue headerBlock; 204 headerBlock["status"] = "0"; 205 206 /* Attach the header block */ 207 dummyMessage["header"] = headerBlock; 208 209 /* Construct the payload block */ 210 JSONValue dummyPayloadBlock; 211 dummyPayloadBlock["data"] = null; 212 dummyPayloadBlock["type"] = payloadType; 213 dummyPayloadBlock["id"] = payloadTag; 214 215 /* Attach the payload block */ 216 dummyMessage["payload"] = dummyPayloadBlock; 217 218 try 219 { 220 /* Send the message */ 221 sendMessage(clientConnection, dummyMessage); 222 } 223 catch(NetworkException e) 224 { 225 debugPrint("Error sending status message, fatal closing connection"); 226 /* TODO: We should deactivate the connection when this happens */ 227 return false; 228 } 229 } 230 /* Check if the payload is a built-in command */ 231 else if(cmp(payloadType, "builtin") == 0) 232 { 233 /* TODO: Implement me */ 234 debugPrint("Built-in payload type"); 235 236 /** 237 * Built-in commands follow the structure of 238 * "command" : {"type" : "cmdType", "command" : ...} 239 */ 240 JSONValue commandBlock = payloadData["command"]; 241 string commandType = commandBlock["type"].str; 242 //JSONValue command = commandBlock["args"]; 243 244 /* If the command is `close` */ 245 if(cmp(commandType, "close") == 0) 246 { 247 debugPrint("Closing socket..."); 248 isActive = false; 249 250 // sendStatus(0, JSONValue()); 251 } 252 else 253 { 254 debugPrint("Invalid built-in command type"); 255 /* TODO: Generate error response */ 256 // dispatchStatus = false; 257 258 /* TODO: Send a response as the "builtin" message handler */ 259 } 260 } 261 /* If an external handler is found (i.e. not a built-in command) */ 262 else if(chosenHandler) 263 { 264 /* TODO: Implement me */ 265 debugPrint("Chosen handler for payload type \"" ~ payloadType ~ "\" is " ~ chosenHandler.getPluginName()); 266 267 try 268 { 269 /* Provide the handler the message and let it process it and send us a reply */ 270 HandlerResponse handlerResponse = chosenHandler.handleMessage(payloadData); 271 272 /* TODO: Continue here, we will make all error handling do on construction as to make this all more compact */ 273 debugPrint("<<< Message Handler [" ~ chosenHandler.getPluginName() ~ "] response >>>\n\n" ~ handlerResponse.toString()); 274 275 /* Execute the message handler's command (as per its reply) and pass in the tag */ 276 handlerResponse.execute(this, payloadTag); 277 } 278 catch(ResponseError e) 279 { 280 /* In the case of an error with the message handler, send an error to the client/server */ 281 282 /* TODO: Clean up comments */ 283 284 /* Send error message to client */ 285 sendStatusReport(StatusType.FAILURE, payloadTag); 286 } 287 /* TODO: Be more specific with errors and reporting in the future */ 288 catch(Exception e) 289 { 290 /* TODO: Remove me */ 291 debugPrint("fhjhfsdjhfdjhgsdkjh UUUUH:" ~e.toString()); 292 293 /* Send error message to client */ 294 sendStatusReport(StatusType.FAILURE, payloadTag); 295 } 296 297 debugPrint("Handler section done (for client)"); 298 } 299 /* If no message handler for the specified type could be found */ 300 else 301 { 302 /* TODO: Implement error handling */ 303 debugPrint("No handler available for payload type \"" ~ payloadType ~ "\""); 304 305 /* Send error message to client */ 306 sendStatusReport(StatusType.FAILURE, payloadTag); 307 } 308 309 return true; 310 } 311 312 /** 313 * Type of the status report. 314 * Either 0 (for success) or 1 (for failure). 315 */ 316 public enum StatusType 317 { 318 SUCCESS, 319 FAILURE 320 } 321 322 /** 323 * Send a status report for the message with id 324 * `id` of type `StatusType`. 325 */ 326 public void sendStatusReport(StatusType statusType, string id) 327 { 328 /* Construct the response */ 329 JSONValue statusMessage; 330 331 /* Construct the header block */ 332 JSONValue headerBlock; 333 headerBlock["status"] = statusType == 0 ? "good" : "bad"; 334 headerBlock["messageType"] = "statusReport"; 335 336 /* Attach the header block */ 337 statusMessage["header"] = headerBlock; 338 339 /* Create the payload block */ 340 JSONValue payloadBlock; 341 payloadBlock["id"] = id; 342 343 /* Attach the payload block */ 344 statusMessage["payload"] = payloadBlock; 345 346 try 347 { 348 /* Send the message */ 349 sendMessage(clientConnection, statusMessage); 350 } 351 catch(NetworkException e) 352 { 353 debugPrint("Error sending status message"); 354 } 355 } 356 357 /** 358 * Given the headerBlock, this returns the requested scope 359 * of the connection. 360 */ 361 private Scope getConnectionScope(JSONValue headerBlock) 362 { 363 /* TODO: Type checking and bounds checking */ 364 365 /* Get the scope block */ 366 JSONValue scopeBlock = headerBlock["scope"]; 367 string scopeString = scopeBlock.str(); 368 369 if(cmp(scopeString, "client") == 0) 370 { 371 return Scope.CLIENT; 372 } 373 else if(cmp(scopeString, "server") == 0) 374 { 375 return Scope.SERVER; 376 } 377 378 return Scope.UNKNOWN; 379 } 380 381 382 /** 383 * Sends an error message on fatal error. 384 * Used before client shutdown on such 385 * an error. 386 */ 387 private void sendFatalMessage() 388 { 389 /* TODO: Implement me */ 390 } 391 392 /* Process the received message */ 393 private void processMessage(JSONValue jsonMessage) 394 { 395 /* Attempt to convert the message to JSON */ 396 try 397 { 398 /* Convert message to JSON */ 399 debugPrint("<<< Received JSON >>>\n\n" ~ jsonMessage.toPrettyString()); 400 401 /* Get the header */ 402 JSONValue headerBlock = jsonMessage["header"]; 403 404 /** 405 * Check to see if this connection is currently "untyped". 406 * 407 * If it is then we set the type. 408 */ 409 if(connectionType == Scope.UNKNOWN) 410 { 411 /* Get the scope of the message */ 412 Scope scopeField = getConnectionScope(headerBlock); 413 414 /* TODO: Authenticate if client, else do ntohing for server */ 415 416 /* Decide what action to take depending on the scope */ 417 if(scopeField == Scope.UNKNOWN) 418 { 419 /* If the host-provided `scope` field was invalid */ 420 debugPrint("Host provided scope was UNKNOWN"); 421 422 /* TODO: Send message back about an invalid scope */ 423 424 /* Send fatal message */ 425 sendFatalMessage(); 426 427 /* Stop the read/write loop */ 428 shutdown(); 429 return; 430 } 431 else if(scopeField == Scope.CLIENT) 432 { 433 /** 434 * If the host-provided `scope` field is `Scope.CLIENT` 435 * then we must attempt authentication, if it fails 436 * send the client a message back and then close the 437 * connection. 438 */ 439 debugPrint("Client scope enabled"); 440 441 442 bool authenticationStatus; 443 444 /* The `authentication` block */ 445 JSONValue authenticationBlock = headerBlock["authentication"]; 446 447 /* Get the username and password */ 448 string username = authenticationBlock["username"].str(), password = authenticationBlock["password"].str(); 449 450 /* Attempt authentication */ 451 authenticationStatus = server.authenticate(username, password); 452 453 /* Check if the authentication was successful or not */ 454 if(authenticationStatus) 455 { 456 /** 457 * If the authentication was successful then store the 458 * client's credentials. 459 */ 460 this.username = username; 461 this.password = password; 462 463 /* Send error message to client */ 464 // sendStatus(5, JSONValue()); 465 466 /* TODO: Send authentication success */ 467 sendStatusReport(StatusType.SUCCESS, "auth_special"); 468 } 469 /* If authentication failed due to malformed message or incorrect details */ 470 else 471 { 472 /** 473 * If the authentication was unsuccessful then send a 474 * message to the client stating so and close the connection. 475 */ 476 debugPrint("Authenticating the user failed, sending error and closing connection."); 477 478 /* Send fatal message */ 479 sendFatalMessage(); 480 481 /* Stop the read/write loop */ 482 shutdown(); 483 return; 484 } 485 } 486 else if(scopeField == Scope.SERVER) 487 { 488 debugPrint("Server scope enabled"); 489 } 490 491 /* Set the connection type to `scopeField` */ 492 connectionType = scopeField; 493 494 if(connectionType == Scope.CLIENT) 495 { 496 return; 497 } 498 } 499 500 /* Get the `payload` block */ 501 JSONValue payloadBlock = jsonMessage["payload"]; 502 debugPrint("<<< Payload is >>>\n\n" ~ payloadBlock.toPrettyString()); 503 504 /** 505 * Dispatch the message. If a fatal failure is 506 * detected then the connection will be shutdown. 507 */ 508 if(dispatchMessage(connectionType, payloadBlock)) 509 { 510 debugPrint("Dispatch succeeded"); 511 } 512 else 513 { 514 debugPrint("Dispatch failed, deactivating connection..."); 515 516 /* Send fatal message */ 517 sendFatalMessage(); 518 519 /* Shutdown the connection */ 520 shutdown(); 521 } 522 } 523 /* If the attempt to convert the message to JSON fails */ 524 catch(JSONException exception) 525 { 526 debugPrint("Fatal format error, deactivating connection..."); 527 528 /* Send fatal message */ 529 sendFatalMessage(); 530 531 /* Shutdown the connection */ 532 shutdown(); 533 } 534 } 535 }