diff --git a/CMakeLists.txt b/CMakeLists.txt index d40c0b4..e99545f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,5 +46,9 @@ if (NOT NO_STATIC_ANALYSIS) set(CMAKE_CXX_CLANG_TIDY ${clang_tidy}) endif() +find_package(CapnProto CONFIG REQUIRED) +include_directories(${CAPNP_INCLUDE_DIRS}) +add_definitions(${CAPNP_DEFINITIONS}) + add_subdirectory(src) add_subdirectory(test) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 1a0980c..46d088e 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -4,3 +4,13 @@ # cmake_minimum_required(VERSION 3.22.0) + +capnp_generate_cpp(CAPNP_SRCS CAPNP_HDRS calculator.capnp) + +message(STATUS "CAPNP_SRCS: ${CAPNP_SRCS}") +message(STATUS "CAPNP_HDRS: ${CAPNP_HDRS}") + +add_executable(ocvsmd-cli main.cpp ${CAPNP_SRCS}) +target_link_libraries(ocvsmd-cli PRIVATE ${CAPNP_LIBRARIES}) +target_include_directories(ocvsmd-cli PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +set_source_files_properties(${CAPNP_SRCS} PROPERTIES SKIP_LINTING ON) diff --git a/src/cli/calculator.capnp b/src/cli/calculator.capnp new file mode 100644 index 0000000..cfd2a26 --- /dev/null +++ b/src/cli/calculator.capnp @@ -0,0 +1,97 @@ +@0x85150b117366d14b; + +interface Calculator { + # A "simple" mathematical calculator, callable via RPC. + # + # But, to show off Cap'n Proto, we add some twists: + # + # - You can use the result from one call as the input to the next + # without a network round trip. To accomplish this, evaluate() + # returns a `Value` object wrapping the actual numeric value. + # This object may be used in a subsequent expression. With + # promise pipelining, the Value can actually be used before + # the evaluate() call that creates it returns! + # + # - You can define new functions, and then call them. This again + # shows off pipelining, but it also gives the client the + # opportunity to define a function on the client side and have + # the server call back to it. + # + # - The basic arithmetic operators are exposed as Functions, and + # you have to call getOperator() to obtain them from the server. + # This again demonstrates pipelining -- using getOperator() to + # get each operator and then using them in evaluate() still + # only takes one network round trip. + + evaluate @0 (expression :Expression) -> (value :Value); + # Evaluate the given expression and return the result. The + # result is returned wrapped in a Value interface so that you + # may pass it back to the server in a pipelined request. To + # actually get the numeric value, you must call read() on the + # Value -- but again, this can be pipelined so that it incurs + # no additional latency. + + struct Expression { + # A numeric expression. + + union { + literal @0 :Float64; + # A literal numeric value. + + previousResult @1 :Value; + # A value that was (or, will be) returned by a previous + # evaluate(). + + parameter @2 :UInt32; + # A parameter to the function (only valid in function bodies; + # see defFunction). + + call :group { + # Call a function on a list of parameters. + function @3 :Function; + params @4 :List(Expression); + } + } + } + + interface Value { + # Wraps a numeric value in an RPC object. This allows the value + # to be used in subsequent evaluate() requests without the client + # waiting for the evaluate() that returns the Value to finish. + + read @0 () -> (value :Float64); + # Read back the raw numeric value. + } + + defFunction @1 (paramCount :Int32, body :Expression) + -> (func :Function); + # Define a function that takes `paramCount` parameters and returns the + # evaluation of `body` after substituting these parameters. + + interface Function { + # An algebraic function. Can be called directly, or can be used inside + # an Expression. + # + # A client can create a Function that runs on the server side using + # `defFunction()` or `getOperator()`. Alternatively, a client can + # implement a Function on the client side and the server will call back + # to it. However, a function defined on the client side will require a + # network round trip whenever the server needs to call it, whereas + # functions defined on the server and then passed back to it are called + # locally. + + call @0 (params :List(Float64)) -> (value :Float64); + # Call the function on the given parameters. + } + + getOperator @2 (op :Operator) -> (func :Function); + # Get a Function representing an arithmetic operator, which can then be + # used in Expressions. + + enum Operator { + add @0; + subtract @1; + multiply @2; + divide @3; + } +} \ No newline at end of file diff --git a/src/cli/main.cpp b/src/cli/main.cpp new file mode 100644 index 0000000..713f0a8 --- /dev/null +++ b/src/cli/main.cpp @@ -0,0 +1,377 @@ +// +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT +// + +#include "calculator.capnp.h" + +#include +#include +#include +#include +#include +#include +#include + +// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) + +class PowerFunction final : public Calculator::Function::Server +{ + // An implementation of the Function interface wrapping pow(). Note that + // we're implementing this on the client side and will pass a reference to + // the server. The server will then be able to make calls back to the client. + +public: + kj::Promise call(CallContext context) override + { + auto params = context.getParams().getParams(); + KJ_REQUIRE(params.size() == 2, "Wrong number of parameters."); // NOLINT + context.getResults().setValue(pow(params[0], params[1])); + return kj::READY_NOW; + } +}; + +int main(int argc, const char* argv[]) +{ + if (argc != 2) + { + std::cerr << "usage: " << argv[0] // NOLINT + << " HOST:PORT\n" + "Connects to the Calculator server at the given address and " + "does some RPCs." + << std::endl; // NOLINT + return 1; + } + + // First we need to set up the KJ async event loop. This should happen one + // per thread that needs to perform RPC. + auto io = kj::setupAsyncIo(); + + // Keep an eye on `waitScope`. Whenever you see it used is a place where we + // stop and wait for the server to respond. If a line of code does not use + // `waitScope`, then it does not block! + auto& waitScope = io.waitScope; + + // Using KJ APIs, let's parse our network address and connect to it. + kj::Network& network = io.provider->getNetwork(); + kj::Own addr = network.parseAddress(argv[1]).wait(waitScope); // NOLINT + kj::Own conn = addr->connect().wait(waitScope); + + // Now we can start the Cap'n Proto RPC system on this connection. + capnp::TwoPartyClient client(*conn); + + // The server exports a "bootstrap" capability implementing the + // `Calculator` interface. + Calculator::Client calculator = client.bootstrap().castAs(); + + { + // Make a request that just evaluates the literal value 123. + // + // What's interesting here is that evaluate() returns a "Value", which is + // another interface and therefore points back to an object living on the + // server. We then have to call read() on that object to read it. + // However, even though we are making two RPC's, this block executes in + // *one* network round trip because of promise pipelining: we do not wait + // for the first call to complete before we send the second call to the + // server. + + std::cout << "Evaluating a literal... "; + std::cout.flush(); + + // Set up the request. + auto request = calculator.evaluateRequest(); + request.getExpression().setLiteral(123); + + // Send it, which returns a promise for the result (without blocking). + auto evalPromise = request.send(); + + // Using the promise, create a pipelined request to call read() on the + // returned object, and then send that. + auto readPromise = evalPromise.getValue().readRequest().send(); + + // Now that we've sent all the requests, wait for the response. Until this + // point, we haven't waited at all! + auto response = readPromise.wait(waitScope); + KJ_ASSERT(response.getValue() == 123); // NOLINT + + std::cout << "PASS" << std::endl; // NOLINT + } + + { + // Make a request to evaluate 123 + 45 - 67. + // + // The Calculator interface requires that we first call getOperator() to + // get the addition and subtraction functions, then call evaluate() to use + // them. But, once again, we can get both functions, call evaluate(), and + // then read() the result -- four RPCs -- in the time of *one* network + // round trip, because of promise pipelining. + + std::cout << "Using add and subtract... "; + std::cout.flush(); + + Calculator::Function::Client add = nullptr; + Calculator::Function::Client subtract = nullptr; + + { + // Get the "add" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::ADD); + add = request.send().getFunc(); + } + + { + // Get the "subtract" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::SUBTRACT); + subtract = request.send().getFunc(); + } + + // Build the request to evaluate 123 + 45 - 67. + auto request = calculator.evaluateRequest(); + + auto subtractCall = request.getExpression().initCall(); + subtractCall.setFunction(subtract); + auto subtractParams = subtractCall.initParams(2); + subtractParams[1].setLiteral(67); + + auto addCall = subtractParams[0].initCall(); + addCall.setFunction(add); + auto addParams = addCall.initParams(2); + addParams[0].setLiteral(123); + addParams[1].setLiteral(45); + + // Send the evaluate() request, read() the result, and wait for read() to + // finish. + auto evalPromise = request.send(); + auto readPromise = evalPromise.getValue().readRequest().send(); + + auto response = readPromise.wait(waitScope); + KJ_ASSERT(response.getValue() == 101); // NOLINT + + std::cout << "PASS" << std::endl; // NOLINT + } + + { + // Make a request to evaluate 4 * 6, then use the result in two more + // requests that add 3 and 5. + // + // Since evaluate() returns its result wrapped in a `Value`, we can pass + // that `Value` back to the server in subsequent requests before the first + // `evaluate()` has actually returned. Thus, this example again does only + // one network round trip. + + std::cout << "Pipelining eval() calls... "; + std::cout.flush(); + + Calculator::Function::Client add = nullptr; + Calculator::Function::Client multiply = nullptr; + + { + // Get the "add" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::ADD); + add = request.send().getFunc(); + } + + { + // Get the "multiply" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::MULTIPLY); + multiply = request.send().getFunc(); + } + + // Build the request to evaluate 4 * 6 + auto request = calculator.evaluateRequest(); + + auto multiplyCall = request.getExpression().initCall(); + multiplyCall.setFunction(multiply); + auto multiplyParams = multiplyCall.initParams(2); + multiplyParams[0].setLiteral(4); + multiplyParams[1].setLiteral(6); + + auto multiplyResult = request.send().getValue(); + + // Use the result in two calls that add 3 and add 5. + + auto add3Request = calculator.evaluateRequest(); + auto add3Call = add3Request.getExpression().initCall(); + add3Call.setFunction(add); + auto add3Params = add3Call.initParams(2); + add3Params[0].setPreviousResult(multiplyResult); + add3Params[1].setLiteral(3); + auto add3Promise = add3Request.send().getValue().readRequest().send(); + + auto add5Request = calculator.evaluateRequest(); + auto add5Call = add5Request.getExpression().initCall(); + add5Call.setFunction(add); + auto add5Params = add5Call.initParams(2); + add5Params[0].setPreviousResult(multiplyResult); + add5Params[1].setLiteral(5); + auto add5Promise = add5Request.send().getValue().readRequest().send(); + + // Now wait for the results. + KJ_ASSERT(add3Promise.wait(waitScope).getValue() == 27); // NOLINT + KJ_ASSERT(add5Promise.wait(waitScope).getValue() == 29); // NOLINT + + std::cout << "PASS" << std::endl; // NOLINT + } + + { + // Our calculator interface supports defining functions. Here we use it + // to define two functions and then make calls to them as follows: + // + // f(x, y) = x * 100 + y + // g(x) = f(x, x + 1) * 2; + // f(12, 34) + // g(21) + // + // Once again, the whole thing takes only one network round trip. + + std::cout << "Defining functions... "; + std::cout.flush(); + + Calculator::Function::Client add = nullptr; + Calculator::Function::Client multiply = nullptr; + Calculator::Function::Client f = nullptr; + Calculator::Function::Client g = nullptr; + + { + // Get the "add" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::ADD); + add = request.send().getFunc(); + } + + { + // Get the "multiply" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::MULTIPLY); + multiply = request.send().getFunc(); + } + + { + // Define f. + auto request = calculator.defFunctionRequest(); + request.setParamCount(2); + + { + // Build the function body. + auto addCall = request.getBody().initCall(); + addCall.setFunction(add); + auto addParams = addCall.initParams(2); + addParams[1].setParameter(1); // y + + auto multiplyCall = addParams[0].initCall(); + multiplyCall.setFunction(multiply); + auto multiplyParams = multiplyCall.initParams(2); + multiplyParams[0].setParameter(0); // x + multiplyParams[1].setLiteral(100); + } + + f = request.send().getFunc(); + } + + { + // Define g. + auto request = calculator.defFunctionRequest(); + request.setParamCount(1); + + { + // Build the function body. + auto multiplyCall = request.getBody().initCall(); + multiplyCall.setFunction(multiply); + auto multiplyParams = multiplyCall.initParams(2); + multiplyParams[1].setLiteral(2); + + auto fCall = multiplyParams[0].initCall(); + fCall.setFunction(f); + auto fParams = fCall.initParams(2); + fParams[0].setParameter(0); + + auto addCall = fParams[1].initCall(); + addCall.setFunction(add); + auto addParams = addCall.initParams(2); + addParams[0].setParameter(0); + addParams[1].setLiteral(1); + } + + g = request.send().getFunc(); + } + + // OK, we've defined all our functions. Now create our eval requests. + + // f(12, 34) + auto fEvalRequest = calculator.evaluateRequest(); + auto fCall = fEvalRequest.initExpression().initCall(); + fCall.setFunction(f); + auto fParams = fCall.initParams(2); + fParams[0].setLiteral(12); + fParams[1].setLiteral(34); + auto fEvalPromise = fEvalRequest.send().getValue().readRequest().send(); + + // g(21) + auto gEvalRequest = calculator.evaluateRequest(); + auto gCall = gEvalRequest.initExpression().initCall(); + gCall.setFunction(g); + gCall.initParams(1)[0].setLiteral(21); + auto gEvalPromise = gEvalRequest.send().getValue().readRequest().send(); + + // Wait for the results. + KJ_ASSERT(fEvalPromise.wait(waitScope).getValue() == 1234); // NOLINT + KJ_ASSERT(gEvalPromise.wait(waitScope).getValue() == 4244); // NOLINT + + std::cout << "PASS" << std::endl; // NOLINT + } + + { + // Make a request that will call back to a function defined locally. + // + // Specifically, we will compute 2^(4 + 5). However, exponent is not + // defined by the Calculator server. So, we'll implement the Function + // interface locally and pass it to the server for it to use when + // evaluating the expression. + // + // This example requires two network round trips to complete, because the + // server calls back to the client once before finishing. In this + // particular case, this could potentially be optimized by using a tail + // call on the server side -- see CallContext::tailCall(). However, to + // keep the example simpler, we haven't implemented this optimization in + // the sample server. + + std::cout << "Using a callback... "; + std::cout.flush(); + + Calculator::Function::Client add = nullptr; + + { + // Get the "add" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::ADD); + add = request.send().getFunc(); + } + + // Build the eval request for 2^(4+5). + auto request = calculator.evaluateRequest(); + + auto powCall = request.getExpression().initCall(); + powCall.setFunction(kj::heap()); + auto powParams = powCall.initParams(2); + powParams[0].setLiteral(2); + + auto addCall = powParams[1].initCall(); + addCall.setFunction(add); + auto addParams = addCall.initParams(2); + addParams[0].setLiteral(4); + addParams[1].setLiteral(5); + + // Send the request and wait. + auto response = request.send().getValue().readRequest().send().wait(waitScope); + KJ_ASSERT(response.getValue() == 512); // NOLINT + + std::cout << "PASS" << std::endl; // NOLINT + } + + return 0; +} + +// NOLINTEND(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index c1a837f..59b7354 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -5,4 +5,12 @@ cmake_minimum_required(VERSION 3.22.0) -add_executable(ocvsmd main.cpp) +capnp_generate_cpp(CAPNP_SRCS CAPNP_HDRS calculator.capnp) + +message(STATUS "CAPNP_SRCS: ${CAPNP_SRCS}") +message(STATUS "CAPNP_HDRS: ${CAPNP_HDRS}") + +add_executable(ocvsmd main.cpp ${CAPNP_SRCS}) +target_link_libraries(ocvsmd PRIVATE ${CAPNP_LIBRARIES}) +target_include_directories(ocvsmd PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +set_source_files_properties(${CAPNP_SRCS} PROPERTIES SKIP_LINTING ON) diff --git a/src/daemon/calculator.capnp b/src/daemon/calculator.capnp new file mode 100644 index 0000000..cfd2a26 --- /dev/null +++ b/src/daemon/calculator.capnp @@ -0,0 +1,97 @@ +@0x85150b117366d14b; + +interface Calculator { + # A "simple" mathematical calculator, callable via RPC. + # + # But, to show off Cap'n Proto, we add some twists: + # + # - You can use the result from one call as the input to the next + # without a network round trip. To accomplish this, evaluate() + # returns a `Value` object wrapping the actual numeric value. + # This object may be used in a subsequent expression. With + # promise pipelining, the Value can actually be used before + # the evaluate() call that creates it returns! + # + # - You can define new functions, and then call them. This again + # shows off pipelining, but it also gives the client the + # opportunity to define a function on the client side and have + # the server call back to it. + # + # - The basic arithmetic operators are exposed as Functions, and + # you have to call getOperator() to obtain them from the server. + # This again demonstrates pipelining -- using getOperator() to + # get each operator and then using them in evaluate() still + # only takes one network round trip. + + evaluate @0 (expression :Expression) -> (value :Value); + # Evaluate the given expression and return the result. The + # result is returned wrapped in a Value interface so that you + # may pass it back to the server in a pipelined request. To + # actually get the numeric value, you must call read() on the + # Value -- but again, this can be pipelined so that it incurs + # no additional latency. + + struct Expression { + # A numeric expression. + + union { + literal @0 :Float64; + # A literal numeric value. + + previousResult @1 :Value; + # A value that was (or, will be) returned by a previous + # evaluate(). + + parameter @2 :UInt32; + # A parameter to the function (only valid in function bodies; + # see defFunction). + + call :group { + # Call a function on a list of parameters. + function @3 :Function; + params @4 :List(Expression); + } + } + } + + interface Value { + # Wraps a numeric value in an RPC object. This allows the value + # to be used in subsequent evaluate() requests without the client + # waiting for the evaluate() that returns the Value to finish. + + read @0 () -> (value :Float64); + # Read back the raw numeric value. + } + + defFunction @1 (paramCount :Int32, body :Expression) + -> (func :Function); + # Define a function that takes `paramCount` parameters and returns the + # evaluation of `body` after substituting these parameters. + + interface Function { + # An algebraic function. Can be called directly, or can be used inside + # an Expression. + # + # A client can create a Function that runs on the server side using + # `defFunction()` or `getOperator()`. Alternatively, a client can + # implement a Function on the client side and the server will call back + # to it. However, a function defined on the client side will require a + # network round trip whenever the server needs to call it, whereas + # functions defined on the server and then passed back to it are called + # locally. + + call @0 (params :List(Float64)) -> (value :Float64); + # Call the function on the given parameters. + } + + getOperator @2 (op :Operator) -> (func :Function); + # Get a Function representing an arithmetic operator, which can then be + # used in Expressions. + + enum Operator { + add @0; + subtract @1; + multiply @2; + divide @3; + } +} \ No newline at end of file diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 71f7416..237ee63 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -3,10 +3,23 @@ // SPDX-License-Identifier: MIT // +#include "calculator.capnp.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include #include +#include #include #include #include @@ -21,6 +34,173 @@ namespace { +kj::Promise readValue(Calculator::Value::Client value) +{ + return value.readRequest() + .send() // + .then([](const capnp::Response& result) { return result.getValue(); }); +} + +// NOLINTNEXTLINE(misc-no-recursion) +kj::Promise evaluateImpl(const Calculator::Expression::Reader& expression, + const capnp::List::Reader& params = capnp::List::Reader()) +{ + switch (expression.which()) + { + case Calculator::Expression::LITERAL: { + return expression.getLiteral(); + } + + case Calculator::Expression::PREVIOUS_RESULT: { + return readValue(expression.getPreviousResult()); + } + + case Calculator::Expression::PARAMETER: { + KJ_REQUIRE(expression.getParameter() < params.size(), "Parameter index out-of-range."); // NOLINT + return params[expression.getParameter()]; + } + + case Calculator::Expression::CALL: { + auto call = expression.getCall(); + auto func = call.getFunction(); + + // Evaluate each parameter. + kj::Array> paramPromises = KJ_MAP(param, call.getParams()) + { + return evaluateImpl(param, params); + }; + + // Join the array of promises into a promise for an array. + kj::Promise> joinedParams = kj::joinPromises(kj::mv(paramPromises)); + + // When the parameters are complete, call the function. + return joinedParams.then([KJ_CPCAP(func)](const kj::Array& paramValues) mutable { + auto request = func.callRequest(); + request.setParams(paramValues); + return request.send().then( + [](const capnp::Response& result) { return result.getValue(); }); + }); + } + + default: { + // Throw an exception. + KJ_FAIL_REQUIRE("Unknown expression type."); // NOLINT + } + } +} + +class ValueImpl final : public Calculator::Value::Server +{ +public: + explicit ValueImpl(const double value) + : value(value) + { + } + + kj::Promise read(ReadContext context) override + { + context.getResults().setValue(value); + return kj::READY_NOW; + } + +private: + double value; +}; + +class FunctionImpl final : public Calculator::Function::Server +{ +public: + FunctionImpl(const std::uint32_t paramCount, Calculator::Expression::Reader body) + : paramCount(paramCount) + { + this->body.setRoot(body); + } + + kj::Promise call(CallContext context) override + { + auto params = context.getParams().getParams(); + KJ_REQUIRE(params.size() == paramCount, "Wrong number of parameters."); // NOLINT + + return evaluateImpl(body.getRoot(), params) + .then([KJ_CPCAP(context)](const double value) mutable { context.getResults().setValue(value); }); + } + +private: + std::uint32_t paramCount; + + capnp::MallocMessageBuilder body; +}; + +class OperatorImpl final : public Calculator::Function::Server +{ +public: + explicit OperatorImpl(const Calculator::Operator op) + : op(op) + { + } + + kj::Promise call(CallContext context) override + { + auto params = context.getParams().getParams(); + KJ_REQUIRE(params.size() == 2, "Wrong number of parameters."); // NOLINT + + double result = 0.0; + switch (op) + { + case Calculator::Operator::ADD: // NOLINT + result = params[0] + params[1]; + break; + case Calculator::Operator::SUBTRACT: + result = params[0] - params[1]; + break; + case Calculator::Operator::MULTIPLY: + result = params[0] * params[1]; + break; + case Calculator::Operator::DIVIDE: + result = params[0] / params[1]; + break; + default: + KJ_FAIL_REQUIRE("Unknown operator."); // NOLINT + break; + } + + context.getResults().setValue(result); + return kj::READY_NOW; + } + +private: + Calculator::Operator op; +}; + +class CalculatorImpl final : public Calculator::Server +{ +public: + kj::Promise evaluate(EvaluateContext context) override + { + return evaluateImpl(context.getParams().getExpression()).then([KJ_CPCAP(context)](double value) mutable { + context.getResults().setValue(kj::heap(value)); + }); + } + + kj::Promise defFunction(DefFunctionContext context) override + { + auto params = context.getParams(); + context.getResults().setFunc(kj::heap(params.getParamCount(), params.getBody())); + return kj::READY_NOW; + } + + kj::Promise getOperator(GetOperatorContext context) override + { + context.getResults().setFunc(kj::heap(context.getParams().getOp())); + return kj::READY_NOW; + } +}; + +} // namespace + +namespace +{ + const auto* const s_init_complete = "init_complete"; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) @@ -39,11 +219,17 @@ extern "C" void handle_signal(const int sig) } } +bool write_string(const int fd, const char* const str) +{ + const auto str_len = strlen(str); + return str_len == ::write(fd, str, str_len); +} + void exit_with_failure(const int fd, const char* const msg) { const char* const err_txt = strerror(errno); - ::write(fd, msg, strlen(msg)); - ::write(fd, err_txt, strlen(err_txt)); + write_string(fd, msg); + write_string(fd, err_txt); ::exit(EXIT_FAILURE); } @@ -212,7 +398,7 @@ void step_14_notify_init_complete(int& pipe_write_fd) // hence available in both the original and the daemon process. // Closing the writing end of the pipe will signal the original process that the daemon is ready. - (void) ::write(pipe_write_fd, s_init_complete, strlen(s_init_complete)); + write_string(pipe_write_fd, s_init_complete); ::close(pipe_write_fd); pipe_write_fd = -1; } @@ -277,21 +463,56 @@ void daemonize() step_15_exit_org_process(pipe_read_fd); } - - ::openlog("ocvsmd", LOG_PID, LOG_DAEMON); } } // namespace int main(const int argc, const char** const argv) { - (void) argc; - (void) argv; + bool is_dev = false; + for (int i = 1; i < argc; ++i) + { + if (::strcmp(argv[i], "--dev") == 0) // NOLINT + { + is_dev = true; + } + } - daemonize(); + if (!is_dev) + { + daemonize(); + } + ::openlog("ocvsmd", LOG_PID, is_dev ? LOG_USER : LOG_DAEMON); ::syslog(LOG_NOTICE, "ocvsmd daemon started."); // NOLINT *-vararg + // First, we need to set up the KJ async event loop. This should happen one + // per thread that needs to perform RPC. + auto io = kj::setupAsyncIo(); + + // Using KJ APIs, let's parse our network address and listen on it. + kj::Network& network = io.provider->getNetwork(); + kj::Own addr = network.parseAddress("127.0.0.1", 5923).wait(io.waitScope); // NOLINT + kj::Own listener = addr->listen(); + + // Write the port number to stdout, in case it was chosen automatically. + const auto port = listener->getPort(); + if (port == 0) + { + // The address format "unix:/path/to/socket" opens a unix domain socket, + // in which case the port will be zero. + std::cout << "Listening on Unix socket...\n" << std::endl; // NOLINT performance-avoid-endl + } + else + { + std::cout << "Listening on port " << port << "..." << std::endl; // NOLINT performance-avoid-endl + } + + // Start the RPC server. + capnp::TwoPartyServer server(kj::heap()); + + server.listen(*listener).wait(io.waitScope); + while (s_running == 1) { // TODO: Insert daemon code here.