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 }