c@75: 
c@75: #include "vamp-json/VampJson.h"
c@75: #include "vamp-capnp/VampnProto.h"
c@75: #include "vamp-support/RequestOrResponse.h"
c@75: #include "vamp-support/PreservingPluginHandleMapper.h"
c@75: 
c@75: #include <iostream>
c@75: #include <sstream>
c@75: #include <stdexcept>
c@75: 
c@91: #include <capnp/serialize.h>
c@91: 
c@75: using namespace std;
c@75: using namespace json11;
c@97: using namespace piper_vamp;
c@75: 
c@75: void usage()
c@75: {
c@75:     string myname = "piper-convert";
c@75:     cerr << "\n" << myname <<
c@75: 	": Validate and convert Piper request and response messages\n\n"
c@75: 	"    Usage: " << myname << " [-i <informat>] [-o <outformat>] request\n"
c@75: 	"           " << myname << " [-i <informat>] [-o <outformat>] response\n\n"
c@75: 	"    where\n"
c@75: 	"       <informat>: the format to read from stdin\n"
c@75: 	"           (\"json\" or \"capnp\", default is \"json\")\n"
c@75: 	"       <outformat>: the format to convert to and write to stdout\n"
c@75: 	"           (\"json\", \"json-b64\" or \"capnp\", default is \"json\")\n"
c@75: 	"       request|response: whether messages are Vamp request or response type\n\n"
c@75: 	"If <informat> and <outformat> differ, convert from <informat> to <outformat>.\n"
c@75: 	"If <informat> and <outformat> are the same, just check validity of incoming\n"
c@75: 	"messages and pass them to output.\n\n"
c@75: 	"Specifying \"json-b64\" as output format forces base64 encoding for process and\n"
c@75: 	"feature blocks, unlike the \"json\" output format which uses text encoding.\n"
c@75: 	"The \"json\" input format accepts either.\n\n";
c@75: 
c@75:     exit(2);
c@75: }
c@75: 
c@75: Json
c@75: convertRequestJson(string input, string &err)
c@75: {
c@75:     Json j = Json::parse(input, err);
c@75:     if (err != "") {
c@75: 	err = "invalid json: " + err;
c@75: 	return {};
c@75:     }
c@75:     if (!j.is_object()) {
c@75: 	err = "object expected at top level";
c@75:     } else if (!j["method"].is_string()) {
c@75: 	err = "string expected for method field";
c@75:     } else if (!j["params"].is_null() && !j["params"].is_object()) {
c@75: 	err = "object expected for params field";
c@75:     }
c@75:     return j;
c@75: }
c@75: 
c@75: Json
c@75: convertResponseJson(string input, string &err)
c@75: {
c@75:     Json j = Json::parse(input, err);
c@75:     if (err != "") {
c@75: 	err = "invalid json: " + err;
c@75: 	return {};
c@75:     }
c@75:     if (!j.is_object()) {
c@75: 	err = "object expected at top level";
c@75:     } else {
c@75:         if (!j["result"].is_object()) {
c@75:             if (!j["error"].is_object()) {
c@75:                 err = "expected either result or error object";
c@75:             }
c@75:         }
c@75:     }
c@75:     return j;
c@75: }
c@75: 
c@75: //!!! Lots of potential for refactoring the conversion classes based
c@75: //!!! on the common matter in the following eight functions...
c@75: 
c@75: PreservingPluginHandleMapper mapper;
c@75: 
c@75: static RequestOrResponse::RpcId
c@75: readJsonId(const Json &j)
c@75: {
c@75:     RequestOrResponse::RpcId id;
c@75: 
c@75:     if (j["id"].is_number()) {
c@75:         id.type = RequestOrResponse::RpcId::Number;
c@75:         id.number = j["id"].number_value();
c@75:     } else if (j["id"].is_string()) {
c@75:         id.type = RequestOrResponse::RpcId::Tag;
c@75:         id.tag = j["id"].string_value();
c@75:     } else {
c@75:         id.type = RequestOrResponse::RpcId::Absent;
c@75:     }
c@75: 
c@75:     return id;
c@75: }
c@75: 
c@75: static Json
c@75: writeJsonId(const RequestOrResponse::RpcId &id)
c@75: {
c@75:     if (id.type == RequestOrResponse::RpcId::Number) {
c@75:         return id.number;
c@75:     } else if (id.type == RequestOrResponse::RpcId::Tag) {
c@75:         return id.tag;
c@75:     } else {
c@75:         return Json();
c@75:     }
c@75: }
c@75: 
c@75: template <typename Reader>
c@75: static RequestOrResponse::RpcId
c@75: readCapnpId(const Reader &r)
c@75: {
c@75:     int number;
c@75:     string tag;
c@75:     switch (r.getId().which()) {
c@97:     case piper::RpcRequest::Id::Which::NUMBER:
c@75:         number = r.getId().getNumber();
c@75:         return { RequestOrResponse::RpcId::Number, number, "" };
c@97:     case piper::RpcRequest::Id::Which::TAG:
c@75:         tag = r.getId().getTag();
c@75:         return { RequestOrResponse::RpcId::Tag, 0, tag };
c@97:     case piper::RpcRequest::Id::Which::NONE:
c@75:         return { RequestOrResponse::RpcId::Absent, 0, "" };
c@75:     }
c@75:     return {};
c@75: }
c@75: 
c@75: template <typename Builder>
c@75: static void
c@75: buildCapnpId(Builder &b, const RequestOrResponse::RpcId &id)
c@75: {
c@75:     switch (id.type) {
c@75:     case RequestOrResponse::RpcId::Number:
c@75:         b.getId().setNumber(id.number);
c@75:         break;
c@75:     case RequestOrResponse::RpcId::Tag:
c@75:         b.getId().setTag(id.tag);
c@75:         break;
c@75:     case RequestOrResponse::RpcId::Absent:
c@75:         b.getId().setNone();
c@75:         break;
c@75:     }
c@75: }
c@75: 
c@75: RequestOrResponse
c@75: readRequestJson(string &err)
c@75: {
c@75:     RequestOrResponse rr;
c@75:     rr.direction = RequestOrResponse::Request;
c@75: 
c@75:     string input;
c@75:     if (!getline(cin, input)) {
c@75: 	// the EOF case, not actually an error
c@75: 	rr.type = RRType::NotValid;
c@75: 	return rr;
c@75:     }
c@75:     
c@75:     Json j = convertRequestJson(input, err);
c@75:     if (err != "") return {};
c@75: 
c@75:     rr.type = VampJson::getRequestResponseType(j, err);
c@75:     if (err != "") return {};
c@75: 
c@75:     rr.id = readJsonId(j);
c@75: 
c@75:     VampJson::BufferSerialisation serialisation =
c@75:         VampJson::BufferSerialisation::Array;
c@75: 
c@75:     switch (rr.type) {
c@75: 
c@75:     case RRType::List:
c@75: 	VampJson::toRpcRequest_List(j, err); // type check only
c@75: 	break;
c@75:     case RRType::Load:
c@75: 	rr.loadRequest = VampJson::toRpcRequest_Load(j, err);
c@75: 	break;
c@75:     case RRType::Configure:
c@75: 	rr.configurationRequest = VampJson::toRpcRequest_Configure(j, mapper, err);
c@75: 	break;
c@75:     case RRType::Process:
c@75: 	rr.processRequest = VampJson::toRpcRequest_Process(j, mapper, serialisation, err);
c@75: 	break;
c@75:     case RRType::Finish:
c@75: 	rr.finishRequest = VampJson::toRpcRequest_Finish(j, mapper, err);
c@75: 	break;
c@75:     case RRType::NotValid:
c@75: 	break;
c@75:     }
c@75: 
c@75:     return rr;
c@75: }
c@75: 
c@75: void
c@75: writeRequestJson(RequestOrResponse &rr, bool useBase64)
c@75: {
c@75:     Json j;
c@75: 
c@75:     VampJson::BufferSerialisation serialisation =
c@75:         (useBase64 ?
c@75:          VampJson::BufferSerialisation::Base64 :
c@75:          VampJson::BufferSerialisation::Array);
c@75: 
c@75:     Json id = writeJsonId(rr.id);
c@75:     
c@75:     switch (rr.type) {
c@75: 
c@75:     case RRType::List:
c@75: 	j = VampJson::fromRpcRequest_List(id);
c@75: 	break;
c@75:     case RRType::Load:
c@75: 	j = VampJson::fromRpcRequest_Load(rr.loadRequest, id);
c@75: 	break;
c@75:     case RRType::Configure:
c@75: 	j = VampJson::fromRpcRequest_Configure(rr.configurationRequest, mapper, id);
c@75: 	break;
c@75:     case RRType::Process:
c@75: 	j = VampJson::fromRpcRequest_Process
c@75: 	    (rr.processRequest, mapper, serialisation, id);
c@75: 	break;
c@75:     case RRType::Finish:
c@75: 	j = VampJson::fromRpcRequest_Finish(rr.finishRequest, mapper, id);
c@75: 	break;
c@75:     case RRType::NotValid:
c@75: 	break;
c@75:     }
c@75: 
c@75:     cout << j.dump() << endl;
c@75: }
c@75: 
c@75: RequestOrResponse
c@75: readResponseJson(string &err)
c@75: {
c@75:     RequestOrResponse rr;
c@75:     rr.direction = RequestOrResponse::Response;
c@75: 
c@75:     string input;
c@75:     if (!getline(cin, input)) {
c@75: 	// the EOF case, not actually an error
c@75: 	rr.type = RRType::NotValid;
c@75: 	return rr;
c@75:     }
c@75: 
c@75:     Json j = convertResponseJson(input, err);
c@75:     if (err != "") return {};
c@75: 
c@75:     rr.type = VampJson::getRequestResponseType(j, err);
c@75:     if (err != "") return {};
c@75: 
c@75:     rr.id = readJsonId(j);
c@75:     
c@75:     VampJson::BufferSerialisation serialisation =
c@75:         VampJson::BufferSerialisation::Array;
c@75: 
c@75:     rr.success = j["success"].bool_value();
c@75:     rr.errorText = j["errorText"].string_value();
c@75: 
c@75:     switch (rr.type) {
c@75: 
c@75:     case RRType::List:
c@75: 	rr.listResponse = VampJson::toRpcResponse_List(j, err);
c@75: 	break;
c@75:     case RRType::Load:
c@75: 	rr.loadResponse = VampJson::toRpcResponse_Load(j, mapper, err);
c@75: 	break;
c@75:     case RRType::Configure:
c@75: 	rr.configurationResponse = VampJson::toRpcResponse_Configure(j, mapper, err);
c@75: 	break;
c@75:     case RRType::Process: 
c@75: 	rr.processResponse = VampJson::toRpcResponse_Process(j, mapper, serialisation, err);
c@75: 	break;
c@75:     case RRType::Finish:
c@75: 	rr.finishResponse = VampJson::toRpcResponse_Finish(j, mapper, serialisation, err);
c@75: 	break;
c@75:     case RRType::NotValid:
c@75: 	break;
c@75:     }
c@75: 
c@75:     return rr;
c@75: }
c@75: 
c@75: void
c@75: writeResponseJson(RequestOrResponse &rr, bool useBase64)
c@75: {
c@75:     Json j;
c@75: 
c@75:     VampJson::BufferSerialisation serialisation =
c@75:         (useBase64 ?
c@75:          VampJson::BufferSerialisation::Base64 :
c@75:          VampJson::BufferSerialisation::Array);
c@75: 
c@75:     Json id = writeJsonId(rr.id);
c@75: 
c@75:     if (!rr.success) {
c@75: 
c@75: 	j = VampJson::fromError(rr.errorText, rr.type, id);
c@75: 
c@75:     } else {
c@75:     
c@75: 	switch (rr.type) {
c@75: 
c@75: 	case RRType::List:
c@75: 	    j = VampJson::fromRpcResponse_List(rr.listResponse, id);
c@75: 	    break;
c@75: 	case RRType::Load:
c@75: 	    j = VampJson::fromRpcResponse_Load(rr.loadResponse, mapper, id);
c@75: 	    break;
c@75: 	case RRType::Configure:
c@75: 	    j = VampJson::fromRpcResponse_Configure(rr.configurationResponse,
c@75:                                                     mapper, id);
c@75: 	    break;
c@75: 	case RRType::Process:
c@75: 	    j = VampJson::fromRpcResponse_Process
c@75: 		(rr.processResponse, mapper, serialisation, id);
c@75: 	    break;
c@75: 	case RRType::Finish:
c@75: 	    j = VampJson::fromRpcResponse_Finish
c@75: 		(rr.finishResponse, mapper, serialisation, id);
c@75: 	    break;
c@75: 	case RRType::NotValid:
c@75: 	    break;
c@75: 	}
c@75:     }
c@75:     
c@75:     cout << j.dump() << endl;
c@75: }
c@75: 
c@75: RequestOrResponse
c@75: readRequestCapnp(kj::BufferedInputStreamWrapper &buffered)
c@75: {
c@75:     RequestOrResponse rr;
c@75:     rr.direction = RequestOrResponse::Request;
c@75: 
c@97:     capnp::InputStreamMessageReader message(buffered);
c@97:     piper::RpcRequest::Reader reader = message.getRoot<piper::RpcRequest>();
c@75:     
c@75:     rr.type = VampnProto::getRequestResponseType(reader);
c@75:     rr.id = readCapnpId(reader);
c@75: 
c@75:     switch (rr.type) {
c@75: 
c@75:     case RRType::List:
c@75: 	VampnProto::readRpcRequest_List(reader); // type check only
c@75: 	break;
c@75:     case RRType::Load:
c@75: 	VampnProto::readRpcRequest_Load(rr.loadRequest, reader);
c@75: 	break;
c@75:     case RRType::Configure:
c@75: 	VampnProto::readRpcRequest_Configure(rr.configurationRequest,
c@75: 					      reader, mapper);
c@75: 	break;
c@75:     case RRType::Process:
c@75: 	VampnProto::readRpcRequest_Process(rr.processRequest, reader, mapper);
c@75: 	break;
c@75:     case RRType::Finish:
c@75: 	VampnProto::readRpcRequest_Finish(rr.finishRequest, reader, mapper);
c@75: 	break;
c@75:     case RRType::NotValid:
c@75: 	break;
c@75:     }
c@75: 
c@75:     return rr;
c@75: }
c@75: 
c@75: void
c@75: writeRequestCapnp(RequestOrResponse &rr)
c@75: {
c@97:     capnp::MallocMessageBuilder message;
c@97:     piper::RpcRequest::Builder builder = message.initRoot<piper::RpcRequest>();
c@75: 
c@75:     buildCapnpId(builder, rr.id);
c@75:     
c@75:     switch (rr.type) {
c@75: 
c@75:     case RRType::List:
c@75: 	VampnProto::buildRpcRequest_List(builder);
c@75: 	break;
c@75:     case RRType::Load:
c@75: 	VampnProto::buildRpcRequest_Load(builder, rr.loadRequest);
c@75: 	break;
c@75:     case RRType::Configure:
c@75: 	VampnProto::buildRpcRequest_Configure(builder,
c@75:                                               rr.configurationRequest, mapper);
c@75: 	break;
c@75:     case RRType::Process:
c@75: 	VampnProto::buildRpcRequest_Process(builder, rr.processRequest, mapper);
c@75: 	break;
c@75:     case RRType::Finish:
c@75: 	VampnProto::buildRpcRequest_Finish(builder, rr.finishRequest, mapper);
c@75: 	break;
c@75:     case RRType::NotValid:
c@75: 	break;
c@75:     }
c@75: 
c@75:     writeMessageToFd(1, message);
c@75: }
c@75: 
c@75: RequestOrResponse
c@75: readResponseCapnp(kj::BufferedInputStreamWrapper &buffered)
c@75: {
c@75:     RequestOrResponse rr;
c@75:     rr.direction = RequestOrResponse::Response;
c@75: 
c@97:     capnp::InputStreamMessageReader message(buffered);
c@97:     piper::RpcResponse::Reader reader = message.getRoot<piper::RpcResponse>();
c@75:     
c@75:     rr.type = VampnProto::getRequestResponseType(reader);
c@75:     rr.success = true;
c@75:     rr.errorText = "";
c@75:     rr.id = readCapnpId(reader);
c@75:     int errorCode = 0;
c@75: 
c@75:     switch (rr.type) {
c@75: 
c@75:     case RRType::List:
c@75: 	VampnProto::readRpcResponse_List(rr.listResponse, reader);
c@75: 	break;
c@75:     case RRType::Load:
c@75: 	VampnProto::readRpcResponse_Load(rr.loadResponse, reader, mapper);
c@75: 	break;
c@75:     case RRType::Configure:
c@75: 	VampnProto::readRpcResponse_Configure(rr.configurationResponse,
c@75: 					       reader, mapper);
c@75: 	break;
c@75:     case RRType::Process:
c@75: 	VampnProto::readRpcResponse_Process(rr.processResponse, reader, mapper);
c@75: 	break;
c@75:     case RRType::Finish:
c@75: 	VampnProto::readRpcResponse_Finish(rr.finishResponse, reader, mapper);
c@75: 	break;
c@75:     case RRType::NotValid:
c@75:         // error
c@75:         rr.success = false;
c@75:         VampnProto::readRpcResponse_Error(errorCode, rr.errorText, reader);
c@75: 	break;
c@75:     }
c@75: 
c@75:     return rr;
c@75: }
c@75: 
c@75: void
c@75: writeResponseCapnp(RequestOrResponse &rr)
c@75: {
c@97:     capnp::MallocMessageBuilder message;
c@97:     piper::RpcResponse::Builder builder = message.initRoot<piper::RpcResponse>();
c@75: 
c@75:     buildCapnpId(builder, rr.id);
c@75: 
c@75:     if (!rr.success) {
c@75: 
c@75: 	VampnProto::buildRpcResponse_Error(builder, rr.errorText, rr.type);
c@75: 
c@75:     } else {
c@75: 	
c@75: 	switch (rr.type) {
c@75: 
c@75: 	case RRType::List:
c@75: 	    VampnProto::buildRpcResponse_List(builder, rr.listResponse);
c@75: 	    break;
c@75: 	case RRType::Load:
c@75: 	    VampnProto::buildRpcResponse_Load(builder, rr.loadResponse, mapper);
c@75: 	    break;
c@75: 	case RRType::Configure:
c@75: 	    VampnProto::buildRpcResponse_Configure(builder, rr.configurationResponse, mapper);
c@75: 	    break;
c@75: 	case RRType::Process:
c@75: 	    VampnProto::buildRpcResponse_Process(builder, rr.processResponse, mapper);
c@75: 	    break;
c@75: 	case RRType::Finish:
c@75: 	    VampnProto::buildRpcResponse_Finish(builder, rr.finishResponse, mapper);
c@75: 	    break;
c@75: 	case RRType::NotValid:
c@75: 	    break;
c@75: 	}
c@75:     }
c@75:     
c@75:     writeMessageToFd(1, message);
c@75: }
c@75: 
c@75: RequestOrResponse
c@75: readInputJson(RequestOrResponse::Direction direction, string &err)
c@75: {
c@75:     if (direction == RequestOrResponse::Request) {
c@75: 	return readRequestJson(err);
c@75:     } else {
c@75: 	return readResponseJson(err);
c@75:     }
c@75: }
c@75: 
c@75: RequestOrResponse
c@75: readInputCapnp(RequestOrResponse::Direction direction)
c@75: {
c@75:     static kj::FdInputStream stream(0); // stdin
c@75:     static kj::BufferedInputStreamWrapper buffered(stream);
c@75: 
c@75:     if (buffered.tryGetReadBuffer() == nullptr) {
c@75: 	return {};
c@75:     }
c@75:     
c@75:     if (direction == RequestOrResponse::Request) {
c@75: 	return readRequestCapnp(buffered);
c@75:     } else {
c@75: 	return readResponseCapnp(buffered);
c@75:     }
c@75: }
c@75: 
c@75: RequestOrResponse
c@75: readInput(string format, RequestOrResponse::Direction direction)
c@75: {
c@75:     if (format == "json") {
c@75: 	string err;
c@75: 	auto result = readInputJson(direction, err);
c@75: 	if (err != "") throw runtime_error(err);
c@75: 	else return result;
c@75:     } else if (format == "capnp") {
c@75: 	return readInputCapnp(direction);
c@75:     } else {
c@75: 	throw runtime_error("unknown input format \"" + format + "\"");
c@75:     }
c@75: }
c@75: 
c@75: void
c@75: writeOutput(string format, RequestOrResponse &rr)
c@75: {
c@75:     if (format == "json") {
c@75: 	if (rr.direction == RequestOrResponse::Request) {
c@75: 	    writeRequestJson(rr, false);
c@75: 	} else {
c@75: 	    writeResponseJson(rr, false);
c@75: 	}
c@75:     } else if (format == "json-b64") {
c@75: 	if (rr.direction == RequestOrResponse::Request) {
c@75: 	    writeRequestJson(rr, true);
c@75: 	} else {
c@75: 	    writeResponseJson(rr, true);
c@75: 	}
c@75:     } else if (format == "capnp") {
c@75: 	if (rr.direction == RequestOrResponse::Request) {
c@75: 	    writeRequestCapnp(rr);
c@75: 	} else {
c@75: 	    writeResponseCapnp(rr);
c@75: 	}
c@75:     } else {
c@75: 	throw runtime_error("unknown output format \"" + format + "\"");
c@75:     }
c@75: }
c@75: 
c@75: int main(int argc, char **argv)
c@75: {
c@75:     if (argc < 2) {
c@75: 	usage();
c@75:     }
c@75: 
c@75:     string informat = "json", outformat = "json";
c@75:     RequestOrResponse::Direction direction = RequestOrResponse::Request;
c@75:     bool haveDirection = false;
c@75:     
c@75:     for (int i = 1; i < argc; ++i) {
c@75: 
c@75: 	string arg = argv[i];
c@75: 	bool final = (i + 1 == argc);
c@75: 	
c@75: 	if (arg == "-i") {
c@75: 	    if (final) usage();
c@75: 	    else informat = argv[++i];
c@75: 
c@75: 	} else if (arg == "-o") {
c@75: 	    if (final) usage();
c@75: 	    else outformat = argv[++i];
c@75: 
c@75: 	} else if (arg == "request") {
c@75: 	    direction = RequestOrResponse::Request;
c@75: 	    haveDirection = true;
c@75: 
c@75: 	} else if (arg == "response") {
c@75: 	    direction = RequestOrResponse::Response;
c@75: 	    haveDirection = true;
c@75: 	    
c@75: 	} else {
c@75: 	    usage();
c@75: 	}
c@75:     }
c@75: 
c@75:     if (informat == "" || outformat == "" || !haveDirection) {
c@75: 	usage();
c@75:     }
c@75: 
c@75:     while (true) {
c@75: 
c@75: 	try {
c@75: 
c@75: 	    RequestOrResponse rr = readInput(informat, direction);
c@75: 
c@75: 	    // NotValid without an exception indicates EOF:
c@75: 	    if (rr.type == RRType::NotValid) break;
c@75: 
c@75: 	    writeOutput(outformat, rr);
c@75: 	    
c@75: 	} catch (std::exception &e) {
c@75: 
c@75: 	    cerr << "Error: " << e.what() << endl;
c@75: 	    exit(1);
c@75: 	}
c@75:     }
c@75: 
c@75:     exit(0);
c@75: }
c@75: 
c@75: