2019-01-18 21:32:51 +02:00
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Protocol Client
|
|
|
|
***********************************************************************************************************************************/
|
2019-04-26 08:08:23 -04:00
|
|
|
#include "build.auto.h"
|
|
|
|
|
2019-01-18 21:32:51 +02:00
|
|
|
#include "common/debug.h"
|
|
|
|
#include "common/log.h"
|
|
|
|
#include "common/memContext.h"
|
|
|
|
#include "common/time.h"
|
|
|
|
#include "common/type/json.h"
|
|
|
|
#include "common/type/keyValue.h"
|
2020-03-30 20:52:57 -04:00
|
|
|
#include "common/type/object.h"
|
2019-01-18 21:32:51 +02:00
|
|
|
#include "protocol/client.h"
|
|
|
|
#include "version.h"
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Constants
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
STRING_EXTERN(PROTOCOL_GREETING_NAME_STR, PROTOCOL_GREETING_NAME);
|
|
|
|
STRING_EXTERN(PROTOCOL_GREETING_SERVICE_STR, PROTOCOL_GREETING_SERVICE);
|
|
|
|
STRING_EXTERN(PROTOCOL_GREETING_VERSION_STR, PROTOCOL_GREETING_VERSION);
|
|
|
|
|
|
|
|
STRING_EXTERN(PROTOCOL_COMMAND_NOOP_STR, PROTOCOL_COMMAND_NOOP);
|
|
|
|
STRING_EXTERN(PROTOCOL_COMMAND_EXIT_STR, PROTOCOL_COMMAND_EXIT);
|
|
|
|
|
|
|
|
STRING_EXTERN(PROTOCOL_ERROR_STR, PROTOCOL_ERROR);
|
2019-04-18 10:36:21 -04:00
|
|
|
STRING_EXTERN(PROTOCOL_ERROR_STACK_STR, PROTOCOL_ERROR_STACK);
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
STRING_EXTERN(PROTOCOL_OUTPUT_STR, PROTOCOL_OUTPUT);
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Object type
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
struct ProtocolClient
|
|
|
|
{
|
|
|
|
MemContext *memContext;
|
|
|
|
const String *name;
|
|
|
|
const String *errorPrefix;
|
|
|
|
IoRead *read;
|
|
|
|
IoWrite *write;
|
|
|
|
TimeMSec keepAliveTime;
|
|
|
|
};
|
|
|
|
|
2019-08-30 14:36:02 -04:00
|
|
|
OBJECT_DEFINE_MOVE(PROTOCOL_CLIENT);
|
2019-05-03 18:52:54 -04:00
|
|
|
OBJECT_DEFINE_FREE(PROTOCOL_CLIENT);
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Close protocol connection
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
OBJECT_DEFINE_FREE_RESOURCE_BEGIN(PROTOCOL_CLIENT, LOG, logLevelTrace)
|
|
|
|
{
|
|
|
|
// Send an exit command but don't wait to see if it succeeds
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
protocolClientWriteCommand(this, protocolCommandNew(PROTOCOL_COMMAND_EXIT_STR));
|
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
}
|
|
|
|
OBJECT_DEFINE_FREE_RESOURCE_END(LOG);
|
|
|
|
|
2020-04-03 18:01:28 -04:00
|
|
|
/**********************************************************************************************************************************/
|
2019-01-18 21:32:51 +02:00
|
|
|
ProtocolClient *
|
|
|
|
protocolClientNew(const String *name, const String *service, IoRead *read, IoWrite *write)
|
|
|
|
{
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(STRING, name);
|
2019-02-02 15:18:10 +02:00
|
|
|
FUNCTION_LOG_PARAM(STRING, service);
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_PARAM(IO_READ, read);
|
|
|
|
FUNCTION_LOG_PARAM(IO_WRITE, write);
|
|
|
|
FUNCTION_LOG_END();
|
2019-01-18 21:32:51 +02:00
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
ASSERT(name != NULL);
|
|
|
|
ASSERT(read != NULL);
|
|
|
|
ASSERT(write != NULL);
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
ProtocolClient *this = NULL;
|
|
|
|
|
|
|
|
MEM_CONTEXT_NEW_BEGIN("ProtocolClient")
|
|
|
|
{
|
|
|
|
this = memNew(sizeof(ProtocolClient));
|
|
|
|
|
2020-01-23 14:15:58 -07:00
|
|
|
*this = (ProtocolClient)
|
|
|
|
{
|
|
|
|
.memContext = memContextCurrent(),
|
|
|
|
.name = strDup(name),
|
|
|
|
.errorPrefix = strNewFmt("raised from %s", strPtr(name)),
|
|
|
|
.read = read,
|
|
|
|
.write = write,
|
|
|
|
.keepAliveTime = timeMSec(),
|
|
|
|
};
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
// Read, parse, and check the protocol greeting
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
String *greeting = ioReadLine(this->read);
|
2019-04-22 18:41:01 -04:00
|
|
|
KeyValue *greetingKv = jsonToKv(greeting);
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
const String *expected[] =
|
|
|
|
{
|
2019-04-16 13:39:58 -04:00
|
|
|
PROTOCOL_GREETING_NAME_STR, STRDEF(PROJECT_NAME),
|
2019-01-18 21:32:51 +02:00
|
|
|
PROTOCOL_GREETING_SERVICE_STR, service,
|
2019-04-16 13:39:58 -04:00
|
|
|
PROTOCOL_GREETING_VERSION_STR, STRDEF(PROJECT_VERSION),
|
2019-01-18 21:32:51 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
for (unsigned int expectedIdx = 0; expectedIdx < sizeof(expected) / sizeof(char *) / 2; expectedIdx++)
|
|
|
|
{
|
|
|
|
const String *expectedKey = expected[expectedIdx * 2];
|
|
|
|
const String *expectedValue = expected[expectedIdx * 2 + 1];
|
|
|
|
|
2019-04-17 08:04:22 -04:00
|
|
|
const Variant *actualValue = kvGet(greetingKv, VARSTR(expectedKey));
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
if (actualValue == NULL)
|
|
|
|
THROW_FMT(ProtocolError, "unable to find greeting key '%s'", strPtr(expectedKey));
|
|
|
|
|
|
|
|
if (varType(actualValue) != varTypeString)
|
|
|
|
THROW_FMT(ProtocolError, "greeting key '%s' must be string type", strPtr(expectedKey));
|
|
|
|
|
|
|
|
if (!strEq(varStr(actualValue), expectedValue))
|
|
|
|
{
|
|
|
|
THROW_FMT(
|
|
|
|
ProtocolError, "expected value '%s' for greeting key '%s' but got '%s'", strPtr(expectedValue),
|
|
|
|
strPtr(expectedKey), strPtr(varStr(actualValue)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
|
|
|
|
// Send one noop to catch any errors that might happen after the greeting
|
|
|
|
protocolClientNoOp(this);
|
|
|
|
|
2019-02-02 15:18:10 +02:00
|
|
|
// Set a callback to shutdown the protocol
|
2019-05-03 18:52:54 -04:00
|
|
|
memContextCallbackSet(this->memContext, protocolClientFreeResource, this);
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
MEM_CONTEXT_NEW_END();
|
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_RETURN(PROTOCOL_CLIENT, this);
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
|
2020-04-03 18:01:28 -04:00
|
|
|
/**********************************************************************************************************************************/
|
2019-11-16 17:05:34 -05:00
|
|
|
// Helper to process errors
|
|
|
|
static void
|
|
|
|
protocolClientProcessError(ProtocolClient *this, KeyValue *errorKv)
|
2019-01-18 21:32:51 +02:00
|
|
|
{
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
2019-11-16 17:05:34 -05:00
|
|
|
FUNCTION_LOG_PARAM(KEY_VALUE, errorKv);
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_END();
|
2019-01-18 21:32:51 +02:00
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
ASSERT(this != NULL);
|
2019-11-16 17:05:34 -05:00
|
|
|
ASSERT(errorKv != NULL);
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
// Process error if any
|
2019-11-16 17:05:34 -05:00
|
|
|
const Variant *error = kvGet(errorKv, VARSTR(PROTOCOL_ERROR_STR));
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
if (error != NULL)
|
|
|
|
{
|
2019-04-18 10:36:21 -04:00
|
|
|
const ErrorType *type = errorTypeFromCode(varIntForce(error));
|
2019-11-16 17:05:34 -05:00
|
|
|
const String *message = varStr(kvGet(errorKv, VARSTR(PROTOCOL_OUTPUT_STR)));
|
2019-01-18 21:32:51 +02:00
|
|
|
|
2019-05-11 14:51:51 -04:00
|
|
|
// Required part of the message
|
|
|
|
String *throwMessage = strNewFmt(
|
|
|
|
"%s: %s", strPtr(this->errorPrefix), message == NULL ? "no details available" : strPtr(message));
|
|
|
|
|
|
|
|
// Add stack trace if the error is an assertion or debug-level logging is enabled
|
2019-05-11 18:20:57 -04:00
|
|
|
if (type == &AssertError || logAny(logLevelDebug))
|
2019-05-11 14:51:51 -04:00
|
|
|
{
|
2019-11-16 17:05:34 -05:00
|
|
|
const String *stack = varStr(kvGet(errorKv, VARSTR(PROTOCOL_ERROR_STACK_STR)));
|
2019-05-11 14:51:51 -04:00
|
|
|
|
2020-06-24 12:09:24 -04:00
|
|
|
strCat(throwMessage, LF_STR);
|
|
|
|
strCat(throwMessage, stack == NULL ? STRDEF("no stack trace available") : stack);
|
2019-05-11 14:51:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
THROWP(type, strPtr(throwMessage));
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
2019-11-16 17:05:34 -05:00
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
|
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
|
|
|
}
|
|
|
|
|
|
|
|
const Variant *
|
|
|
|
protocolClientReadOutput(ProtocolClient *this, bool outputRequired)
|
|
|
|
{
|
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
|
|
|
FUNCTION_LOG_PARAM(BOOL, outputRequired);
|
|
|
|
FUNCTION_LOG_END();
|
|
|
|
|
|
|
|
ASSERT(this != NULL);
|
|
|
|
|
|
|
|
const Variant *result = NULL;
|
|
|
|
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
// Read the response
|
|
|
|
String *response = ioReadLine(this->read);
|
|
|
|
KeyValue *responseKv = varKv(jsonToVar(response));
|
|
|
|
|
|
|
|
// Process error if any
|
|
|
|
protocolClientProcessError(this, responseKv);
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
// Get output
|
2019-04-17 08:04:22 -04:00
|
|
|
result = kvGet(responseKv, VARSTR(PROTOCOL_OUTPUT_STR));
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
if (outputRequired)
|
|
|
|
{
|
|
|
|
// Just move the entire response kv since the output is the largest part if it
|
2020-01-17 13:29:49 -07:00
|
|
|
kvMove(responseKv, memContextPrior());
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
// Else if no output is required then there should not be any
|
2019-02-19 20:57:38 +02:00
|
|
|
else if (result != NULL)
|
2019-01-18 21:32:51 +02:00
|
|
|
THROW(AssertError, "no output required by command");
|
|
|
|
|
|
|
|
// Reset the keep alive time
|
|
|
|
this->keepAliveTime = timeMSec();
|
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
|
2019-02-19 20:57:38 +02:00
|
|
|
FUNCTION_LOG_RETURN_CONST(VARIANT, result);
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
|
2020-04-03 18:01:28 -04:00
|
|
|
/**********************************************************************************************************************************/
|
2019-01-18 21:32:51 +02:00
|
|
|
void
|
2019-02-27 19:48:30 +02:00
|
|
|
protocolClientWriteCommand(ProtocolClient *this, const ProtocolCommand *command)
|
2019-01-18 21:32:51 +02:00
|
|
|
{
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
2019-02-27 19:48:30 +02:00
|
|
|
FUNCTION_LOG_PARAM(PROTOCOL_COMMAND, command);
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_END();
|
2019-01-18 21:32:51 +02:00
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
ASSERT(this != NULL);
|
|
|
|
ASSERT(command != NULL);
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
// Write out the command
|
2019-04-22 18:46:29 -04:00
|
|
|
ioWriteStrLine(this->write, protocolCommandJson(command));
|
2019-01-18 21:32:51 +02:00
|
|
|
ioWriteFlush(this->write);
|
|
|
|
|
|
|
|
// Reset the keep alive time
|
|
|
|
this->keepAliveTime = timeMSec();
|
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
|
2020-04-03 18:01:28 -04:00
|
|
|
/**********************************************************************************************************************************/
|
2019-02-19 20:57:38 +02:00
|
|
|
const Variant *
|
2019-02-27 19:48:30 +02:00
|
|
|
protocolClientExecute(ProtocolClient *this, const ProtocolCommand *command, bool outputRequired)
|
2019-01-18 21:32:51 +02:00
|
|
|
{
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
2019-02-27 19:48:30 +02:00
|
|
|
FUNCTION_LOG_PARAM(PROTOCOL_COMMAND, command);
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_PARAM(BOOL, outputRequired);
|
|
|
|
FUNCTION_LOG_END();
|
2019-01-18 21:32:51 +02:00
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
ASSERT(this != NULL);
|
|
|
|
ASSERT(command != NULL);
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
protocolClientWriteCommand(this, command);
|
|
|
|
|
2019-02-19 20:57:38 +02:00
|
|
|
FUNCTION_LOG_RETURN_CONST(VARIANT, protocolClientReadOutput(this, outputRequired));
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
|
2020-04-03 18:01:28 -04:00
|
|
|
/**********************************************************************************************************************************/
|
2019-01-18 21:32:51 +02:00
|
|
|
void
|
|
|
|
protocolClientNoOp(ProtocolClient *this)
|
|
|
|
{
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
|
|
|
FUNCTION_LOG_END();
|
2019-01-18 21:32:51 +02:00
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
ASSERT(this != NULL);
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
2019-02-27 19:48:30 +02:00
|
|
|
protocolClientExecute(this, protocolCommandNew(PROTOCOL_COMMAND_NOOP_STR), false);
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
|
2020-04-03 18:01:28 -04:00
|
|
|
/**********************************************************************************************************************************/
|
2019-11-16 17:05:34 -05:00
|
|
|
String *
|
|
|
|
protocolClientReadLine(ProtocolClient *this)
|
|
|
|
{
|
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
|
|
|
FUNCTION_LOG_END();
|
|
|
|
|
|
|
|
ASSERT(this != NULL);
|
|
|
|
|
|
|
|
String *result = NULL;
|
|
|
|
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
result = ioReadLine(this->read);
|
|
|
|
|
|
|
|
if (strSize(result) == 0)
|
|
|
|
{
|
|
|
|
THROW(FormatError, "unexpected empty line");
|
|
|
|
}
|
|
|
|
else if (strPtr(result)[0] == '{')
|
|
|
|
{
|
|
|
|
KeyValue *responseKv = varKv(jsonToVar(result));
|
|
|
|
|
|
|
|
// Process expected error
|
|
|
|
protocolClientProcessError(this, responseKv);
|
|
|
|
|
|
|
|
// If not an error then there is probably a protocol bug
|
|
|
|
THROW(FormatError, "expected error but got output");
|
|
|
|
}
|
|
|
|
else if (strPtr(result)[0] != '.')
|
|
|
|
THROW_FMT(FormatError, "invalid prefix in '%s'", strPtr(result));
|
|
|
|
|
2020-01-17 13:29:49 -07:00
|
|
|
MEM_CONTEXT_PRIOR_BEGIN()
|
|
|
|
{
|
|
|
|
result = strSub(result, 1);
|
|
|
|
}
|
|
|
|
MEM_CONTEXT_PRIOR_END();
|
2019-11-16 17:05:34 -05:00
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
|
|
|
|
FUNCTION_LOG_RETURN(STRING, result);
|
|
|
|
}
|
|
|
|
|
2020-04-03 18:01:28 -04:00
|
|
|
/**********************************************************************************************************************************/
|
2019-01-18 21:32:51 +02:00
|
|
|
IoRead *
|
|
|
|
protocolClientIoRead(const ProtocolClient *this)
|
|
|
|
{
|
|
|
|
FUNCTION_TEST_BEGIN();
|
|
|
|
FUNCTION_TEST_PARAM(PROTOCOL_CLIENT, this);
|
|
|
|
FUNCTION_TEST_END();
|
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
ASSERT(this != NULL);
|
|
|
|
|
2019-01-28 15:06:28 +02:00
|
|
|
FUNCTION_TEST_RETURN(this->read);
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
|
2020-04-03 18:01:28 -04:00
|
|
|
/**********************************************************************************************************************************/
|
2019-01-18 21:32:51 +02:00
|
|
|
IoWrite *
|
|
|
|
protocolClientIoWrite(const ProtocolClient *this)
|
|
|
|
{
|
|
|
|
FUNCTION_TEST_BEGIN();
|
|
|
|
FUNCTION_TEST_PARAM(PROTOCOL_CLIENT, this);
|
|
|
|
FUNCTION_TEST_END();
|
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
ASSERT(this != NULL);
|
|
|
|
|
2019-01-28 15:06:28 +02:00
|
|
|
FUNCTION_TEST_RETURN(this->write);
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
|
2020-04-03 18:01:28 -04:00
|
|
|
/**********************************************************************************************************************************/
|
2019-01-18 21:32:51 +02:00
|
|
|
String *
|
|
|
|
protocolClientToLog(const ProtocolClient *this)
|
|
|
|
{
|
|
|
|
return strNewFmt("{name: %s}", strPtr(this->name));
|
|
|
|
}
|