Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/estate-clock/tests/gunnar_time_compat.rs

//! **GUNNAR-TIME/1 across the move** (lane G3b, 2026-10-21). gunnar.rs and
//! test2 update the front first, then the twin, then the main: for a while an
//! old Gunnar front (gunnar-clock) serves new boxes (estate-clock), and a new
//! front serves old boxes. Both sides here are real: the old side is
//! gunnar-clock's `peer.rs` verbatim (`gunnar_time_compat/gunnar_clock_peer.rs`),
//! the new side is this crate.

#[allow(dead_code)]
#[path = "gunnar_time_compat/gunnar_clock_peer.rs"]
mod gunnar_clock;

use std::net::TcpListener;

use estate_clock::peer::{self, Dialect};

const NOW_MS: i64 = 1_789_394_521_628;

fn key() -> (ssh_key::PrivateKey, String) {
    let k = peer::generate_key().unwrap();
    let line = k.public_key().to_openssh().unwrap();
    (k, line)
}

/// An old Gunnar front: gunnar-clock's own `serve` on loopback.
fn old_front(signer: ssh_key::PrivateKey, vouched: Result<(), String>) -> std::net::SocketAddr {
    let listener = TcpListener::bind("127.0.0.1:0").unwrap();
    let addr = listener.local_addr().unwrap();
    std::thread::spawn(move || gunnar_clock::serve(listener, &signer, &move || vouched.clone(), &|_, _| {}));
    addr
}

/// A new front: this crate's `serve` on loopback.
fn new_front(signer: ssh_key::PrivateKey, vouched: Result<(), String>) -> std::net::SocketAddr {
    let listener = TcpListener::bind("127.0.0.1:0").unwrap();
    let addr = listener.local_addr().unwrap();
    std::thread::spawn(move || peer::serve(listener, &signer, &move || vouched.clone(), &|_, _| {}));
    addr
}

#[test]
fn an_old_gunnar_front_with_a_new_box() {
    let (signer, line) = key();
    let addr = old_front(signer, Ok(()));
    let sample = peer::query(addr, &peer::PeerKey::parse(&line).unwrap()).expect("a new box follows an old Gunnar front");
    assert_eq!(sample.source, format!("peer://{addr}"));
    assert!(sample.offset_ms.abs() < 1_000, "{sample:?}");
}

#[test]
fn a_new_front_with_an_old_gunnar_box() {
    let (signer, line) = key();
    let addr = new_front(signer, Ok(()));
    let sample = gunnar_clock::query(addr, &gunnar_clock::PeerKey::parse(&line).unwrap()).expect("an old Gunnar box follows a new front");
    assert_eq!(sample.source, format!("peer://{addr}"));
    assert!(sample.offset_ms.abs() < 1_000, "{sample:?}");
}

#[test]
fn a_new_front_with_a_new_box_still_speaks_estate_time() {
    let (signer, line) = key();
    let addr = new_front(signer, Ok(()));
    let key = peer::PeerKey::parse(&line).unwrap();
    assert!(peer::query_in(Dialect::Estate, addr, &key).is_ok());
    assert!(peer::query(addr, &key).is_ok());
}

#[test]
fn signed_by_the_old_front_verified_by_the_new_box() {
    let (signer, line) = key();
    let nonce = peer::fresh_nonce();
    let reply = gunnar_clock::answer(peer::request_line_in(Dialect::Gunnar, &nonce).as_bytes(), &signer, NOW_MS, Ok(()));
    let key = peer::PeerKey::parse(&line).unwrap();
    assert_eq!(peer::verify_reply_in(Dialect::Gunnar, &reply, &key, &nonce), Ok(NOW_MS));
    // Not as estate time: the namespace is Gunnar's.
    assert!(peer::verify_reply_in(Dialect::Estate, &reply, &key, &nonce).is_err());
}

#[test]
fn signed_by_the_new_front_verified_by_the_old_box() {
    let (signer, line) = key();
    let nonce = gunnar_clock::fresh_nonce();
    let reply = peer::answer(gunnar_clock::request_line(&nonce).as_bytes(), &signer, NOW_MS, Ok(()));
    let key = gunnar_clock::PeerKey::parse(&line).unwrap();
    assert_eq!(gunnar_clock::verify_reply(&reply, &key, &nonce), Ok(NOW_MS));
    // The exact bytes the old box asked about: Gunnar's message, Gunnar's namespace.
    let text = String::from_utf8(reply).unwrap();
    assert!(text.starts_with(&format!("GUNNAR-TIME/1 {nonce} {NOW_MS}\n-----BEGIN SSH SIGNATURE-----")), "{text}");
}

#[test]
fn the_new_front_refuses_an_old_box_in_words_it_reads() {
    let (signer, line) = key();
    let addr = new_front(signer, Err("this front's own clock has not synchronised".into()));
    let refusal = gunnar_clock::query(addr, &gunnar_clock::PeerKey::parse(&line).unwrap()).expect_err("unvouched");
    assert!(refusal.contains("the peer refused to sign: this front's own clock has not synchronised"), "{refusal}");
}

#[test]
fn an_old_front_that_refuses_is_said_to_the_new_box_not_swallowed() {
    let (signer, line) = key();
    let addr = old_front(signer, Err("this front's own clock has not synchronised".into()));
    let refusal = peer::query(addr, &peer::PeerKey::parse(&line).unwrap()).expect_err("unvouched");
    assert!(refusal.contains("has not synchronised"), "{refusal}");
}

#[test]
fn an_old_front_signs_with_another_key_and_the_new_box_refuses_it() {
    let (signer, _) = key();
    let (_, other_line) = key();
    let addr = old_front(signer, Ok(()));
    let refusal = peer::query(addr, &peer::PeerKey::parse(&other_line).unwrap()).expect_err("wrong key");
    assert!(refusal.contains("not the configured peer key"), "{refusal}");
}