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 }