Skip to main content

toolkit_zero/
lib.rs

1//! # toolkit-zero
2//!
3//! A feature-selective Rust utility crate. Declare only the functionality your
4//! project requires via Cargo feature flags; each feature compiles exclusively
5//! the code it depends on, with no extraneous overhead.
6//!
7//! ---
8//!
9//! ## Table of Contents
10//!
11//! 1. [Feature flags](#feature-flags)
12//! 2. [Serialization](#serialization)
13//! 3. [Socket — server](#socket--server)
14//! 4. [Socket — client](#socket--client)
15//! 5. [Location](#location)
16//! 6. [Encryption — Timelock](#encryption--timelock)
17//! 7. [Dependency Graph — IronPrint](#dependency-graph--ironprint)
18//! 8. [Backend deps](#backend-deps-1)
19//!
20//! ---
21//!
22//! ## Feature flags
23//!
24//! | Feature | Enables | Exposes |
25//! |---|---||---|
26//! | `serialization` | VEIL cipher (seal / open) | [`serialization`] |
27//! | `socket-server` | VEIL + typed HTTP server builder | [`socket::server`] |
28//! | `socket-client` | VEIL + typed HTTP client builder | [`socket::client`] |
29//! | `socket` | Both `socket-server` and `socket-client` | both |
30//! | `location-native` | Browser-based geolocation | [`location::browser`] |
31//! | `location` | Alias for `location-native` | [`location`] |
32//! | `enc-timelock-keygen-now` | Time-lock key derivation from the system clock | [`encryption::timelock::derive_key_now`] |
33//! | `enc-timelock-keygen-input` | Time-lock key derivation from a caller-supplied time | [`encryption::timelock::derive_key_at`] |
34//! | `enc-timelock-async-keygen-now` | Async variant of `enc-timelock-keygen-now` | [`encryption::timelock::derive_key_now_async`] |
35//! | `enc-timelock-async-keygen-input` | Async variant of `enc-timelock-keygen-input` | [`encryption::timelock::derive_key_at_async`] |
36//! | `encryption` | All four `enc-timelock-*` features | [`encryption::timelock`] |
37//! | `dependency-graph-build` | Attach a normalised dependency-graph snapshot (`ironprint.json`) at build time | [`dependency_graph::build`] |
38//! | `dependency-graph-capture` | Read the embedded `ironprint.json` snapshot at runtime | [`dependency_graph::capture`] |
39//! | `backend-deps` | Re-exports all third-party deps used by each active module | `*::backend_deps` |
40//!
41//! ```toml
42//! [dependencies]
43//! # Only the VEIL cipher
44//! toolkit-zero = { version = "3", features = ["serialization"] }
45//!
46//! # HTTP server only
47//! toolkit-zero = { version = "3", features = ["socket-server"] }
48//!
49//! # HTTP client only
50//! toolkit-zero = { version = "3", features = ["socket-client"] }
51//!
52//! # Both sides of the socket
53//! toolkit-zero = { version = "3", features = ["socket"] }
54//!
55//! # Geolocation (bundles socket-server automatically)
56//! toolkit-zero = { version = "3", features = ["location"] }
57//!
58//! # Full time-lock encryption suite
59//! toolkit-zero = { version = "3", features = ["encryption"] }
60//!
61//! # Attach IronPrint fingerprint in build.rs
62//! # [build-dependencies]
63//! toolkit-zero = { version = "3", features = ["dependency-graph-build"] }
64//!
65//! # Read IronPrint fingerprint at runtime
66//! # [dependencies]
67//! toolkit-zero = { version = "3", features = ["dependency-graph-capture"] }
68//!
69//! # Re-export deps alongside socket-server
70//! toolkit-zero = { version = "3", features = ["socket-server", "backend-deps"] }
71//! ```
72//!
73//! ---
74//!
75//! ## Serialization
76//!
77//! The `serialization` feature exposes the **VEIL cipher** — a custom,
78//! key-dependent binary codec that transforms any [`bincode`]-encodable value
79//! into an opaque byte sequence and back.
80//!
81//! The two entry points are [`serialization::seal`] and [`serialization::open`].
82//! Every output byte is a function of the complete input and the key; without
83//! the exact key, the output cannot be reversed.
84//!
85//! ```rust,ignore
86//! use toolkit_zero::serialization::{seal, open, Encode, Decode};
87//!
88//! #[derive(Encode, Decode, Debug, PartialEq)]
89//! struct Point { x: f64, y: f64 }
90//!
91//! let p = Point { x: 1.0, y: -2.0 };
92//! let blob = seal(&p, None).unwrap();                          // default key
93//! let back: Point = open(&blob, None).unwrap();
94//! assert_eq!(p, back);
95//!
96//! let blob2 = seal(&p, Some("my-key")).unwrap();              // custom key
97//! let back2: Point = open(&blob2, Some("my-key")).unwrap();
98//! assert_eq!(p, back2);
99//! ```
100//!
101//! ---
102//!
103//! ## Socket — server
104//!
105//! The `socket-server` feature exposes a fluent, type-safe builder API for
106//! declaring and serving HTTP routes. Begin each route with
107//! [`socket::server::ServerMechanism`], optionally attach a JSON body expectation,
108//! URL query parameters, or shared state, then finalise with `.onconnect(handler)`.
109//! Register routes on a [`socket::server::Server`] and serve them with a single
110//! call to `.serve(addr).await`.
111//!
112//! The [`socket::server::reply!`] macro is the primary way to construct responses.
113//!
114//! ```rust,ignore
115//! use toolkit_zero::socket::server::{Server, ServerMechanism, reply, Status};
116//! use serde::{Deserialize, Serialize};
117//! use std::sync::{Arc, Mutex};
118//!
119//! #[derive(Deserialize, Serialize, Clone)]
120//! struct Item { id: u32, name: String }
121//!
122//! #[derive(Deserialize)]
123//! struct NewItem { name: String }
124//!
125//! #[derive(Deserialize)]
126//! struct Filter { page: u32 }
127//!
128//! # async fn run() {
129//! let store: Arc<Mutex<Vec<Item>>> = Arc::new(Mutex::new(vec![]));
130//!
131//! let mut server = Server::default();
132//! server
133//!     // Plain GET — no body, no state
134//!     .mechanism(
135//!         ServerMechanism::get("/health")
136//!             .onconnect(|| async { reply!() })
137//!     )
138//!     // POST with a JSON body
139//!     .mechanism(
140//!         ServerMechanism::post("/items")
141//!             .json::<NewItem>()
142//!             .onconnect(|body: NewItem| async move {
143//!                 reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
144//!             })
145//!     )
146//!     // GET with shared state
147//!     .mechanism(
148//!         ServerMechanism::get("/items")
149//!             .state(store.clone())
150//!             .onconnect(|state: Arc<Mutex<Vec<Item>>>| async move {
151//!                 let items = state.lock().unwrap().clone();
152//!                 reply!(json => items)
153//!             })
154//!     )
155//!     // GET with URL query parameters
156//!     .mechanism(
157//!         ServerMechanism::get("/items/search")
158//!             .query::<Filter>()
159//!             .onconnect(|f: Filter| async move {
160//!                 let _ = f.page;
161//!                 reply!()
162//!             })
163//!     );
164//!
165//! server.serve(([127, 0, 0, 1], 8080)).await;
166//! # }
167//! ```
168//!
169//! VEIL-encrypted routes are also supported via
170//! [`socket::server::ServerMechanism::encryption`] and
171//! [`socket::server::ServerMechanism::encrypted_query`].
172//! The body or query is decrypted before the handler is called; a wrong key or
173//! corrupt payload returns `403 Forbidden` automatically.
174//!
175//! ### `#[mechanism]` attribute macro
176//!
177//! The [`socket::server::mechanism`] attribute macro is a concise alternative to
178//! the builder calls above. It replaces the decorated `async fn` in-place with the
179//! equivalent `server.mechanism(...)` statement — no separate registration step
180//! required. All 10 builder combinations are supported: plain, `json`, `query`,
181//! `state`, `encrypted`, `encrypted_query`, and every `state + …` combination.
182//!
183//! ```rust,ignore
184//! use toolkit_zero::socket::server::{Server, mechanism, reply, Status};
185//! use serde::{Deserialize, Serialize};
186//!
187//! #[derive(Deserialize)]
188//! struct NewItem { name: String }
189//!
190//! #[derive(Serialize, Clone)]
191//! struct Item { id: u32, name: String }
192//!
193//! # async fn run() {
194//! let mut server = Server::default();
195//!
196//! #[mechanism(server, GET, "/health")]
197//! async fn health() { reply!() }
198//!
199//! #[mechanism(server, POST, "/items", json)]
200//! async fn create_item(body: NewItem) {
201//!     reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
202//! }
203//!
204//! server.serve(([127, 0, 0, 1], 8080)).await;
205//! # }
206//! ```
207//!
208//! See [`socket::server::mechanism`] for the full syntax reference.
209//!
210//! ---
211//!
212//! ## Socket — client
213//!
214//! The `socket-client` feature exposes a fluent [`socket::client::Client`] for
215//! issuing HTTP requests. Construct a client from a
216//! [`socket::client::Target`] (a localhost port or a remote URL), select an HTTP
217//! method, optionally attach a body or query parameters, and call `.send().await`
218//! (async) or `.send_sync()` (blocking).
219//!
220//! ```rust,ignore
221//! use toolkit_zero::socket::client::{Client, Target};
222//! use serde::{Deserialize, Serialize};
223//!
224//! #[derive(Deserialize, Serialize, Clone)]
225//! struct Item { id: u32, name: String }
226//!
227//! #[derive(Serialize)]
228//! struct NewItem { name: String }
229//!
230//! #[derive(Serialize)]
231//! struct Filter { page: u32 }
232//!
233//! # async fn run() -> Result<(), reqwest::Error> {
234//! // Async-only client — safe inside #[tokio::main]
235//! let client = Client::new_async(Target::Localhost(8080));
236//!
237//! // Plain GET
238//! let items: Vec<Item> = client.get("/items").send().await?;
239//!
240//! // POST with JSON body
241//! let created: Item = client
242//!     .post("/items")
243//!     .json(NewItem { name: "widget".into() })
244//!     .send()
245//!     .await?;
246//!
247//! // GET with query params
248//! let page: Vec<Item> = client
249//!     .get("/items")
250//!     .query(Filter { page: 2 })
251//!     .send()
252//!     .await?;
253//!
254//! // Synchronous DELETE (Client::new_sync must be called outside any async runtime)
255//! let _: Item = client.delete("/items/1").send_sync()?;
256//! # Ok(())
257//! # }
258//! ```
259//!
260//! VEIL-encrypted requests are available via
261//! [`socket::client::RequestBuilder::encryption`] and
262//! [`socket::client::RequestBuilder::encrypted_query`].
263//! The body or query parameters are sealed before the wire send; the response is
264//! opened automatically.
265//!
266//! ### `#[request]` attribute macro
267//!
268//! The [`socket::client::request`] attribute macro is a concise alternative to
269//! the builder calls above. It replaces the decorated `fn` in-place with a `let`
270//! binding that performs the HTTP request. The **function name** becomes the
271//! binding name; the **return type** becomes `R` in the `.send::<R>()` turbofish.
272//! The function body is discarded. A return type annotation is **required**.
273//!
274//! All five builder modes are supported: plain, `json`, `query`, `encrypted`,
275//! and `encrypted_query`. Each mode accepts either `async` (`.send::<R>().await?`)
276//! or `sync` (`.send_sync::<R>()?`).
277//!
278//! ```rust,ignore
279//! use toolkit_zero::socket::client::{Client, Target, request};
280//! use serde::{Deserialize, Serialize};
281//!
282//! #[derive(Deserialize, Serialize, Clone)]
283//! struct Item { id: u32, name: String }
284//!
285//! #[derive(Serialize)]
286//! struct NewItem { name: String }
287//!
288//! # async fn run() -> Result<(), reqwest::Error> {
289//! let client = Client::new_async(Target::Localhost(8080));
290//!
291//! // Plain async GET
292//! #[request(client, GET, "/items", async)]
293//! async fn items() -> Vec<Item> {}
294//!
295//! // POST with JSON body
296//! #[request(client, POST, "/items", json(NewItem { name: "widget".into() }), async)]
297//! async fn created() -> Item {}
298//!
299//! // Synchronous DELETE
300//! #[request(client, DELETE, "/items/1", sync)]
301//! fn deleted() -> Item {}
302//! # Ok(())
303//! # }
304//! ```
305//!
306//! See [`socket::client::request`] for the full syntax reference.
307//!
308//! ---
309//!
310//! ## Location
311//!
312//! The `location` (or `location-native`) feature provides browser-based geographic
313//! coordinate acquisition. A temporary HTTP server is bound on a randomly assigned
314//! local port, the system default browser is directed to a consent page, and the
315//! coordinates are submitted via the standard Web Geolocation API. The server
316//! shuts itself down upon receiving a result.
317//!
318//! Two entry points are available in [`location::browser`]:
319//!
320//! | Function | Context |
321//! |---|---|
322//! | [`location::browser::__location__`] | Blocking — safe from sync or async |
323//! | [`location::browser::__location_async__`] | Async — preferred inside `#[tokio::main]` |
324//!
325//! ```rust,ignore
326//! use toolkit_zero::location::browser::{__location__, __location_async__, PageTemplate};
327//!
328//! // Blocking — works from sync main or from inside a Tokio runtime
329//! match __location__(PageTemplate::default()) {
330//!     Ok(data) => println!("lat={:.6}  lon={:.6}  ±{:.0}m",
331//!                          data.latitude, data.longitude, data.accuracy),
332//!     Err(e)   => eprintln!("location error: {e}"),
333//! }
334//!
335//! // Async — preferred when already inside #[tokio::main]
336//! # async fn run() {
337//! match __location_async__(PageTemplate::default()).await {
338//!     Ok(data) => println!("lat={:.6}  lon={:.6}", data.latitude, data.longitude),
339//!     Err(e)   => eprintln!("location error: {e}"),
340//! }
341//! # }
342//! ```
343//!
344//! The [`location::browser::PageTemplate`] enum controls what the user sees:
345//! a plain single-button page, a checkbox-gated variant, or a fully custom HTML
346//! document.
347//!
348//! ---
349//!
350//! ## Encryption — Timelock
351//!
352//! The `encryption` feature (or any `enc-timelock-*` sub-feature) exposes a
353//! **time-locked key derivation** scheme.  A 32-byte key is derived from a
354//! time string through a three-pass KDF chain:
355//!
356//! > **Argon2id** (pass 1) → **scrypt** (pass 2) → **Argon2id** (pass 3)
357//!
358//! The key is reproducible only when the same time value, precision, format,
359//! and salts are supplied. An additional passphrase may be incorporated for a
360//! combined time × passphrase security model.
361//!
362//! **Features:**
363//!
364//! | Feature | Enables |
365//! |---|---|
366//! | `enc-timelock-keygen-now` | [`encryption::timelock::timelock`]`(None)` — decryption path (key from system clock) |
367//! | `enc-timelock-keygen-input` | [`encryption::timelock::timelock`]`(Some(t))` — encryption path (key from explicit time) |
368//! | `enc-timelock-async-keygen-now` | [`encryption::timelock::timelock_async`]`(None)` — async decryption path |
369//! | `enc-timelock-async-keygen-input` | [`encryption::timelock::timelock_async`]`(Some(t))` — async encryption path |
370//! | `encryption` | All four of the above |
371//!
372//! **Presets:** [`encryption::timelock::KdfPreset`] provides named parameter sets
373//! tuned per platform: `Balanced`, `Paranoid`, `BalancedMac`, `ParanoidMac`,
374//! `BalancedX86`, `ParanoidX86`, `BalancedArm`, `ParanoidArm`, and `Custom(KdfParams)`.
375//!
376//! ```rust,no_run
377//! use toolkit_zero::encryption::timelock::*;
378//!
379//! // Encryption side — caller sets the unlock time
380//! let salts = TimeLockSalts::generate();
381//! let kdf   = KdfPreset::BalancedMac.params();
382//! let at    = TimeLockTime::new(14, 30).unwrap();
383//! // params = None → _at (encryption) path
384//! let enc_key = timelock(
385//!     Some(TimeLockCadence::None),
386//!     Some(at),
387//!     Some(TimePrecision::Minute),
388//!     Some(TimeFormat::Hour24),
389//!     Some(salts.clone()),
390//!     Some(kdf),
391//!     None,
392//! ).unwrap();
393//!
394//! // Pack all settings (incl. salts + KDF params) into a self-contained header;
395//! // store it in the ciphertext — salts and KDF params are not secret.
396//! let header = pack(TimePrecision::Minute, TimeFormat::Hour24,
397//!                   &TimeLockCadence::None, salts, kdf);
398//!
399//! // Decryption side — load header from ciphertext; call at 14:30 local time.
400//! // params = Some(header) → _now (decryption) path
401//! let dec_key = timelock(
402//!     None, None, None, None, None, None,
403//!     Some(header),
404//! ).unwrap();
405//! // enc_key.as_bytes() == dec_key.as_bytes() when called at 14:30 local time
406//! ```
407//!
408//! ---
409//!
410//! ## Dependency Graph — IronPrint
411//!
412//! Two features; one for each side of the boundary.
413//!
414//! **`dependency-graph-build`** (goes in `[build-dependencies]`):
415//! [`dependency_graph::build::generate_ironprint`] runs `cargo metadata`, hashes
416//! `Cargo.lock` and every `.rs` file under `src/`, captures the profile, target
417//! triple, rustc version, and active features, then writes a compact, normalised
418//! JSON document to `$OUT_DIR/ironprint.json`.
419//! [`dependency_graph::build::export`] optionally writes a pretty-printed copy
420//! alongside `Cargo.toml` for local inspection — pass `false` or
421//! `cfg!(debug_assertions)` to suppress it in release builds.
422//!
423//! **`dependency-graph-capture`** (goes in `[dependencies]`):
424//! [`dependency_graph::capture::parse`] deserialises the embedded snapshot into a
425//! typed [`dependency_graph::capture::IronprintData`] struct.
426//! [`dependency_graph::capture::as_bytes`] returns the raw JSON bytes, which are
427//! stable and deterministic across equivalent builds.
428//!
429//! ### Sections captured in `ironprint.json`
430//!
431//! | Section | Contents |
432//! |---|---|
433//! | `package` | crate name + version |
434//! | `build` | profile, opt-level, target triple, rustc version, active feature flags |
435//! | `deps` | full normalised `cargo metadata` graph (sorted, no absolute paths) |
436//! | `cargo_lock_sha256` | SHA-256 of `Cargo.lock` (comment lines stripped) |
437//! | `source` | SHA-256 of every `.rs` file under `src/` |
438//!
439//! ### Setup
440//!
441//! ```toml
442//! [dependencies]
443//! toolkit-zero = { version = "3", features = ["dependency-graph-capture"] }
444//!
445//! [build-dependencies]
446//! toolkit-zero = { version = "3", features = ["dependency-graph-build"] }
447//! ```
448//!
449//! `build.rs`:
450//!
451//! ```rust,ignore
452//! fn main() {
453//!     toolkit_zero::dependency_graph::build::generate_ironprint()
454//!         .expect("ironprint generation failed");
455//!     // optional: pretty-print to crate root for local inspection
456//!     toolkit_zero::dependency_graph::build::export(cfg!(debug_assertions))
457//!         .expect("ironprint export failed");
458//! }
459//! ```
460//!
461//! `src/main.rs` (or any binary):
462//!
463//! ```rust,ignore
464//! use toolkit_zero::dependency_graph::capture;
465//!
466//! const IRONPRINT: &str = include_str!(concat!(env!("OUT_DIR"), "/ironprint.json"));
467//!
468//! fn main() {
469//!     let data = capture::parse(IRONPRINT).expect("failed to parse ironprint");
470//!     println!("{} v{}", data.package.name, data.package.version);
471//!     println!("target : {}", data.build.target);
472//!     println!("lock   : {}", data.cargo_lock_sha256);
473//!
474//!     let raw = capture::as_bytes(IRONPRINT);
475//!     println!("{} bytes", raw.len());
476//! }
477//! ```
478//!
479//! ### Risks and considerations
480//!
481//! * **Not tamper-proof** — the fingerprint is embedded as plain text in the
482//!   binary's read-only data section and is readable by anyone with access to
483//!   the binary. It is informational in nature; it does not constitute a
484//!   security boundary.
485//! * **Export file** — `export(true)` writes `ironprint.json` to the crate root.
486//!   Add it to `.gitignore` to prevent accidental commits.
487//! * **Build-time overhead** — `cargo metadata` runs on every rebuild triggered
488//!   by the `cargo:rerun-if-changed` directives (changes to `src/`, `Cargo.toml`,
489//!   or `Cargo.lock`).
490//! * **Feature scope** — `build.features` captures active features of the crate
491//!   being built, not toolkit-zero's own features.
492//! * **Path stripping** — absolute and machine-specific paths are removed from
493//!   `cargo metadata` output so the fingerprint is stable across machines.
494//! * **Compile-time only** — the snapshot reflects the build environment at
495//!   compile time; it does not change at runtime.
496//!
497//! ---
498//!
499//! ## Backend deps
500//!
501//! The `backend-deps` feature appends a `backend_deps` sub-module to each active
502//! module. Every such sub-module re-exports via `pub use` all third-party crates
503//! used internally by the parent module, allowing downstream crates to access
504//! those dependencies without separate `Cargo.toml` declarations.
505//!
506//! | Module | Path | Re-exports |
507//! |---|---|---|
508//! | serialization | [`serialization::backend_deps`] | `bincode`, `base64` |
509//! | socket (server) | [`socket::backend_deps`] | `bincode`, `base64`, `serde`, `tokio`, `log`, `bytes`, `serde_urlencoded`, `warp` |
510//! | socket (client) | [`socket::backend_deps`] | `bincode`, `base64`, `serde`, `tokio`, `log`, `reqwest` |
511//! | location | [`location::backend_deps`] | `tokio`, `serde`, `webbrowser`, `rand` |
512//! | encryption (timelock) | [`encryption::timelock::backend_deps`] | `argon2`, `scrypt`, `zeroize`, `chrono`, `rand`; `tokio` (async variants only) |
513//!
514//! Enabling `backend-deps` without any other feature compiles successfully but
515//! exposes no symbols; every re-export within `backend_deps` is individually
516//! gated on the corresponding parent feature.
517
518#[cfg(any(feature = "socket", feature = "socket-server", feature = "socket-client"))]
519pub mod socket;
520
521#[cfg(any(feature = "location", feature = "location-native"))]
522pub mod location;
523
524#[cfg(feature = "serialization")]
525pub mod serialization;
526
527#[cfg(any(feature = "encryption", feature = "enc-timelock-keygen-now", feature = "enc-timelock-keygen-input", feature = "enc-timelock-async-keygen-now", feature = "enc-timelock-async-keygen-input"))]
528pub mod encryption;
529
530#[cfg(any(feature = "dependency-graph-build", feature = "dependency-graph-capture"))]
531#[path = "dependency-graph/mod.rs"]
532pub mod dependency_graph;