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