1use std::collections::HashMap;
27use std::net::SocketAddr;
28use std::sync::Arc;
29use std::time::Duration;
30
31use crate::utils::clock::Instant;
32
33use super::codec::{self, BlockValue};
34
35pub const MAX_TRANSFERS: usize = 32;
38
39pub const TRANSFER_SERVE_FACTOR: usize = 2;
44
45pub const MAX_UPLOADS: usize = 16;
49
50pub const MAX_UPLOADS_PER_IP: usize = 2;
53
54pub const TRANSFER_TTL: Duration = Duration::from_secs(60);
59
60#[derive(Debug, Clone, PartialEq, Eq, Hash)]
62pub struct ResourceKey {
63 pub peer: SocketAddr,
64 pub path: String,
65 pub query: Option<String>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct Representation {
71 pub code: u8,
72 pub payload: Vec<u8>,
73 pub content_format: Option<u16>,
74 pub etag: Vec<u8>,
77}
78
79impl Representation {
80 pub fn new(code: u8, payload: Vec<u8>, content_format: Option<u16>) -> Representation {
81 let etag = etag_for(&payload, content_format);
82 Representation {
83 code,
84 payload,
85 content_format,
86 etag,
87 }
88 }
89
90 pub fn block(&self, block: BlockValue) -> Option<(&[u8], bool)> {
93 let offset = block.offset();
94 if offset >= self.payload.len() && !(offset == 0 && self.payload.is_empty()) {
95 return None;
96 }
97 let end = (offset + block.size()).min(self.payload.len());
98 Some((&self.payload[offset..end], end < self.payload.len()))
99 }
100}
101
102pub fn etag_for(payload: &[u8], content_format: Option<u16>) -> Vec<u8> {
105 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
106 let cf = content_format.map_or([0xFF, 0xFF, 0xFF], |c| {
107 let b = c.to_be_bytes();
108 [1, b[0], b[1]]
109 });
110 for byte in cf.iter().chain(payload.iter()) {
111 hash ^= u64::from(*byte);
112 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
113 }
114 hash.to_be_bytes().to_vec()
115}
116
117#[derive(Debug)]
118struct Cached {
119 cached: Instant,
121 rep: Arc<Representation>,
122 served: usize,
124}
125
126#[derive(Debug)]
128pub struct Transfers {
129 entries: HashMap<ResourceKey, Cached>,
130 cap: usize,
131 ttl: Duration,
132}
133
134impl Default for Transfers {
135 fn default() -> Self {
136 Transfers::new(MAX_TRANSFERS, TRANSFER_TTL)
137 }
138}
139
140impl Transfers {
141 pub fn new(cap: usize, ttl: Duration) -> Transfers {
142 Transfers {
143 entries: HashMap::new(),
144 cap: cap.max(1),
145 ttl,
146 }
147 }
148
149 pub fn len(&self) -> usize {
150 self.entries.len()
151 }
152
153 pub fn is_empty(&self) -> bool {
154 self.entries.is_empty()
155 }
156
157 fn expire(&mut self, now: Instant) {
158 let ttl = self.ttl;
159 self.entries
160 .retain(|_, c| now.saturating_duration_since(c.cached) < ttl);
161 }
162
163 pub fn get(
169 &mut self,
170 key: &ResourceKey,
171 block: BlockValue,
172 now: Instant,
173 ) -> Option<Arc<Representation>> {
174 self.expire(now);
175 let entry = self.entries.get_mut(key)?;
176 let len = entry.rep.block(block).map_or(0, |(slice, _)| slice.len());
177 let budget = entry
178 .rep
179 .payload
180 .len()
181 .saturating_mul(TRANSFER_SERVE_FACTOR);
182 if entry.served.saturating_add(len) > budget {
183 self.entries.remove(key);
184 return None;
185 }
186 entry.served += len;
187 Some(entry.rep.clone())
188 }
189
190 pub fn insert(
195 &mut self,
196 key: ResourceKey,
197 rep: Arc<Representation>,
198 served: usize,
199 now: Instant,
200 ) -> Option<ResourceKey> {
201 self.expire(now);
202 if self
203 .entries
204 .get(&key)
205 .is_some_and(|c| Arc::ptr_eq(&c.rep, &rep))
206 {
207 return None;
208 }
209 let mut evicted = None;
210 if !self.entries.contains_key(&key) && self.entries.len() >= self.cap {
211 evicted = self
212 .entries
213 .iter()
214 .min_by_key(|(_, c)| c.cached)
215 .map(|(k, _)| k.clone());
216 if let Some(old) = &evicted {
217 self.entries.remove(old);
218 }
219 }
220 self.entries.insert(
221 key,
222 Cached {
223 cached: now,
224 rep,
225 served,
226 },
227 );
228 evicted
229 }
230
231 pub fn remove(&mut self, key: &ResourceKey) {
233 self.entries.remove(key);
234 }
235}
236
237#[derive(Debug, Clone, PartialEq, Eq, Hash)]
239pub struct UploadKey {
240 pub resource: ResourceKey,
241 pub method: u8,
242}
243
244#[derive(Debug)]
245struct Upload {
246 body: Vec<u8>,
247 touched: Instant,
248}
249
250#[derive(Debug, Clone, PartialEq, Eq)]
252pub enum UploadStep {
253 Continue(BlockValue),
255 Complete {
257 body: Vec<u8>,
258 blocks: u32,
259 last: BlockValue,
260 },
261 Refuse {
263 code: u8,
264 size1: Option<usize>,
265 decision: &'static str,
267 reason: String,
268 },
269}
270
271#[derive(Debug)]
273pub struct Uploads {
274 entries: HashMap<UploadKey, Upload>,
275 cap: usize,
276 per_ip: usize,
277 ttl: Duration,
278 max_body: usize,
279}
280
281impl Default for Uploads {
282 fn default() -> Self {
283 Uploads::new(
284 MAX_UPLOADS,
285 MAX_UPLOADS_PER_IP,
286 TRANSFER_TTL,
287 codec::MAX_REPRESENTATION_LEN,
288 )
289 }
290}
291
292impl Uploads {
293 pub fn new(cap: usize, per_ip: usize, ttl: Duration, max_body: usize) -> Uploads {
294 Uploads {
295 entries: HashMap::new(),
296 cap: cap.max(1),
297 per_ip: per_ip.max(1),
298 ttl,
299 max_body,
300 }
301 }
302
303 pub fn len(&self) -> usize {
304 self.entries.len()
305 }
306
307 pub fn is_empty(&self) -> bool {
308 self.entries.is_empty()
309 }
310
311 fn too_large(&self, what: String) -> UploadStep {
312 UploadStep::Refuse {
313 code: codec::CODE_REQUEST_ENTITY_TOO_LARGE,
314 size1: Some(self.max_body),
315 decision: "refused_upload_too_large",
316 reason: format!("{what}; this server takes at most {} bytes", self.max_body),
317 }
318 }
319
320 pub fn accept(
322 &mut self,
323 key: UploadKey,
324 block: BlockValue,
325 payload: &[u8],
326 size1: Option<u32>,
327 now: Instant,
328 ) -> UploadStep {
329 let ttl = self.ttl;
330 self.entries
331 .retain(|_, u| now.saturating_duration_since(u.touched) < ttl);
332
333 if let Some(announced) = size1 {
335 if announced as usize > self.max_body {
336 self.entries.remove(&key);
337 return self.too_large(format!("the client announced {announced} bytes"));
338 }
339 }
340 if block.more && payload.len() != block.size() {
341 self.entries.remove(&key);
342 return UploadStep::Refuse {
343 code: codec::CODE_BAD_REQUEST,
344 size1: None,
345 decision: "refused_upload_bad_block",
346 reason: format!(
347 "block {} says more follow but carries {} bytes, not its size {}",
348 block.num,
349 payload.len(),
350 block.size()
351 ),
352 };
353 }
354 if payload.len() > block.size() {
355 self.entries.remove(&key);
356 return UploadStep::Refuse {
357 code: codec::CODE_BAD_REQUEST,
358 size1: None,
359 decision: "refused_upload_bad_block",
360 reason: format!(
361 "block {} carries {} bytes, over its size {}",
362 block.num,
363 payload.len(),
364 block.size()
365 ),
366 };
367 }
368
369 if block.num == 0 {
370 self.entries.remove(&key);
371 if !block.more {
372 return UploadStep::Complete {
373 body: payload.to_vec(),
374 blocks: 1,
375 last: block,
376 };
377 }
378 let ip = key.resource.peer.ip();
381 loop {
382 let held = self
383 .entries
384 .iter()
385 .filter(|(k, _)| k.resource.peer.ip() == ip);
386 if held.clone().count() < self.per_ip {
387 break;
388 }
389 let Some(oldest) = held.min_by_key(|(_, u)| u.touched).map(|(k, _)| k.clone())
390 else {
391 break;
392 };
393 self.entries.remove(&oldest);
394 tracing::info!(
395 "CoAP Block1 upload {} {}{} from {} evicted: {} uploads from that IP are \
396 in progress decision=upload_evicted_per_ip",
397 codec::method_name(oldest.method).unwrap_or("?"),
398 oldest.resource.path,
399 oldest
400 .resource
401 .query
402 .as_ref()
403 .map(|q| format!("?{q}"))
404 .unwrap_or_default(),
405 oldest.resource.peer,
406 self.per_ip
407 );
408 }
409 if self.entries.len() >= self.cap {
410 return UploadStep::Refuse {
411 code: codec::CODE_SERVICE_UNAVAILABLE,
412 size1: None,
413 decision: "refused_upload_table_full",
414 reason: format!("{} uploads are already in progress", self.cap),
415 };
416 }
417 self.entries.insert(
418 key,
419 Upload {
420 body: payload.to_vec(),
421 touched: now,
422 },
423 );
424 return UploadStep::Continue(block);
425 }
426
427 let expected = self.entries.get(&key).map(|u| u.body.len());
430 if expected != Some(block.offset()) {
431 self.entries.remove(&key);
432 return UploadStep::Refuse {
433 code: codec::CODE_REQUEST_ENTITY_INCOMPLETE,
434 size1: None,
435 decision: "refused_upload_out_of_order",
436 reason: match expected {
437 Some(have) => format!(
438 "block {} starts at byte {}, but {have} bytes have arrived",
439 block.num,
440 block.offset()
441 ),
442 None => format!("block {} arrived with no upload in progress", block.num),
443 },
444 };
445 }
446 let upload = self.entries.get_mut(&key).expect("checked above");
447 if upload.body.len() + payload.len() > self.max_body {
448 let have = upload.body.len() + payload.len();
449 self.entries.remove(&key);
450 return self.too_large(format!("the upload reached {have} bytes"));
451 }
452 upload.body.extend_from_slice(payload);
453 upload.touched = now;
454 if block.more {
455 return UploadStep::Continue(block);
456 }
457 let upload = self.entries.remove(&key).expect("present above");
458 UploadStep::Complete {
459 body: upload.body,
460 blocks: block.num + 1,
461 last: block,
462 }
463 }
464}