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
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
// The Unix-domain-socket RPC listener: $XDG_RUNTIME_DIR/velox/velox.sock, mode 0600,
|
||||
// SO_PEERCRED same-UID check (docs/01 §2, AGENT-DAEMON.md build step 1). NDJSON framing.
|
||||
// Non-blocking throughout; every fd runs through the shared EventLoop so one slow client
|
||||
// never stalls another.
|
||||
//
|
||||
// session.hello and session.subscribe are handled here because they are connection- and
|
||||
// transport-stateful (protocol-major check, sessionId, per-connection subscription set).
|
||||
// Every other method is routed through the generated velox::proto::dispatch(), which does
|
||||
// the envelope, the -32003 privileged-transport refusal and the typed param parse.
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
#include "rpc/ndjson.hpp"
|
||||
#include "velox_proto.hpp"
|
||||
|
||||
namespace velox::daemon::rpc {
|
||||
|
||||
class EventLoop;
|
||||
|
||||
class UdsServer {
|
||||
public:
|
||||
UdsServer(EventLoop& loop, velox::proto::Dispatcher& dispatcher, std::string socket_path);
|
||||
~UdsServer();
|
||||
|
||||
UdsServer(const UdsServer&) = delete;
|
||||
UdsServer& operator=(const UdsServer&) = delete;
|
||||
|
||||
// Create the socket, bind, chmod 0600, listen, and register with the loop. A stale
|
||||
// socket file left by a crashed daemon is removed first. Returns a non-ok error_code
|
||||
// (and changes nothing) on any failure.
|
||||
std::error_code start();
|
||||
|
||||
const std::string& socket_path() const noexcept { return path_; }
|
||||
std::size_t connection_count() const noexcept { return conns_.size(); }
|
||||
|
||||
private:
|
||||
struct Conn {
|
||||
int fd;
|
||||
FrameReader reader;
|
||||
std::string outbuf;
|
||||
std::size_t out_off = 0; // bytes of outbuf already written
|
||||
bool close_after_flush = false;
|
||||
bool hello_ok = false;
|
||||
std::string session_id;
|
||||
};
|
||||
|
||||
void on_listener_readable();
|
||||
void on_conn_event(int fd, unsigned events);
|
||||
void handle_line(Conn& c, const std::string& line);
|
||||
|
||||
// Returns true and fills `reply` if `method` is one this layer answers directly
|
||||
// (session.hello / session.subscribe). Returns false to let dispatch() handle it.
|
||||
bool handle_session_method(Conn& c, const std::string& method, const nlohmann::json& request,
|
||||
nlohmann::json& reply);
|
||||
|
||||
void queue_reply(Conn& c, const nlohmann::json& reply);
|
||||
void flush(Conn& c);
|
||||
void close_conn(int fd);
|
||||
|
||||
EventLoop& loop_;
|
||||
velox::proto::Dispatcher& dispatcher_;
|
||||
std::string path_;
|
||||
int listen_fd_ = -1;
|
||||
bool bound_ = false; // path_ is ours to unlink on destruction
|
||||
std::unordered_map<int, std::unique_ptr<Conn>> conns_;
|
||||
};
|
||||
|
||||
} // namespace velox::daemon::rpc
|
||||
Reference in New Issue
Block a user