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 }