Skip to main content

deaddrop_core/
caps.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
4pub struct CapabilityDoc {
5    pub protocol: u16,
6    pub max_frame: u32,
7    pub max_drop: u64,
8    pub chunking: Vec<String>,
9    pub compression: Vec<String>,
10    pub inventory: Vec<String>,
11    pub routing: Vec<String>,
12    pub receipts: bool,
13    pub extensions: Vec<String>,
14    pub relay_capacity: RelayCapacity,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
18#[serde(rename_all = "snake_case")]
19pub enum RelayCapacity {
20    #[default]
21    Full,
22    Low,
23    None,
24}
25
26impl CapabilityDoc {
27    pub fn local_v2() -> Self {
28        Self {
29            protocol: crate::PROTOCOL_VERSION,
30            max_frame: crate::limits::MAX_FRAME_SIZE,
31            max_drop: crate::limits::MAX_DROP_SIZE,
32            chunking: vec!["fixed".into(), "cdc-v1".into(), "erasure-xor-v1".into()],
33            compression: vec!["none".into(), "zstd".into()],
34            inventory: vec!["sorted-v1".into(), "bloom-v1".into()],
35            routing: vec![
36                "direct".into(),
37                "epidemic".into(),
38                "spray".into(),
39                "encounter".into(),
40                "adaptive".into(),
41            ],
42            receipts: true,
43            extensions: vec![
44                "dd.group/1".into(),
45                "sealed-drop-v1".into(),
46                "erasure-v1".into(),
47                "receipt-v2".into(),
48                "spaces-v1".into(),
49                "quic-v1".into(),
50                "lan-v1".into(),
51            ],
52            relay_capacity: RelayCapacity::Full,
53        }
54    }
55
56    /// Negotiate the intersection. Unknown optional capabilities are ignored.
57    pub fn negotiate(&self, other: &Self) -> crate::Result<NegotiatedCaps> {
58        if other.protocol != self.protocol {
59            return Err(crate::DdError::protocol(
60                crate::ErrorCode::Ddp1002UnsupportedVersion,
61                format!("peer speaks DDP/{}", other.protocol),
62            ));
63        }
64        Ok(NegotiatedCaps {
65            protocol: self.protocol,
66            max_frame: self.max_frame.min(other.max_frame),
67            max_drop: self.max_drop.min(other.max_drop),
68            chunking: intersect(&self.chunking, &other.chunking),
69            compression: intersect(&self.compression, &other.compression),
70            inventory: intersect(&self.inventory, &other.inventory),
71            routing: intersect(&self.routing, &other.routing),
72            receipts: self.receipts && other.receipts,
73            peer_relay: other.relay_capacity,
74        })
75    }
76}
77
78fn intersect(a: &[String], b: &[String]) -> Vec<String> {
79    a.iter().filter(|x| b.contains(x)).cloned().collect()
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct NegotiatedCaps {
84    pub protocol: u16,
85    pub max_frame: u32,
86    pub max_drop: u64,
87    pub chunking: Vec<String>,
88    pub compression: Vec<String>,
89    pub inventory: Vec<String>,
90    pub routing: Vec<String>,
91    pub receipts: bool,
92    pub peer_relay: RelayCapacity,
93}
94
95impl NegotiatedCaps {
96    pub fn prefer_inventory(&self) -> &'static str {
97        if self.inventory.iter().any(|s| s == "bloom-v1") {
98            "bloom-v1"
99        } else {
100            "sorted-v1"
101        }
102    }
103}