1use super::config::MediaTransportConfig;
2use super::receiver::MediaReceiver;
3use super::sender::MediaSender;
4use super::transport::{BoxedSink, BoxedSource, MediaTransportKind, ReliableSink};
5use crate::prelude::{CitadelClientServerConnection, PeerChannel, UdpChannel};
6use crate::remote_ext::remote_specialization::PeerRemote;
7use crate::remote_ext::results::PeerConnectSuccess;
8use citadel_io::time::{timeout, Instant};
9use citadel_io::tokio::sync::oneshot::Receiver;
10use citadel_io::ErrorCode;
11use citadel_proto::prelude::{NetworkError, OutboundUdpSender, PeerChannelRecvHalf, Ratchet};
12
13pub struct MediaEndpoint {
17 kind: MediaTransportKind,
18 sender: MediaSender,
19 receiver: MediaReceiver,
20}
21
22impl std::fmt::Debug for MediaEndpoint {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.debug_struct("MediaEndpoint")
25 .field("kind", &self.kind)
26 .finish()
27 }
28}
29
30#[derive(Debug)]
32pub struct MediaEndpointParts {
33 pub kind: MediaTransportKind,
34 pub sender: MediaSender,
35 pub receiver: MediaReceiver,
36}
37
38impl MediaEndpoint {
39 pub async fn from_peer_connection<R: Ratchet>(
44 conn: PeerConnectSuccess<R>,
45 cfg: MediaTransportConfig,
46 ) -> Result<(Self, PeerRemote<R>), NetworkError> {
47 let endpoint = Self::from_channels(conn.channel, conn.udp_channel_rx, cfg).await?;
48 Ok((endpoint, conn.remote))
49 }
50
51 pub async fn from_channels<R: Ratchet>(
54 channel: PeerChannel<R>,
55 udp_channel_rx: Option<Receiver<UdpChannel<R>>>,
56 cfg: MediaTransportConfig,
57 ) -> Result<Self, NetworkError> {
58 Self::build(channel, udp_channel_rx, cfg).await
59 }
60
61 pub async fn from_c2s<R: Ratchet>(
63 conn: &mut CitadelClientServerConnection<R>,
64 cfg: MediaTransportConfig,
65 ) -> Result<Self, NetworkError> {
66 let channel = conn.take_channel().ok_or_else(|| {
67 citadel_io::error!(
68 ErrorCode::MediaTransportClosed,
69 "reliable channel already taken"
70 )
71 })?;
72 Self::from_channels(channel, conn.udp_channel_rx.take(), cfg).await
73 }
74
75 async fn build<R: Ratchet>(
76 channel: PeerChannel<R>,
77 udp_rx: Option<Receiver<UdpChannel<R>>>,
78 cfg: MediaTransportConfig,
79 ) -> Result<Self, NetworkError> {
80 cfg.validate()?;
81 let start = Instant::now();
82 let (reliable_tx, reliable_rx) = channel.split();
83 let control_sink = ReliableSink::spawn(reliable_tx);
84 let control_src: BoxedSource = Box::pin(reliable_rx);
85
86 match await_udp(udp_rx, &cfg).await {
87 Some((udp_tx, udp_rx)) => {
88 if cfg.udp_payload_budget > udp_tx.max_payload_len() {
89 return Err(citadel_io::error!(
90 ErrorCode::MediaConfigInvalid,
91 format!(
92 "udp_payload_budget {} exceeds the UDP channel's max payload {}",
93 cfg.udp_payload_budget,
94 udp_tx.max_payload_len()
95 )
96 ));
97 }
98 Self::assemble(
99 MediaTransportKind::Unreliable,
100 Box::new(udp_tx),
101 Box::new(control_sink),
102 Box::pin(udp_rx),
103 Some(control_src),
104 cfg,
105 start,
106 )
107 }
108 None => Self::assemble(
109 MediaTransportKind::Reliable,
110 Box::new(control_sink.clone()),
111 Box::new(control_sink),
112 control_src,
113 None,
114 cfg,
115 start,
116 ),
117 }
118 }
119
120 pub(crate) fn assemble(
121 kind: MediaTransportKind,
122 media_sink: BoxedSink,
123 control_sink: BoxedSink,
124 media_src: BoxedSource,
125 control_src: Option<BoxedSource>,
126 cfg: MediaTransportConfig,
127 start: Instant,
128 ) -> Result<Self, NetworkError> {
129 Ok(Self {
130 kind,
131 sender: MediaSender::new(
132 kind,
133 media_sink,
134 control_sink,
135 cfg.media,
136 cfg.send_queue_frames,
137 )?,
138 receiver: MediaReceiver::new(kind, media_src, control_src, cfg.media, start)?,
139 })
140 }
141
142 pub fn kind(&self) -> MediaTransportKind {
143 self.kind
144 }
145
146 pub fn split(self) -> (MediaSender, MediaReceiver) {
149 (self.sender, self.receiver)
150 }
151
152 pub fn into_parts(self) -> MediaEndpointParts {
153 MediaEndpointParts {
154 kind: self.kind,
155 sender: self.sender,
156 receiver: self.receiver,
157 }
158 }
159}
160
161async fn await_udp<R: Ratchet>(
165 udp_rx: Option<Receiver<UdpChannel<R>>>,
166 cfg: &MediaTransportConfig,
167) -> Option<(OutboundUdpSender, PeerChannelRecvHalf<R>)> {
168 let rx = udp_rx?;
169 match timeout(cfg.udp_wait, rx).await {
170 Ok(Ok(chan)) => Some(chan.split()),
171 Ok(Err(_)) => {
172 log::warn!(target: "citadel", "media: UDP channel sender dropped; using reliable transport");
173 None
174 }
175 Err(_) => {
176 log::warn!(target: "citadel", "media: UDP channel not ready within {:?}; using reliable transport", cfg.udp_wait);
177 None
178 }
179 }
180}