nwep/types.rs
1use crate::ffi;
2use std::fmt;
3
4/// `Tstamp` is a nanosecond-resolution wall-clock timestamp represented as a
5/// `u64` count of nanoseconds since the Unix epoch.
6///
7/// This type is used wherever the C library returns or accepts an absolute time
8/// value (e.g., log entry timestamps, checkpoint creation times).
9pub type Tstamp = u64;
10
11/// `Duration` is a nanosecond-resolution time interval represented as a `u64`.
12///
13/// Construct values using the multiplier constants [`NANOSECONDS`],
14/// [`MICROSECONDS`], [`MILLISECONDS`], and [`SECONDS`]:
15///
16/// ```rust
17/// use nwep::types::{Duration, SECONDS};
18///
19/// let five_seconds: Duration = 5 * SECONDS;
20/// ```
21pub type Duration = u64;
22
23/// `NANOSECONDS` is the multiplier for nanosecond durations (value: `1`).
24///
25/// Multiplying by this constant is a no-op but makes intent explicit.
26pub const NANOSECONDS: Duration = 1;
27
28/// `MICROSECONDS` is the multiplier for microsecond durations (value: `1_000`
29/// nanoseconds).
30pub const MICROSECONDS: Duration = 1_000;
31
32/// `MILLISECONDS` is the multiplier for millisecond durations (value:
33/// `1_000_000` nanoseconds).
34pub const MILLISECONDS: Duration = 1_000_000;
35
36/// `SECONDS` is the multiplier for second durations (value: `1_000_000_000`
37/// nanoseconds).
38pub const SECONDS: Duration = 1_000_000_000;
39
40/// `PROTO_VER` is the NWEP protocol version string sent in the `:version`
41/// pseudo-header of every `connect` request.
42pub const PROTO_VER: &str = "WEB/1";
43
44/// `ALPN` is the QUIC Application-Layer Protocol Negotiation token used during
45/// the TLS handshake to identify NWEP connections.
46pub const ALPN: &str = "WEB/1";
47
48/// `ALPN_LEN` is the byte length of the [`ALPN`] string (5).
49pub const ALPN_LEN: usize = 5;
50
51/// `DEFAULT_PORT` is the default UDP port on which NWEP servers listen
52/// (6937).
53pub const DEFAULT_PORT: u16 = 6937;
54
55/// `DEFAULT_MAX_MESSAGE_SIZE` is the default maximum allowed size of a single
56/// NWEP message body in bytes (24 MiB). This limit is negotiated during the
57/// `connect` handshake via the `max-message-size` header and may be lowered by
58/// either peer.
59pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 25_165_824;
60
61/// `MAX_HEADERS` is the maximum number of headers permitted in a single NWEP
62/// request or response message. Exceeding this limit produces
63/// [`crate::error::ERR_PROTO_TOO_MANY_HEADERS`].
64pub const MAX_HEADERS: usize = 128;
65
66/// `MAX_HEADER_SIZE` is the maximum allowed byte length of a single header
67/// name or value. Exceeding this limit produces
68/// [`crate::error::ERR_PROTO_HEADER_TOO_LARGE`].
69pub const MAX_HEADER_SIZE: usize = 8192;
70
71/// `DEFAULT_MAX_STREAMS` is the default maximum number of concurrent QUIC
72/// streams per connection (100). This is the value advertised in the
73/// `max-streams` header during the `connect` handshake.
74pub const DEFAULT_MAX_STREAMS: u32 = 100;
75
76/// `DEFAULT_TIMEOUT` is the default request timeout (30 seconds expressed in
77/// nanoseconds). Operations that do not complete within this window return
78/// [`crate::error::ERR_NETWORK_TIMEOUT`].
79pub const DEFAULT_TIMEOUT: Duration = 30 * SECONDS;
80
81/// `ED25519_PUBKEY_LEN` is the byte length of an Ed25519 public key (32).
82pub const ED25519_PUBKEY_LEN: usize = 32;
83
84/// `ED25519_PRIVKEY_LEN` is the byte length of an Ed25519 private key seed
85/// (32). The full expanded private key is derived from this seed.
86pub const ED25519_PRIVKEY_LEN: usize = 32;
87
88/// `ED25519_SIG_LEN` is the byte length of an Ed25519 signature (64).
89pub const ED25519_SIG_LEN: usize = 64;
90
91/// `NODEID_LEN` is the byte length of an NWEP node ID (32). A node ID is
92/// derived by hashing an Ed25519 public key.
93pub const NODEID_LEN: usize = 32;
94
95/// `CHALLENGE_LEN` is the byte length of a random challenge nonce used in
96/// mutual authentication (32 bytes).
97pub const CHALLENGE_LEN: usize = 32;
98
99/// `REQUEST_ID_LEN` is the byte length of a request ID (16 bytes). Request IDs
100/// are cryptographically random and appear in the `request-id` header.
101pub const REQUEST_ID_LEN: usize = 16;
102
103/// `TRACE_ID_LEN` is the byte length of a distributed trace ID (16 bytes).
104/// Trace IDs propagate across hops in the `trace-id` header.
105pub const TRACE_ID_LEN: usize = 16;
106
107/// `NOTIFY_ID_LEN` is the byte length of a notification ID (16 bytes). Notify
108/// IDs uniquely identify server-push notification messages.
109pub const NOTIFY_ID_LEN: usize = 16;
110
111/// `BASE58_ADDR_LEN` is the maximum byte length of a Base58-encoded NWEP node
112/// address string (66 bytes including the null terminator).
113pub const BASE58_ADDR_LEN: usize = 66;
114
115/// `URL_MAX_LEN` is the maximum allowed byte length of a URL or `:path` value
116/// in an NWEP request (512 bytes).
117pub const URL_MAX_LEN: usize = 512;
118
119/// `BLS_PUBKEY_LEN` is the byte length of a BLS12-381 public key in compressed
120/// G2 form (48 bytes).
121pub const BLS_PUBKEY_LEN: usize = 48;
122
123/// `BLS_PRIVKEY_LEN` is the byte length of a BLS12-381 private key scalar (32
124/// bytes).
125pub const BLS_PRIVKEY_LEN: usize = 32;
126
127/// `BLS_SIG_LEN` is the byte length of a BLS12-381 signature in compressed G1
128/// form (96 bytes).
129pub const BLS_SIG_LEN: usize = 96;
130
131/// `SHAMIR_MAX_SHARES` is the maximum number of Shamir secret-sharing shares
132/// that can be generated for a single secret (255).
133pub const SHAMIR_MAX_SHARES: usize = 255;
134
135/// `SHAMIR_MIN_THRESHOLD` is the minimum number of shares required to
136/// reconstruct a secret (2). A threshold of 1 would be equivalent to no
137/// sharing at all.
138pub const SHAMIR_MIN_THRESHOLD: usize = 2;
139
140/// `KEY_OVERLAP_SECONDS` is the number of seconds during which both the old and
141/// new keypair remain valid following a key rotation (300 seconds / 5 minutes).
142/// This overlap window allows in-flight connections using the old key to
143/// complete gracefully.
144pub const KEY_OVERLAP_SECONDS: u64 = 300;
145
146/// `MAX_ACTIVE_KEYS` is the maximum number of simultaneously active keypairs
147/// for a single identity during a rotation overlap period (2).
148pub const MAX_ACTIVE_KEYS: usize = 2;
149
150/// `MAX_ANCHORS` is the maximum number of BLS anchor nodes that can be
151/// registered in the trust system (32).
152pub const MAX_ANCHORS: usize = 32;
153
154/// `DEFAULT_ANCHOR_THRESHOLD` is the default BLS quorum threshold: at least
155/// this many anchor signatures are required to ratify a checkpoint (5).
156pub const DEFAULT_ANCHOR_THRESHOLD: usize = 5;
157
158/// `MAX_CHECKPOINTS` is the maximum number of historical checkpoints retained
159/// in memory or on disk (168, i.e., one week of hourly checkpoints).
160pub const MAX_CHECKPOINTS: usize = 168;
161
162/// `LOG_ENTRY_MAX_SIZE` is the maximum serialized byte size of a single trust
163/// log entry (256 bytes).
164pub const LOG_ENTRY_MAX_SIZE: usize = 256;
165
166/// `MERKLE_PROOF_MAX_DEPTH` is the maximum depth of a Merkle inclusion proof
167/// path (64 levels), sufficient for a tree with up to 2^64 leaves.
168pub const MERKLE_PROOF_MAX_DEPTH: usize = 64;
169
170/// `CACHE_DEFAULT_CAPACITY` is the default maximum number of entries in an
171/// in-memory identity or result cache (10,000 entries).
172pub const CACHE_DEFAULT_CAPACITY: usize = 10_000;
173
174/// `POOL_MAX_SERVERS` is the maximum number of server endpoints that can be
175/// held in a single client connection pool (32).
176pub const POOL_MAX_SERVERS: usize = 32;
177
178/// `POOL_HEALTH_CHECK_FAILURES` is the number of consecutive health-check
179/// failures before a server is removed from the active pool (3).
180pub const POOL_HEALTH_CHECK_FAILURES: usize = 3;
181
182/// `FRAME_HEADER_SIZE` is the byte size of the fixed NWEP frame header that
183/// precedes every message on the wire (4 bytes: a big-endian `u32` length
184/// prefix).
185pub const FRAME_HEADER_SIZE: usize = 4;
186
187/// `MSG_TYPE_SIZE` is the byte size of the message-type discriminant within an
188/// NWEP frame (1 byte).
189pub const MSG_TYPE_SIZE: usize = 1;
190
191/// `STALENESS_WARNING_NS` is the age beyond which an identity record triggers a
192/// staleness warning (1 hour expressed in nanoseconds).
193pub const STALENESS_WARNING_NS: Duration = 3600 * SECONDS;
194
195/// `STALENESS_REJECT_NS` is the age beyond which an identity record is rejected
196/// outright as too stale to trust (24 hours expressed in nanoseconds).
197pub const STALENESS_REJECT_NS: Duration = 86400 * SECONDS;
198
199/// `IDENTITY_CACHE_TTL` is the time-to-live for cached identity records (1 hour
200/// in nanoseconds). Entries older than this are re-fetched from the log server.
201pub const IDENTITY_CACHE_TTL: Duration = 3600 * SECONDS;
202
203/// `DEFAULT_EPOCH_INTERVAL` is the default interval between trust-log epochs
204/// (1 hour in nanoseconds). At each epoch boundary the anchor nodes produce a
205/// new BLS checkpoint.
206pub const DEFAULT_EPOCH_INTERVAL: Duration = 3600 * SECONDS;
207
208/// `CHECKPOINT_DST` is the domain-separation tag prepended to data before it is
209/// signed into a BLS checkpoint, preventing cross-protocol signature reuse.
210pub const CHECKPOINT_DST: &str = "WEB/1-CHECKPOINT";
211
212/// `MSG_REQUEST` is the wire-format type byte for a client request message
213/// (0x00).
214pub const MSG_REQUEST: u8 = 0;
215
216/// `MSG_RESPONSE` is the wire-format type byte for a server response message
217/// (0x01).
218pub const MSG_RESPONSE: u8 = 1;
219
220/// `MSG_STREAM` is the wire-format type byte for a bidirectional streaming data
221/// chunk (0x02).
222pub const MSG_STREAM: u8 = 2;
223
224/// `MSG_NOTIFY` is the wire-format type byte for a server-initiated push
225/// notification (0x03).
226pub const MSG_NOTIFY: u8 = 3;
227
228/// `NodeId` is the 32-byte identifier that uniquely names a node in the NWEP
229/// network.
230///
231/// A node ID is derived by applying a cryptographic hash to the node's Ed25519
232/// public key, so it is bound to the key material and cannot be forged without
233/// possession of the private key.
234///
235/// Node IDs are displayed as lowercase hex strings (64 characters). The
236/// all-zero value is reserved and considered invalid; use [`NodeId::is_zero`]
237/// to test for it.
238///
239/// # Example
240///
241/// ```rust
242/// use nwep::types::NodeId;
243///
244/// let id = NodeId([0u8; 32]);
245/// assert!(id.is_zero());
246/// println!("{id}"); // 0000...0000
247/// ```
248#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
249pub struct NodeId(pub [u8; NODEID_LEN]);
250
251impl NodeId {
252 /// `is_zero` returns `true` if all bytes of this node ID are zero.
253 ///
254 /// An all-zero node ID is the uninitialized/invalid sentinel value. Use
255 /// this check to guard against unset node IDs before sending them over the
256 /// wire.
257 pub fn is_zero(&self) -> bool {
258 self.0.iter().all(|&b| b == 0)
259 }
260
261 /// `as_bytes` returns a reference to the raw 32-byte node ID array.
262 ///
263 /// Useful when the bytes must be passed to a C API or hashed directly
264 /// without copying.
265 pub fn as_bytes(&self) -> &[u8; NODEID_LEN] {
266 &self.0
267 }
268}
269
270impl fmt::Display for NodeId {
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 for b in &self.0 {
273 write!(f, "{:02x}", b)?;
274 }
275 Ok(())
276 }
277}
278
279impl fmt::Debug for NodeId {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 write!(f, "NodeId({})", self)
282 }
283}
284
285impl From<ffi::nwep_nodeid> for NodeId {
286 fn from(n: ffi::nwep_nodeid) -> Self {
287 NodeId(n.data)
288 }
289}
290
291impl From<NodeId> for ffi::nwep_nodeid {
292 fn from(n: NodeId) -> Self {
293 ffi::nwep_nodeid { data: n.0 }
294 }
295}
296
297/// `MerkleHash` is a 32-byte SHA-256 digest used as a node value in the
298/// distributed trust log's Merkle tree.
299///
300/// Interior nodes are computed by hashing the concatenation of their left and
301/// right children. Leaf nodes are computed by hashing a serialized log entry.
302/// The root hash of the Merkle tree is included in BLS checkpoints so that any
303/// entry can be proven to belong to the log.
304///
305/// Like [`NodeId`], `MerkleHash` displays as a lowercase 64-character hex
306/// string.
307#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
308pub struct MerkleHash(pub [u8; 32]);
309
310impl fmt::Display for MerkleHash {
311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 for b in &self.0 {
313 write!(f, "{:02x}", b)?;
314 }
315 Ok(())
316 }
317}
318
319impl fmt::Debug for MerkleHash {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 write!(f, "MerkleHash({})", self)
322 }
323}
324
325impl From<ffi::nwep_merkle_hash> for MerkleHash {
326 fn from(h: ffi::nwep_merkle_hash) -> Self {
327 MerkleHash(h.data)
328 }
329}
330
331impl From<MerkleHash> for ffi::nwep_merkle_hash {
332 fn from(h: MerkleHash) -> Self {
333 ffi::nwep_merkle_hash { data: h.0 }
334 }
335}
336
337/// `Header` is a single HTTP-style name/value metadata pair attached to an
338/// NWEP request or response.
339///
340/// Header names follow the same conventions as HTTP/2 pseudo-headers and
341/// lowercase field names. Pseudo-headers (`:method`, `:path`, `:status`,
342/// `:version`) must appear before regular headers. See the constants in
343/// [`crate::protocol`] for the well-known header name strings.
344///
345/// Both `name` and `value` are arbitrary UTF-8 strings. The C library enforces
346/// a maximum byte length of [`MAX_HEADER_SIZE`] for each field and a maximum
347/// count of [`MAX_HEADERS`] per message.
348///
349/// # Example
350///
351/// ```rust
352/// use nwep::types::Header;
353/// use nwep::protocol::HDR_TRACE_ID;
354///
355/// let h = Header::new(HDR_TRACE_ID, "deadbeef...");
356/// assert_eq!(h.name, "trace-id");
357/// ```
358#[derive(Clone, Debug)]
359pub struct Header {
360 /// `name` is the header field name (e.g., `"content-type"`, `":method"`).
361 pub name: String,
362 /// `value` is the header field value (e.g., `"application/octet-stream"`,
363 /// `"read"`).
364 pub value: String,
365}
366
367impl Header {
368 /// `new` constructs a [`Header`] from any types that convert into
369 /// `String`.
370 ///
371 /// # Example
372 ///
373 /// ```rust
374 /// use nwep::types::Header;
375 ///
376 /// let h = Header::new("content-type", "application/json");
377 /// ```
378 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
379 Header {
380 name: name.into(),
381 value: value.into(),
382 }
383 }
384}
385
386/// `Identity` pairs an Ed25519 public key with its derived [`NodeId`],
387/// representing a fully authenticated peer identity.
388///
389/// The `pubkey` field holds the raw 32-byte Ed25519 public key bytes. The
390/// `node_id` field holds the node ID derived from that key. Together they allow
391/// the caller to verify both the cryptographic material and the stable
392/// network-layer identifier of a remote peer.
393///
394/// `Identity` values are returned by the server's `on_connect` callback and by
395/// client-side peer inspection APIs.
396///
397/// # Example
398///
399/// ```rust
400/// use nwep::types::Identity;
401///
402/// let id = Identity::default();
403/// assert!(id.node_id.is_zero());
404/// ```
405#[derive(Clone, Debug, Default)]
406pub struct Identity {
407 /// `pubkey` is the raw 32-byte Ed25519 public key of the peer.
408 pub pubkey: [u8; ED25519_PUBKEY_LEN],
409 /// `node_id` is the 32-byte node ID derived from [`Identity::pubkey`].
410 pub node_id: NodeId,
411}
412
413impl From<ffi::nwep_identity> for Identity {
414 fn from(i: ffi::nwep_identity) -> Self {
415 Identity {
416 pubkey: i.pubkey,
417 node_id: NodeId(i.nodeid.data),
418 }
419 }
420}