Skip to main content

hydro_lang/networking/
mod.rs

1//! Types for configuring network channels with serialization formats, transports, etc.
2
3use std::marker::PhantomData;
4
5use serde::Serialize;
6use serde::de::DeserializeOwned;
7
8use crate::live_collections::stream::networking::{deserialize_bincode, serialize_bincode};
9use crate::live_collections::stream::{
10    AtLeastOnce, ExactlyOnce, NoOrder, Ordering, Retries, TotalOrder,
11};
12use crate::location::cluster::{Consistency, EventualConsistency, NoConsistency};
13use crate::nondet::NonDet;
14
15#[sealed::sealed]
16trait SerKind<T: ?Sized> {
17    fn serialize_thunk(is_demux: bool) -> syn::Expr;
18
19    fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr;
20
21    /// Whether this serialization backend leaves serialization to code outside of Hydro (see
22    /// [`Embedded`]). When `true`, [`Self::serialize_thunk`] and [`Self::deserialize_thunk`] are
23    /// never called; the raw element type flows across the channel unserialized.
24    fn is_embedded() -> bool {
25        false
26    }
27}
28
29/// Serialize items using the [`bincode`] crate.
30pub enum Bincode {}
31
32#[sealed::sealed]
33impl<T: Serialize + DeserializeOwned> SerKind<T> for Bincode {
34    fn serialize_thunk(is_demux: bool) -> syn::Expr {
35        serialize_bincode::<T>(is_demux)
36    }
37
38    fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr {
39        deserialize_bincode::<T>(tagged)
40    }
41}
42
43/// Leaves serialization of items to code outside of Hydro. Every [`EMBEDDED`] channel uses it.
44///
45/// This serialization backend is only supported by the embedded deployment backend (it will panic
46/// on all other backends). The generated network channel exposes the raw element type `T` to the
47/// developer (rather than serialized bytes), so they can perform custom serialization logic outside
48/// of the Hydro program for that channel.
49pub enum Embedded {}
50
51#[sealed::sealed]
52impl<T> SerKind<T> for Embedded {
53    fn serialize_thunk(_is_demux: bool) -> syn::Expr {
54        unreachable!("embedded serialization does not use a serialize thunk")
55    }
56
57    fn deserialize_thunk(_tagged: Option<&syn::Type>) -> syn::Expr {
58        unreachable!("embedded serialization does not use a deserialize thunk")
59    }
60
61    fn is_embedded() -> bool {
62        true
63    }
64}
65
66/// An unconfigured serialization backend.
67pub enum NoSer {}
68
69/// A transport backend for network channels.
70#[sealed::sealed]
71pub trait TransportKind {
72    /// The ordering guarantee provided by this transport.
73    type OrderingGuarantee: Ordering;
74
75    /// The retry guarantee provided by this transport: [`ExactlyOnce`] if it never delivers a
76    /// message more than once.
77    type RetriesGuarantee: Retries;
78
79    /// The consistency guarantee this transport can preserve for replicated outputs
80    /// (see [`NetworkFor::ConsistencyGuarantee`]).
81    type ConsistencyGuarantee: Consistency;
82
83    /// Returns the [`NetworkingInfo`] describing this transport's configuration.
84    fn networking_info() -> NetworkingInfo;
85}
86
87#[sealed::sealed]
88#[diagnostic::on_unimplemented(
89    message = "the TCP transport requires a failure policy",
90    note = "for example, `TCP.fail_stop()` stops delivering messages after a failed connection"
91)]
92/// A failure policy for TCP connections, determining how the transport handles
93/// connection failures and what ordering guarantees the output stream provides.
94pub trait TcpFailPolicy {
95    /// The ordering guarantee provided by this failure policy.
96    type OrderingGuarantee: Ordering;
97
98    /// The consistency guarantee this failure policy can preserve for replicated outputs
99    /// (see [`NetworkFor::ConsistencyGuarantee`]).
100    type ConsistencyGuarantee: Consistency;
101
102    /// Returns the [`TcpFault`] variant for this failure policy.
103    fn tcp_fault() -> TcpFault;
104}
105
106/// A TCP failure policy that stops sending messages after a failed connection.
107pub enum FailStop {}
108#[sealed::sealed]
109impl TcpFailPolicy for FailStop {
110    type OrderingGuarantee = TotalOrder;
111
112    // A failed connection stops *all* future deliveries to that recipient, which models the
113    // recipient as having failed. Consistency guarantees only apply to live members, so
114    // eventual consistency of replicated outputs is preserved.
115    type ConsistencyGuarantee = EventualConsistency;
116
117    fn tcp_fault() -> TcpFault {
118        TcpFault::FailStop
119    }
120}
121
122/// A failure policy that allows messages to be lost.
123pub enum Lossy {}
124#[sealed::sealed]
125impl TcpFailPolicy for Lossy {
126    type OrderingGuarantee = TotalOrder;
127
128    // A lossy channel can drop an arbitrary message for one recipient while continuing to
129    // deliver later messages, so replicated outputs can diverge across members forever.
130    type ConsistencyGuarantee = NoConsistency;
131
132    fn tcp_fault() -> TcpFault {
133        TcpFault::Lossy
134    }
135}
136
137/// A failure policy that treats dropped messages as indefinitely delayed.
138///
139/// Unlike [`Lossy`], this does not require a [`NonDet`] annotation because the output
140/// stream is always lower in the partial order than the ideal stream (dropped messages
141/// are modeled as infinite delays). The tradeoff is that the output has [`NoOrder`]
142/// guarantees, imposing stricter conditions on downstream consumers.
143///
144/// When using this mode in the Hydro simulator, you must call
145/// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only): the simulator
146/// will not actually drop packets—it delays "dropped" messages until the end of the
147/// execution, which catches safety bugs but cannot test liveness.
148pub enum LossyDelayedForever {}
149#[sealed::sealed]
150impl TcpFailPolicy for LossyDelayedForever {
151    type OrderingGuarantee = NoOrder;
152
153    // Dropped messages are modeled as indefinitely delayed, so the output on each member is
154    // always a lower bound of the ideal stream that is eventually delivered in full; replicated
155    // outputs therefore remain eventually consistent.
156    type ConsistencyGuarantee = EventualConsistency;
157
158    fn tcp_fault() -> TcpFault {
159        TcpFault::LossyDelayedForever
160    }
161}
162
163#[sealed::sealed]
164#[diagnostic::on_unimplemented(
165    message = "the UDP transport requires a failure policy",
166    note = "for example, `UDP.lossy_delayed_forever()` treats dropped messages as indefinitely delayed"
167)]
168/// A failure policy for UDP channels, determining how the transport handles
169/// message loss. Because UDP provides no ordering guarantees, all policies
170/// produce [`NoOrder`] output streams, and there is no `fail_stop` option
171/// (UDP is connectionless, so there is no connection to fail).
172pub trait UdpFailPolicy {
173    /// The consistency guarantee this failure policy can preserve for replicated outputs
174    /// (see [`NetworkFor::ConsistencyGuarantee`]).
175    type ConsistencyGuarantee: Consistency;
176
177    /// Returns the [`UdpFault`] variant for this failure policy.
178    fn udp_fault() -> UdpFault;
179}
180
181#[sealed::sealed]
182impl UdpFailPolicy for Lossy {
183    // A lossy channel can drop an arbitrary message for one recipient while continuing to
184    // deliver later messages, so replicated outputs can diverge across members forever.
185    type ConsistencyGuarantee = NoConsistency;
186
187    fn udp_fault() -> UdpFault {
188        UdpFault::Lossy
189    }
190}
191
192#[sealed::sealed]
193impl UdpFailPolicy for LossyDelayedForever {
194    // Dropped messages are modeled as indefinitely delayed, so the output on each member is
195    // always a lower bound of the ideal stream that is eventually delivered in full; replicated
196    // outputs therefore remain eventually consistent.
197    type ConsistencyGuarantee = EventualConsistency;
198
199    fn udp_fault() -> UdpFault {
200        UdpFault::LossyDelayedForever
201    }
202}
203
204/// Send items across a length-delimited TCP channel.
205pub struct Tcp<F> {
206    _phantom: PhantomData<F>,
207}
208
209#[sealed::sealed]
210impl<F: TcpFailPolicy> TransportKind for Tcp<F> {
211    type OrderingGuarantee = F::OrderingGuarantee;
212
213    type RetriesGuarantee = ExactlyOnce;
214
215    type ConsistencyGuarantee = F::ConsistencyGuarantee;
216
217    fn networking_info() -> NetworkingInfo {
218        NetworkingInfo::Tcp {
219            fault: F::tcp_fault(),
220        }
221    }
222}
223
224/// Items cross a channel that the host of an embedded program implements however it likes (a
225/// network connection, an in-memory queue, ...), keeping the retries `R`, ordering `O`, and
226/// failure policy `F` that follow from what the channel declares about each anomaly (`()` until
227/// declared).
228pub struct EmbeddedTransport<R, O, F> {
229    _phantom: PhantomData<(R, O, F)>,
230}
231
232/// The retries an [`EMBEDDED`] channel declares: [`ExactlyOnce`]
233/// ([`never_duplicates`](NetworkingConfig::never_duplicates)) or [`AtLeastOnce`]
234/// ([`may_duplicate`](NetworkingConfig::may_duplicate)).
235#[sealed::sealed]
236#[diagnostic::on_unimplemented(
237    message = "this `EMBEDDED` channel does not declare whether it may duplicate messages",
238    note = "add `.never_duplicates()` or `.may_duplicate()` to the network config"
239)]
240pub trait EmbeddedRetries: Retries {}
241
242#[sealed::sealed]
243impl EmbeddedRetries for ExactlyOnce {}
244
245#[sealed::sealed]
246impl EmbeddedRetries for AtLeastOnce {}
247
248/// The ordering an [`EMBEDDED`] channel declares: [`TotalOrder`]
249/// ([`never_reorders`](NetworkingConfig::never_reorders)) or [`NoOrder`]
250/// ([`may_reorder`](NetworkingConfig::may_reorder)).
251#[sealed::sealed]
252#[diagnostic::on_unimplemented(
253    message = "this `EMBEDDED` channel does not declare whether it may reorder messages",
254    note = "add `.never_reorders()` or `.may_reorder()` to the network config"
255)]
256pub trait EmbeddedOrdering: Ordering {}
257
258#[sealed::sealed]
259impl EmbeddedOrdering for TotalOrder {}
260
261#[sealed::sealed]
262impl EmbeddedOrdering for NoOrder {}
263
264/// The failure policy an [`EMBEDDED`] channel declares: [`FailStop`]
265/// ([`never_drops`](NetworkingConfig::never_drops)), [`Lossy`]
266/// ([`may_drop`](NetworkingConfig::may_drop)), or [`LossyDelayedForever`]
267/// ([`may_delay_forever`](NetworkingConfig::may_delay_forever)).
268#[sealed::sealed]
269#[diagnostic::on_unimplemented(
270    message = "this `EMBEDDED` channel does not declare whether it may drop messages",
271    note = "add `.never_drops()`, `.may_drop(nondet!(..))`, or `.may_delay_forever()` to the network config"
272)]
273pub trait EmbeddedFailPolicy {
274    /// The ordering of the received stream when the channel declares ordering `O`.
275    type Ordering<O: Ordering>: Ordering;
276
277    /// The consistency guarantee this failure policy can preserve for replicated outputs
278    /// (see [`NetworkFor::ConsistencyGuarantee`]).
279    type ConsistencyGuarantee: Consistency;
280
281    /// Returns the [`EmbeddedFault`] variant for this failure policy.
282    fn embedded_fault() -> EmbeddedFault;
283}
284
285#[sealed::sealed]
286impl EmbeddedFailPolicy for FailStop {
287    type Ordering<O: Ordering> = O;
288
289    type ConsistencyGuarantee = EventualConsistency;
290
291    fn embedded_fault() -> EmbeddedFault {
292        EmbeddedFault::FailStop
293    }
294}
295
296#[sealed::sealed]
297impl EmbeddedFailPolicy for Lossy {
298    type Ordering<O: Ordering> = O;
299
300    type ConsistencyGuarantee = NoConsistency;
301
302    fn embedded_fault() -> EmbeddedFault {
303        EmbeddedFault::Lossy
304    }
305}
306
307#[sealed::sealed]
308impl EmbeddedFailPolicy for LossyDelayedForever {
309    type Ordering<O: Ordering> = NoOrder;
310
311    type ConsistencyGuarantee = EventualConsistency;
312
313    fn embedded_fault() -> EmbeddedFault {
314        EmbeddedFault::LossyDelayedForever
315    }
316}
317
318/// An [`EMBEDDED`] channel's guarantees are the ones its declarations leave, with the
319/// consistency guarantee of its failure policy (as for [`Tcp`] and [`Udp`]).
320#[sealed::sealed]
321impl<R: EmbeddedRetries, O: EmbeddedOrdering, F: EmbeddedFailPolicy> TransportKind
322    for EmbeddedTransport<R, O, F>
323{
324    type OrderingGuarantee = F::Ordering<O>;
325
326    type RetriesGuarantee = R;
327
328    type ConsistencyGuarantee = F::ConsistencyGuarantee;
329
330    fn networking_info() -> NetworkingInfo {
331        NetworkingInfo::Embedded {
332            retries: R::RETRIES_KIND,
333            ordering: O::ORDERING_KIND,
334            fault: F::embedded_fault(),
335        }
336    }
337}
338
339/// Send items across a UDP channel, which does not guarantee delivery or ordering.
340pub struct Udp<F> {
341    _phantom: PhantomData<F>,
342}
343
344#[sealed::sealed]
345impl<F: UdpFailPolicy> TransportKind for Udp<F> {
346    type OrderingGuarantee = NoOrder;
347
348    type RetriesGuarantee = ExactlyOnce;
349
350    type ConsistencyGuarantee = F::ConsistencyGuarantee;
351
352    fn networking_info() -> NetworkingInfo {
353        NetworkingInfo::Udp {
354            fault: F::udp_fault(),
355        }
356    }
357}
358
359/// A networking backend implementation that supports items of type `T`.
360#[sealed::sealed]
361pub trait NetworkFor<T: ?Sized> {
362    /// The ordering guarantee provided by this network configuration.
363    /// When combined with an input stream's ordering `O`, the output ordering
364    /// will be `<O as MinOrder<Self::OrderingGuarantee>>::Min`.
365    type OrderingGuarantee: Ordering;
366
367    /// The retry guarantee provided by this network configuration.
368    /// When combined with an input stream's retries `R`, the output retries
369    /// will be `<R as MinRetries<Self::RetriesGuarantee>>::Min`.
370    type RetriesGuarantee: Retries;
371
372    /// The consistency guarantee this network configuration can preserve when the same data is
373    /// replicated to several recipients (e.g. via
374    /// [`Stream::broadcast_closed`](crate::live_collections::stream::Stream::broadcast_closed)).
375    ///
376    /// Failure policies that guarantee each recipient eventually observes the full stream of
377    /// sent messages, or that model failures as the recipient stopping entirely (such as
378    /// `fail_stop` or `lossy_delayed_forever`), preserve
379    /// [`EventualConsistency`]. Plain `lossy`
380    /// channels can silently drop individual messages for some recipients while others receive
381    /// them, so replicated outputs can permanently diverge and only
382    /// [`NoConsistency`] is guaranteed.
383    type ConsistencyGuarantee: Consistency;
384
385    /// Generates serialization logic for sending `T`.
386    fn serialize_thunk(is_demux: bool) -> syn::Expr;
387
388    /// Generates deserialization logic for receiving `T`.
389    fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr;
390
391    /// Whether this network channel leaves serialization to code outside of Hydro (an
392    /// [`EMBEDDED`] channel). When `true`, [`Self::serialize_thunk`] and
393    /// [`Self::deserialize_thunk`] are never called; the raw element type flows across the
394    /// channel unserialized.
395    fn is_embedded() -> bool {
396        false
397    }
398
399    /// Returns the optional name of the network channel.
400    fn name(&self) -> Option<&str>;
401
402    /// Returns the [`NetworkingInfo`] describing this network channel's transport and fault model.
403    fn networking_info() -> NetworkingInfo;
404}
405
406/// The fault model for a TCP connection.
407#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
408pub enum TcpFault {
409    /// Stops sending messages after a failed connection.
410    FailStop,
411    /// Messages may be lost (e.g. due to network partitions).
412    Lossy,
413    /// Dropped messages are treated as indefinitely delayed with no ordering guarantee.
414    LossyDelayedForever,
415}
416
417/// The failure policy of an [`EMBEDDED`] channel.
418#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
419pub enum EmbeddedFault {
420    /// Stops delivering a sender's messages once its channel fails.
421    FailStop,
422    /// Messages may be lost.
423    Lossy,
424    /// Undelivered messages are treated as indefinitely delayed.
425    LossyDelayedForever,
426}
427
428/// The fault model for a UDP channel.
429///
430/// UDP is connectionless and never guarantees delivery, so there is no
431/// `FailStop` variant — messages can always be dropped.
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
433pub enum UdpFault {
434    /// Messages may be lost (e.g. due to network partitions or congestion).
435    Lossy,
436    /// Dropped messages are treated as indefinitely delayed.
437    LossyDelayedForever,
438}
439
440/// Describes the networking configuration for a network channel at the IR level.
441#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
442pub enum NetworkingInfo {
443    /// A TCP-based network channel with a specific fault model.
444    Tcp {
445        /// The fault model for this TCP connection.
446        fault: TcpFault,
447    },
448    /// A UDP-based network channel with a specific fault model.
449    Udp {
450        /// The fault model for this UDP channel.
451        fault: UdpFault,
452    },
453    /// A channel implemented by the host of an embedded program ([`EMBEDDED`]), described by
454    /// the guarantees it is declared with, which the host's channel must keep.
455    Embedded {
456        /// Whether the channel may deliver a message more than once (`AtLeastOnce`) or not
457        /// (`ExactlyOnce`).
458        retries: crate::compile::ir::StreamRetry,
459        /// The order the channel must deliver each sender's messages in: `TotalOrder` (the order
460        /// they were sent) or `NoOrder` (any).
461        ordering: crate::compile::ir::StreamOrder,
462        /// What the channel may leave undelivered.
463        fault: EmbeddedFault,
464    },
465}
466
467/// A network channel configuration with `T` as transport backend and `S` as the serialization
468/// backend.
469pub struct NetworkingConfig<Tr: ?Sized, S: ?Sized, Name = ()> {
470    name: Option<Name>,
471    _phantom: (PhantomData<Tr>, PhantomData<S>),
472}
473
474impl<Tr: ?Sized, S: ?Sized> NetworkingConfig<Tr, S> {
475    /// Names the network channel and enables stable communication across multiple service versions.
476    pub fn name(self, name: impl Into<String>) -> NetworkingConfig<Tr, S, String> {
477        NetworkingConfig {
478            name: Some(name.into()),
479            _phantom: (PhantomData, PhantomData),
480        }
481    }
482}
483
484impl<Tr: ?Sized, N> NetworkingConfig<Tr, NoSer, N> {
485    /// Configures the network channel to use [`bincode`] to serialize items.
486    pub const fn bincode(mut self) -> NetworkingConfig<Tr, Bincode, N> {
487        let taken_name = self.name.take();
488        std::mem::forget(self); // nothing else is stored
489        NetworkingConfig {
490            name: taken_name,
491            _phantom: (PhantomData, PhantomData),
492        }
493    }
494}
495
496impl<S: ?Sized> NetworkingConfig<Tcp<()>, S> {
497    /// Configures the TCP transport to stop sending messages after a failed connection.
498    ///
499    /// Note that the Hydro simulator will not simulate connection failures that impact the
500    /// *liveness* of a program. If an output assertion depends on a `fail_stop` channel
501    /// making progress, that channel will not experience a failure that would cause the test to
502    /// block indefinitely. However, any *safety* issues caused by connection failures will still
503    /// be caught, such as a race condition between a failed connection and some other message.
504    #[must_use]
505    pub const fn fail_stop(self) -> NetworkingConfig<Tcp<FailStop>, S> {
506        NetworkingConfig {
507            name: self.name,
508            _phantom: (PhantomData, PhantomData),
509        }
510    }
511
512    /// Configures the TCP transport to allow messages to be lost.
513    ///
514    /// This is appropriate for networks where messages may be dropped, such as when
515    /// running under a Maelstrom partition nemesis. Unlike `fail_stop`, which guarantees
516    /// a prefix of messages is delivered, `lossy` makes no such guarantee.
517    ///
518    /// # Non-Determinism
519    /// A lossy TCP channel will non-deterministically drop messages during execution.
520    #[must_use]
521    pub const fn lossy(self, nondet: NonDet) -> NetworkingConfig<Tcp<Lossy>, S> {
522        let _ = nondet;
523        NetworkingConfig {
524            name: self.name,
525            _phantom: (PhantomData, PhantomData),
526        }
527    }
528
529    /// Configures the TCP transport to treat dropped messages as indefinitely delayed.
530    ///
531    /// This is appropriate for networks where messages may be dropped, such as when
532    /// running under a Maelstrom partition nemesis. Unlike [`Self::lossy`], this does
533    /// *not* require a [`NonDet`] annotation because the output is always lower in the
534    /// partial order than the ideal stream. However, the output stream will have
535    /// [`NoOrder`] guarantees, imposing stricter conditions on downstream consumers.
536    ///
537    /// Unlike [`Self::lossy`], this mode can easily be simulated in exhaustive mode
538    /// without running into fairness issues.
539    ///
540    /// When using this mode in the Hydro simulator, you must call
541    /// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only) to opt in:
542    /// the simulator will not actually drop packets—it delays "dropped" messages until
543    /// the end of the execution, which catches safety bugs but cannot test liveness.
544    #[must_use]
545    pub const fn lossy_delayed_forever(self) -> NetworkingConfig<Tcp<LossyDelayedForever>, S> {
546        NetworkingConfig {
547            name: self.name,
548            _phantom: (PhantomData, PhantomData),
549        }
550    }
551}
552
553impl<O, F, S: ?Sized> NetworkingConfig<EmbeddedTransport<(), O, F>, S> {
554    /// Declares that the host's channel never delivers a message more than once, so the
555    /// received stream keeps the sent stream's retries.
556    #[must_use]
557    pub const fn never_duplicates(
558        self,
559    ) -> NetworkingConfig<EmbeddedTransport<ExactlyOnce, O, F>, S> {
560        NetworkingConfig {
561            name: self.name,
562            _phantom: (PhantomData, PhantomData),
563        }
564    }
565
566    /// Declares that the host's channel may deliver a message more than once, so the received
567    /// stream is [`AtLeastOnce`].
568    #[must_use]
569    pub const fn may_duplicate(self) -> NetworkingConfig<EmbeddedTransport<AtLeastOnce, O, F>, S> {
570        NetworkingConfig {
571            name: self.name,
572            _phantom: (PhantomData, PhantomData),
573        }
574    }
575}
576
577impl<R, F, S: ?Sized> NetworkingConfig<EmbeddedTransport<R, (), F>, S> {
578    /// Declares that the host's channel delivers each sender's messages in the order it sent
579    /// them, so the received stream keeps the sent stream's order.
580    #[must_use]
581    pub const fn never_reorders(self) -> NetworkingConfig<EmbeddedTransport<R, TotalOrder, F>, S> {
582        NetworkingConfig {
583            name: self.name,
584            _phantom: (PhantomData, PhantomData),
585        }
586    }
587
588    /// Declares that the host's channel may deliver a sender's messages in a different order
589    /// than it sent them, so the received stream is [`NoOrder`].
590    #[must_use]
591    pub const fn may_reorder(self) -> NetworkingConfig<EmbeddedTransport<R, NoOrder, F>, S> {
592        NetworkingConfig {
593            name: self.name,
594            _phantom: (PhantomData, PhantomData),
595        }
596    }
597}
598
599impl<R, O, S: ?Sized> NetworkingConfig<EmbeddedTransport<R, O, ()>, S> {
600    /// Declares that the host's channel never drops a message: while it stays live, it delivers
601    /// every message sent. Like [`never_reorders`](NetworkingConfig::never_reorders) and
602    /// [`never_duplicates`](NetworkingConfig::never_duplicates), this holds as long as the
603    /// channel remains live; if it fails, it delivers nothing more, like
604    /// [`TCP.fail_stop()`](NetworkingConfig::fail_stop).
605    #[must_use]
606    pub const fn never_drops(self) -> NetworkingConfig<EmbeddedTransport<R, O, FailStop>, S> {
607        NetworkingConfig {
608            name: self.name,
609            _phantom: (PhantomData, PhantomData),
610        }
611    }
612
613    /// Declares that the host's channel may lose messages and keep delivering later ones, like
614    /// [`TCP.lossy()`](NetworkingConfig::lossy).
615    ///
616    /// # Non-Determinism
617    /// A lossy channel will non-deterministically drop messages during execution.
618    #[must_use]
619    pub const fn may_drop(
620        self,
621        nondet: NonDet,
622    ) -> NetworkingConfig<EmbeddedTransport<R, O, Lossy>, S> {
623        let _ = nondet;
624        NetworkingConfig {
625            name: self.name,
626            _phantom: (PhantomData, PhantomData),
627        }
628    }
629
630    /// Declares that the host's channel may never deliver a message, which is treated as
631    /// indefinitely delayed, like
632    /// [`TCP.lossy_delayed_forever()`](NetworkingConfig::lossy_delayed_forever). The received
633    /// stream is [`NoOrder`].
634    ///
635    /// The Hydro simulator requires
636    /// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only) for this mode.
637    #[must_use]
638    pub const fn may_delay_forever(
639        self,
640    ) -> NetworkingConfig<EmbeddedTransport<R, O, LossyDelayedForever>, S> {
641        NetworkingConfig {
642            name: self.name,
643            _phantom: (PhantomData, PhantomData),
644        }
645    }
646}
647
648impl<S: ?Sized> NetworkingConfig<Udp<()>, S> {
649    /// Configures the UDP transport to allow messages to be lost.
650    ///
651    /// UDP never guarantees delivery or ordering, so unlike TCP there is no `fail_stop`
652    /// policy — messages may always be dropped and the output stream always has
653    /// [`NoOrder`] guarantees.
654    ///
655    /// # Non-Determinism
656    /// A lossy UDP channel will non-deterministically drop messages during execution.
657    #[must_use]
658    pub const fn lossy(self, nondet: NonDet) -> NetworkingConfig<Udp<Lossy>, S> {
659        let _ = nondet;
660        NetworkingConfig {
661            name: self.name,
662            _phantom: (PhantomData, PhantomData),
663        }
664    }
665
666    /// Configures the UDP transport to treat dropped messages as indefinitely delayed.
667    ///
668    /// UDP never guarantees delivery or ordering, so unlike TCP there is no `fail_stop`
669    /// policy. Unlike [`Self::lossy`], this does *not* require a [`NonDet`] annotation
670    /// because the output is always lower in the partial order than the ideal stream
671    /// (dropped messages are modeled as infinite delays). The output stream has
672    /// [`NoOrder`] guarantees, imposing stricter conditions on downstream consumers.
673    ///
674    /// Unlike [`Self::lossy`], this mode can easily be simulated in exhaustive mode
675    /// without running into fairness issues.
676    ///
677    /// When using this mode in the Hydro simulator, you must call
678    /// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only) to opt in:
679    /// the simulator will not actually drop packets—it delays "dropped" messages until
680    /// the end of the execution, which catches safety bugs but cannot test liveness.
681    #[must_use]
682    pub const fn lossy_delayed_forever(self) -> NetworkingConfig<Udp<LossyDelayedForever>, S> {
683        NetworkingConfig {
684            name: self.name,
685            _phantom: (PhantomData, PhantomData),
686        }
687    }
688}
689
690#[sealed::sealed]
691impl<Tr, S, T: ?Sized> NetworkFor<T> for NetworkingConfig<Tr, S>
692where
693    Tr: ?Sized + TransportKind,
694    S: ?Sized + SerKind<T>,
695{
696    type OrderingGuarantee = Tr::OrderingGuarantee;
697
698    type RetriesGuarantee = Tr::RetriesGuarantee;
699
700    type ConsistencyGuarantee = Tr::ConsistencyGuarantee;
701
702    fn serialize_thunk(is_demux: bool) -> syn::Expr {
703        S::serialize_thunk(is_demux)
704    }
705
706    fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr {
707        S::deserialize_thunk(tagged)
708    }
709
710    fn is_embedded() -> bool {
711        S::is_embedded()
712    }
713
714    fn name(&self) -> Option<&str> {
715        None
716    }
717
718    fn networking_info() -> NetworkingInfo {
719        Tr::networking_info()
720    }
721}
722
723#[sealed::sealed]
724impl<Tr, S, T: ?Sized> NetworkFor<T> for NetworkingConfig<Tr, S, String>
725where
726    Tr: ?Sized + TransportKind,
727    S: ?Sized + SerKind<T>,
728{
729    type OrderingGuarantee = Tr::OrderingGuarantee;
730
731    type RetriesGuarantee = Tr::RetriesGuarantee;
732
733    type ConsistencyGuarantee = Tr::ConsistencyGuarantee;
734
735    fn serialize_thunk(is_demux: bool) -> syn::Expr {
736        S::serialize_thunk(is_demux)
737    }
738
739    fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr {
740        S::deserialize_thunk(tagged)
741    }
742
743    fn is_embedded() -> bool {
744        S::is_embedded()
745    }
746
747    fn name(&self) -> Option<&str> {
748        self.name.as_deref()
749    }
750
751    fn networking_info() -> NetworkingInfo {
752        Tr::networking_info()
753    }
754}
755
756/// A network channel that uses length-delimited TCP for transport.
757pub const TCP: NetworkingConfig<Tcp<()>, NoSer> = NetworkingConfig {
758    name: None,
759    _phantom: (PhantomData, PhantomData),
760};
761
762/// A network channel implemented by the host of an embedded program, which chooses how messages
763/// travel: a network connection, an in-memory queue, or anything else. The program declares, for
764/// each anomaly, whether the host's channel may introduce it, and the received stream is weakened
765/// by each one it may:
766///
767/// - reordering: [`never_reorders`](NetworkingConfig::never_reorders) or
768///   [`may_reorder`](NetworkingConfig::may_reorder);
769/// - duplication: [`never_duplicates`](NetworkingConfig::never_duplicates) or
770///   [`may_duplicate`](NetworkingConfig::may_duplicate);
771/// - loss: [`never_drops`](NetworkingConfig::never_drops),
772///   [`may_drop`](NetworkingConfig::may_drop), or
773///   [`may_delay_forever`](NetworkingConfig::may_delay_forever).
774///
775/// Each axis must be declared (for example
776/// `EMBEDDED.may_reorder().never_duplicates().never_drops().name("messages")`). The `never_*`
777/// declarations hold as long as the channel remains live.
778///
779/// Items cross the channel unserialized: the generated channel ends carry the raw element type,
780/// and the host encodes it if its transport needs to. The program names only the anomalies, not
781/// a transport, so it is only supported by the embedded deployment backend and the Hydro
782/// simulator (which carries the raw values in memory); other backends use [`TCP`] or [`UDP`].
783pub const EMBEDDED: NetworkingConfig<EmbeddedTransport<(), (), ()>, Embedded> = NetworkingConfig {
784    name: None,
785    _phantom: (PhantomData, PhantomData),
786};
787
788/// A network channel that uses UDP for transport.
789///
790/// Unlike [`TCP`], UDP does not guarantee delivery or ordering, so output streams
791/// always have [`NoOrder`] guarantees. Because UDP is connectionless, there is no
792/// `fail_stop` policy; only [`lossy`](NetworkingConfig::lossy) and
793/// [`lossy_delayed_forever`](NetworkingConfig::lossy_delayed_forever) are available.
794///
795/// # Availability
796/// UDP is **not yet available** in "deploy" deployment mode (via Hydro Deploy,
797/// including Docker and ECS deployments); attempting to deploy a UDP channel there
798/// will panic at compile time. Both UDP modes are available for embedded
799/// deployments (the only production deployment option) and Maelstrom testing. In
800/// the Hydro simulator, only `lossy_delayed_forever` is supported, and it requires
801/// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only): the
802/// simulator will not actually drop packets—it delays "dropped" messages until the
803/// end of the execution, which catches safety bugs but cannot test liveness.
804pub const UDP: NetworkingConfig<Udp<()>, NoSer> = NetworkingConfig {
805    name: None,
806    _phantom: (PhantomData, PhantomData),
807};
808
809#[cfg(test)]
810mod tests {
811    use super::*;
812
813    /// Compiles only if `K` gives retries `R`, ordering `O`, and consistency guarantee `C`.
814    fn guarantees<K, R, O, C>()
815    where
816        K: TransportKind<RetriesGuarantee = R, OrderingGuarantee = O, ConsistencyGuarantee = C>,
817    {
818    }
819
820    /// An `EMBEDDED` channel's retries and ordering are the declared ones (except that
821    /// `lossy_delayed_forever` is `NoOrder`), and its consistency guarantee follows from its
822    /// failure policy: only `lossy` drops to `NoConsistency`.
823    #[test]
824    fn embedded_guarantees_follow_declaration() {
825        type E<R, O, F> = EmbeddedTransport<R, O, F>;
826        guarantees::<
827            E<ExactlyOnce, TotalOrder, FailStop>,
828            ExactlyOnce,
829            TotalOrder,
830            EventualConsistency,
831        >();
832        guarantees::<E<AtLeastOnce, NoOrder, FailStop>, AtLeastOnce, NoOrder, EventualConsistency>(
833        );
834        guarantees::<E<ExactlyOnce, TotalOrder, Lossy>, ExactlyOnce, TotalOrder, NoConsistency>();
835        guarantees::<E<AtLeastOnce, NoOrder, Lossy>, AtLeastOnce, NoOrder, NoConsistency>();
836        guarantees::<
837            E<ExactlyOnce, TotalOrder, LossyDelayedForever>,
838            ExactlyOnce,
839            NoOrder,
840            EventualConsistency,
841        >();
842        guarantees::<
843            E<AtLeastOnce, NoOrder, LossyDelayedForever>,
844            AtLeastOnce,
845            NoOrder,
846            EventualConsistency,
847        >();
848    }
849}