Files
vdm/daemon/tests/ndjson_test.cpp
T
samiandClaude Sonnet 5 e60d6669d8 daemon: rpc/ — Unix-socket transport + generated dispatch wiring (build step 1)
First real code in daemon/. veloxd now listens on
$XDG_RUNTIME_DIR/velox/velox.sock (0600, SO_PEERCRED same-UID check),
frames NDJSON, and routes every method through the generated
velox::proto::dispatch(). The CLI and GUI have a server to talk to.

Modules:
- rpc/ndjson.hpp    — newline-delimited framing, 8 MiB frame cap, CRLF-
                       tolerant, partial-tail buffering. Header-only, tested.
- rpc/event_loop    — single-threaded poll(2) reactor; never blocks the
                       loop. stop()/wake() are async-signal-safe (eventfd).
- rpc/runtime_dir   — $XDG_RUNTIME_DIR/velox resolution, 0700, owner-checked;
                       refuses an insecure fallback rather than using /tmp.
- rpc/uds_server    — listener + non-blocking per-conn read/write with
                       backpressure; handles session.hello (protocol-major
                       check -> -32001, sessionId, transport=uds) and
                       session.subscribe in the server layer; routes the
                       rest through dispatch().
- rpc/dispatcher    — VeloxDispatcher : proto::Dispatcher, all 39 methods.
                       download.list answers an empty table; the rest return
                       "not implemented" (-> -32603) until the store lands.
- main.cpp          — abstract-namespace single-instance lock, signal ->
                       clean shutdown, socket unlinked on exit.

Tests (ASan+UBSan and TSan clean):
- veloxd.ndjson         — framing edge cases
- veloxd.uds_roundtrip  — real socket: hello ok / version mismatch / empty
                          list / -32601 / -32700 / pipelined requests, and a
                          guard on the -32603 collapse documented in P1.

Known gap, filed not worked around: daemon/docs/proto-requests-m1.md P1 —
the generated Dispatcher has no error channel below -32603, so handlers
cannot yet return -32010/-32011/-32013 with their data payloads. The
server layer handles -32001/-32002/-32003 around dispatch(); genuine
in-handler errors collapse to -32603 until PROTO gives handlers a real
error return. Three error fixtures are non-conformant until then.

Not in this drop: rpc/ws_server (next; needs the store for hashed pairing
tokens), store/, sched/, cli/. WS reuses this event loop.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Upd9WhG9oppieig5nRDLig
2026-09-10 15:27:20 +04:00

65 lines
1.7 KiB
C++

#include "rpc/ndjson.hpp"
#include <string>
#include "check.hpp"
using velox::daemon::rpc::FrameReader;
using velox::daemon::rpc::frame;
void run() {
// One chunk, one frame.
{
FrameReader r;
auto f = r.feed("{\"a\":1}\n");
CHECK_EQ(f.size(), 1u);
CHECK_EQ(f.at(0), std::string("{\"a\":1}"));
CHECK_EQ(r.buffered(), 0u);
}
// A frame split across two feeds is delivered only when the newline arrives.
{
FrameReader r;
CHECK_EQ(r.feed("{\"a\":").size(), 0u);
auto f = r.feed("1}\n");
CHECK_EQ(f.size(), 1u);
CHECK_EQ(f.at(0), std::string("{\"a\":1}"));
}
// Several frames in one chunk, plus a partial tail held back.
{
FrameReader r;
auto f = r.feed("1\n2\n3\n4");
CHECK_EQ(f.size(), 3u);
CHECK_EQ(f.at(0), std::string("1"));
CHECK_EQ(f.at(2), std::string("3"));
CHECK_EQ(r.buffered(), 1u);
auto g = r.feed("\n");
CHECK_EQ(g.size(), 1u);
CHECK_EQ(g.at(0), std::string("4"));
}
// CRLF terminator is trimmed; blank lines are skipped.
{
FrameReader r;
auto f = r.feed("x\r\n\r\n\ny\r\n");
CHECK_EQ(f.size(), 2u);
CHECK_EQ(f.at(0), std::string("x"));
CHECK_EQ(f.at(1), std::string("y"));
}
// Overflow latches once the unframed tail passes the cap.
{
FrameReader r;
CHECK(!r.overflowed());
std::string big(9u * 1024 * 1024, 'a'); // no newline
r.feed(big);
CHECK(r.overflowed());
}
// frame() appends exactly one newline.
CHECK_EQ(frame("hello"), std::string("hello\n"));
}
TEST_MAIN()