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:
2026-09-10 15:27:20 +04:00
co-authored by Claude Sonnet 5
parent 170bcfdb3e
commit e60d6669d8
18 changed files with 1563 additions and 0 deletions
+188
View File
@@ -0,0 +1,188 @@
#include "rpc/dispatcher.hpp"
namespace velox::daemon::rpc {
namespace proto = velox::proto;
namespace {
// The generated Result<T> carries only proto::ParseError, whose {path, message} the
// generated dispatch() forwards as -32603 data. Until P1 (daemon/docs/proto-requests-m1.md)
// gives handlers a real error channel, an unimplemented method says so plainly here.
template <class T>
proto::Result<T> not_implemented(const char* method) {
return std::unexpected(proto::ParseError{method, "not implemented in this build"});
}
} // namespace
// --- session.* : handled in the server layer, unreachable here in the running daemon ---
// Kept as explicit stubs so a direct dispatch() caller (a test, a future in-process client)
// gets a clear answer rather than undefined behaviour from a missing override.
proto::Result<proto::SessionHelloResult>
VeloxDispatcher::on_session_hello(const proto::SessionHelloParams&) {
return not_implemented<proto::SessionHelloResult>("session.hello");
}
proto::Result<proto::SessionPairResult>
VeloxDispatcher::on_session_pair(const proto::SessionPairParams&) {
return not_implemented<proto::SessionPairResult>("session.pair");
}
proto::Result<proto::SessionSubscribeResult>
VeloxDispatcher::on_session_subscribe(const proto::SessionSubscribeParams&) {
return not_implemented<proto::SessionSubscribeResult>("session.subscribe");
}
// --- download.list : an empty table, so a client can connect and render ---------------
proto::Result<proto::DownloadListResult>
VeloxDispatcher::on_download_list(const proto::DownloadListParams&) {
proto::DownloadListResult r;
r.total = 0;
return r;
}
// --- everything else : not implemented until the store and scheduler land -------------
proto::Result<proto::CaptureRules>
VeloxDispatcher::on_capture_getRules(const proto::CaptureGetRulesParams&) {
return not_implemented<proto::CaptureRules>("capture.getRules");
}
proto::Result<proto::CaptureOfferResult>
VeloxDispatcher::on_capture_offer(const proto::CaptureOfferParams&) {
return not_implemented<proto::CaptureOfferResult>("capture.offer");
}
proto::Result<proto::CategoryListResult>
VeloxDispatcher::on_category_list(const proto::CategoryListParams&) {
return not_implemented<proto::CategoryListResult>("category.list");
}
proto::Result<proto::CategoryRemoveResult>
VeloxDispatcher::on_category_remove(const proto::CategoryRemoveParams&) {
return not_implemented<proto::CategoryRemoveResult>("category.remove");
}
proto::Result<proto::CategoryUpsertResult>
VeloxDispatcher::on_category_upsert(const proto::CategoryUpsertParams&) {
return not_implemented<proto::CategoryUpsertResult>("category.upsert");
}
proto::Result<proto::DownloadAddResult>
VeloxDispatcher::on_download_add(const proto::DownloadSpec&) {
return not_implemented<proto::DownloadAddResult>("download.add");
}
proto::Result<proto::DownloadAddBatchResult>
VeloxDispatcher::on_download_addBatch(const proto::DownloadAddBatchParams&) {
return not_implemented<proto::DownloadAddBatchResult>("download.addBatch");
}
proto::Result<proto::BulkTaskResult>
VeloxDispatcher::on_download_cancel(const proto::DownloadCancelParams&) {
return not_implemented<proto::BulkTaskResult>("download.cancel");
}
proto::Result<proto::TaskDetail>
VeloxDispatcher::on_download_get(const proto::DownloadGetParams&) {
return not_implemented<proto::TaskDetail>("download.get");
}
proto::Result<proto::BulkTaskResult>
VeloxDispatcher::on_download_pause(const proto::DownloadPauseParams&) {
return not_implemented<proto::BulkTaskResult>("download.pause");
}
proto::Result<proto::DownloadProbeResult>
VeloxDispatcher::on_download_probe(const proto::DownloadProbeParams&) {
return not_implemented<proto::DownloadProbeResult>("download.probe");
}
proto::Result<proto::DownloadProvideAuthResult>
VeloxDispatcher::on_download_provideAuth(const proto::DownloadProvideAuthParams&) {
return not_implemented<proto::DownloadProvideAuthResult>("download.provideAuth");
}
proto::Result<proto::DownloadRefreshUrlResult>
VeloxDispatcher::on_download_refreshUrl(const proto::DownloadRefreshUrlParams&) {
return not_implemented<proto::DownloadRefreshUrlResult>("download.refreshUrl");
}
proto::Result<proto::DownloadRemoveResult>
VeloxDispatcher::on_download_remove(const proto::DownloadRemoveParams&) {
return not_implemented<proto::DownloadRemoveResult>("download.remove");
}
proto::Result<proto::BulkTaskResult>
VeloxDispatcher::on_download_resume(const proto::DownloadResumeParams&) {
return not_implemented<proto::BulkTaskResult>("download.resume");
}
proto::Result<proto::BulkTaskResult>
VeloxDispatcher::on_download_start(const proto::DownloadStartParams&) {
return not_implemented<proto::BulkTaskResult>("download.start");
}
proto::Result<proto::TaskSummary>
VeloxDispatcher::on_download_update(const proto::DownloadUpdateParams&) {
return not_implemented<proto::TaskSummary>("download.update");
}
proto::Result<proto::GrabberHarvestResult>
VeloxDispatcher::on_grabber_harvest(const proto::GrabberHarvestParams&) {
return not_implemented<proto::GrabberHarvestResult>("grabber.harvest");
}
proto::Result<proto::GrabberStartResult>
VeloxDispatcher::on_grabber_start(const proto::GrabberStartParams&) {
return not_implemented<proto::GrabberStartResult>("grabber.start");
}
proto::Result<proto::GrabberStatusResult>
VeloxDispatcher::on_grabber_status(const proto::GrabberStatusParams&) {
return not_implemented<proto::GrabberStatusResult>("grabber.status");
}
proto::Result<proto::Limiter> VeloxDispatcher::on_limiter_get(const proto::LimiterGetParams&) {
return not_implemented<proto::Limiter>("limiter.get");
}
proto::Result<proto::Limiter> VeloxDispatcher::on_limiter_set(const proto::Limiter&) {
return not_implemented<proto::Limiter>("limiter.set");
}
proto::Result<proto::MediaAddVariantResult>
VeloxDispatcher::on_media_addVariant(const proto::MediaAddVariantParams&) {
return not_implemented<proto::MediaAddVariantResult>("media.addVariant");
}
proto::Result<proto::MediaListVariantsResult>
VeloxDispatcher::on_media_listVariants(const proto::MediaListVariantsParams&) {
return not_implemented<proto::MediaListVariantsResult>("media.listVariants");
}
proto::Result<proto::QueueListResult>
VeloxDispatcher::on_queue_list(const proto::QueueListParams&) {
return not_implemented<proto::QueueListResult>("queue.list");
}
proto::Result<proto::QueueReorderResult>
VeloxDispatcher::on_queue_reorder(const proto::QueueReorderParams&) {
return not_implemented<proto::QueueReorderResult>("queue.reorder");
}
proto::Result<proto::QueueStartResult>
VeloxDispatcher::on_queue_start(const proto::QueueStartParams&) {
return not_implemented<proto::QueueStartResult>("queue.start");
}
proto::Result<proto::QueueStopResult>
VeloxDispatcher::on_queue_stop(const proto::QueueStopParams&) {
return not_implemented<proto::QueueStopResult>("queue.stop");
}
proto::Result<proto::QueueUpsertResult>
VeloxDispatcher::on_queue_upsert(const proto::QueueUpsertParams&) {
return not_implemented<proto::QueueUpsertResult>("queue.upsert");
}
proto::Result<proto::RulesListResult>
VeloxDispatcher::on_rules_list(const proto::RulesListParams&) {
return not_implemented<proto::RulesListResult>("rules.list");
}
proto::Result<proto::RulesUpsertResult>
VeloxDispatcher::on_rules_upsert(const proto::RulesUpsertParams&) {
return not_implemented<proto::RulesUpsertResult>("rules.upsert");
}
proto::Result<proto::ScheduleGetResult>
VeloxDispatcher::on_schedule_get(const proto::ScheduleGetParams&) {
return not_implemented<proto::ScheduleGetResult>("schedule.get");
}
proto::Result<proto::ScheduleSetResult>
VeloxDispatcher::on_schedule_set(const proto::ScheduleSetParams&) {
return not_implemented<proto::ScheduleSetResult>("schedule.set");
}
proto::Result<proto::SettingsGetResult>
VeloxDispatcher::on_settings_get(const proto::SettingsGetParams&) {
return not_implemented<proto::SettingsGetResult>("settings.get");
}
proto::Result<proto::SettingsSetResult>
VeloxDispatcher::on_settings_set(const proto::SettingsSetParams&) {
return not_implemented<proto::SettingsSetResult>("settings.set");
}
} // namespace velox::daemon::rpc