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