tokio_quiche/quic/dscp.rs
1// Copyright (C) 2026, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// * Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9// * Redistributions in binary form must reproduce the above copyright
10// notice, this list of conditions and the following disclaimer in the
11// documentation and/or other materials provided with the distribution.
12//
13// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
17// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23// POSSIBILITY OF SUCH DAMAGE.
24
25use std::sync::atomic::AtomicU8;
26use std::sync::atomic::Ordering;
27use std::sync::Arc;
28
29const UNMARKED: u8 = 64;
30
31/// A six-bit Differentiated Services Code Point for outgoing UDP packets.
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct Dscp(u8);
34
35impl Dscp {
36 /// Creates a DSCP value, rejecting values outside the six-bit range.
37 pub const fn new(value: u8) -> Option<Self> {
38 if value < UNMARKED {
39 Some(Self(value))
40 } else {
41 None
42 }
43 }
44
45 /// Returns the six-bit DSCP value.
46 pub const fn value(self) -> u8 {
47 self.0
48 }
49
50 #[cfg(any(all(target_os = "linux", not(feature = "fuzzing")), test))]
51 pub(crate) const fn tos(self) -> u8 {
52 self.0 << 2
53 }
54}
55
56/// Per-connection DSCP shared with the QUIC I/O worker.
57///
58/// Clone this handle before starting the handshake and call [`Self::set`] once
59/// the connection's final marking is known. `Dscp::new(0)` explicitly
60/// clears an earlier marking even when the shared socket has a nonzero default;
61/// `None` omits the per-packet control message.
62#[derive(Clone, Debug)]
63pub struct DscpHandle(Arc<AtomicU8>);
64
65impl DscpHandle {
66 pub(crate) fn new() -> Self {
67 Self(Arc::new(AtomicU8::new(UNMARKED)))
68 }
69
70 /// Changes the DSCP used for subsequent packets on this connection.
71 pub fn set(&self, dscp: Option<Dscp>) {
72 // Relaxed ordering is enough; the byte synchronizes no other data.
73 self.0
74 .store(dscp.map_or(UNMARKED, Dscp::value), Ordering::Relaxed);
75 }
76
77 /// Returns the currently selected marking, or `None` if disabled.
78 pub fn get(&self) -> Option<Dscp> {
79 Dscp::new(self.0.load(Ordering::Relaxed))
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn dscp_is_six_bits() {
89 assert_eq!(Dscp::new(0).map(Dscp::tos), Some(0));
90 assert_eq!(Dscp::new(34).map(Dscp::tos), Some(136));
91 assert_eq!(Dscp::new(63).map(Dscp::tos), Some(252));
92 assert_eq!(Dscp::new(64), None);
93 assert_eq!(Dscp::new(255), None);
94 }
95
96 #[test]
97 fn handles_share_only_their_own_connection() {
98 let first = DscpHandle::new();
99 let cloned = first.clone();
100 let second = DscpHandle::new();
101
102 assert_eq!(first.get(), None);
103 assert_eq!(second.get(), None);
104 second.set(Dscp::new(1));
105
106 cloned.set(Dscp::new(28));
107 assert_eq!(first.get(), Dscp::new(28));
108 assert_eq!(second.get(), Dscp::new(1));
109
110 cloned.set(Dscp::new(0));
111 assert_eq!(first.get(), Dscp::new(0));
112 cloned.set(None);
113 assert_eq!(first.get(), None);
114 assert_eq!(second.get(), Dscp::new(1));
115 }
116}