Skip to main content

decdn_protocol/
lib.rs

1//! The deCDN wire protocol: the ALPN identifiers, the message types each ALPN
2//! carries, and the framing that puts them on a QUIC stream.
3//!
4//! deCDN speaks three ALPNs ([ADR 005]):
5//!
6//! | ALPN | Constant | Messages | Purpose |
7//! |---|---|---|---|
8//! | `cdn/probe/v1` | [`ALPN_PROBE`] | [`ProbeMessage`] | Latency and availability probing |
9//! | `cdn/client/v1` | [`ALPN_CLIENT`] | [`ClientMessage`] | All paid delivery, client to node and node to node |
10//! | `cdn/dht/v1` | [`ALPN_DHT`] | [`DhtMessage`] | Content discovery over a Kademlia DHT ([ADR 022]) |
11//!
12//! # Modules
13//!
14//! | Module | Contents |
15//! |---|---|
16//! | [`framing`] | The length-prefixed frame, [`encode_message`] / [`decode_message`], and the async [`read_frame`] / [`write_frame`] |
17//! | [`message`] | `cdn/probe/v1`: [`ProbeRequest`], [`ProbeResponse`] |
18//! | [`client`] | `cdn/client/v1`: [`StreamRequest`], [`StreamResponse`], [`ChunkData`], the wire [`Voucher`], [`StreamError`] |
19//! | [`dht`] | `cdn/dht/v1`: store and find requests, provider records, closer-node lists |
20//! | [`coverage`] | The block coverage bitmap a partial holder advertises |
21//! | [`identity`] | The 32-byte [`NodeId`] and [`ContentHash`] newtypes |
22//! | [`region`] | ISO 3166-1 alpha-2 [`Region`] codes |
23//!
24//! # Frames and extensions
25//!
26//! Every message is a postcard encoding behind a varint length prefix, capped at
27//! [`MAX_MESSAGE_SIZE`] ([ADR 013 § Wire Framing]). A signed message has a frozen
28//! base: the fields its signature covers. Unsigned fields travel as trailing
29//! bytes after the base. [`decode_message`] returns them as a remainder, and the
30//! `parse_*_ext` functions read them ([ADR 013 § Three Tiers of Evolution]).
31//!
32//! ```
33//! use decdn_protocol::{ProbeMessage, ProbeRequest, decode_message, encode_message};
34//!
35//! let request = ProbeMessage::Request(ProbeRequest {
36//!     hash: [7; 32],
37//!     timestamp_us: 1_700_000_000_000_000,
38//! });
39//! let frame = encode_message(&request)?;
40//! let (decoded, remainder) = decode_message::<ProbeMessage>(&frame)?;
41//! assert_eq!(decoded, request);
42//! assert!(remainder.is_empty());
43//! # Ok::<(), decdn_protocol::FrameError>(())
44//! ```
45//!
46//! This crate holds no cryptography. The signatures carried in these messages
47//! are made and checked by [`decdn-incentive`]. It has few dependencies, so
48//! code that only speaks the protocol links no blob store, Ethereum client, or
49//! cloud SDK.
50//!
51//! [ADR 005]: https://github.com/decdn/decdn/blob/main/adr/005-protocol.md
52//! [ADR 022]: https://github.com/decdn/decdn/blob/main/adr/022-content-discovery.md
53//! [ADR 013 § Wire Framing]: https://github.com/decdn/decdn/blob/main/adr/013-schema-evolution.md#wire-framing
54//! [ADR 013 § Three Tiers of Evolution]: https://github.com/decdn/decdn/blob/main/adr/013-schema-evolution.md#three-tiers-of-evolution
55//! [`decdn-incentive`]: https://docs.rs/decdn-incentive
56
57pub mod client;
58pub mod coverage;
59pub mod dht;
60pub mod framing;
61pub mod identity;
62pub mod message;
63pub mod region;
64
65pub use client::{
66    BINDING_SIG_LEN, CHUNK_BYTES, ChunkData, ChunkPreimage, ClientBinding, ClientMessage,
67    MAX_CHAIN_LENGTH, MB_BYTES, StreamError, StreamRequest, StreamRequestExt, StreamResponse,
68    StreamResponseBody, StreamResponseExt, VOUCHER_SIG_LEN, Voucher, VoucherRejectReason,
69    encode_chunk_frame, encode_stream_request, encode_stream_response, parse_stream_request_ext,
70    parse_stream_response_ext,
71};
72pub use coverage::{Coverage, DISCOVERY_BLOCK_BYTES, discovery_block_bytes, num_blocks};
73#[cfg(feature = "test-support")]
74pub use coverage::{TestBlockSizeGuard, override_discovery_block_bytes_for_test};
75pub use dht::{
76    BatchStoreAck, BatchStoreRequest, CloserNodes, CloserNodesError, DhtMessage, FindNodeRequest,
77    FindNodeResponse, FindValueRequest, FindValueResponse, FindValueResponseExt,
78    MAX_BATCH_STORE_HASHES, MAX_CLOSER_NODES, MAX_PROVIDERS_PER_HASH, StoreAck, StoreRequest,
79    StoreRequestExt, encode_find_value_response, encode_store_request,
80    parse_find_value_response_ext, parse_store_request_ext,
81};
82pub use framing::{
83    FrameError, MAX_MESSAGE_SIZE, TopLevelEnum, decode_message, encode_message, is_unknown_variant,
84    read_frame, write_frame,
85};
86pub use identity::{ContentHash, ID_LEN, NodeId};
87pub use message::{
88    MAX_RATE_PER_MB, MessageValidationError, ProbeMessage, ProbeRequest, ProbeResponse,
89    ProbeResponseBody, ProbeResponseExt, SLASH_SIG_LEN, encode_probe_response,
90    parse_probe_response_ext,
91};
92pub use region::{InvalidRegion, Region, is_valid_region};
93
94/// ALPN protocol identifier for latency and availability probing.
95pub const ALPN_PROBE: &[u8] = b"cdn/probe/v1";
96
97/// ALPN protocol identifier for paid blob delivery (client-to-node and node-to-node).
98pub const ALPN_CLIENT: &[u8] = b"cdn/client/v1";
99
100/// ALPN protocol identifier for Kademlia-based content discovery (ADR 022).
101pub const ALPN_DHT: &[u8] = b"cdn/dht/v1";
102
103/// Connection rejected by the per-source or global rate limiter (ADR 013 §0x10).
104///
105/// Unlike `0x01`–`0x03`, this code is delivered via `CONNECTION_CLOSE` rather
106/// than `RESET_STREAM` because the rejection happens before any application
107/// stream is opened. The close-frame reason bytes carry a short layer label
108/// (e.g. `global-full`, `per-source`) so the peer can pick an appropriate
109/// backoff. Peers that receive this code SHOULD back off before
110/// reconnecting; they MUST NOT treat it as a protocol error (the server is
111/// functioning normally — the client is overloading it). The requester side
112/// (`decdn-client`) types this code as `UpstreamRateLimited` so the pull
113/// orchestrator suppresses the peer briefly instead of scoring it unreachable.
114pub const APP_ERR_RATE_LIMITED: u32 = 0x10;