2019-01-18 21:32:51 +02:00
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Protocol Client
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
#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"
|
|
|
|
#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_STR, PROTOCOL_COMMAND);
|
|
|
|
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);
|
|
|
|
|
|
|
|
STRING_EXTERN(PROTOCOL_OUTPUT_STR, PROTOCOL_OUTPUT);
|
|
|
|
|
|
|
|
STRING_EXTERN(PROTOCOL_PARAMETER_STR, PROTOCOL_PARAMETER);
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Object type
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
struct ProtocolClient
|
|
|
|
{
|
|
|
|
MemContext *memContext;
|
|
|
|
const String *name;
|
|
|
|
const String *errorPrefix;
|
|
|
|
IoRead *read;
|
|
|
|
IoWrite *write;
|
|
|
|
TimeMSec keepAliveTime;
|
|
|
|
};
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
2019-02-05 15:34:18 +02:00
|
|
|
Create object
|
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));
|
|
|
|
this->memContext = memContextCurrent();
|
|
|
|
|
|
|
|
this->name = strDup(name);
|
|
|
|
this->errorPrefix = strNewFmt("raised from %s", strPtr(this->name));
|
|
|
|
this->read = read;
|
|
|
|
this->write = write;
|
|
|
|
this->keepAliveTime = timeMSec();
|
|
|
|
|
|
|
|
// Read, parse, and check the protocol greeting
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
String *greeting = ioReadLine(this->read);
|
2019-02-05 15:34:18 +02:00
|
|
|
KeyValue *greetingKv = varKv(jsonToVar(greeting));
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
const String *expected[] =
|
|
|
|
{
|
|
|
|
PROTOCOL_GREETING_NAME_STR, strNew(PROJECT_NAME),
|
|
|
|
PROTOCOL_GREETING_SERVICE_STR, service,
|
|
|
|
PROTOCOL_GREETING_VERSION_STR, strNew(PROJECT_VERSION),
|
|
|
|
};
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
|
|
|
const Variant *actualValue = kvGet(greetingKv, varNewStr(expectedKey));
|
|
|
|
|
|
|
|
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-01-18 21:32:51 +02:00
|
|
|
memContextCallback(this->memContext, (MemContextCallback)protocolClientFree, this);
|
|
|
|
}
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Read the command output
|
|
|
|
***********************************************************************************************************************************/
|
2019-02-19 20:57:38 +02:00
|
|
|
const Variant *
|
2019-01-18 21:32:51 +02:00
|
|
|
protocolClientReadOutput(ProtocolClient *this, bool outputRequired)
|
|
|
|
{
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
|
|
|
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);
|
2019-01-18 21:32:51 +02:00
|
|
|
|
2019-02-19 20:57:38 +02:00
|
|
|
const Variant *result = NULL;
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
// Read the response
|
|
|
|
String *response = ioReadLine(this->read);
|
2019-02-05 15:34:18 +02:00
|
|
|
KeyValue *responseKv = varKv(jsonToVar(response));
|
2019-01-18 21:32:51 +02:00
|
|
|
|
|
|
|
// Process error if any
|
|
|
|
const Variant *error = kvGet(responseKv, varNewStr(PROTOCOL_ERROR_STR));
|
|
|
|
|
|
|
|
if (error != NULL)
|
|
|
|
{
|
|
|
|
const String *message = varStr(kvGet(responseKv, varNewStr(PROTOCOL_OUTPUT_STR)));
|
|
|
|
|
|
|
|
THROWP_FMT(
|
|
|
|
errorTypeFromCode(varIntForce(error)), "%s: %s", strPtr(this->errorPrefix),
|
|
|
|
message == NULL ? "no details available" : strPtr(message));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get output
|
2019-02-19 20:57:38 +02:00
|
|
|
result = kvGet(responseKv, varNewStr(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
|
|
|
|
kvMove(responseKv, MEM_CONTEXT_OLD());
|
|
|
|
}
|
|
|
|
// 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
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Write the protocol command
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
void
|
|
|
|
protocolClientWriteCommand(ProtocolClient *this, const KeyValue *command)
|
|
|
|
{
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
|
|
|
FUNCTION_LOG_PARAM(KEY_VALUE, command);
|
|
|
|
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
|
|
|
|
ioWriteLine(this->write, kvToJson(command, 0));
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Execute a protocol command and get the output
|
|
|
|
***********************************************************************************************************************************/
|
2019-02-19 20:57:38 +02:00
|
|
|
const Variant *
|
2019-01-18 21:32:51 +02:00
|
|
|
protocolClientExecute(ProtocolClient *this, const KeyValue *command, bool outputRequired)
|
|
|
|
{
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
|
|
|
FUNCTION_LOG_PARAM(KEY_VALUE, command);
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Move the file object to a new context
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
ProtocolClient *
|
|
|
|
protocolClientMove(ProtocolClient *this, MemContext *parentNew)
|
|
|
|
{
|
|
|
|
FUNCTION_TEST_BEGIN();
|
|
|
|
FUNCTION_TEST_PARAM(PROTOCOL_CLIENT, this);
|
|
|
|
FUNCTION_TEST_PARAM(MEM_CONTEXT, parentNew);
|
|
|
|
FUNCTION_TEST_END();
|
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
ASSERT(parentNew != NULL);
|
|
|
|
|
2019-01-18 21:32:51 +02:00
|
|
|
if (this != NULL)
|
|
|
|
memContextMove(this->memContext, parentNew);
|
|
|
|
|
2019-01-28 15:06:28 +02:00
|
|
|
FUNCTION_TEST_RETURN(this);
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Send noop to test connection or keep it alive
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
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()
|
|
|
|
{
|
|
|
|
protocolClientExecute(this, kvPut(kvNew(), varNewStr(PROTOCOL_COMMAND_STR), varNewStr(PROTOCOL_COMMAND_NOOP_STR)), false);
|
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Get read interface
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Get write interface
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Render as string for logging
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
String *
|
|
|
|
protocolClientToLog(const ProtocolClient *this)
|
|
|
|
{
|
|
|
|
return strNewFmt("{name: %s}", strPtr(this->name));
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Free the file
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
void
|
|
|
|
protocolClientFree(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
|
|
|
|
|
|
|
if (this != NULL)
|
|
|
|
{
|
|
|
|
memContextCallbackClear(this->memContext);
|
|
|
|
|
|
|
|
// Send an exit command but don't wait to see if it succeeds
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
protocolClientWriteCommand(this, kvPut(kvNew(), varNewStr(PROTOCOL_COMMAND_STR), varNewStr(PROTOCOL_COMMAND_EXIT_STR)));
|
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
|
|
|
|
memContextFree(this->memContext);
|
|
|
|
}
|
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
2019-01-18 21:32:51 +02:00
|
|
|
}
|