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