1 /** 2 * Configuration-related types 3 */ 4 module birchwood.config.conninfo; 5 6 import std.socket : SocketException, Address, getAddress; 7 import birchwood.client.exceptions; 8 import std.conv : to, ConvException; 9 10 /** 11 * Represents the connection details for a server 12 * to connect to 13 */ 14 public shared struct ConnectionInfo 15 { 16 /** 17 * Server address 18 */ 19 private Address addrInfo; 20 21 /** 22 * Nickname to use 23 */ 24 public string nickname; 25 26 /** 27 * Username 28 */ 29 public string username; 30 31 /** 32 * Real name 33 */ 34 public string realname; 35 36 /** 37 * Size to use to dequeue bytes 38 * from socket in read-loop 39 */ 40 private ulong bulkReadSize; 41 42 //TODO: Make this a Duration 43 /** 44 * Time to wait (in seconds) between 45 * sending messages 46 */ 47 private ulong fakeLag; 48 49 /** 50 * Quit message 51 */ 52 public const string quitMessage; 53 54 /** 55 * Key-value pairs learnt from the 56 * server 57 */ 58 private string[string] db; 59 60 /* TODO: before publishing change this bulk size */ 61 62 /** 63 * Constructs a new ConnectionInfo instance with the 64 * provided details 65 * 66 * Params: 67 * addrInfo = the server's endpoint 68 * nickname = the nickname to use 69 * bulkReadSize = the dequeue read size 70 * quitMessage = the message to use when quitting 71 */ 72 private this(Address addrInfo, string nickname, string username, string realname, ulong bulkReadSize = 20, string quitMessage = "birchwood client disconnecting...") 73 { 74 // NOTE: Not sure if much mutable in Address anyways 75 this.addrInfo = cast(shared Address)addrInfo; 76 this.nickname = nickname; 77 this.username = username; 78 this.realname = realname; 79 this.bulkReadSize = bulkReadSize; 80 this.quitMessage = quitMessage; 81 82 // Set the default fakelag to 1 83 this.fakeLag = 1; 84 } 85 86 /** 87 * Retrieve the read-dequeue size 88 * 89 * Returns: the number of bytes 90 */ 91 public ulong getBulkReadSize() 92 { 93 return this.bulkReadSize; 94 } 95 96 /** 97 * Sets the read-dequeue size 98 * 99 * Params: 100 * bytes = the number of bytes to dequeue at a time 101 */ 102 public void setBulkReadSize(ulong bytes) 103 { 104 this.bulkReadSize = bytes; 105 } 106 107 /** 108 * Get the address of the endpoint server 109 * 110 * Returns: the server's address 111 */ 112 public Address getAddr() 113 { 114 return cast(Address)addrInfo; 115 } 116 117 /** 118 * Get the chosen fake lag 119 * 120 * Returns: the fake lag in seconds 121 */ 122 public ulong getFakeLag() 123 { 124 return fakeLag; 125 } 126 127 /** 128 * Sets the fake lag in seconds 129 * 130 * Params: 131 * fakeLag = the fake lag to use 132 */ 133 public void setFakeLag(ulong fakeLag) 134 { 135 this.fakeLag = fakeLag; 136 } 137 138 /** 139 * Update a value in the key-value pair database 140 * 141 * Params: 142 * key = the key to set 143 * value = the value to set to 144 */ 145 public void updateDB(string key, string value) 146 { 147 db[key] = value; 148 } 149 150 /** 151 * Retrieve a value from the key-value pair database 152 * 153 * Params: 154 * key = the key to lookup 155 * Returns: the value as type T, if not able to convert then T.init 156 * Throws: 157 * BirchwoodException if the key is not found 158 */ 159 public T getDB(T)(string key) 160 { 161 if(key in db) 162 { 163 /* Attempt conversion into T */ 164 try 165 { 166 /* Fetch and convert */ 167 T value = to!(T)(db[key]); 168 return value; 169 } 170 /* If conversion to type T fails */ 171 catch(ConvException e) 172 { 173 /* Return the initial value for such a paremeter */ 174 return T.init; 175 } 176 } 177 else 178 { 179 throw new BirchwoodException(ErrorType.DB_KEY_NOT_FOUND, "Could not find key '"~key~"'"); 180 } 181 } 182 183 184 /** 185 * Creates a ConnectionInfo struct representing a client configuration which 186 * can be provided to the Client class to create a new connection based on its 187 * parameters 188 * 189 * Params: 190 * hostname = hostname of the server 191 * port = server port 192 * nickname = nickname to use 193 * 194 * Returns: ConnectionInfo for this server 195 */ 196 public static ConnectionInfo newConnection(string hostname, ushort port, string nickname, string username, string realname) 197 { 198 try 199 { 200 /* Attempt to resolve the address (may throw SocketException) */ 201 Address[] addrInfo = getAddress(hostname, port); 202 203 /* Username check */ 204 if(!nickname.length) 205 { 206 throw new BirchwoodException(ErrorType.INVALID_CONN_INFO); 207 } 208 209 /* TODO: Add feature to choose which address to use, prefer v4 or v6 type of thing */ 210 Address chosenAddress = addrInfo[0]; 211 212 return ConnectionInfo(chosenAddress, nickname, username, realname); 213 } 214 catch(SocketException e) 215 { 216 throw new BirchwoodException(ErrorType.INVALID_CONN_INFO); 217 } 218 } 219 220 /** 221 * Tests invalid conneciton information 222 * 223 * 1. Invalid hostnames 224 * 2. Invalid usernames 225 */ 226 unittest 227 { 228 try 229 { 230 newConnection("1.", 21, "deavmi", "thedeavmi", "Tristan Brice Birchwood Kildaire"); 231 assert(false); 232 } 233 catch(BirchwoodException e) 234 { 235 assert(e.getType() == ErrorType.INVALID_CONN_INFO); 236 } 237 238 try 239 { 240 newConnection("1.1.1.1", 21, "", "thedeavmi", "Tristan Brice Birchwood Kildaire"); 241 assert(false); 242 } 243 catch(BirchwoodException e) 244 { 245 assert(e.getType() == ErrorType.INVALID_CONN_INFO); 246 } 247 248 } 249 } 250 251 /** 252 * Sets the default values as per rfc1459 in the 253 * key-value pair DB 254 * 255 * Params: 256 * connInfo = a reference to the ConnectionInfo struct to update 257 */ 258 public void setDefaults(ref ConnectionInfo connInfo) 259 { 260 /* Set the `MAXNICKLEN` to a default of 9 */ 261 connInfo.updateDB("MAXNICKLEN", "9"); 262 }