1use quic_reverse_control::Features;
18use std::time::Duration;
19
20#[derive(Debug, Clone)]
22pub struct Config {
23 pub supported_versions: Vec<u16>,
27
28 pub features: Features,
32
33 pub open_timeout: Duration,
37
38 pub stream_bind_timeout: Duration,
43
44 pub negotiation_timeout: Duration,
48
49 pub max_inflight_opens: usize,
54
55 pub max_concurrent_streams: usize,
59
60 pub ping_interval: Option<Duration>,
65
66 pub ping_timeout: Duration,
71
72 pub agent: Option<String>,
76}
77
78impl Default for Config {
79 fn default() -> Self {
80 Self {
81 supported_versions: vec![1],
82 features: Features::empty(),
83 open_timeout: Duration::from_secs(30),
84 stream_bind_timeout: Duration::from_secs(10),
85 negotiation_timeout: Duration::from_secs(30),
86 max_inflight_opens: 100,
87 max_concurrent_streams: 1000,
88 ping_interval: None,
89 ping_timeout: Duration::from_secs(10),
90 agent: None,
91 }
92 }
93}
94
95impl Config {
96 #[must_use]
98 pub fn new() -> Self {
99 Self::default()
100 }
101
102 #[must_use]
104 pub fn with_versions(mut self, versions: Vec<u16>) -> Self {
105 self.supported_versions = versions;
106 self
107 }
108
109 #[must_use]
111 pub const fn with_features(mut self, features: Features) -> Self {
112 self.features = features;
113 self
114 }
115
116 #[must_use]
118 pub const fn with_open_timeout(mut self, timeout: Duration) -> Self {
119 self.open_timeout = timeout;
120 self
121 }
122
123 #[must_use]
125 pub const fn with_stream_bind_timeout(mut self, timeout: Duration) -> Self {
126 self.stream_bind_timeout = timeout;
127 self
128 }
129
130 #[must_use]
132 pub const fn with_negotiation_timeout(mut self, timeout: Duration) -> Self {
133 self.negotiation_timeout = timeout;
134 self
135 }
136
137 #[must_use]
139 pub const fn with_max_inflight_opens(mut self, max: usize) -> Self {
140 self.max_inflight_opens = max;
141 self
142 }
143
144 #[must_use]
146 pub const fn with_max_concurrent_streams(mut self, max: usize) -> Self {
147 self.max_concurrent_streams = max;
148 self
149 }
150
151 #[must_use]
153 pub fn with_ping_interval(mut self, interval: Duration) -> Self {
154 self.ping_interval = Some(interval);
155 self.features |= Features::PING_PONG;
156 self
157 }
158
159 #[must_use]
161 pub const fn with_ping_timeout(mut self, timeout: Duration) -> Self {
162 self.ping_timeout = timeout;
163 self
164 }
165
166 #[must_use]
168 pub fn with_agent(mut self, agent: impl Into<String>) -> Self {
169 self.agent = Some(agent.into());
170 self
171 }
172
173 pub fn validate(&self) -> Result<(), ConfigError> {
179 if self.supported_versions.is_empty() {
180 return Err(ConfigError::NoSupportedVersions);
181 }
182
183 if self.max_inflight_opens == 0 {
184 return Err(ConfigError::InvalidLimit("max_inflight_opens must be > 0"));
185 }
186
187 if self.max_concurrent_streams == 0 {
188 return Err(ConfigError::InvalidLimit(
189 "max_concurrent_streams must be > 0",
190 ));
191 }
192
193 Ok(())
194 }
195}
196
197#[derive(Debug, thiserror::Error)]
199pub enum ConfigError {
200 #[error("at least one protocol version must be supported")]
202 NoSupportedVersions,
203
204 #[error("invalid limit: {0}")]
206 InvalidLimit(&'static str),
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn default_config_is_valid() {
215 let config = Config::default();
216 assert!(config.validate().is_ok());
217 }
218
219 #[test]
220 fn config_builder() {
221 let config = Config::new()
222 .with_versions(vec![1, 2])
223 .with_features(Features::STRUCTURED_METADATA)
224 .with_open_timeout(Duration::from_secs(60))
225 .with_max_concurrent_streams(500)
226 .with_agent("test/1.0");
227
228 assert_eq!(config.supported_versions, vec![1, 2]);
229 assert!(config.features.contains(Features::STRUCTURED_METADATA));
230 assert_eq!(config.open_timeout, Duration::from_secs(60));
231 assert_eq!(config.max_concurrent_streams, 500);
232 assert_eq!(config.agent.as_deref(), Some("test/1.0"));
233 }
234
235 #[test]
236 fn ping_interval_enables_feature() {
237 let config = Config::new().with_ping_interval(Duration::from_secs(30));
238 assert!(config.features.contains(Features::PING_PONG));
239 }
240
241 #[test]
242 fn empty_versions_is_invalid() {
243 let config = Config::new().with_versions(vec![]);
244 assert!(matches!(
245 config.validate(),
246 Err(ConfigError::NoSupportedVersions)
247 ));
248 }
249
250 #[test]
251 fn zero_limits_are_invalid() {
252 let config = Config::new().with_max_inflight_opens(0);
253 assert!(matches!(
254 config.validate(),
255 Err(ConfigError::InvalidLimit(_))
256 ));
257
258 let config = Config::new().with_max_concurrent_streams(0);
259 assert!(matches!(
260 config.validate(),
261 Err(ConfigError::InvalidLimit(_))
262 ));
263 }
264}