Skip to main content

toolkit_zero/
lib.rs

1//! # toolkit-zero
2//!
3//! A feature-selective Rust utility toolkit.  Pull in only what you need via Cargo
4//! feature flags — each feature compiles exactly the modules it requires and nothing
5//! more.
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. [Backend deps](#backend-deps-1)
18//!
19//! ---
20//!
21//! ## Feature flags
22//!
23//! | Feature | Enables | Exposes |
24//! |---|---||---|
25//! | `serialization` | VEIL cipher (seal / open) | [`serialization`] |
26//! | `socket-server` | VEIL + typed HTTP server builder | [`socket::server`] |
27//! | `socket-client` | VEIL + typed HTTP client builder | [`socket::client`] |
28//! | `socket` | Both `socket-server` and `socket-client` | both |
29//! | `location-native` | Browser-based geolocation | [`location::browser`] |
30//! | `location` | Alias for `location-native` | [`location`] |
31//! | `enc-timelock-keygen-now` | Time-lock key derivation from the system clock | [`encryption::timelock::derive_key_now`] |
32//! | `enc-timelock-keygen-input` | Time-lock key derivation from a caller-supplied time | [`encryption::timelock::derive_key_at`] |
33//! | `enc-timelock-async-keygen-now` | Async variant of `enc-timelock-keygen-now` | [`encryption::timelock::derive_key_now_async`] |
34//! | `enc-timelock-async-keygen-input` | Async variant of `enc-timelock-keygen-input` | [`encryption::timelock::derive_key_at_async`] |
35//! | `encryption` | All four `enc-timelock-*` features | [`encryption::timelock`] |
36//! | `backend-deps` | Re-exports all third-party deps used by each active module | `*::backend_deps` |
37//!
38//! ```toml
39//! [dependencies]
40//! # Only the VEIL cipher
41//! toolkit-zero = { version = "3", features = ["serialization"] }
42//!
43//! # HTTP server only
44//! toolkit-zero = { version = "3", features = ["socket-server"] }
45//!
46//! # HTTP client only
47//! toolkit-zero = { version = "3", features = ["socket-client"] }
48//!
49//! # Both sides of the socket
50//! toolkit-zero = { version = "3", features = ["socket"] }
51//!
52//! # Geolocation (bundles socket-server automatically)
53//! toolkit-zero = { version = "3", features = ["location"] }
54//!
55//! # Full time-lock encryption suite
56//! toolkit-zero = { version = "3", features = ["encryption"] }
57//!
58//! # Re-export deps alongside socket-server
59//! toolkit-zero = { version = "3", features = ["socket-server", "backend-deps"] }
60//! ```
61//!
62//! ---
63//!
64//! ## Serialization
65//!
66//! The `serialization` feature exposes the **VEIL cipher** — a custom,
67//! key-dependent binary codec that converts any [`bincode`]-encodable value into
68//! an opaque byte sequence and back.
69//!
70//! The two entry points are [`serialization::seal`] and [`serialization::open`].
71//! Every output byte depends on the full message and the key; without the exact
72//! key, the output cannot be inverted.
73//!
74//! ```rust,ignore
75//! use toolkit_zero::serialization::{seal, open, Encode, Decode};
76//!
77//! #[derive(Encode, Decode, Debug, PartialEq)]
78//! struct Point { x: f64, y: f64 }
79//!
80//! let p = Point { x: 1.0, y: -2.0 };
81//! let blob = seal(&p, None).unwrap();                          // default key
82//! let back: Point = open(&blob, None).unwrap();
83//! assert_eq!(p, back);
84//!
85//! let blob2 = seal(&p, Some("my-key")).unwrap();              // custom key
86//! let back2: Point = open(&blob2, Some("my-key")).unwrap();
87//! assert_eq!(p, back2);
88//! ```
89//!
90//! ---
91//!
92//! ## Socket — server
93//!
94//! The `socket-server` feature exposes a fluent builder API for declaring typed
95//! HTTP routes and serving them.  Start every route with [`socket::server::ServerMechanism`],
96//! optionally add a JSON body expectation, URL query parameters, or shared state,
97//! then finalise with `.onconnect(async_handler)`.  Register all routes on a
98//! [`socket::server::Server`] and call `.serve(addr).await`.
99//!
100//! The [`socket::server::reply!`] macro is the primary way to construct responses.
101//!
102//! ```rust,ignore
103//! use toolkit_zero::socket::server::{Server, ServerMechanism, reply, Status};
104//! use serde::{Deserialize, Serialize};
105//! use std::sync::{Arc, Mutex};
106//!
107//! #[derive(Deserialize, Serialize, Clone)]
108//! struct Item { id: u32, name: String }
109//!
110//! #[derive(Deserialize)]
111//! struct NewItem { name: String }
112//!
113//! #[derive(Deserialize)]
114//! struct Filter { page: u32 }
115//!
116//! # async fn run() {
117//! let store: Arc<Mutex<Vec<Item>>> = Arc::new(Mutex::new(vec![]));
118//!
119//! let mut server = Server::default();
120//! server
121//!     // Plain GET — no body, no state
122//!     .mechanism(
123//!         ServerMechanism::get("/health")
124//!             .onconnect(|| async { reply!() })
125//!     )
126//!     // POST with a JSON body
127//!     .mechanism(
128//!         ServerMechanism::post("/items")
129//!             .json::<NewItem>()
130//!             .onconnect(|body: NewItem| async move {
131//!                 reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
132//!             })
133//!     )
134//!     // GET with shared state
135//!     .mechanism(
136//!         ServerMechanism::get("/items")
137//!             .state(store.clone())
138//!             .onconnect(|state: Arc<Mutex<Vec<Item>>>| async move {
139//!                 let items = state.lock().unwrap().clone();
140//!                 reply!(json => items)
141//!             })
142//!     )
143//!     // GET with URL query parameters
144//!     .mechanism(
145//!         ServerMechanism::get("/items/search")
146//!             .query::<Filter>()
147//!             .onconnect(|f: Filter| async move {
148//!                 let _ = f.page;
149//!                 reply!()
150//!             })
151//!     );
152//!
153//! server.serve(([127, 0, 0, 1], 8080)).await;
154//! # }
155//! ```
156//!
157//! VEIL-encrypted routes are also supported via
158//! [`socket::server::ServerMechanism::encryption`] and
159//! [`socket::server::ServerMechanism::encrypted_query`].
160//! The body or query is decrypted before the handler is called; a wrong key or
161//! corrupt payload returns `403 Forbidden` automatically.
162//!
163//! ---
164//!
165//! ## Socket — client
166//!
167//! The `socket-client` feature exposes a fluent [`socket::client::Client`] for
168//! issuing typed HTTP requests.  Construct a client from a
169//! [`socket::client::Target`] (a `localhost` port or a remote URL), pick an HTTP
170//! method, optionally attach a body or query, and call `.send().await` (async) or
171//! `.send_sync()` (blocking).
172//!
173//! ```rust,ignore
174//! use toolkit_zero::socket::client::{Client, Target};
175//! use serde::{Deserialize, Serialize};
176//!
177//! #[derive(Deserialize, Serialize, Clone)]
178//! struct Item { id: u32, name: String }
179//!
180//! #[derive(Serialize)]
181//! struct NewItem { name: String }
182//!
183//! #[derive(Serialize)]
184//! struct Filter { page: u32 }
185//!
186//! # async fn run() -> Result<(), reqwest::Error> {
187//! // Async-only client — safe inside #[tokio::main]
188//! let client = Client::new_async(Target::Localhost(8080));
189//!
190//! // Plain GET
191//! let items: Vec<Item> = client.get("/items").send().await?;
192//!
193//! // POST with JSON body
194//! let created: Item = client
195//!     .post("/items")
196//!     .json(NewItem { name: "widget".into() })
197//!     .send()
198//!     .await?;
199//!
200//! // GET with query params
201//! let page: Vec<Item> = client
202//!     .get("/items")
203//!     .query(Filter { page: 2 })
204//!     .send()
205//!     .await?;
206//!
207//! // Synchronous DELETE (Client::new_sync must be called outside any async runtime)
208//! let _: Item = client.delete("/items/1").send_sync()?;
209//! # Ok(())
210//! # }
211//! ```
212//!
213//! VEIL-encrypted requests are available via
214//! [`socket::client::RequestBuilder::encryption`] and
215//! [`socket::client::RequestBuilder::encrypted_query`].
216//! The body or query parameters are sealed before the wire send; the response is
217//! opened automatically.
218//!
219//! ---
220//!
221//! ## Location
222//!
223//! The `location` (or `location-native`) feature exposes browser-based geographic
224//! coordinate acquisition.  A temporary local HTTP server is bound on a random
225//! port, the system's default browser is opened to a consent page, and the
226//! standard browser Geolocation API POSTs the coordinates back.  The server shuts
227//! itself down once a result arrives.
228//!
229//! Two entry points are available in [`location::browser`]:
230//!
231//! | Function | Context |
232//! |---|---|
233//! | [`location::browser::__location__`] | Blocking — safe from sync or async |
234//! | [`location::browser::__location_async__`] | Async — preferred inside `#[tokio::main]` |
235//!
236//! ```rust,ignore
237//! use toolkit_zero::location::browser::{__location__, __location_async__, PageTemplate};
238//!
239//! // Blocking — works from sync main or from inside a Tokio runtime
240//! match __location__(PageTemplate::default()) {
241//!     Ok(data) => println!("lat={:.6}  lon={:.6}  ±{:.0}m",
242//!                          data.latitude, data.longitude, data.accuracy),
243//!     Err(e)   => eprintln!("location error: {e}"),
244//! }
245//!
246//! // Async — preferred when already inside #[tokio::main]
247//! # async fn run() {
248//! match __location_async__(PageTemplate::default()).await {
249//!     Ok(data) => println!("lat={:.6}  lon={:.6}", data.latitude, data.longitude),
250//!     Err(e)   => eprintln!("location error: {e}"),
251//! }
252//! # }
253//! ```
254//!
255//! The [`location::browser::PageTemplate`] enum controls what the user sees:
256//! a plain single-button page, a checkbox-gated variant, or a fully custom HTML
257//! document.
258//!
259//! ---
260//!
261//! ## Encryption — Timelock
262//!
263//! The `encryption` feature (or any `enc-timelock-*` sub-feature) exposes a
264//! **time-locked key derivation** scheme.  A 32-byte key is derived from a
265//! time string through a three-pass KDF chain:
266//!
267//! > **Argon2id** (pass 1) → **scrypt** (pass 2) → **Argon2id** (pass 3)
268//!
269//! The key is only reproducible when the same time value, precision, format,
270//! and salts are provided.  Pair with an additional secret for a joint
271//! time ✕ passphrase security model.
272//!
273//! **Features:**
274//!
275//! | Feature | Enables |
276//! |---|---|
277//! | `enc-timelock-keygen-now` | [`encryption::timelock::timelock`]`(None)` — decryption path (key from system clock) |
278//! | `enc-timelock-keygen-input` | [`encryption::timelock::timelock`]`(Some(t))` — encryption path (key from explicit time) |
279//! | `enc-timelock-async-keygen-now` | [`encryption::timelock::timelock_async`]`(None)` — async decryption path |
280//! | `enc-timelock-async-keygen-input` | [`encryption::timelock::timelock_async`]`(Some(t))` — async encryption path |
281//! | `encryption` | All four of the above |
282//!
283//! **Presets:** [`encryption::timelock::KdfPreset`] provides named parameter sets
284//! tuned per platform: `Balanced`, `Paranoid`, `BalancedMac`, `ParanoidMac`,
285//! `BalancedX86`, `ParanoidX86`, `BalancedArm`, `ParanoidArm`, and `Custom(KdfParams)`.
286//!
287//! ```rust,no_run
288//! use toolkit_zero::encryption::timelock::*;
289//!
290//! // Encryption side — caller sets the unlock time
291//! let salts = TimeLockSalts::generate();
292//! let kdf   = KdfPreset::BalancedMac.params();
293//! let at    = TimeLockTime::new(14, 30).unwrap();
294//! // params = None → _at (encryption) path
295//! let enc_key = timelock(
296//!     Some(TimeLockCadence::None),
297//!     Some(at),
298//!     Some(TimePrecision::Minute),
299//!     Some(TimeFormat::Hour24),
300//!     Some(salts.clone()),
301//!     Some(kdf),
302//!     None,
303//! ).unwrap();
304//!
305//! // Pack all settings (incl. salts + KDF params) into a self-contained header;
306//! // store it in the ciphertext — salts and KDF params are not secret.
307//! let header = pack(TimePrecision::Minute, TimeFormat::Hour24,
308//!                   &TimeLockCadence::None, salts, kdf);
309//!
310//! // Decryption side — load header from ciphertext; call at 14:30 local time.
311//! // params = Some(header) → _now (decryption) path
312//! let dec_key = timelock(
313//!     None, None, None, None, None, None,
314//!     Some(header),
315//! ).unwrap();
316//! // enc_key.as_bytes() == dec_key.as_bytes() when called at 14:30 local time
317//! ```
318//!
319//! ---
320//!
321//! ## Backend deps
322//!
323//! The `backend-deps` feature adds a `backend_deps` sub-module to every active
324//! module.  Each `backend_deps` module re-exports with `pub use` every
325//! third-party crate that its parent module uses internally.
326//!
327//! This lets downstream crates access those dependencies without declaring them
328//! separately in their own `Cargo.toml`.
329//!
330//! | Module | Path | Re-exports |
331//! |---|---|---|
332//! | serialization | [`serialization::backend_deps`] | `bincode`, `base64` |
333//! | socket (server) | [`socket::backend_deps`] | `bincode`, `base64`, `serde`, `tokio`, `log`, `bytes`, `serde_urlencoded`, `warp` |
334//! | socket (client) | [`socket::backend_deps`] | `bincode`, `base64`, `serde`, `tokio`, `log`, `reqwest` |
335//! | location | [`location::backend_deps`] | `tokio`, `serde`, `webbrowser`, `rand` |
336//! | encryption (timelock) | [`encryption::timelock::backend_deps`] | `argon2`, `scrypt`, `zeroize`, `chrono`, `rand`; `tokio` (async variants only) |
337//!
338//! `backend-deps` on its own (without any other feature) compiles but exposes
339//! nothing — the re-exports inside each `backend_deps` module are individually
340//! gated on their parent feature.
341
342#[cfg(any(feature = "socket", feature = "socket-server", feature = "socket-client"))]
343pub mod socket;
344
345#[cfg(any(feature = "location", feature = "location-native"))]
346pub mod location;
347
348#[cfg(feature = "serialization")]
349pub mod serialization;
350
351#[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"))]
352pub mod encryption;