RKIK

May 11, 2026 · View on GitHub

Table of Contents


Architecture

rkik is a Rust CLI with a reusable library. The split is intentional and strict: no clap, console, or process::exit in library code.

The main execution path for an NTP probe:

CLI (legacy.rs / rkik.rs)
  └─ services::query::query_one()
       ├─ adapters::resolver::resolve_ip()   (DNS)
       └─ adapters::ntp_client              (rsntp → ProbeResult)
            └─ [--nts] adapters::nts_client (rkik-nts → NtsTimeResult)

Compare mode runs all queries concurrently via futures::join_all.

Feature flags:

FeatureDefaultDescription
jsonyesserde::Serialize on domain types; JSON formatters
ntsyesNTS support via rkik-nts
syncyesSystem clock sync (Unix, root)
network-testsnoIntegration tests hitting real servers

Code layout

src/
  bin/rkik.rs          # modern subcommand CLI (clap)
  bin/rkik/
    legacy.rs          # legacy one-shot CLI (backwards compat)
    config_store.rs    # TOML-backed config + presets
  lib.rs               # public API re-exports
  adapters/
    resolver.rs        # DNS resolution
    ntp_client.rs      # rsntp wrapper → ProbeResult
    nts_client.rs      # rkik-nts wrapper (feature nts)
  domain/
    ntp.rs             # Target, ProbeResult
  services/
    query.rs           # query_one()
    compare.rs         # compare_many()
  fmt/
    text.rs            # terminal rendering
    json.rs            # JSON serialization
  stats.rs             # Stats, compute_stats()
  sync/                # clock sync (feature sync)
  error.rs             # RkikError
tests/
  integration.rs       # basic lib integration tests
  nts_test.rs          # NTS rendering and validation tests
  cli_test.rs          # CLI smoke tests (assert_cmd)

Library API

Add to Cargo.toml:

[dependencies]
rkik = { version = "2", default-features = false, features = ["json"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

Types

pub struct Target {
    pub name: String,
    pub ip: std::net::IpAddr,
    pub port: u16,
}

pub struct ProbeResult {
    pub target: Target,
    pub offset_ms: f64,      // signed: positive = local clock ahead
    pub rtt_ms: f64,
    pub stratum: u8,
    pub ref_id: String,
    pub utc: chrono::DateTime<chrono::Utc>,
    pub local: chrono::DateTime<chrono::Local>,
    pub timestamp: i64,
    pub authenticated: bool,
}

With the json feature, these derive serde::Serialize.

Functions

pub async fn query_one(
    target: &str,
    ipv6_only: bool,
    timeout: std::time::Duration,
    use_nts: bool,
    nts_ke_port: u16,
) -> Result<ProbeResult, RkikError>;

pub async fn compare_many(
    targets: &[String],
    ipv6_only: bool,
    timeout: std::time::Duration,
    use_nts: bool,
    nts_ke_port: u16,
) -> Result<Vec<ProbeResult>, RkikError>;

Example

use rkik::{query_one, compare_many};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Single probe
    let r = query_one("time.google.com", false, Duration::from_secs(3), false, 4460).await?;
    println!("{}: offset={:.3}ms rtt={:.3}ms", r.target.name, r.offset_ms, r.rtt_ms);

    // Parallel compare
    let targets = vec!["pool.ntp.org".into(), "time.cloudflare.com".into()];
    let results = compare_many(&targets, false, Duration::from_secs(3), false, 4460).await?;
    for p in &results {
        println!("{}: {:.3}ms", p.target.name, p.offset_ms);
    }
    Ok(())
}

Building and testing

# Standard build
cargo build --release

# Run tests (no network)
cargo test

# Run with real NTP servers
cargo test --features network-tests

# Minimal build (no NTS, no sync)
cargo build --no-default-features --features json

# Lint
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings

Run cargo test for unit and integration tests. Network integration tests (hitting real servers) require --features network-tests.


Packaging and releases

Packaging metadata lives in Cargo.toml under [package.metadata.deb] and [package.metadata.generate-rpm]. CI builds .deb, .rpm, and tar.gz artifacts and publishes them to GitHub Releases alongside a cargo publish for crates.io.

Distribution packages:

PlatformPackageLink
Arch Linuxrkik-git (AUR)https://aur.archlinux.org/packages/rkik-git
Nix / NixOSrkik (nixpkgs)https://search.nixos.org/packages?query=rkik
All Linux.deb / .rpm / tar.gzGitHub Releases

Contributing

  1. Fork the repo and create a feature branch from master.
  2. Keep the library/CLI split: no clap, console, or process::exit in src/lib.rs or anything it re-exports.
  3. Run cargo fmt and cargo clippy -- -D warnings before pushing.
  4. Add or update tests for any behaviour change.
  5. Open a PR referencing the relevant issue. Commit messages follow the type: description convention (feat:, fix:, docs:, chore:).

If you're looking for a starting point, check the good first issue label.