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