Skip to main content

sendmer/core/
options.rs

1//! 发送和接收选项定义。
2//!
3//! 本文件定义:SendOptions, ReceiveOptions, RelayModeOption, AddrInfoOptions。
4
5use iroh::RelayUrl;
6use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
7
8#[derive(Debug, Default)]
9pub struct SendOptions {
10    pub relay_mode: RelayModeOption,
11    pub ticket_type: AddrInfoOptions,
12    pub magic_ipv4_addr: Option<SocketAddrV4>,
13    pub magic_ipv6_addr: Option<SocketAddrV6>,
14}
15
16#[derive(Debug, Clone, Copy)]
17pub struct ReceiveRetryPolicy {
18    pub size_fetch_retry_limit: u32,
19    pub size_fetch_chunk_size: u64,
20    pub size_fetch_backoff_ms: u64,
21}
22
23impl Default for ReceiveRetryPolicy {
24    fn default() -> Self {
25        Self {
26            size_fetch_retry_limit: 3,
27            size_fetch_chunk_size: 1024 * 1024 * 32,
28            size_fetch_backoff_ms: 250,
29        }
30    }
31}
32
33impl ReceiveRetryPolicy {
34    /// Validate the size-fetch settings before network and temporary-store setup begins.
35    pub fn validate(self) -> anyhow::Result<()> {
36        anyhow::ensure!(
37            self.size_fetch_retry_limit > 0,
38            "size-fetch retry limit must be greater than zero"
39        );
40        anyhow::ensure!(
41            self.size_fetch_chunk_size > 0,
42            "size-fetch chunk size must be greater than zero"
43        );
44        Ok(())
45    }
46}
47
48#[derive(Debug, Default)]
49pub struct ReceiveOptions {
50    pub output_dir: Option<std::path::PathBuf>,
51    pub relay_mode: RelayModeOption,
52    pub magic_ipv4_addr: Option<SocketAddrV4>,
53    pub magic_ipv6_addr: Option<SocketAddrV6>,
54    pub retry_policy: ReceiveRetryPolicy,
55}
56
57pub trait EndpointOptions: BindAddressOptions {
58    fn relay_mode(&self) -> RelayModeOption;
59}
60
61pub trait BindAddressOptions {
62    fn magic_ipv4_addr(&self) -> Option<SocketAddrV4>;
63    fn magic_ipv6_addr(&self) -> Option<SocketAddrV6>;
64}
65
66impl EndpointOptions for SendOptions {
67    fn relay_mode(&self) -> RelayModeOption {
68        self.relay_mode.clone()
69    }
70}
71
72impl BindAddressOptions for SendOptions {
73    fn magic_ipv4_addr(&self) -> Option<SocketAddrV4> {
74        self.magic_ipv4_addr
75    }
76
77    fn magic_ipv6_addr(&self) -> Option<SocketAddrV6> {
78        self.magic_ipv6_addr
79    }
80}
81
82impl EndpointOptions for ReceiveOptions {
83    fn relay_mode(&self) -> RelayModeOption {
84        self.relay_mode.clone()
85    }
86}
87
88impl BindAddressOptions for ReceiveOptions {
89    fn magic_ipv4_addr(&self) -> Option<SocketAddrV4> {
90        self.magic_ipv4_addr
91    }
92
93    fn magic_ipv6_addr(&self) -> Option<SocketAddrV6> {
94        self.magic_ipv6_addr
95    }
96}
97
98pub fn apply_bind_addrs<T: BindAddressOptions>(
99    mut builder: iroh::endpoint::Builder,
100    options: &T,
101) -> anyhow::Result<iroh::endpoint::Builder> {
102    if let Some(addr) = options.magic_ipv4_addr() {
103        builder = builder.bind_addr(SocketAddr::V4(addr))?;
104    }
105    if let Some(addr) = options.magic_ipv6_addr() {
106        builder = builder.bind_addr(SocketAddr::V6(addr))?;
107    }
108    Ok(builder)
109}
110
111#[derive(Clone, Debug, Default)]
112pub enum RelayModeOption {
113    Disabled,
114    #[default]
115    Default,
116    Custom(RelayUrl),
117}
118
119impl From<RelayModeOption> for iroh::RelayMode {
120    fn from(value: RelayModeOption) -> Self {
121        match value {
122            RelayModeOption::Disabled => Self::Disabled,
123            RelayModeOption::Default => Self::Default,
124            RelayModeOption::Custom(url) => Self::Custom(url.into()),
125        }
126    }
127}
128
129impl std::str::FromStr for RelayModeOption {
130    type Err = anyhow::Error;
131
132    fn from_str(s: &str) -> Result<Self, Self::Err> {
133        match s {
134            "disabled" => Ok(Self::Disabled),
135            "default" => Ok(Self::Default),
136            _ => Ok(Self::Custom(RelayUrl::from_str(s)?)),
137        }
138    }
139}
140
141impl std::fmt::Display for RelayModeOption {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        match self {
144            Self::Disabled => f.write_str("disabled"),
145            Self::Default => f.write_str("default"),
146            Self::Custom(url) => url.fmt(f),
147        }
148    }
149}
150
151#[derive(
152    Copy,
153    Clone,
154    PartialEq,
155    Eq,
156    Default,
157    Debug,
158    derive_more::Display,
159    derive_more::FromStr,
160    serde::Serialize,
161    serde::Deserialize,
162)]
163pub enum AddrInfoOptions {
164    #[default]
165    Id,
166    RelayAndAddresses,
167    Relay,
168    Addresses,
169}
170pub fn apply_options(addr: &mut iroh::EndpointAddr, opts: AddrInfoOptions) {
171    use iroh::TransportAddr;
172    match opts {
173        AddrInfoOptions::Id => {
174            addr.addrs = Default::default();
175        }
176        AddrInfoOptions::RelayAndAddresses => {
177            // nothing to do
178        }
179        AddrInfoOptions::Relay => {
180            addr.addrs = addr
181                .addrs
182                .iter()
183                .filter(|addr| matches!(addr, TransportAddr::Relay(_)))
184                .cloned()
185                .collect();
186        }
187        AddrInfoOptions::Addresses => {
188            addr.addrs = addr
189                .addrs
190                .iter()
191                .filter(|addr| matches!(addr, TransportAddr::Ip(_)))
192                .cloned()
193                .collect();
194        }
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::ReceiveRetryPolicy;
201
202    #[test]
203    fn receive_retry_policy_defaults_match_receiver_expectations() {
204        let policy = ReceiveRetryPolicy::default();
205        assert_eq!(policy.size_fetch_retry_limit, 3);
206        assert_eq!(policy.size_fetch_chunk_size, 1024 * 1024 * 32);
207        assert_eq!(policy.size_fetch_backoff_ms, 250);
208    }
209
210    #[test]
211    fn receive_retry_policy_rejects_zero_retry_limit() {
212        let policy = ReceiveRetryPolicy {
213            size_fetch_retry_limit: 0,
214            ..Default::default()
215        };
216
217        let error = policy
218            .validate()
219            .expect_err("zero retries should be rejected");
220        assert!(error.to_string().contains("retry limit"));
221    }
222
223    #[test]
224    fn receive_retry_policy_rejects_zero_chunk_size() {
225        let policy = ReceiveRetryPolicy {
226            size_fetch_chunk_size: 0,
227            ..Default::default()
228        };
229
230        let error = policy
231            .validate()
232            .expect_err("zero chunk size should be rejected");
233        assert!(error.to_string().contains("chunk size"));
234    }
235
236    #[test]
237    fn receive_retry_policy_allows_zero_backoff() {
238        let policy = ReceiveRetryPolicy {
239            size_fetch_backoff_ms: 0,
240            ..Default::default()
241        };
242
243        policy
244            .validate()
245            .expect("zero backoff should allow immediate retries");
246    }
247}