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 }