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 }