Milestone 6: DiscoveryProvider seam, signed announcements, redoal sketch
The discovery module defines the seam: DiscoveryProvider (subscribe/ announce over 32-byte TopicKeys) and a signed Announcement carrying root hash, ed25519 author, metadata and provider addresses. Signatures cover a deterministic postcard encoding including the topic (no cross- channel replay) and the provider identities (addresses stay refreshable hints). LanDiscovery conforms to the trait — mdns sightings become locally-authored announcements, one per pinned root — and the daemon's auto-sync now runs entirely through it: verify, index unconditionally, fetch only for trusted authors from allowlisted providers on already- pinned incomplete roots. The milestone-4 real-mdns sync test passes unchanged through the new path. Verification unit tests cover round trip, tampered root, wrong topic, forged author, mismatched signing key, and serde survival. docs/redoal-integration.md sketches the gesture-topic gossip provider against this contract. Also: fix a flaky hang in the socket-activation test (dup2(3,3) leaves CLOEXEC set when the listener already sits on fd 3; parent's listener copy masked daemon death), and give the test client a read-timeout hang guard. Add a top-level README. Dependencies: ed25519-dalek (Signature type; same implementation iroh keys use), postcard (deterministic signed encoding, iroh's canonical compact codec). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,8 +13,11 @@ use varde_proto::{PinPolicy, Request, ResponseData};
|
||||
|
||||
extern "C" {
|
||||
fn dup2(oldfd: i32, newfd: i32) -> i32;
|
||||
fn fcntl(fd: i32, cmd: i32, arg: i32) -> i32;
|
||||
}
|
||||
|
||||
const F_SETFD: i32 = 2;
|
||||
|
||||
/// Spawn the daemon with an activation socket on fd 3, the LISTEN_FDS
|
||||
/// protocol systemd uses.
|
||||
#[test]
|
||||
@@ -38,17 +41,28 @@ fn systemd_socket_activation() {
|
||||
.env("VARDE_DISCOVERY", "false")
|
||||
.env("LISTEN_FDS", "1")
|
||||
.env_remove("LISTEN_PID");
|
||||
// SAFETY: dup2 is async-signal-safe; we place the listener on fd 3
|
||||
// in the child, which also clears CLOEXEC on the duplicate.
|
||||
// SAFETY: dup2/fcntl are async-signal-safe; we place the listener on
|
||||
// fd 3 in the child. dup2 clears CLOEXEC on the duplicate, but when
|
||||
// the listener already *is* fd 3, dup2(3,3) is a no-op that leaves
|
||||
// CLOEXEC set and exec would close the socket — clear it explicitly.
|
||||
unsafe {
|
||||
command.pre_exec(move || {
|
||||
if dup2(fd, 3) < 0 {
|
||||
let rc = if fd == 3 {
|
||||
fcntl(3, F_SETFD, 0)
|
||||
} else {
|
||||
dup2(fd, 3)
|
||||
};
|
||||
if rc < 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
let mut child = command.spawn().expect("spawning daemon");
|
||||
// Drop our copy of the listener: if the daemon dies, connects must
|
||||
// fail (and this test fail cleanly) rather than queue in the backlog
|
||||
// of a socket nobody accepts on.
|
||||
drop(listener);
|
||||
|
||||
// The daemon must answer on the activation socket.
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
@@ -56,6 +70,7 @@ fn systemd_socket_activation() {
|
||||
while Instant::now() < deadline {
|
||||
if std::os::unix::net::UnixStream::connect(&socket_path).is_ok() {
|
||||
let mut client = support::Client::connect(&socket_path);
|
||||
client.set_read_timeout(Duration::from_secs(5));
|
||||
let reply = client.request(&Request::Status { hash: None });
|
||||
if reply.ok {
|
||||
answered = true;
|
||||
|
||||
@@ -101,6 +101,11 @@ pub struct Client {
|
||||
impl Client {
|
||||
pub fn connect(socket: &Path) -> Client {
|
||||
let stream = UnixStream::connect(socket).expect("connecting to daemon");
|
||||
// Hang guard: a daemon that accepts but never answers should fail
|
||||
// the test, not wedge the whole suite.
|
||||
stream
|
||||
.set_read_timeout(Some(Duration::from_secs(120)))
|
||||
.expect("setting read timeout");
|
||||
let reader = BufReader::new(stream.try_clone().expect("cloning stream"));
|
||||
Client {
|
||||
reader,
|
||||
|
||||
Reference in New Issue
Block a user