From 5ed2d36464bc1f6b6e3e8b45d447dece0ac23df1 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Sun, 3 May 2026 17:36:35 +0000 Subject: [PATCH] Snapshot-pin all 18 control query handlers as v0.3.0 schema baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add hand-rolled JSON snapshot harness in src/control/queries.rs to detect silent schema drift in operator-facing control-socket responses. Builds a Node with deterministic identity (Identity::from_secret_bytes(&[0xAB; 32])), invokes each of the 18 show_* handlers, redacts 17 volatile fields (version, pid, exe_path, control_socket, tun_name, allow_file, deny_file, *_ms / *_secs_ago / uptime_secs), sorts object keys recursively, and compares against a fixture in src/control/snapshots/. First run writes snapshots and passes; subsequent runs enforce. Future schema changes show as a snapshot diff that operators update intentionally — not a stability contract, just a tripwire so drift is never silent. A 19th meta-test dispatch_covers_all_snapshotted_handlers walks every name through dispatch() to confirm each returns status: ok and trips if a 19th handler is added without a matching snapshot. No new dependencies (insta deliberately not added; Cargo.toml [dev-dependencies] keeps tempfile + criterion only). 18 fixture files added, ~544 lines combined; harness is 367 added lines, all inside #[cfg(test)] mod tests. --- src/control/queries.rs | 367 ++++++++++++++++++ src/control/snapshots/show_acl.json | 16 + src/control/snapshots/show_bloom.json | 24 ++ src/control/snapshots/show_cache.json | 12 + src/control/snapshots/show_connections.json | 6 + .../snapshots/show_identity_cache.json | 8 + src/control/snapshots/show_links.json | 6 + src/control/snapshots/show_mmp.json | 7 + src/control/snapshots/show_peers.json | 6 + src/control/snapshots/show_routing.json | 65 ++++ src/control/snapshots/show_sessions.json | 6 + .../snapshots/show_stats_all_history.json | 180 +++++++++ src/control/snapshots/show_stats_history.json | 20 + .../show_stats_history_all_peers.json | 10 + src/control/snapshots/show_stats_list.json | 95 +++++ src/control/snapshots/show_stats_peers.json | 7 + src/control/snapshots/show_status.json | 52 +++ src/control/snapshots/show_transports.json | 6 + src/control/snapshots/show_tree.json | 36 ++ 19 files changed, 929 insertions(+) create mode 100644 src/control/snapshots/show_acl.json create mode 100644 src/control/snapshots/show_bloom.json create mode 100644 src/control/snapshots/show_cache.json create mode 100644 src/control/snapshots/show_connections.json create mode 100644 src/control/snapshots/show_identity_cache.json create mode 100644 src/control/snapshots/show_links.json create mode 100644 src/control/snapshots/show_mmp.json create mode 100644 src/control/snapshots/show_peers.json create mode 100644 src/control/snapshots/show_routing.json create mode 100644 src/control/snapshots/show_sessions.json create mode 100644 src/control/snapshots/show_stats_all_history.json create mode 100644 src/control/snapshots/show_stats_history.json create mode 100644 src/control/snapshots/show_stats_history_all_peers.json create mode 100644 src/control/snapshots/show_stats_list.json create mode 100644 src/control/snapshots/show_stats_peers.json create mode 100644 src/control/snapshots/show_status.json create mode 100644 src/control/snapshots/show_transports.json create mode 100644 src/control/snapshots/show_tree.json diff --git a/src/control/queries.rs b/src/control/queries.rs index eed1677..8e4fcdc 100644 --- a/src/control/queries.rs +++ b/src/control/queries.rs @@ -1091,3 +1091,370 @@ pub fn dispatch(node: &Node, command: &str, params: Option<&Value>) -> super::pr _ => super::protocol::Response::error(format!("unknown command: {}", command)), } } + +#[cfg(test)] +mod tests { + //! Schema-stability snapshot tests for all 18 control-socket query + //! handlers. + //! + //! Each handler is invoked against a deterministically-constructed + //! `Node` (fixed identity seed, empty peer/link/transport/cache + //! state). The resulting JSON is normalized — fields whose values + //! depend on wall-clock, PID, build environment, or filesystem + //! layout are replaced with the literal string `""` — + //! and compared against versioned fixtures under + //! `src/control/snapshots/`. + //! + //! The point is to catch accidental schema drift (renames, type + //! changes, dropped fields) in the operator-facing wire format. + //! Empty-state snapshots are sufficient because every top-level + //! key still appears, and per-element shapes inside `[]` arrays + //! are covered by the dispatcher contract test plus serde + //! derives elsewhere. + //! + //! ## Updating snapshots + //! + //! When a schema change is intentional, regenerate fixtures by + //! deleting the relevant `.json` files (or the whole + //! `snapshots/` directory) and re-running this test. Missing + //! fixtures are written from the current output rather than + //! failing — the next run then enforces the new shape. Review + //! the resulting diff before committing. + //! + //! ## Determinism + //! + //! The `Node` is built via `Node::with_identity` from a fixed + //! 32-byte seed (`[0xAB; 32]`), so `npub`, `node_addr`, and + //! `ipv6_addr` are stable across runs and machines. + //! Time-dependent scalars are redacted in `normalize_value` — + //! see the `VOLATILE_KEYS` list there for the exact set. + //! Empty arrays/maps are intrinsically stable and need no + //! redaction. + //! + //! Schnorr signatures are non-deterministic, but the only + //! signature surfaced by these handlers is `declaration_signed: + //! bool` (a flag, not the signature itself), so no redaction is + //! needed for that. + use super::*; + use crate::config::Config; + use crate::identity::Identity; + use crate::node::Node; + use serde_json::{Map, Value, json}; + use std::path::PathBuf; + + /// 32-byte seed for the deterministic test identity. + /// Any non-zero secret-key-shaped value works; 0xAB-fill is just + /// readable in hex. + const TEST_SEED: [u8; 32] = [0xAB; 32]; + + /// Fields whose value is environment-, time-, or build-dependent + /// and therefore must be redacted before comparison. Matched by + /// JSON key name anywhere in the document. + const VOLATILE_KEYS: &[&str] = &[ + // Process / build environment + "version", + "pid", + "exe_path", + "control_socket", + "tun_name", + // Filesystem layout (ACL, hosts, etc.) + "allow_file", + "deny_file", + // Wall-clock derived + "uptime_secs", + "started_at_ms", + "session_start_ms", + "authenticated_at_ms", + "last_seen_ms", + "last_activity_ms", + "last_recv_ms", + "created_at_ms", + "initiated_ms", + "last_sent_ms", + "age_ms", + "last_used_ms", + "idle_ms", + "first_seen_secs_ago", + "last_contact_secs_ago", + ]; + + /// Build a Node with a fixed identity, default config, and empty + /// runtime state (no peers, links, sessions, transports, or cache + /// entries). This keeps every per-element list empty and every + /// scalar deterministic modulo `VOLATILE_KEYS`. + fn build_test_node() -> Node { + let identity = + Identity::from_secret_bytes(&TEST_SEED).expect("test seed is a valid secret key"); + let config = Config::new(); + Node::with_identity(identity, config).expect("default config is valid") + } + + /// Recursively walk a JSON value, replacing the value of any key + /// listed in `VOLATILE_KEYS` with the literal string + /// `""`. Array elements are recursed into. + fn normalize_value(value: &mut Value) { + match value { + Value::Object(map) => { + for (key, v) in map.iter_mut() { + if VOLATILE_KEYS.contains(&key.as_str()) { + *v = Value::String("".to_string()); + } else { + normalize_value(v); + } + } + } + Value::Array(items) => { + for item in items.iter_mut() { + normalize_value(item); + } + } + _ => {} + } + } + + /// Wrap a handler value in the on-the-wire `Response` envelope so + /// the snapshot reflects exactly what a control-socket client + /// receives. Pretty-printed and sorted-keyed for readable diffs. + fn render(value: Value) -> String { + let mut wrapped = json!({ "status": "ok", "data": value }); + normalize_value(&mut wrapped); + let sorted = sort_object_keys(&wrapped); + serde_json::to_string_pretty(&sorted).expect("json serialization is infallible") + } + + /// Same as `render` but takes a `Response` directly (for handlers + /// that return `Response`, not `Value`). + fn render_response(resp: super::super::protocol::Response) -> String { + let value = serde_json::to_value(&resp).expect("response always serializes"); + let mut value = value; + normalize_value(&mut value); + let sorted = sort_object_keys(&value); + serde_json::to_string_pretty(&sorted).expect("json serialization is infallible") + } + + /// Recursively sort object keys for stable diff-friendly output. + /// `serde_json::Value` preserves insertion order; handlers don't + /// guarantee any particular emit order, so normalize here. + fn sort_object_keys(value: &Value) -> Value { + match value { + Value::Object(map) => { + let mut sorted: Map = Map::new(); + let mut keys: Vec<&String> = map.keys().collect(); + keys.sort(); + for key in keys { + sorted.insert(key.clone(), sort_object_keys(&map[key])); + } + Value::Object(sorted) + } + Value::Array(items) => Value::Array(items.iter().map(sort_object_keys).collect()), + other => other.clone(), + } + } + + fn snapshot_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("src") + .join("control") + .join("snapshots") + } + + /// Compare `actual` against the on-disk fixture for `name`. If the + /// fixture does not exist, write it (first-run convention) and + /// pass. Any subsequent mismatch fails with an inline diff hint. + fn assert_snapshot(name: &str, actual: &str) { + let path = snapshot_dir().join(format!("{name}.json")); + if !path.exists() { + std::fs::create_dir_all(path.parent().unwrap()) + .expect("failed to create snapshots dir"); + std::fs::write(&path, actual).expect("failed to write new snapshot"); + // Newly written: nothing to compare. Subsequent runs enforce. + return; + } + let expected = std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("failed to read snapshot {}: {e}", path.display())); + // Tolerate trailing newline differences from editors. + if expected.trim_end() != actual.trim_end() { + panic!( + "snapshot mismatch for {name}\n\ + fixture: {}\n\ + -- expected --\n{expected}\n\ + -- actual --\n{actual}\n\ + -- end --\n\ + If the schema change is intentional, delete the fixture \ + and re-run to regenerate.", + path.display() + ); + } + } + + // ---- 18 handler snapshot tests -------------------------------------- + + #[test] + fn snapshot_show_status() { + let node = build_test_node(); + assert_snapshot("show_status", &render(show_status(&node))); + } + + #[test] + fn snapshot_show_acl() { + let node = build_test_node(); + assert_snapshot("show_acl", &render(show_acl(&node))); + } + + #[test] + fn snapshot_show_peers() { + let node = build_test_node(); + assert_snapshot("show_peers", &render(show_peers(&node))); + } + + #[test] + fn snapshot_show_links() { + let node = build_test_node(); + assert_snapshot("show_links", &render(show_links(&node))); + } + + #[test] + fn snapshot_show_tree() { + let node = build_test_node(); + assert_snapshot("show_tree", &render(show_tree(&node))); + } + + #[test] + fn snapshot_show_sessions() { + let node = build_test_node(); + assert_snapshot("show_sessions", &render(show_sessions(&node))); + } + + #[test] + fn snapshot_show_bloom() { + let node = build_test_node(); + assert_snapshot("show_bloom", &render(show_bloom(&node))); + } + + #[test] + fn snapshot_show_mmp() { + let node = build_test_node(); + assert_snapshot("show_mmp", &render(show_mmp(&node))); + } + + #[test] + fn snapshot_show_cache() { + let node = build_test_node(); + assert_snapshot("show_cache", &render(show_cache(&node))); + } + + #[test] + fn snapshot_show_connections() { + let node = build_test_node(); + assert_snapshot("show_connections", &render(show_connections(&node))); + } + + #[test] + fn snapshot_show_transports() { + let node = build_test_node(); + assert_snapshot("show_transports", &render(show_transports(&node))); + } + + #[test] + fn snapshot_show_routing() { + let node = build_test_node(); + assert_snapshot("show_routing", &render(show_routing(&node))); + } + + #[test] + fn snapshot_show_identity_cache() { + let node = build_test_node(); + assert_snapshot("show_identity_cache", &render(show_identity_cache(&node))); + } + + #[test] + fn snapshot_show_stats_list() { + // Static — no Node needed. + assert_snapshot("show_stats_list", &render(show_stats_list())); + } + + #[test] + fn snapshot_show_stats_history() { + let node = build_test_node(); + // Pin the empty-history series shape for one node-level metric. + let params = json!({ "metric": "mesh_size", "window": "10s", "granularity": "1s" }); + let resp = show_stats_history(&node, Some(¶ms)); + assert_snapshot("show_stats_history", &render_response(resp)); + } + + #[test] + fn snapshot_show_stats_all_history() { + let node = build_test_node(); + // Empty-history all-node series; small window keeps the + // per-series `values` arrays short and stable. + let params = json!({ "window": "10s", "granularity": "1s" }); + let resp = show_stats_all_history(&node, Some(¶ms)); + assert_snapshot("show_stats_all_history", &render_response(resp)); + } + + #[test] + fn snapshot_show_stats_peers() { + let node = build_test_node(); + assert_snapshot("show_stats_peers", &render(show_stats_peers(&node))); + } + + #[test] + fn snapshot_show_stats_history_all_peers() { + let node = build_test_node(); + // No peers tracked → empty `peers: []` envelope. Per-peer + // `values` shape is exercised once a real peer is wired in; + // here we only pin the envelope. + let params = json!({ "metric": "srtt_ms", "window": "10s", "granularity": "1s" }); + let resp = show_stats_history_all_peers(&node, Some(¶ms)); + assert_snapshot("show_stats_history_all_peers", &render_response(resp)); + } + + /// Sanity check: every handler advertised in `dispatch` is also + /// covered by a snapshot test above. If a new handler is added + /// without a matching snapshot, this test fails. + #[test] + fn dispatch_covers_all_snapshotted_handlers() { + let expected = [ + "show_status", + "show_acl", + "show_peers", + "show_links", + "show_tree", + "show_sessions", + "show_bloom", + "show_mmp", + "show_cache", + "show_connections", + "show_transports", + "show_routing", + "show_identity_cache", + "show_stats_list", + "show_stats_history", + "show_stats_all_history", + "show_stats_peers", + "show_stats_history_all_peers", + ]; + assert_eq!(expected.len(), 18, "expected exactly 18 query handlers"); + let node = build_test_node(); + for cmd in expected { + // Each must dispatch successfully (status == "ok") with + // minimal params. Handlers requiring params get them. + let params = match cmd { + "show_stats_history" => Some(json!({ + "metric": "mesh_size", "window": "10s", "granularity": "1s" + })), + "show_stats_all_history" => Some(json!({ "window": "10s", "granularity": "1s" })), + "show_stats_history_all_peers" => Some(json!({ + "metric": "srtt_ms", "window": "10s", "granularity": "1s" + })), + _ => None, + }; + let resp = dispatch(&node, cmd, params.as_ref()); + assert_eq!( + resp.status, "ok", + "dispatch({cmd}) returned status={} message={:?}", + resp.status, resp.message + ); + } + } +} diff --git a/src/control/snapshots/show_acl.json b/src/control/snapshots/show_acl.json new file mode 100644 index 0000000..b395099 --- /dev/null +++ b/src/control/snapshots/show_acl.json @@ -0,0 +1,16 @@ +{ + "data": { + "allow_all": false, + "allow_entries": [], + "allow_file": "", + "allow_file_entries": [], + "default_decision": "allow", + "deny_all": false, + "deny_entries": [], + "deny_file": "", + "deny_file_entries": [], + "effective_mode": "default_open", + "enforcement_active": false + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_bloom.json b/src/control/snapshots/show_bloom.json new file mode 100644 index 0000000..1fdb6c8 --- /dev/null +++ b/src/control/snapshots/show_bloom.json @@ -0,0 +1,24 @@ +{ + "data": { + "is_leaf_only": false, + "leaf_dependent_count": 0, + "leaf_dependents": [], + "own_node_addr": "1b4788b7ab7a436a611fc59fb1e34c6e", + "peer_filters": [], + "sequence": 0, + "stats": { + "accepted": 0, + "debounce_suppressed": 0, + "decode_error": 0, + "fill_exceeded": 0, + "invalid": 0, + "non_v1": 0, + "received": 0, + "send_failed": 0, + "sent": 0, + "stale": 0, + "unknown_peer": 0 + } + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_cache.json b/src/control/snapshots/show_cache.json new file mode 100644 index 0000000..91ffc9f --- /dev/null +++ b/src/control/snapshots/show_cache.json @@ -0,0 +1,12 @@ +{ + "data": { + "avg_age_ms": 0, + "count": 0, + "default_ttl_ms": 300000, + "entries": [], + "expired": 0, + "fill_ratio": 0.0, + "max_entries": 50000 + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_connections.json b/src/control/snapshots/show_connections.json new file mode 100644 index 0000000..0df3739 --- /dev/null +++ b/src/control/snapshots/show_connections.json @@ -0,0 +1,6 @@ +{ + "data": { + "connections": [] + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_identity_cache.json b/src/control/snapshots/show_identity_cache.json new file mode 100644 index 0000000..a1ae97a --- /dev/null +++ b/src/control/snapshots/show_identity_cache.json @@ -0,0 +1,8 @@ +{ + "data": { + "count": 0, + "entries": [], + "max_entries": 10000 + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_links.json b/src/control/snapshots/show_links.json new file mode 100644 index 0000000..bb6d0a9 --- /dev/null +++ b/src/control/snapshots/show_links.json @@ -0,0 +1,6 @@ +{ + "data": { + "links": [] + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_mmp.json b/src/control/snapshots/show_mmp.json new file mode 100644 index 0000000..938f8b2 --- /dev/null +++ b/src/control/snapshots/show_mmp.json @@ -0,0 +1,7 @@ +{ + "data": { + "peers": [], + "sessions": [] + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_peers.json b/src/control/snapshots/show_peers.json new file mode 100644 index 0000000..b7eeab4 --- /dev/null +++ b/src/control/snapshots/show_peers.json @@ -0,0 +1,6 @@ +{ + "data": { + "peers": [] + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_routing.json b/src/control/snapshots/show_routing.json new file mode 100644 index 0000000..8be6c56 --- /dev/null +++ b/src/control/snapshots/show_routing.json @@ -0,0 +1,65 @@ +{ + "data": { + "congestion": { + "ce_forwarded": 0, + "ce_received": 0, + "congestion_detected": 0, + "kernel_drop_events": 0 + }, + "coord_cache_entries": 0, + "discovery": { + "req_backoff_suppressed": 0, + "req_bloom_miss": 0, + "req_decode_error": 0, + "req_deduplicated": 0, + "req_duplicate": 0, + "req_fallback_forwarded": 0, + "req_forward_rate_limited": 0, + "req_forwarded": 0, + "req_initiated": 0, + "req_no_tree_peer": 0, + "req_received": 0, + "req_target_is_us": 0, + "req_ttl_exhausted": 0, + "resp_accepted": 0, + "resp_decode_error": 0, + "resp_forwarded": 0, + "resp_identity_miss": 0, + "resp_proof_failed": 0, + "resp_received": 0, + "resp_timed_out": 0 + }, + "error_signals": { + "coords_required": 0, + "mtu_exceeded": 0, + "path_broken": 0 + }, + "forwarding": { + "decode_error_bytes": 0, + "decode_error_packets": 0, + "delivered_bytes": 0, + "delivered_packets": 0, + "drop_mtu_exceeded_bytes": 0, + "drop_mtu_exceeded_packets": 0, + "drop_no_route_bytes": 0, + "drop_no_route_packets": 0, + "drop_send_error_bytes": 0, + "drop_send_error_packets": 0, + "forwarded_bytes": 0, + "forwarded_packets": 0, + "originated_bytes": 0, + "originated_packets": 0, + "received_bytes": 0, + "received_packets": 0, + "ttl_exhausted_bytes": 0, + "ttl_exhausted_packets": 0 + }, + "identity_cache_entries": 0, + "pending_lookups": [], + "pending_tun_destinations": 0, + "pending_tun_packets": 0, + "recent_requests": 0, + "retries": [] + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_sessions.json b/src/control/snapshots/show_sessions.json new file mode 100644 index 0000000..1cc8471 --- /dev/null +++ b/src/control/snapshots/show_sessions.json @@ -0,0 +1,6 @@ +{ + "data": { + "sessions": [] + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_stats_all_history.json b/src/control/snapshots/show_stats_all_history.json new file mode 100644 index 0000000..49631dc --- /dev/null +++ b/src/control/snapshots/show_stats_all_history.json @@ -0,0 +1,180 @@ +{ + "data": { + "granularity_seconds": 1, + "peer": null, + "series": [ + { + "granularity_seconds": 1, + "metric": "mesh_size", + "unit": "nodes", + "values": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + { + "granularity_seconds": 1, + "metric": "tree_depth", + "unit": "hops", + "values": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + { + "granularity_seconds": 1, + "metric": "peer_count", + "unit": "peers", + "values": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + { + "granularity_seconds": 1, + "metric": "parent_switches", + "unit": "events/s", + "values": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + { + "granularity_seconds": 1, + "metric": "bytes_in", + "unit": "bytes/s", + "values": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + { + "granularity_seconds": 1, + "metric": "bytes_out", + "unit": "bytes/s", + "values": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + { + "granularity_seconds": 1, + "metric": "packets_in", + "unit": "packets/s", + "values": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + { + "granularity_seconds": 1, + "metric": "packets_out", + "unit": "packets/s", + "values": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + { + "granularity_seconds": 1, + "metric": "loss_rate", + "unit": "fraction", + "values": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + { + "granularity_seconds": 1, + "metric": "active_sessions", + "unit": "sessions", + "values": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + } + ], + "window_seconds": 10 + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_stats_history.json b/src/control/snapshots/show_stats_history.json new file mode 100644 index 0000000..30fd47f --- /dev/null +++ b/src/control/snapshots/show_stats_history.json @@ -0,0 +1,20 @@ +{ + "data": { + "granularity_seconds": 1, + "metric": "mesh_size", + "unit": "nodes", + "values": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_stats_history_all_peers.json b/src/control/snapshots/show_stats_history_all_peers.json new file mode 100644 index 0000000..bf9dabb --- /dev/null +++ b/src/control/snapshots/show_stats_history_all_peers.json @@ -0,0 +1,10 @@ +{ + "data": { + "granularity_seconds": 1, + "metric": "srtt_ms", + "peers": [], + "unit": "ms", + "window_seconds": 10 + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_stats_list.json b/src/control/snapshots/show_stats_list.json new file mode 100644 index 0000000..93b30a1 --- /dev/null +++ b/src/control/snapshots/show_stats_list.json @@ -0,0 +1,95 @@ +{ + "data": { + "fast_ring_seconds": 3600, + "metrics": [ + { + "name": "mesh_size", + "scope": "node", + "unit": "nodes" + }, + { + "name": "tree_depth", + "scope": "node", + "unit": "hops" + }, + { + "name": "peer_count", + "scope": "node", + "unit": "peers" + }, + { + "name": "parent_switches", + "scope": "node", + "unit": "events/s" + }, + { + "name": "bytes_in", + "scope": "node", + "unit": "bytes/s" + }, + { + "name": "bytes_out", + "scope": "node", + "unit": "bytes/s" + }, + { + "name": "packets_in", + "scope": "node", + "unit": "packets/s" + }, + { + "name": "packets_out", + "scope": "node", + "unit": "packets/s" + }, + { + "name": "loss_rate", + "scope": "node", + "unit": "fraction" + }, + { + "name": "active_sessions", + "scope": "node", + "unit": "sessions" + }, + { + "name": "srtt_ms", + "scope": "peer", + "unit": "ms" + }, + { + "name": "loss_rate", + "scope": "peer", + "unit": "fraction" + }, + { + "name": "bytes_in", + "scope": "peer", + "unit": "bytes/s" + }, + { + "name": "bytes_out", + "scope": "peer", + "unit": "bytes/s" + }, + { + "name": "packets_in", + "scope": "peer", + "unit": "packets/s" + }, + { + "name": "packets_out", + "scope": "peer", + "unit": "packets/s" + }, + { + "name": "ecn_ce", + "scope": "peer", + "unit": "events/s" + } + ], + "peer_retention_seconds": 86400, + "slow_ring_minutes": 1440 + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_stats_peers.json b/src/control/snapshots/show_stats_peers.json new file mode 100644 index 0000000..1ee5354 --- /dev/null +++ b/src/control/snapshots/show_stats_peers.json @@ -0,0 +1,7 @@ +{ + "data": { + "count": 0, + "peers": [] + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_status.json b/src/control/snapshots/show_status.json new file mode 100644 index 0000000..7351178 --- /dev/null +++ b/src/control/snapshots/show_status.json @@ -0,0 +1,52 @@ +{ + "data": { + "connection_count": 0, + "control_socket": "", + "effective_ipv6_mtu": 1203, + "estimated_mesh_size": null, + "exe_path": "", + "forwarding": { + "decode_error_bytes": 0, + "decode_error_packets": 0, + "delivered_bytes": 0, + "delivered_packets": 0, + "drop_mtu_exceeded_bytes": 0, + "drop_mtu_exceeded_packets": 0, + "drop_no_route_bytes": 0, + "drop_no_route_packets": 0, + "drop_send_error_bytes": 0, + "drop_send_error_packets": 0, + "forwarded_bytes": 0, + "forwarded_packets": 0, + "originated_bytes": 0, + "originated_packets": 0, + "received_bytes": 0, + "received_packets": 0, + "ttl_exhausted_bytes": 0, + "ttl_exhausted_packets": 0 + }, + "ipv6_addr": "fd1b:4788:b7ab:7a43:6a61:1fc5:9fb1:e34c", + "is_leaf_only": false, + "link_count": 0, + "node_addr": "1b4788b7ab7a436a611fc59fb1e34c6e", + "npub": "npub1sx42mj99aql52aklsg70y2jmr95u7uz2p40k769aw46ppjv302kqkhmu5r", + "peer_count": 0, + "pid": "", + "session_count": 0, + "sparklines": { + "bytes_in": [], + "bytes_out": [], + "loss_rate": [], + "mesh_size": [], + "peer_count": [], + "tree_depth": [] + }, + "state": "created", + "transport_count": 0, + "tun_name": "", + "tun_state": "disabled", + "uptime_secs": "", + "version": "" + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_transports.json b/src/control/snapshots/show_transports.json new file mode 100644 index 0000000..81f500f --- /dev/null +++ b/src/control/snapshots/show_transports.json @@ -0,0 +1,6 @@ +{ + "data": { + "transports": [] + }, + "status": "ok" +} \ No newline at end of file diff --git a/src/control/snapshots/show_tree.json b/src/control/snapshots/show_tree.json new file mode 100644 index 0000000..97eea5f --- /dev/null +++ b/src/control/snapshots/show_tree.json @@ -0,0 +1,36 @@ +{ + "data": { + "declaration_sequence": 1, + "declaration_signed": true, + "depth": 0, + "is_root": true, + "my_coords": [ + "1b4788b7ab7a436a611fc59fb1e34c6e" + ], + "my_node_addr": "1b4788b7ab7a436a611fc59fb1e34c6e", + "parent": "1b4788b7ab7a436a611fc59fb1e34c6e", + "parent_display_name": "1b4788b7...", + "peer_tree_count": 0, + "peers": [], + "root": "1b4788b7ab7a436a611fc59fb1e34c6e", + "stats": { + "accepted": 0, + "addr_mismatch": 0, + "ancestry_changed": 0, + "decode_error": 0, + "flap_dampened": 0, + "loop_detected": 0, + "parent_losses": 0, + "parent_switched": 0, + "parent_switches": 0, + "rate_limited": 0, + "received": 0, + "send_failed": 0, + "sent": 0, + "sig_failed": 0, + "stale": 0, + "unknown_peer": 0 + } + }, + "status": "ok" +} \ No newline at end of file