1#[cfg(test)]
2mod endpoint_test;
3
4use std::{
5 collections::HashMap,
6 fmt, iter,
7 net::SocketAddr,
8 ops::{Index, IndexMut},
9 sync::Arc,
10 time::Instant,
11};
12
13use rustc_hash::FxHashMap;
14
15use crate::Payload;
16use crate::association::Association;
17use crate::chunk::chunk_type::CT_INIT;
18use crate::config::{ClientConfig, EndpointConfig, ServerConfig, TransportConfig};
19use crate::packet::PartialDecode;
20use crate::shared::{
21 AssociationEvent, AssociationEventInner, AssociationId, EndpointEvent, EndpointEventInner,
22};
23use crate::util::{AssociationIdGenerator, RandomAssociationIdGenerator};
24use shared::{EcnCodepoint, TransportContext, TransportMessage, TransportProtocol};
25
26use bytes::Bytes;
27use log::{debug, trace, warn};
28use slab::Slab;
29use thiserror::Error;
30
31pub struct Endpoint {
37 local_addr: SocketAddr,
38 transport_protocol: TransportProtocol,
39 association_ids_init: HashMap<AssociationId, AssociationHandle>,
44 association_ids: FxHashMap<AssociationId, AssociationHandle>,
48
49 associations: Slab<AssociationMeta>,
50 local_cid_generator: Box<dyn AssociationIdGenerator>,
51 endpoint_config: Arc<EndpointConfig>,
52 server_config: Option<Arc<ServerConfig>>,
53 reject_new_associations: bool,
57}
58
59impl fmt::Debug for Endpoint {
60 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
61 fmt.debug_struct("Endpoint<T>")
62 .field("association_ids_initial", &self.association_ids_init)
63 .field("association_ids", &self.association_ids)
64 .field("associations", &self.associations)
65 .field("config", &self.endpoint_config)
66 .field("server_config", &self.server_config)
67 .field("reject_new_associations", &self.reject_new_associations)
68 .finish()
69 }
70}
71
72impl Endpoint {
73 pub fn new(
77 local_addr: SocketAddr,
78 transport_protocol: TransportProtocol,
79 endpoint_config: Arc<EndpointConfig>,
80 server_config: Option<Arc<ServerConfig>>,
81 ) -> Self {
82 Self {
83 local_addr,
84 transport_protocol,
85 association_ids_init: HashMap::default(),
86 association_ids: FxHashMap::default(),
87 associations: Slab::new(),
88 local_cid_generator: (endpoint_config.aid_generator_factory.as_ref())(),
89 reject_new_associations: false,
90 endpoint_config,
91 server_config,
92 }
93 }
94
95 pub fn set_server_config(&mut self, server_config: Option<Arc<ServerConfig>>) {
97 self.server_config = server_config;
98 }
99
100 pub fn handle_event(&mut self, ch: AssociationHandle, event: EndpointEvent) {
102 match event.0 {
103 EndpointEventInner::Drained => {
104 let conn = self.associations.remove(ch.0);
105 self.association_ids_init.remove(&conn.init_cid);
106 for cid in conn.loc_cids.values() {
107 self.association_ids.remove(cid);
108 }
109 }
110 }
111 }
112
113 pub fn handle(
115 &mut self,
116 now: Instant,
117 remote: SocketAddr,
118 ecn: Option<EcnCodepoint>,
119 data: Bytes,
120 ) -> Option<(AssociationHandle, DatagramEvent)> {
121 let partial_decode = match PartialDecode::unmarshal(&data) {
122 Ok(x) => x,
123 Err(err) => {
124 trace!("malformed header: {}", err);
125 return None;
126 }
127 };
128
129 let dst_cid = partial_decode.common_header.verification_tag;
133 let known_ch = if dst_cid > 0 {
134 self.association_ids.get(&dst_cid).cloned()
135 } else {
136 if partial_decode.first_chunk_type == CT_INIT {
138 if let Some(dst_cid) = partial_decode.initiate_tag {
139 self.association_ids.get(&dst_cid).cloned()
140 } else {
141 None
142 }
143 } else {
144 None
145 }
146 };
147
148 if let Some(ch) = known_ch {
149 return Some((
150 ch,
151 DatagramEvent::AssociationEvent(AssociationEvent(AssociationEventInner::Datagram(
152 TransportMessage {
153 now,
154 transport: TransportContext {
155 local_addr: self.local_addr,
156 peer_addr: remote,
157 ecn,
158 transport_protocol: self.transport_protocol,
159 },
160 message: Payload::PartialDecode(partial_decode),
161 },
162 ))),
163 ));
164 }
165
166 self.handle_first_packet(now, remote, ecn, partial_decode)
170 .map(|(ch, a)| (ch, DatagramEvent::NewAssociation(a)))
171 }
172
173 pub fn connect(
175 &mut self,
176 config: ClientConfig,
177 remote: SocketAddr,
178 ) -> Result<(AssociationHandle, Association), ConnectError> {
179 if self.is_full() {
180 return Err(ConnectError::TooManyAssociations);
181 }
182 if remote.port() == 0 {
183 return Err(ConnectError::InvalidRemoteAddress(remote));
184 }
185
186 let remote_aid = RandomAssociationIdGenerator::new().generate_aid();
187 let local_aid = self.new_aid();
188
189 let (ch, conn) = self.add_association(
190 remote_aid,
191 local_aid,
192 remote,
193 Instant::now(),
194 None,
195 config.transport,
196 );
197 Ok((ch, conn))
198 }
199
200 fn new_aid(&mut self) -> AssociationId {
201 loop {
202 let aid = self.local_cid_generator.generate_aid();
203 if !self.association_ids.contains_key(&aid) {
204 break aid;
205 }
206 }
207 }
208
209 fn handle_first_packet(
210 &mut self,
211 now: Instant,
212 remote: SocketAddr,
213 ecn: Option<EcnCodepoint>,
214 partial_decode: PartialDecode,
215 ) -> Option<(AssociationHandle, Association)> {
216 if partial_decode.first_chunk_type != CT_INIT
217 || (partial_decode.first_chunk_type == CT_INIT && partial_decode.initiate_tag.is_none())
218 {
219 debug!("refusing first packet with Non-INIT or empty initial_tag INIT");
220 return None;
221 }
222
223 let server_config = if let Some(server_config) = self.server_config.as_ref() {
224 server_config
225 } else {
226 warn!("refusing first packet due to empty server_config");
227 return None;
228 };
229
230 if self.associations.len() >= server_config.concurrent_associations as usize
231 || self.reject_new_associations
232 || self.is_full()
233 {
234 debug!("refusing association");
235 return None;
237 }
238
239 let server_config = server_config.clone();
240 let transport_config = server_config.transport.clone();
241
242 let remote_aid = *partial_decode.initiate_tag.as_ref().unwrap();
243 let local_aid = self.new_aid();
244
245 let (ch, mut conn) = self.add_association(
246 remote_aid,
247 local_aid,
248 remote,
249 now,
250 Some(server_config),
251 transport_config,
252 );
253
254 conn.handle_event(AssociationEvent(AssociationEventInner::Datagram(
255 TransportMessage {
256 now,
257 transport: TransportContext {
258 local_addr: self.local_addr,
259 peer_addr: remote,
260 ecn,
261 transport_protocol: self.transport_protocol,
262 },
263 message: Payload::PartialDecode(partial_decode),
264 },
265 )));
266
267 Some((ch, conn))
268 }
269
270 #[allow(clippy::too_many_arguments)]
271 fn add_association(
272 &mut self,
273 remote_aid: AssociationId,
274 local_aid: AssociationId,
275 remote_addr: SocketAddr,
276 now: Instant,
277 server_config: Option<Arc<ServerConfig>>,
278 transport_config: Arc<TransportConfig>,
279 ) -> (AssociationHandle, Association) {
280 let conn = Association::new(
281 server_config,
282 transport_config,
283 self.endpoint_config.get_max_payload_size(),
284 local_aid,
285 remote_addr,
286 self.local_addr,
287 self.transport_protocol,
288 now,
289 );
290
291 let id = self.associations.insert(AssociationMeta {
292 init_cid: remote_aid,
293 cids_issued: 0,
294 loc_cids: iter::once((0, local_aid)).collect(),
295 initial_remote: remote_addr,
296 });
297
298 let ch = AssociationHandle(id);
299 self.association_ids.insert(local_aid, ch);
300
301 (ch, conn)
302 }
303
304 pub fn reject_new_associations(&mut self) {
306 self.reject_new_associations = true;
307 }
308
309 pub fn endpoint_config(&self) -> &EndpointConfig {
311 &self.endpoint_config
312 }
313
314 fn is_full(&self) -> bool {
316 (((u32::MAX >> 1) + (u32::MAX >> 2)) as usize) < self.association_ids.len()
317 }
318}
319
320#[derive(Debug)]
321pub(crate) struct AssociationMeta {
322 init_cid: AssociationId,
323 cids_issued: u64,
325 loc_cids: HashMap<u64, AssociationId>,
326 initial_remote: SocketAddr,
331}
332
333#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
335pub struct AssociationHandle(pub usize);
336
337impl From<AssociationHandle> for usize {
338 fn from(x: AssociationHandle) -> usize {
339 x.0
340 }
341}
342
343impl Index<AssociationHandle> for Slab<AssociationMeta> {
344 type Output = AssociationMeta;
345 fn index(&self, ch: AssociationHandle) -> &AssociationMeta {
346 &self[ch.0]
347 }
348}
349
350impl IndexMut<AssociationHandle> for Slab<AssociationMeta> {
351 fn index_mut(&mut self, ch: AssociationHandle) -> &mut AssociationMeta {
352 &mut self[ch.0]
353 }
354}
355
356#[allow(clippy::large_enum_variant)] pub enum DatagramEvent {
359 AssociationEvent(AssociationEvent),
361 NewAssociation(Association),
363}
364
365#[derive(Debug, Error, Clone, PartialEq, Eq)]
369pub enum ConnectError {
370 #[error("endpoint stopping")]
374 EndpointStopping,
375 #[error("too many associations")]
379 TooManyAssociations,
380 #[error("invalid DNS name: {0}")]
382 InvalidDnsName(String),
383 #[error("invalid remote address: {0}")]
387 InvalidRemoteAddress(SocketAddr),
388 #[error("no default client config")]
392 NoDefaultClientConfig,
393}