1 module server.server;
2 
3 import utils.debugging : debugPrint;
4 import std.conv : to;
5 import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress;
6 import core.thread : Thread;
7 import core.sync.mutex;
8 import std.stdio : writeln, File;
9 import std.json : JSONValue, parseJSON, JSONException, JSONType, toJSON;
10 import std.string : cmp, strip;
11 import handlers.handler : MessageHandler;
12 import listeners.listener : BesterListener;
13 import connection.connection : BesterConnection;
14 import server.informer.informer : BesterInformer;
15 import server.accounts.base : BesterDataStore;
16 import server.accounts.redis : RedisDataStore;
17 
18 /**
19 * Represents an instance of a Bester server.
20 */
21 public final class BesterServer
22 {
23 	/**
24 	* Array of message handlers attached to
25 	* this server.
26 	*/
27 	public MessageHandler[] handlers;
28 
29 	/**
30 	* The server's socket.
31 	*/
32 	private Socket serverSocket;
33 	/* TODO: The above to be replaced */
34 
35 	/**
36 	* Socket listeners for incoming connections.
37 	*/
38 	public BesterListener[] listeners;
39 
40 	/**
41 	* Connected clients.
42 	*/
43 	public BesterConnection[] clients;
44 	private Mutex clientsMutex;
45 
46 	/**
47 	* The informer server.
48 	*/
49 	private BesterInformer informer;
50 
51 	/**
52 	* Admin information regarding this server.
53 	*/
54 	private JSONValue adminInfo;
55 
56 	/**
57 	* The datastore for the account information.
58 	*/
59 	private BesterDataStore dataStore;
60 
61 	/**
62 	 * Returns a list of BesterConnection objects that
63 	 * match the usernames provided.
64 	 *
65 	 * @param usernames Array of strings of usernames to match to
66 	 */
67 	public BesterConnection[] getClients(string[] usernames)
68 	{
69 		/* List of authenticated users matching `usernames` */
70 		BesterConnection[] matchedUsers;
71 
72 		//debugPrint("All clients (ever connected): " ~ to!(string)(clients));
73 
74 		/* Search through the provided usernames */
75 		for(ulong i = 0; i < usernames.length; i++)
76 		{
77 			for(ulong k = 0; k < clients.length; k++)
78 			{
79 				/* The potentially-matched user */
80 				BesterConnection potentialMatch = clients[k];
81 				
82 				/* Check if the user is authenticated */
83 				if(potentialMatch.getType() == BesterConnection.Scope.CLIENT && cmp(potentialMatch.getCredentials()[0], usernames[i]) == 0)
84 				{
85 					matchedUsers ~= potentialMatch;
86 				}	
87 			}
88 		}
89 
90 		return matchedUsers;
91 	}
92 
93 	/**
94 	* Adds a new Connection, `connection`, to the server.
95 	*/
96 	public void addConnection(BesterConnection connection)
97 	{
98 		/**
99 		* Lock the mutex so that only one listener thread
100 		* may access the array at a time.
101 		*/
102 		clientsMutex.lock();
103 		
104 		/**
105 		* Append the connection to the array
106 		*/
107 		clients ~= connection;
108 
109 		/**
110 		* Release the mutex so other listeners can now append
111 		* to the array.
112 		*/
113 		clientsMutex.unlock();
114 	}
115 
116 	/* TODO: Add more thread sfaety here and abroad */
117 
118 	/**
119 	* Adds a listener, `listener`, to this server's
120 	* listener set.
121 	*/
122 	public void addListener(BesterListener listener)
123 	{
124 		this.listeners ~= listener;
125 	}
126 
127 	/**
128 	* Constructs a new BesterServer with the given
129 	* JSON configuration.
130 	*/
131 	this(JSONValue config)
132 	{
133 		/* TODO: Bounds check and JSON type check */
134 		//debugPrint("Setting up socket...");
135 		//setupServerSocket(config["network"]);
136 
137 		/* TODO: Bounds check and JSON type check */
138 		debugPrint("Setting up message handlers...");
139 		setupHandlers(config["handlers"]);
140 		setupDatabase(config["database"]);
141 
142 		/* Initialize the `clients` array mutex */
143 		clientsMutex = new Mutex();
144 	}
145 
146 	/* TODO: Add comment, implement me */
147 	private void setupDatabase(JSONValue databaseBlock)
148 	{
149 		/* Get the type */
150 		string dbType = databaseBlock["type"].str();
151 
152 		if(cmp(dbType, "redis") == 0)
153 		{
154 			/* get the redis block */
155 			JSONValue redisBlock = databaseBlock["redis"];
156 
157 			/* Get information */
158 			string address = redisBlock["address"].str();
159 			ushort port = to!(ushort)(redisBlock["port"].str());
160 
161 			/* Create the redis datastore */
162 			// dataStore = new RedisDataStore(address, port);
163 			// dataStore.createAccount("bruh","fdgdg");
164 			// writeln("brdfsfdhjk: ", dataStore.userExists("bruh"));
165 			// writeln("brfdddhjk: ", dataStore.userExists("brsdfuh"));
166 			// writeln("brfddhjk: ", dataStore.userExists("bradsfuh"));
167 			// writeln("brfddhjk: ", dataStore.userExists("brasuh"));
168 			// writeln("brfdhdgfgsfdsgfdgfdsjk: ", dataStore.userExists("brdfsauh"));
169 			// writeln("brfdhjk: ", dataStore.userExists("brfdsasuh"));
170 			// writeln("brfdhjk: ", dataStore.userExists("brasuh"));
171 			// writeln("brfdhjk: ", dataStore.userExists("brsauh"));
172 			// writeln("brfdhjk: ", dataStore.userExists("brsaasuh"));
173 			// writeln("brfdhjk: ", dataStore.userExists("brusaasfh"));
174 			// writeln("fhdjfhjdf");
175 			// dataStore.authenticate("dd","dd");
176 			
177 		}
178 	}
179 
180 	/**
181 	* Given JSON, `handlerBlock`, this will setup the
182 	* relevant message handlers.
183 	*/
184 	private void setupHandlers(JSONValue handlerBlock)
185 	{
186 		/* TODO: Implement me */
187 		debugPrint("Constructing message handlers...");
188 		handlers = MessageHandler.constructHandlers(this, handlerBlock);
189 	}
190 
191 	/**
192 	* Setup the server socket.
193 	*/
194 	private void setupServerSocket(JSONValue networkBlock)
195 	{
196 		string bindAddress;
197 		ushort listenPort;
198 		
199 		JSONValue jsonAddress, jsonPort;
200 
201 		debugPrint(networkBlock);
202 
203 		/* TODO: Bounds check */
204 		jsonAddress = networkBlock["address"];
205 		jsonPort = networkBlock["port"];
206 
207 		bindAddress = jsonAddress.str;
208 		listenPort = cast(ushort)jsonPort.integer;
209 
210 		debugPrint("Binding to address: " ~ bindAddress ~ " and port " ~ to!(string)(listenPort));
211 		
212 		/* Create a socket */
213 		serverSocket = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
214 		serverSocket.bind(parseAddress(bindAddress, listenPort));
215 	}
216 
217 	/**
218 	* Starts all the listeners.
219 	*/
220 	private void startListeners()
221 	{
222 		for(ulong i = 0; i < listeners.length; i++)
223 		{
224 			debugPrint("Starting listener \"" ~ listeners[i].toString() ~ "\"...");
225 			listeners[i].start();
226 		}
227 	}
228 
229 	/**
230 	* Starts the BesterInformer.
231 	*/
232 	private void startInformer()
233 	{
234 		informer = new BesterInformer(this);
235 		informer.start();
236 	}
237 
238 	/**
239 	* Start listen loop.
240 	*/
241 	public void run()
242 	{
243 		/* Start the listeners */
244 		startListeners();
245 
246 		/* Start the informer */
247 		startInformer();
248 	}
249 
250 	/**
251 	* Authenticate the user.
252 	*/
253 	public bool authenticate(string username, string password)
254 	{
255 		/* TODO: Implement me */
256 		debugPrint("Attempting to authenticate:\n\nUsername: " ~ username ~ "\nPassword: " ~ password);
257 
258 		/* If the authentication went through */
259 		bool authed = true;
260 
261 		/* Strip the username of whitespace (TODO: Should we?) */
262 		username = strip(username);
263 
264 		/* Make sure username and password are not empty */
265 		if(cmp(username, "") != 0 && cmp(password, "") != 0)
266 		{
267 			/* TODO: Fix me */
268 			//authed = dataStore.authenticate(username, password);
269 		}
270 		else
271 		{
272 			authed = false;
273 		}
274 
275 		debugPrint("Auth" ~ to!(string)(authed));
276 
277 		return authed;
278 	}
279 
280 	/**
281 	* Returns the MessageHandler object of the requested type.
282 	*/
283 	public MessageHandler findHandler(string payloadType)
284 	{
285 		/* The found MessageHandler */
286 		MessageHandler foundHandler;
287 		
288 		for(uint i = 0; i < handlers.length; i++)
289 		{
290 			if(cmp(handlers[i].getPluginName(), payloadType) == 0)
291 			{
292 				foundHandler = handlers[i];
293 				break;
294 			}
295 		}
296 		return foundHandler;
297 	}
298 
299 	public static bool isBuiltInCommand(string command)
300 	{
301 		/* Whether or not `payloadType` is a built-in command */
302 		bool isBuiltIn = true;
303 
304 
305 		return isBuiltIn;
306 	}
307 
308 	/**
309 	* Stops the server.
310 	*/
311 	private void shutdown()
312 	{
313 		/* Stop the informer service */
314 		informer.shutdown();
315 
316 		/* Shutdown all the listeners */
317 		shutdownListeners();
318 
319 		/* Shutdown all the clients */
320 		shutdownClients();
321 
322 		/* Shutdown the datastore */
323 		dataStore.shutdown();
324 	}
325 
326 	/**
327 	* Loops through the list of `BesterListener`s and
328 	* shuts each of them down.
329 	*/
330 	private void shutdownListeners()
331 	{
332 		/* Shutdown all the listeners */
333 		for(ulong i = 0; i < listeners.length; i++)
334 		{
335 			listeners[i].shutdown();
336 		}
337 	}
338 
339 	/**
340 	* Loops through the list of `BesterConnection`s and
341 	* shuts each of them down.
342 	*/
343 	private void shutdownClients()
344 	{
345 		/* Shutdown all the clients */
346 		for(ulong i = 0; i < clients.length; i++)
347 		{
348 			clients[i].shutdown();
349 		}
350 	}
351 
352 	public JSONValue getAdminInfo()
353 	{
354 		return adminInfo;
355 	}
356 }