fix(daemon): correct the published contract and harden the control socket
A review pass over the diff found seventeen confirmed defects. The worst was
the contract itself: every method's parameters were published as a single
descriptor named `params`, so a client generated from openrpc.json sent
{"params":{"params":{…}}} and every call failed -32602. Parameters are now
published per field, with a test asserting the published names are the keys
the daemon actually deserializes.
Security and lifecycle:
- download.start no longer lets a caller name a directory, which was an
arbitrary write on the daemon's host.
- The Unix socket is bound under a temporary name and renamed into place; it
was briefly connectable by any local user between bind and chmod, and on
that transport connecting is authenticating.
- Pre-auth reads are capped at 1 MiB with a 30s handshake timeout, and
connections are capped, so an unauthenticated peer cannot exhaust memory or
threads.
- A read error no longer returns past the hub unsubscribe, which leaked a
subscriber and two threads per occurrence.
- A client whose event queue fills is now actually disconnected. It used to be
dropped from the fan-out and left connected, working on but blind to every
event after — the documentation already promised otherwise.
- A client whose daemon vanishes mid-transfer is told at once instead of
waiting out its full timeout and reporting a misleading "gave up".
Contract accuracy: errors carry an explicit null id (a missing one reads as a
notification and gets dropped), a second auth answers with the published
shape, and nullable fields are sent rather than omitted so a generated client
sees one shape.
Also splits proto.rs and server.rs, whose concerns had diverged, and applies
the review's simplifications.
This commit is contained in:
@@ -190,7 +190,7 @@ pub fn run(
|
||||
let downloads = Arc::new(Mutex::new(Vec::new()));
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
|
||||
restore(ctx, &store, session.client.as_ref(), &hub, &downloads);
|
||||
let rooms = restore(ctx, &store, session.client.as_ref(), &hub, &downloads);
|
||||
|
||||
let daemon = Arc::new(Daemon {
|
||||
session: Arc::clone(&session.client),
|
||||
@@ -202,7 +202,8 @@ pub fn run(
|
||||
token,
|
||||
started: Instant::now(),
|
||||
stop: Arc::clone(&stop),
|
||||
rooms: Mutex::new(store.load_rooms()),
|
||||
open: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
|
||||
rooms: Mutex::new(rooms),
|
||||
});
|
||||
|
||||
let drainer = {
|
||||
@@ -263,7 +264,7 @@ fn restore(
|
||||
session: &dyn SessionApi,
|
||||
hub: &Hub,
|
||||
downloads: &Mutex<Vec<PendingDownload>>,
|
||||
) {
|
||||
) -> Vec<String> {
|
||||
hub.restore(
|
||||
store
|
||||
.load_messages()
|
||||
@@ -277,8 +278,9 @@ fn restore(
|
||||
.collect(),
|
||||
);
|
||||
|
||||
for room in store.load_rooms() {
|
||||
if let Err(e) = session.join_room(&room) {
|
||||
let rooms = store.load_rooms();
|
||||
for room in &rooms {
|
||||
if let Err(e) = session.join_room(room) {
|
||||
ctx.out.warn(&format!("cannot rejoin {room}: {e}"));
|
||||
}
|
||||
}
|
||||
@@ -289,7 +291,7 @@ fn restore(
|
||||
.filter(|download| !download.completed)
|
||||
.collect();
|
||||
if unfinished.is_empty() {
|
||||
return;
|
||||
return rooms;
|
||||
}
|
||||
ctx.out
|
||||
.status(&format!("resuming {} transfers", unfinished.len()));
|
||||
@@ -314,6 +316,7 @@ fn restore(
|
||||
.warn(&format!("cannot resume {}: {e}", download.filename)),
|
||||
}
|
||||
}
|
||||
rooms
|
||||
}
|
||||
|
||||
/// Write daemon-owned state back. Best-effort: a state file that cannot be
|
||||
|
||||
Reference in New Issue
Block a user