2020-04-24 17:48:22 +02:00
|
|
|
module connection.connection;
|
2020-04-16 15:02:34 +02:00
|
|
|
|
2020-05-01 09:27:17 +02:00
|
|
|
import utils.debugging : debugPrint; /* TODO: Stephen */
|
2020-04-16 16:35:07 +02:00
|
|
|
import std.conv : to;
|
2020-04-20 18:48:06 +02:00
|
|
|
import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress, SocketFlags, Address;
|
2020-04-16 17:05:56 +02:00
|
|
|
import core.thread : Thread;
|
2020-04-17 18:47:05 +02:00
|
|
|
import std.stdio : writeln, File;
|
2020-04-18 15:25:22 +02:00
|
|
|
import std.json : JSONValue, parseJSON, JSONException, JSONType, toJSON;
|
2020-04-17 14:50:12 +02:00
|
|
|
import std.string : cmp;
|
2020-05-01 09:27:17 +02:00
|
|
|
import handlers.handler : MessageHandler;
|
|
|
|
import server.server : BesterServer;
|
|
|
|
import handlers.response : ResponseError, HandlerResponse;
|
2020-05-02 15:20:50 +02:00
|
|
|
import utils.message : receiveMessage, sendMessage;
|
2020-05-01 09:27:17 +02:00
|
|
|
import base.net : NetworkException;
|
|
|
|
import base.types : BesterException;
|
2020-04-20 17:18:40 +02:00
|
|
|
|
2020-04-24 19:19:53 +02:00
|
|
|
public final class BesterConnection : Thread
|
2020-04-16 17:05:56 +02:00
|
|
|
{
|
|
|
|
|
|
|
|
/* The socket to the client */
|
|
|
|
private Socket clientConnection;
|
|
|
|
|
2020-04-16 23:25:22 +02:00
|
|
|
/* The server backend */
|
2020-04-25 18:58:32 +02:00
|
|
|
public BesterServer server;
|
2020-04-16 23:25:22 +02:00
|
|
|
|
2020-04-19 18:53:16 +02:00
|
|
|
/* The client's credentials */
|
2020-04-26 15:01:48 +02:00
|
|
|
private string username;
|
|
|
|
private string password;
|
2020-04-19 18:53:16 +02:00
|
|
|
|
2020-05-01 09:13:42 +02:00
|
|
|
/* If the connection is active */
|
|
|
|
private bool isActive = true;
|
|
|
|
|
2020-04-26 17:01:24 +02:00
|
|
|
/* The connection scope */
|
|
|
|
public enum Scope
|
|
|
|
{
|
|
|
|
CLIENT,
|
|
|
|
SERVER,
|
|
|
|
UNKNOWN
|
|
|
|
}
|
2020-04-26 14:25:56 +02:00
|
|
|
|
|
|
|
/* The type of this connection */
|
|
|
|
private Scope connectionType = Scope.UNKNOWN;
|
|
|
|
|
2020-05-01 09:27:17 +02:00
|
|
|
/* Get the type of the connection */
|
2020-04-26 15:01:48 +02:00
|
|
|
public Scope getType()
|
2020-04-25 19:10:17 +02:00
|
|
|
{
|
2020-04-26 15:01:48 +02:00
|
|
|
return connectionType;
|
2020-04-25 19:10:17 +02:00
|
|
|
}
|
|
|
|
|
2020-05-01 09:27:17 +02:00
|
|
|
/* Get the socket */
|
2020-04-27 15:18:53 +02:00
|
|
|
public Socket getSocket()
|
|
|
|
{
|
|
|
|
return clientConnection;
|
|
|
|
}
|
|
|
|
|
2020-04-16 23:25:22 +02:00
|
|
|
this(Socket clientConnection, BesterServer server)
|
2020-04-16 17:05:56 +02:00
|
|
|
{
|
|
|
|
/* Save socket and set thread worker function pointer */
|
|
|
|
super(&run);
|
2020-04-16 17:50:10 +02:00
|
|
|
this.clientConnection = clientConnection;
|
2020-04-16 23:25:22 +02:00
|
|
|
this.server = server;
|
2020-04-16 17:50:10 +02:00
|
|
|
|
|
|
|
debugPrint("New client handler spawned for " ~ clientConnection.remoteAddress().toAddrString());
|
2020-04-16 17:05:56 +02:00
|
|
|
}
|
|
|
|
|
2020-04-25 18:58:32 +02:00
|
|
|
override public string toString()
|
|
|
|
{
|
2020-04-27 15:18:53 +02:00
|
|
|
return username ~ "@" ~ clientConnection.remoteAddress().toAddrString();
|
2020-04-25 18:58:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public string[] getCredentials()
|
|
|
|
{
|
2020-04-26 15:01:48 +02:00
|
|
|
return [username, password];
|
2020-04-25 18:58:32 +02:00
|
|
|
}
|
|
|
|
|
2020-04-16 17:05:56 +02:00
|
|
|
/* Read/send loop */
|
|
|
|
private void run()
|
|
|
|
{
|
2020-04-27 20:00:25 +02:00
|
|
|
debugPrint("<<< Begin read/send loop >>>");
|
2020-05-01 09:13:42 +02:00
|
|
|
while(isActive) /*TODO: Remove and also make the stting of this kak not be closing socket */
|
2020-04-16 16:39:31 +02:00
|
|
|
{
|
2020-04-26 19:01:01 +02:00
|
|
|
/* Received JSON message */
|
|
|
|
JSONValue receivedMessage;
|
2020-04-27 20:00:25 +02:00
|
|
|
|
2020-04-28 12:35:22 +02:00
|
|
|
/* Attempt to receive a message */
|
|
|
|
try
|
2020-04-27 20:00:25 +02:00
|
|
|
{
|
2020-04-28 12:35:22 +02:00
|
|
|
/* Receive a message */
|
|
|
|
receiveMessage(clientConnection, receivedMessage);
|
|
|
|
|
2020-04-27 20:00:25 +02:00
|
|
|
/**
|
2020-04-28 12:35:22 +02:00
|
|
|
* If the message was received successfully then
|
|
|
|
* process the message. */
|
2020-04-27 22:19:14 +02:00
|
|
|
processMessage(receivedMessage);
|
2020-04-30 17:38:32 +02:00
|
|
|
|
|
|
|
/* Check if this is a server connection, if so, end the connection */
|
|
|
|
if(connectionType == Scope.SERVER)
|
|
|
|
{
|
|
|
|
debugPrint("Server connection done, closing BesterConnection.");
|
2020-05-01 09:13:42 +02:00
|
|
|
isActive = false;
|
2020-04-30 17:38:32 +02:00
|
|
|
}
|
2020-04-27 20:00:25 +02:00
|
|
|
}
|
2020-04-28 12:35:22 +02:00
|
|
|
catch(BesterException exception)
|
2020-04-27 20:00:25 +02:00
|
|
|
{
|
2020-04-28 12:35:22 +02:00
|
|
|
debugPrint("Error in read/write loop: " ~ exception.toString());
|
2020-04-28 12:41:08 +02:00
|
|
|
break;
|
2020-04-27 20:00:25 +02:00
|
|
|
}
|
2020-04-16 18:46:26 +02:00
|
|
|
}
|
2020-04-27 20:00:25 +02:00
|
|
|
debugPrint("<<< End read/send loop >>>");
|
2020-04-29 10:16:39 +02:00
|
|
|
|
2020-05-01 09:27:17 +02:00
|
|
|
/* Close the socket */
|
2020-05-01 09:13:42 +02:00
|
|
|
clientConnection.close();
|
2020-05-01 09:27:17 +02:00
|
|
|
|
2020-04-29 10:16:39 +02:00
|
|
|
/* TODO: Remove myself from the connections array */
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destructor for BesterConnection
|
|
|
|
*
|
|
|
|
* Upon the client disconnecting, the only reference to
|
|
|
|
* this object should be through the `connections` array
|
|
|
|
* in the instance of BesterServer but because that will
|
|
|
|
* be removed in the end of the `run` function call.
|
|
|
|
*
|
|
|
|
* And because the thread ends thereafter, there will be
|
|
|
|
* no reference there either.
|
|
|
|
*
|
|
|
|
* Only then will this function be called by the garbage-
|
|
|
|
* collector, this will provide the remaining clean ups.
|
|
|
|
*/
|
|
|
|
~this()
|
|
|
|
{
|
|
|
|
debugPrint("Destructor for \"" ~ this.toString() ~ "\" running...");
|
|
|
|
|
|
|
|
/* Close the socket to the client */
|
|
|
|
clientConnection.close();
|
|
|
|
debugPrint("Closed socket to client");
|
|
|
|
|
|
|
|
debugPrint("Destructor finished");
|
2020-04-16 18:46:26 +02:00
|
|
|
}
|
|
|
|
|
2020-04-26 19:53:59 +02:00
|
|
|
/* TODO: Comment [], rename [] */
|
2020-04-19 20:32:52 +02:00
|
|
|
private bool dispatchMessage(Scope scopeField, JSONValue payloadBlock)
|
2020-04-18 22:34:26 +02:00
|
|
|
{
|
|
|
|
/* Status of dispatch */
|
|
|
|
bool dispatchStatus = true;
|
|
|
|
|
|
|
|
/* TODO: Bounds checking, type checking */
|
|
|
|
|
|
|
|
/* Get the payload type */
|
|
|
|
string payloadType = payloadBlock["type"].str;
|
|
|
|
debugPrint("Payload type is \"" ~ payloadType ~ "\"");
|
|
|
|
|
|
|
|
/* Get the payload data */
|
|
|
|
JSONValue payloadData = payloadBlock["data"];
|
|
|
|
|
|
|
|
/* Lookup the payloadType handler */
|
2020-04-19 14:45:53 +02:00
|
|
|
MessageHandler chosenHandler = server.findHandler(payloadType);
|
2020-04-18 22:50:47 +02:00
|
|
|
|
2020-04-19 13:37:26 +02:00
|
|
|
/* Check if the payload is a built-in command */
|
2020-04-19 14:31:24 +02:00
|
|
|
if(cmp(payloadType, "builtin") == 0)
|
2020-04-19 13:37:26 +02:00
|
|
|
{
|
|
|
|
/* TODO: Implement me */
|
2020-04-19 14:33:44 +02:00
|
|
|
debugPrint("Built-in payload type");
|
2020-04-19 20:32:52 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Built-in commands follow the structure of
|
|
|
|
* "command" : {"type" : "cmdType", "command" : ...}
|
|
|
|
*/
|
|
|
|
JSONValue commandBlock = payloadData["command"];
|
|
|
|
string commandType = commandBlock["type"].str;
|
2020-05-02 18:27:09 +02:00
|
|
|
//JSONValue command = commandBlock["args"];
|
2020-04-19 20:32:52 +02:00
|
|
|
|
2020-04-19 21:03:55 +02:00
|
|
|
/* If the command is `close` */
|
2020-04-26 15:10:48 +02:00
|
|
|
if(cmp(commandType, "close") == 0)
|
2020-04-19 21:03:55 +02:00
|
|
|
{
|
|
|
|
debugPrint("Closing socket...");
|
2020-05-01 09:13:42 +02:00
|
|
|
isActive = false;
|
2020-05-02 16:52:30 +02:00
|
|
|
|
|
|
|
sendStatus(0, JSONValue());
|
2020-04-19 21:03:55 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
debugPrint("Invalid built-in command type");
|
|
|
|
/* TODO: Generate error response */
|
2020-05-02 18:23:35 +02:00
|
|
|
dispatchStatus = false;
|
2020-04-19 20:32:52 +02:00
|
|
|
}
|
2020-04-19 13:37:26 +02:00
|
|
|
}
|
|
|
|
/* If an external handler is found (i.e. not a built-in command) */
|
|
|
|
else if(chosenHandler)
|
|
|
|
{
|
|
|
|
/* TODO: Implement me */
|
2020-04-19 14:33:44 +02:00
|
|
|
debugPrint("Chosen handler for payload type \"" ~ payloadType ~ "\" is " ~ chosenHandler.getPluginName());
|
2020-04-19 14:36:55 +02:00
|
|
|
|
2020-04-24 18:41:25 +02:00
|
|
|
try
|
|
|
|
{
|
2020-05-01 09:13:42 +02:00
|
|
|
/* Provide the handler the message and let it process it and send us a reply */
|
2020-04-28 18:28:38 +02:00
|
|
|
HandlerResponse handlerResponse = chosenHandler.handleMessage(payloadData);
|
2020-04-24 17:12:25 +02:00
|
|
|
|
2020-04-24 18:41:25 +02:00
|
|
|
/* TODO: Continue here, we will make all error handling do on construction as to make this all more compact */
|
|
|
|
debugPrint("<<< Message Handler [" ~ chosenHandler.getPluginName() ~ "] response >>>\n\n" ~ handlerResponse.toString());
|
2020-04-24 18:30:02 +02:00
|
|
|
|
2020-05-01 09:13:42 +02:00
|
|
|
/* Execute the message handler's command (as per its reply) */
|
2020-04-24 18:41:25 +02:00
|
|
|
handlerResponse.execute(this);
|
|
|
|
}
|
|
|
|
catch(ResponseError e)
|
|
|
|
{
|
|
|
|
/* In the case of an error with the message handler, send an error to the client/server */
|
|
|
|
|
|
|
|
/* TODO: Send error here */
|
2020-04-28 18:19:27 +02:00
|
|
|
//JSONValue errorResponse;
|
|
|
|
//errorResponse["dd"] = 2;
|
|
|
|
//debugPrint("Response error");
|
2020-05-02 18:23:35 +02:00
|
|
|
dispatchStatus = false;
|
2020-04-24 18:41:25 +02:00
|
|
|
}
|
2020-04-28 17:44:28 +02:00
|
|
|
catch(Exception e)
|
|
|
|
{
|
2020-04-28 20:01:50 +02:00
|
|
|
/* TODO: Remove me */
|
2020-04-28 17:44:28 +02:00
|
|
|
debugPrint("fhjhfsdjhfdjhgsdkjh UUUUH:" ~e.toString());
|
2020-05-02 18:23:35 +02:00
|
|
|
dispatchStatus = false;
|
2020-04-28 17:44:28 +02:00
|
|
|
}
|
2020-04-24 18:41:25 +02:00
|
|
|
|
2020-04-28 12:35:22 +02:00
|
|
|
debugPrint("Handler section done (for client)");
|
2020-04-24 18:30:02 +02:00
|
|
|
/* TODO: Handle response */
|
2020-04-19 13:37:26 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* TODO: Implement error handling */
|
2020-04-19 14:33:44 +02:00
|
|
|
debugPrint("No handler available for payload type \"" ~ payloadType ~ "\"");
|
2020-05-02 16:19:30 +02:00
|
|
|
|
|
|
|
/* Send error message to client */
|
2020-05-02 17:55:04 +02:00
|
|
|
JSONValue handlerName = payloadType;
|
|
|
|
sendStatus(1, handlerName);
|
2020-05-02 18:23:35 +02:00
|
|
|
dispatchStatus = false;
|
2020-04-19 13:37:26 +02:00
|
|
|
}
|
2020-04-18 22:34:26 +02:00
|
|
|
|
|
|
|
return dispatchStatus;
|
|
|
|
}
|
2020-04-26 14:04:11 +02:00
|
|
|
|
2020-05-02 16:52:30 +02:00
|
|
|
/* Send a status message to the client */
|
|
|
|
public void sendStatus(uint code, JSONValue data)
|
2020-05-02 16:19:30 +02:00
|
|
|
{
|
2020-05-02 16:52:30 +02:00
|
|
|
/* Construct a status message */
|
|
|
|
JSONValue statusMessage;
|
|
|
|
JSONValue statusBlock;
|
|
|
|
statusBlock["code"] = code;
|
|
|
|
statusBlock["data"] = data;
|
|
|
|
statusMessage["status"] = statusBlock;
|
2020-05-02 16:19:30 +02:00
|
|
|
|
2020-05-02 17:55:04 +02:00
|
|
|
try
|
|
|
|
{
|
|
|
|
/* Send the message */
|
|
|
|
sendMessage(clientConnection, statusMessage);
|
|
|
|
}
|
|
|
|
catch(NetworkException e)
|
|
|
|
{
|
|
|
|
debugPrint("Error sending status message");
|
|
|
|
}
|
2020-05-02 16:19:30 +02:00
|
|
|
}
|
|
|
|
|
2020-04-26 14:25:56 +02:00
|
|
|
/**
|
|
|
|
* Given the headerBlock, this returns the requested scope
|
|
|
|
* of the connection.
|
|
|
|
*/
|
2020-04-26 14:04:11 +02:00
|
|
|
private Scope getConnectionScope(JSONValue headerBlock)
|
|
|
|
{
|
|
|
|
/* TODO: Type checking and bounds checking */
|
|
|
|
|
|
|
|
/* Get the scope block */
|
|
|
|
JSONValue scopeBlock = headerBlock["scope"];
|
|
|
|
string scopeString = scopeBlock.str();
|
|
|
|
|
|
|
|
if(cmp(scopeString, "client") == 0)
|
|
|
|
{
|
|
|
|
return Scope.CLIENT;
|
|
|
|
}
|
|
|
|
else if(cmp(scopeString, "server") == 0)
|
|
|
|
{
|
|
|
|
return Scope.SERVER;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Scope.UNKNOWN;
|
|
|
|
}
|
2020-04-18 22:34:26 +02:00
|
|
|
|
2020-04-16 18:46:26 +02:00
|
|
|
/* Process the received message */
|
2020-04-26 19:01:01 +02:00
|
|
|
private void processMessage(JSONValue jsonMessage)
|
2020-04-16 18:46:26 +02:00
|
|
|
{
|
2020-04-18 22:34:26 +02:00
|
|
|
|
|
|
|
/* Attempt to convert the message to JSON */
|
|
|
|
try
|
|
|
|
{
|
|
|
|
/* Convert message to JSON */
|
|
|
|
debugPrint("<<< Received JSON >>>\n\n" ~ jsonMessage.toPrettyString());
|
|
|
|
|
|
|
|
/* TODO: Bounds checking, type checking */
|
|
|
|
|
|
|
|
/* Get the header */
|
|
|
|
JSONValue headerBlock = jsonMessage["header"];
|
|
|
|
|
2020-04-26 14:25:56 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check to see if this connection is currently "untyped".
|
|
|
|
*
|
|
|
|
* If it is then we set the type.
|
|
|
|
*/
|
|
|
|
if(connectionType == Scope.UNKNOWN)
|
|
|
|
{
|
|
|
|
/* Get the scope of the message */
|
|
|
|
Scope scopeField = getConnectionScope(headerBlock);
|
|
|
|
|
|
|
|
/* TODO: Authenticate if client, else do ntohing for server */
|
|
|
|
|
2020-04-26 14:36:04 +02:00
|
|
|
/* Decide what action to take depending on the scope */
|
|
|
|
if(scopeField == Scope.UNKNOWN)
|
|
|
|
{
|
|
|
|
/* If the host-provided `scope` field was invalid */
|
|
|
|
debugPrint("Host provided scope was UNKNOWN");
|
|
|
|
|
|
|
|
/* TODO: Send message back about an invalid scope */
|
2020-04-26 15:01:48 +02:00
|
|
|
|
2020-05-01 09:13:42 +02:00
|
|
|
/* TODO: End this here */
|
|
|
|
isActive = false;
|
2020-05-02 17:55:04 +02:00
|
|
|
return;
|
2020-04-26 15:01:48 +02:00
|
|
|
}
|
|
|
|
else if(scopeField == Scope.CLIENT)
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* If the host-provided `scope` field is `Scope.CLIENT`
|
|
|
|
* then we must attempt authentication, if it fails
|
|
|
|
* send the client a message back and then close the
|
|
|
|
* connection.
|
|
|
|
*/
|
2020-05-01 09:13:42 +02:00
|
|
|
debugPrint("Client scope enabled");
|
2020-04-26 15:01:48 +02:00
|
|
|
|
|
|
|
|
2020-05-02 15:20:50 +02:00
|
|
|
bool authenticationStatus;
|
2020-04-26 15:01:48 +02:00
|
|
|
|
2020-05-02 18:23:35 +02:00
|
|
|
/* The `authentication` block */
|
|
|
|
JSONValue authenticationBlock = headerBlock["authentication"];
|
|
|
|
|
|
|
|
/* Get the username and password */
|
|
|
|
string username = authenticationBlock["username"].str(), password = authenticationBlock["password"].str();
|
|
|
|
|
|
|
|
/* Attempt authentication */
|
|
|
|
authenticationStatus = server.authenticate(username, password);
|
|
|
|
|
|
|
|
/* Check if the authentication was successful or not */
|
|
|
|
if(authenticationStatus)
|
2020-05-02 15:20:50 +02:00
|
|
|
{
|
2020-05-02 18:23:35 +02:00
|
|
|
/**
|
|
|
|
* If the authentication was successful then store the
|
|
|
|
* client's credentials.
|
|
|
|
*/
|
|
|
|
this.username = username;
|
|
|
|
this.password = password;
|
2020-05-02 15:20:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* If authentication failed due to malformed message or incorrect details */
|
|
|
|
if(!authenticationStatus)
|
2020-04-26 15:01:48 +02:00
|
|
|
{
|
|
|
|
/**
|
2020-05-02 15:20:50 +02:00
|
|
|
* If the authentication was unsuccessful then send a
|
|
|
|
* message to the client stating so and close the connection.
|
|
|
|
*/
|
2020-04-26 15:01:48 +02:00
|
|
|
debugPrint("Authenticating the user failed, sending error and closing connection.");
|
|
|
|
|
2020-05-02 15:20:50 +02:00
|
|
|
/* Send error message to client */
|
2020-05-02 17:55:04 +02:00
|
|
|
sendStatus(2, JSONValue());
|
2020-05-02 15:20:50 +02:00
|
|
|
|
|
|
|
/* Stop the read/write loop */
|
2020-05-01 09:13:42 +02:00
|
|
|
isActive = false;
|
2020-05-02 15:20:50 +02:00
|
|
|
return;
|
2020-04-26 15:01:48 +02:00
|
|
|
}
|
2020-04-26 14:36:04 +02:00
|
|
|
}
|
2020-04-26 20:35:37 +02:00
|
|
|
else if(scopeField == Scope.SERVER)
|
|
|
|
{
|
|
|
|
debugPrint("Server scope enabled");
|
|
|
|
}
|
2020-04-26 14:25:56 +02:00
|
|
|
|
|
|
|
/* Set the connection type to `scopeField` */
|
|
|
|
connectionType = scopeField;
|
|
|
|
}
|
2020-05-02 17:55:04 +02:00
|
|
|
|
|
|
|
/* Attempt to get the payload block and dispatch the message */
|
|
|
|
bool dispatchStatus;
|
2020-04-19 13:25:32 +02:00
|
|
|
|
2020-05-02 18:23:35 +02:00
|
|
|
|
|
|
|
/* Get the `payload` block */
|
|
|
|
JSONValue payloadBlock = jsonMessage["payload"];
|
|
|
|
debugPrint("<<< Payload is >>>\n\n" ~ payloadBlock.toPrettyString());
|
2020-04-18 22:34:26 +02:00
|
|
|
|
2020-05-02 18:23:35 +02:00
|
|
|
/* Dispatch the message */
|
|
|
|
dispatchStatus = dispatchMessage(connectionType, payloadBlock);
|
2020-05-01 09:13:42 +02:00
|
|
|
|
|
|
|
/* TODO: Catch error here and not inside dispatchMessage, gets rid of the need for this if statement */
|
2020-04-19 18:53:16 +02:00
|
|
|
if(dispatchStatus)
|
2020-04-16 19:16:38 +02:00
|
|
|
{
|
2020-04-19 18:53:16 +02:00
|
|
|
debugPrint("Dispatch succeeded");
|
2020-04-16 19:16:38 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-19 18:53:16 +02:00
|
|
|
/* TODO: Error handling */
|
|
|
|
debugPrint("Dispatching failed...");
|
|
|
|
}
|
2020-04-16 18:46:26 +02:00
|
|
|
}
|
2020-05-01 09:13:42 +02:00
|
|
|
/* If the attempt to convert the message to JSON fails */
|
2020-04-16 18:46:26 +02:00
|
|
|
catch(JSONException exception)
|
|
|
|
{
|
2020-05-02 18:23:35 +02:00
|
|
|
debugPrint("General format error");
|
|
|
|
sendStatus(3, JSONValue());
|
2020-04-16 16:39:31 +02:00
|
|
|
}
|
2020-04-16 16:35:07 +02:00
|
|
|
}
|
2020-04-26 19:53:59 +02:00
|
|
|
}
|