1 module libester.client;
2 
3 import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress, SocketOSException, SocketException;
4 import libester.execeptions;
5 import std.string : cmp;
6 import bmessage : receiveMessage, sendMessage;
7 import std.json : JSONValue, JSONException;
8 
9 public final class BesterClient
10 {
11 
12     /* Bester server endpoint */
13     private string serverAddress;
14     private ushort serverPort;
15 
16     /* Socket to server */
17     private Socket serverSocket;
18 
19     this(string serverAddress, ushort serverPort)
20     {
21         /* Make sure the `serverAddress` and `serverPort` are valid */
22         try
23         {
24             parseAddress(serverAddress, serverPort);
25         }
26         catch(SocketException)
27         {
28             throw new BesterException("Invalid network parameters");
29         }
30 
31         /* Setup endpoint */
32         this.serverAddress = serverAddress;
33         this.serverPort = serverPort;
34     }
35 
36     public void connect()
37     {
38         try
39         {
40             serverSocket = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
41             serverSocket.connect(parseAddress(serverAddress, serverPort));
42         }
43         catch(SocketOSException)
44         {
45             serverSocket = null;
46         }
47     }
48 
49     private void endpointConnectednessCheck()
50     {
51         if(serverSocket is null)
52         {
53             /* Raise exception */
54             throw new BesterException("Endpoint not connected");
55         }
56     }
57 
58     /**
59      * Authenticates the user to the server.
60      *
61      * The credentials to authenticate with are `username`
62      * and `password` and are both of type `string`.
63      *
64      * Throws a `BesterException` exception if the
65      * endpoint is not connected or on general error
66      * or if authentication fails.
67      *
68      * Returns a `JSONValue` struct with the status.
69      */
70     public void authenticate(string username, string password)
71     {
72         /* Make sure we have an open connection */
73         endpointConnectednessCheck();
74 
75         /* Whether or not the authentication succeded */
76         bool status = true;
77 
78         /* Construct the login message */
79         JSONValue message;
80 
81         /* Create the `header` block */
82         JSONValue headerBlock;
83 
84         /* Create the `authentication` block and attach it to the `header` block */
85         JSONValue authenticationBlock;
86         authenticationBlock["username"] = username;
87         authenticationBlock["password"] = password;
88         headerBlock["authentication"] = authenticationBlock;
89 
90         /* Set the `scope` field */
91         headerBlock["scope"] = "client";
92 
93         /* Add a dummy `payload` block */
94         JSONValue payloadBlock;
95         payloadBlock["data"] = null;
96         payloadBlock["type"] = "dummy"; /* TODO: Add to spec */
97         //payloadBlock["id"] = "auth_special";
98         message["payload"] = payloadBlock;
99 
100         /* Attach the `header` block to the payload */
101         message["header"] = headerBlock;
102 
103         bool netStatus;
104 
105         /* Send the message to the server */
106         netStatus = sendMessage(serverSocket, message);
107 
108         /* The server's response */
109         JSONValue serverResponse;
110 
111         if(netStatus)
112         {
113             /* Receive a response */
114             netStatus = receiveMessage(serverSocket, serverResponse);
115 
116             if(netStatus)
117             {
118                 try
119                 {
120                     /* Now get the `status` block */
121                     JSONValue statusBlock = serverResponse["header"]["status"];
122 
123                     /* Check the code */
124                     string statusCode = statusBlock.str();
125 
126                     /* Valid authentication would be "5" */
127                     if(cmp(statusCode, "good") == 0)
128                     {
129                         /* Authentication succeeded */
130                         /* TODO: Debug print */
131                     }
132                     else
133                     {
134                         /* Authentication failure */
135                         /* TODO: Debug print */
136                         status = false;
137                     }
138                 }
139                 catch(JSONException e)
140                 {
141                     status = false;
142                 }
143             }
144             else
145             {
146                 status = false;
147             }
148         }
149         else
150         {
151             status = false;
152         }
153 
154         if(!status)
155         {
156             /* TODO: Nullify */
157             serverSocket = null;
158 
159             /* TODO: Throw exception for failed authentication */
160             throw new BesterException("Authentication failed");
161         }
162     }
163 
164     /**
165      * Receives a message off the message queue and
166      * returns it as a JSON value.
167      *
168      * Throws a `BesterException` exception if the
169      * endpoint is not connected or on general error.
170      *
171      * Returns a `JSONValue` struct.
172      */
173     public JSONValue receive()
174     {
175         /* Make sure we have an open connection */
176         endpointConnectednessCheck();
177 
178         /* The received message */
179         JSONValue receivedMessage;
180 
181         /* TODO: Error handling; Receives a message */
182         bool receiveStatus = receiveMessage(serverSocket, receivedMessage);
183 
184         if(!receiveStatus)
185         {
186             /* TODO: Nullify */
187             serverSocket = null;
188             throw new BesterException("Error receiving message from server");
189         }
190 
191         return receivedMessage;
192     }
193 
194     public void send(string type, JSONValue data, string id)
195     {
196         /* Make sure we have an open connection */
197         endpointConnectednessCheck();
198 
199         /* TODO: Implement me */
200 
201         /* Construct the `header` block */
202         JSONValue headerBlock;
203 
204 
205         /* Construct the `payload` block */
206         JSONValue payloadBlock;
207 
208         /* Set the type */
209         payloadBlock["type"] = type;
210         
211         /* Set the data */
212         payloadBlock["data"] = data;
213 
214         /* Set the id */
215         payloadBlock["id"] = id;
216 
217 
218         /* The message */
219         JSONValue message;
220         message["header"] = headerBlock;
221         message["payload"] = payloadBlock;
222 
223         sendMessage(serverSocket, message);
224 
225         /* TODO: Add status report code here */
226     }
227 
228     /**
229      * Closes the active connection.
230      *
231      * Throws a `BesterException` exception if the
232      * endpoint is not connected or on general error.
233      *
234      * Returns a `JSONValue` struct with the status of
235      * the `close`.
236      */
237     public JSONValue close()
238     {
239         /* Make sure we have an open connection */
240         endpointConnectednessCheck();
241 
242         /* The received message */
243         JSONValue receivedMessage;
244 
245         /* Whether or not the authentication succeded */
246         bool status = true;
247 
248         /* Construct the authentication payload */
249         // JSONValue payload;
250         // JSONValue headerBlock;
251         // headerBlock[""]
252         // payload["header"] = headerBlock;
253 
254         return receivedMessage;
255     }
256 
257 }