Gunnar

nordisk/edda

crates/estate-clock/Cargo.toml

[package]
name = "estate-clock"
# 0.1.0 (2026-09-28): the estate's wall clock, moved AS IS out of gunnar's
# `gunnar-clock` crate (gunnar gunnar-live 50c3e69) when holger's estate needed
# the same clock without depending on gunnar (owner 2026-09-27: "IF YOU CAN FIND
# COMMON CODE MOVE IT TO EDDA"). gunnar's names removed: the wire magic is
# `ESTATE-TIME/1`, the SSHSIG namespace `estate-time-v1@nordisk`, the variables
# `ESTATE_CLOCK*`. Beside it, module `offer` carries what gunnar-front's
# `src/clock.rs` and gunnar-server's `feed.rs`/`clock.rs` did that is no
# product's (the offer keys, the front's start, medium-vs-door resolution, the
# first fed sync's back-off). gunnar switches to it later itself; until then the
# two are the same code under two names.
version = "0.1.1"
edition = "2021"
rust-version = "1.85"
description = "The estate's wall clock: HTTPS-Date / SNTP / signed-peer time, one quorum rule, step-or-slew, a report a control plane can serve, and the front's signed-time service a box follows over the utility network (ed25519 SSHSIG over a nonce the box chose; no DNS, no internet, no TLS dates)."
license = "MIT OR Apache-2.0"
repository = "https://git.gunnar.rs/nordisk/edda"
keywords = ["clock", "time", "ntp", "appliance", "estate"]
categories = ["date-and-time", "network-programming"]
exclude = ["/.nornir/"]

[lib]
name = "estate_clock"
path = "src/lib.rs"

# **One implementation, three consumers, four weights** (gunnar-clock's, kept).
#
# * a control plane renders the last sync: it needs the REPORT and nothing
#   else, so the default feature set is empty: no socket, no crypto, no syscall.
# * a box (the appliance, PID 1) asks sources and moves its clock:
#   `fetch` + `adjust`.
# * the `estate-clock` binary, or a front medium's PID 1, keeps the front's
#   clock and serves signed time to the boxes: `bin` (or `fetch` + `adjust`).
[features]
default = []
# The signed-peer protocol: sign (front) and verify (box). ed25519 over SSHSIG.
# Also `poll`: asking peer + SNTP sources with NO HTTP client linked — what a
# zero-egress box (an airgapped downstream following a site-local peer) runs.
peer = ["dep:ssh-key", "dep:rand"]
# Asking sources over the network: HTTPS Date (ureq/rustls), SNTP, peer.
fetch = ["peer", "dep:ureq", "dep:rustls", "dep:webpki-root-certs"]
# Moving CLOCK_REALTIME: step (rustix) and slew (adjtimex).
adjust = ["dep:rustix", "dep:libc"]
bin = ["fetch", "adjust"]

[[bin]]
name = "estate-clock"
path = "src/bin/estate-clock.rs"
required-features = ["bin"]

[dependencies]
# `ed25519` + `rand_core` (mint a time key) + `std`. Not `encryption`: gunnar's
# workspace line carried it for its credential keys; a time key is written
# unencrypted (0600) and read the same way, so the AES/bcrypt closure is not linked.
# 0.6 -> =0.7.0-rc.11 (0.1.1, lane G3b, 2026-10-21): Gunnar's version (its
# workspace pin, and russh 0.63's), so Gunnar's graph keeps ONE ssh-key when it
# moves onto this crate; edda's nornir-git pins the same. EXACT pin: rc APIs
# change between candidates; move to the stable when it ships.
ssh-key = { version = "=0.7.0-rc.11", default-features = false, features = ["ed25519", "std", "rand_core"], optional = true }
# rand_core 0.6 -> rand 0.10 (same bump, as Gunnar did it): rand_core 0.10 has
# no OsRng; the OS generator is `rand::rngs::SysRng` (getrandom), wrapped in
# `rand::rand_core::UnwrapErr` where a CryptoRng is asked for.
rand = { version = "0.10", default-features = false, features = ["std", "sys_rng"], optional = true }
# rustls, no curl.
ureq = { version = "3", default-features = false, features = ["rustls"], optional = true }
# Named directly for ONE thing: an HTTPS clock source vouched for by a root the
# site adds ([`fetch::trust_also`]) — the bench's local clock, signed by the
# medium's installer CA. rustls 0.23 on ring, as ureq resolves it.
# ureq 3 (2026-10-03) takes the roots as certificates, so the public set is
# `webpki-root-certs` (the Mozilla roots as DER; already in the graph) when a
# root is added; with none added the agent keeps ureq's own web roots.
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"], optional = true }
webpki-root-certs = { version = "1", optional = true }
rustix = { version = "1", default-features = false, features = ["std", "time"], optional = true }
# `adjtimex(2)` and nothing else. rustix has no spelling for it, and slewing a
# running clock has no other door. `libc` is already in every graph this crate
# joins (tokio, mio, socket2), so this adds no package.
libc = { version = "0.2", optional = true }

[dev-dependencies]
# Mints the test CA and the local clock's certificate for `fetch::trust_also`'s test.
rcgen = { version = "0.13", default-features = false, features = ["crypto", "pem", "ring"] }
tempfile = "3"

# GUNNAR-TIME/1 across Gunnar's move (lane G3b): the old gunnar-clock side
# verbatim against this crate, both directions.
[[test]]
name = "gunnar_time_compat"
required-features = ["peer"]