1use crate::config::types::OutboundLinkConfig;
7use crate::link_carrier::Carrier;
8use std::time::{Duration, Instant};
9
10pub mod carriers;
11pub mod network;
12pub use carriers::Stop;
13
14#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
16#[serde(default, deny_unknown_fields)]
17pub struct ChoiceConfig {
18 pub peers: Option<crate::config::peers::PeersConfig>,
20 pub crossing_deadline_ms: u64,
24 pub broker_address: Option<String>,
26 pub broker_echo: Option<std::net::SocketAddr>,
28 pub udp_listen: std::net::SocketAddr,
30 pub relay_holder: Option<String>,
32 pub relay_address: Option<String>,
34}
35
36impl Default for ChoiceConfig {
37 fn default() -> Self {
38 Self {
39 peers: None,
40 crossing_deadline_ms: 2000,
41 broker_address: None,
42 broker_echo: None,
43 udp_listen: std::net::SocketAddr::from(([0, 0, 0, 0], 0)),
44 relay_holder: None,
45 relay_address: None,
46 }
47 }
48}
49
50#[derive(Clone)]
52pub struct Packet {
53 pub subject: String,
54 pub counter: u64,
55 pub bytes: Vec<u8>,
56 pub deadline: Option<Deadline>,
57}
58
59impl std::fmt::Debug for Packet {
60 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 out.debug_struct("Packet")
62 .field("subject", &self.subject)
63 .field("counter", &self.counter)
64 .field("bytes", &self.bytes.len())
65 .field("deadline", &self.deadline)
66 .finish()
67 }
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub struct Deadline {
73 expires: Instant,
74 budget: Duration,
75}
76
77impl Deadline {
78 pub fn after(budget: Duration) -> Result<Self, OpenError> {
81 let expires = Instant::now()
82 .checked_add(budget)
83 .ok_or_else(|| OpenError("crossing_deadline_out_of_range".to_owned()))?;
84 Ok(Self { expires, budget })
85 }
86 #[must_use]
87 pub const fn budget(self) -> Duration {
88 self.budget
89 }
90 #[must_use]
91 pub const fn expires(self) -> Instant {
92 self.expires
93 }
94}
95
96pub trait Path: Send + std::fmt::Debug {
98 fn carrier(&self) -> Carrier;
99 fn interrupt_handle(&self) -> Option<Stop> {
100 None
101 }
102 fn relay_device(&self) -> Option<String>;
103 fn life(&self) -> Option<crate::link::LinkLife> {
104 None
105 }
106 fn refuse_unsolicited(&self) -> Result<(), OpenError> {
109 Ok(())
110 }
111 fn direct_index(&self) -> Option<u32> {
113 None
114 }
115 fn prepare(&mut self, subject: &str, plain: &[u8]) -> Result<Packet, OpenError>;
118 fn transmit(&mut self, packet: &Packet) -> Result<(), OpenError>;
121 fn receipt(&mut self, counter: u64, bound: Option<Deadline>) -> Result<bool, OpenError>;
125}
126
127pub trait Backend: Send + std::fmt::Debug {
129 fn interrupt_handle(&self) -> Option<Stop> {
131 None
132 }
133 fn wire(
136 &mut self,
137 config: &OutboundLinkConfig,
138 address: &str,
139 ) -> Result<Box<dyn Path>, OpenError>;
140 fn lookup(&mut self, config: &OutboundLinkConfig) -> Result<Vec<String>, OpenError>;
143 fn punch(&mut self, config: &OutboundLinkConfig) -> Result<Box<dyn Path>, OpenError>;
146 fn relay(&mut self, config: &OutboundLinkConfig) -> Result<Box<dyn Path>, OpenError>;
149 fn repunch(
153 &mut self,
154 config: &OutboundLinkConfig,
155 path: &mut dyn Path,
156 ) -> Result<(), OpenError>;
157 fn topology(
160 &mut self,
161 carrier: Option<Carrier>,
162 relay: Option<String>,
163 refusal: Option<&str>,
164 ) -> Result<(), OpenError>;
165}
166
167#[derive(Debug, thiserror::Error)]
169#[error("{0}")]
170pub struct OpenError(pub String);
171
172#[derive(Debug)]
174pub struct Opened<B: Backend> {
175 config: OutboundLinkConfig,
176 choice: ChoiceConfig,
177 backend: B,
178 path: Option<Box<dyn Path>>,
179 timers: usize,
180 interrupt: carriers::StopSlot,
181}
182
183impl<B: Backend> Opened<B> {
184 pub(crate) fn stopped(&self) -> bool {
185 self.interrupt.stopped()
186 }
187 pub(crate) fn life(&self) -> Option<crate::link::LinkLife> {
188 self.path.as_ref().and_then(|path| path.life())
189 }
190 pub(crate) fn refuse_unsolicited(&self) -> Result<(), OpenError> {
191 self.path.as_ref().map_or_else(
192 || Err(OpenError("link_has_no_carrier".to_owned())),
193 |path| path.refuse_unsolicited(),
194 )
195 }
196 pub(crate) const fn description(&self) -> &OutboundLinkConfig {
197 &self.config
198 }
199 pub(crate) const fn backend(&self) -> &B {
200 &self.backend
201 }
202 pub(crate) fn observed_carrier(&self) -> Option<crate::console::records::Carrier> {
203 self.path.as_ref().and_then(|path| match path.carrier() {
204 Carrier::Wire => Some(crate::console::records::Carrier::Wire),
205 Carrier::Direct => Some(crate::console::records::Carrier::Direct),
206 Carrier::Relay => path
207 .relay_device()
208 .map(crate::console::records::Carrier::Relay),
209 })
210 }
211 pub fn interrupt_handle(&self) -> Result<Stop, OpenError> {
214 self.interrupt.handle()
215 }
216 #[must_use]
217 pub fn carrier(&self) -> Option<Carrier> {
218 self.path.as_ref().map(|path| path.carrier())
219 }
220 #[must_use]
221 pub const fn armed_timers(&self) -> usize {
222 self.timers
223 }
224 pub fn send(&mut self, subject: &str, plain: &[u8]) -> Result<(), OpenError> {
229 if self.stopped() {
230 return Err(OpenError("link_stopped".to_owned()));
231 }
232 crate::auth_lys::SubjectBinding::new(
233 "wire".to_owned(),
234 subject.to_owned(),
235 subject.to_owned(),
236 )
237 .map_err(|error| OpenError(error.to_string()))?;
238 if !self.config.exports.iter().any(|prefix| {
239 subject == prefix
240 || subject
241 .strip_prefix(prefix)
242 .is_some_and(|suffix| suffix.starts_with('.'))
243 }) {
244 return Err(OpenError("subject_not_exported_by_link".to_owned()));
245 }
246 if self.path.is_none() {
247 let path = select(&self.config, &mut self.backend)?;
248 self.interrupt.set(
249 path.interrupt_handle()
250 .or_else(|| self.backend.interrupt_handle()),
251 )?;
252 self.path = Some(path);
253 }
254 let mut path = self
255 .path
256 .take()
257 .ok_or_else(|| OpenError("link_has_no_carrier".to_owned()))?;
258 let result = self.cross(path.as_mut(), subject, plain);
259 self.timers = 0;
260 match result {
261 Ok(Crossing::Delivered) => {
262 self.path = Some(path);
263 Ok(())
264 }
265 Ok(Crossing::Relay) => self.cross_relay(subject, plain),
266 Err(error) => {
267 self.backend
268 .topology(Some(path.carrier()), path.relay_device(), Some(&error.0))?;
269 if error.0.starts_with("crossing_refused:") {
270 self.path = Some(path);
271 }
272 Err(error)
273 }
274 }
275 }
276
277 fn cross(
278 &mut self,
279 path: &mut dyn Path,
280 subject: &str,
281 plain: &[u8],
282 ) -> Result<Crossing, OpenError> {
283 let mut packet = match path.prepare(subject, plain) {
284 Ok(packet) => packet,
285 Err(error)
286 if path.carrier() == Carrier::Direct && error.0 == "too_large_for_datagram" =>
287 {
288 self.backend
289 .topology(Some(Carrier::Direct), None, Some(&error.0))?;
290 return Ok(Crossing::Relay);
291 }
292 Err(error) => return Err(error),
293 };
294 if path.carrier() != Carrier::Direct {
295 path.transmit(&packet)?;
296 return if path.receipt(packet.counter, None)? {
297 Ok(Crossing::Delivered)
298 } else {
299 Err(OpenError("stream_crossing_has_no_receipt".to_owned()))
300 };
301 }
302 let bound = Duration::from_millis(self.choice.crossing_deadline_ms);
303 packet.deadline = Some(Deadline::after(bound)?);
304 self.timers = 1;
305 path.transmit(&packet)?;
306 if path.receipt(packet.counter, packet.deadline)? {
307 return Ok(Crossing::Delivered);
308 }
309 self.backend
310 .topology(Some(Carrier::Direct), None, Some("crossing_deadline"))?;
311 if let Err(error) = self.backend.repunch(&self.config, path) {
312 self.backend
313 .topology(Some(Carrier::Direct), None, Some(&error.0))?;
314 return Ok(Crossing::Relay);
315 }
316 packet.deadline = Some(Deadline::after(bound)?);
318 path.transmit(&packet)?;
319 if path.receipt(packet.counter, packet.deadline)? {
320 return Ok(Crossing::Delivered);
321 }
322 self.backend.topology(
323 Some(Carrier::Direct),
324 None,
325 Some("second_crossing_deadline"),
326 )?;
327 Ok(Crossing::Relay)
328 }
329
330 fn cross_relay(&mut self, subject: &str, plain: &[u8]) -> Result<(), OpenError> {
331 let mut relay = match self.backend.relay(&self.config) {
332 Ok(relay) => relay,
333 Err(error) => {
334 self.backend
335 .topology(Some(Carrier::Relay), None, Some(&error.0))?;
336 return Err(error);
337 }
338 };
339 self.interrupt.set(
340 relay
341 .interrupt_handle()
342 .or_else(|| self.backend.interrupt_handle()),
343 )?;
344 self.backend
345 .topology(Some(relay.carrier()), relay.relay_device(), None)?;
346 let crossing = (|| {
347 let packet = relay.prepare(subject, plain)?;
348 relay.transmit(&packet)?;
349 if !relay.receipt(packet.counter, None)? {
350 return Err(OpenError("relay_crossing_has_no_receipt".to_owned()));
351 }
352 Ok(())
353 })();
354 if let Err(error) = crossing {
355 self.backend
356 .topology(Some(Carrier::Relay), relay.relay_device(), Some(&error.0))?;
357 return Err(error);
358 }
359 self.path = Some(relay);
360 Ok(())
361 }
362}
363
364enum Crossing {
365 Delivered,
366 Relay,
367}
368
369fn record<B: Backend>(
370 backend: &mut B,
371 carrier: Carrier,
372 result: Result<Box<dyn Path>, OpenError>,
373 refusals: &mut Vec<String>,
374) -> Result<Option<Box<dyn Path>>, OpenError> {
375 match result {
376 Ok(path) => {
377 backend.topology(Some(path.carrier()), path.relay_device(), None)?;
378 Ok(Some(path))
379 }
380 Err(error) => {
381 let name = match carrier {
382 Carrier::Wire => "wire",
383 Carrier::Direct => "direct",
384 Carrier::Relay => "relay",
385 };
386 refusals.push(format!("{name}={}", error.0));
387 backend.topology(Some(carrier), None, Some(&error.0))?;
388 if error.0.starts_with("link_held:")
389 || error.0.starts_with("link_refused:")
390 || error.0 == "network_stopped"
391 {
392 return Err(error);
393 }
394 Ok(None)
395 }
396 }
397}
398
399fn select<B: Backend>(
400 config: &OutboundLinkConfig,
401 backend: &mut B,
402) -> Result<Box<dyn Path>, OpenError> {
403 let mut refusals = Vec::new();
404 if let Some(address) = &config.peer_address {
405 let result = backend.wire(config, address);
406 if let Some(path) = record(backend, Carrier::Wire, result, &mut refusals)? {
407 return Ok(path);
408 }
409 } else {
410 let addresses = match backend.lookup(config) {
411 Ok(addresses) => addresses,
412 Err(error) => {
413 refusals.push(format!("lookup={}", error.0));
414 backend.topology(None, None, Some(&error.0))?;
415 Vec::new()
416 }
417 };
418 let mut tried = std::collections::BTreeSet::new();
419 for address in addresses {
420 if !tried.insert(address.clone()) {
421 continue;
422 }
423 let result = backend.wire(config, &address);
424 if let Some(path) = record(backend, Carrier::Wire, result, &mut refusals)? {
425 return Ok(path);
426 }
427 }
428 }
429 let result = backend.punch(config);
430 if let Some(path) = record(backend, Carrier::Direct, result, &mut refusals)? {
431 return Ok(path);
432 }
433 let result = backend.relay(config);
434 if let Some(path) = record(backend, Carrier::Relay, result, &mut refusals)? {
435 return Ok(path);
436 }
437 let reason = format!("all_link_carriers_refused: {}", refusals.join("; "));
438 backend.topology(None, None, Some(&reason))?;
439 Err(OpenError(reason))
440}
441
442pub fn open<B: Backend>(
446 config: OutboundLinkConfig,
447 choice: ChoiceConfig,
448 mut backend: B,
449) -> Result<Opened<B>, OpenError> {
450 if choice.crossing_deadline_ms == 0 {
451 return Err(OpenError(
452 "crossing_deadline_ms_must_be_positive".to_owned(),
453 ));
454 }
455 if config.link_id.is_empty()
456 || config
457 .link_id
458 .chars()
459 .any(|character| character.is_whitespace() || character.is_control())
460 {
461 return Err(OpenError("invalid_link_id".to_owned()));
462 }
463 for subject in &config.exports {
464 crate::auth_lys::SubjectBinding::new("wire".to_owned(), subject.clone(), subject.clone())
465 .map_err(|error| OpenError(error.to_string()))?;
466 }
467 let path = select(&config, &mut backend)?;
468 let interrupt = carriers::StopSlot::new(
469 path.interrupt_handle()
470 .or_else(|| backend.interrupt_handle()),
471 );
472 Ok(Opened {
473 config,
474 choice,
475 backend,
476 path: Some(path),
477 timers: 0,
478 interrupt,
479 })
480}