Skip to main content

moq_lite/ietf/
parameters.rs

1use std::collections::{HashMap, hash_map};
2
3use bytes::Buf;
4use num_enum::{FromPrimitive, IntoPrimitive};
5
6use crate::coding::*;
7
8use super::Version;
9use super::{FilterType, Location};
10
11const MAX_PARAMS: u64 = 64;
12/// Maximum byte value length in Key-Value-Pairs per spec Section 1.4.3.
13const MAX_KVP_VALUE_LEN: usize = (1 << 16) - 1;
14
15// ---- Setup Parameters (used in CLIENT_SETUP/SERVER_SETUP) ----
16
17#[derive(Debug, Copy, Clone, FromPrimitive, IntoPrimitive, Eq, Hash, PartialEq)]
18#[repr(u64)]
19pub enum ParameterVarInt {
20	/// Removed in draft-17; only used in draft-14/15/16.
21	MaxRequestId = 2,
22	MaxAuthTokenCacheSize = 4,
23	#[num_enum(catch_all)]
24	Unknown(u64),
25}
26
27#[derive(Debug, Copy, Clone, FromPrimitive, IntoPrimitive, Eq, Hash, PartialEq)]
28#[repr(u64)]
29pub enum ParameterBytes {
30	Path = 1,
31	AuthorizationToken = 3,
32	Authority = 5,
33	Implementation = 7,
34	#[num_enum(catch_all)]
35	Unknown(u64),
36}
37
38#[derive(Default, Debug, Clone)]
39pub struct Parameters {
40	vars: HashMap<ParameterVarInt, u64>,
41	bytes: HashMap<ParameterBytes, Vec<u8>>,
42}
43
44impl Decode<Version> for Parameters {
45	fn decode<R: bytes::Buf>(mut r: &mut R, version: Version) -> Result<Self, DecodeError> {
46		let mut vars = HashMap::new();
47		let mut bytes = HashMap::new();
48
49		match version {
50			Version::Draft17 => {
51				// Draft17: no count prefix, read Key-Value-Pairs until buffer empty.
52				// Delta-encoded types, even = varint value, odd = length-prefixed bytes.
53				let mut prev_type: u64 = 0;
54				let mut i = 0u64;
55				while r.has_remaining() {
56					if i >= MAX_PARAMS {
57						return Err(DecodeError::TooMany);
58					}
59					let delta = u64::decode(&mut r, version)?;
60					let abs = if i == 0 {
61						delta
62					} else {
63						prev_type.checked_add(delta).ok_or(DecodeError::BoundsExceeded)?
64					};
65					prev_type = abs;
66					i += 1;
67
68					if abs % 2 == 0 {
69						let kind = ParameterVarInt::from(abs);
70						match vars.entry(kind) {
71							hash_map::Entry::Occupied(_) => return Err(DecodeError::Duplicate),
72							hash_map::Entry::Vacant(entry) => entry.insert(u64::decode(&mut r, version)?),
73						};
74					} else {
75						let kind = ParameterBytes::from(abs);
76						let val = Vec::<u8>::decode(&mut r, version)?;
77						if val.len() > MAX_KVP_VALUE_LEN {
78							return Err(DecodeError::BoundsExceeded);
79						}
80						match bytes.entry(kind) {
81							hash_map::Entry::Occupied(_) => return Err(DecodeError::Duplicate),
82							hash_map::Entry::Vacant(entry) => entry.insert(val),
83						};
84					}
85				}
86			}
87			_ => {
88				let count = u64::decode(r, version)?;
89
90				if count > MAX_PARAMS {
91					return Err(DecodeError::TooMany);
92				}
93
94				let mut prev_type: u64 = 0;
95
96				for i in 0..count {
97					let kind = match version {
98						Version::Draft16 => {
99							let delta = u64::decode(r, version)?;
100							let abs = if i == 0 {
101								delta
102							} else {
103								prev_type.checked_add(delta).ok_or(DecodeError::BoundsExceeded)?
104							};
105							prev_type = abs;
106							abs
107						}
108						Version::Draft14 | Version::Draft15 => u64::decode(r, version)?,
109						Version::Draft17 => unreachable!("handled above"),
110					};
111
112					if kind % 2 == 0 {
113						let kind = ParameterVarInt::from(kind);
114						match vars.entry(kind) {
115							hash_map::Entry::Occupied(_) => return Err(DecodeError::Duplicate),
116							hash_map::Entry::Vacant(entry) => entry.insert(u64::decode(&mut r, version)?),
117						};
118					} else {
119						let kind = ParameterBytes::from(kind);
120						let val = Vec::<u8>::decode(&mut r, version)?;
121						if val.len() > MAX_KVP_VALUE_LEN {
122							return Err(DecodeError::BoundsExceeded);
123						}
124						match bytes.entry(kind) {
125							hash_map::Entry::Occupied(_) => return Err(DecodeError::Duplicate),
126							hash_map::Entry::Vacant(entry) => entry.insert(val),
127						};
128					}
129				}
130			}
131		}
132
133		Ok(Parameters { vars, bytes })
134	}
135}
136
137impl Encode<Version> for Parameters {
138	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
139		let count = self.vars.len() + self.bytes.len();
140		if count as u64 > MAX_PARAMS {
141			return Err(EncodeError::TooMany);
142		}
143
144		match version {
145			Version::Draft16 | Version::Draft17 => {
146				// Draft16: count prefix + delta encoding
147				// Draft17: NO count prefix + delta encoding
148				if version != Version::Draft17 {
149					count.encode(w, version)?;
150				}
151
152				// Collect all keys, sort, encode deltas
153				enum ParamRef<'a> {
154					Var(&'a u64),
155					Bytes(&'a Vec<u8>),
156				}
157				let mut all: Vec<(u64, ParamRef)> = Vec::new();
158				for (k, v) in self.vars.iter() {
159					all.push((u64::from(*k), ParamRef::Var(v)));
160				}
161				for (k, v) in self.bytes.iter() {
162					all.push((u64::from(*k), ParamRef::Bytes(v)));
163				}
164				all.sort_by_key(|(k, _)| *k);
165
166				let mut prev_type: u64 = 0;
167				for (idx, (kind, val)) in all.iter().enumerate() {
168					let delta = if idx == 0 { *kind } else { kind - prev_type };
169					prev_type = *kind;
170					delta.encode(w, version)?;
171
172					match val {
173						ParamRef::Var(v) => v.encode(w, version)?,
174						ParamRef::Bytes(v) => {
175							if v.len() > MAX_KVP_VALUE_LEN {
176								return Err(EncodeError::BoundsExceeded);
177							}
178							v.encode(w, version)?;
179						}
180					}
181				}
182			}
183			Version::Draft14 | Version::Draft15 => {
184				count.encode(w, version)?;
185
186				for (kind, value) in self.vars.iter() {
187					u64::from(*kind).encode(w, version)?;
188					value.encode(w, version)?;
189				}
190
191				for (kind, value) in self.bytes.iter() {
192					if value.len() > MAX_KVP_VALUE_LEN {
193						return Err(EncodeError::BoundsExceeded);
194					}
195					u64::from(*kind).encode(w, version)?;
196					value.encode(w, version)?;
197				}
198			}
199		}
200
201		Ok(())
202	}
203}
204
205impl Parameters {
206	pub fn get_varint(&self, kind: ParameterVarInt) -> Option<u64> {
207		self.vars.get(&kind).copied()
208	}
209
210	pub fn set_varint(&mut self, kind: ParameterVarInt, value: u64) {
211		self.vars.insert(kind, value);
212	}
213
214	#[cfg(test)]
215	pub fn get_bytes(&self, kind: ParameterBytes) -> Option<&[u8]> {
216		self.bytes.get(&kind).map(|v| v.as_slice())
217	}
218
219	pub fn set_bytes(&mut self, kind: ParameterBytes, value: Vec<u8>) {
220		self.bytes.insert(kind, value);
221	}
222}
223
224// ---- Message Parameter Value Encoding ----
225
226/// Trait for encoding/decoding parameter values with version-specific formats.
227///
228/// Parameter encoding differs from field encoding:
229/// - Draft-14/15/16: u8 and bool are encoded as varints (cast to u64)
230/// - Draft-17: type-specific encoding (u8 as raw byte, bool as raw byte, etc.)
231///
232/// Use `_ =>` for the newest draft behavior so future versions default forward.
233pub trait Param: Sized {
234	fn param_encode<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError>;
235	fn param_decode<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError>;
236
237	/// Whether this parameter should be encoded. Returns false to skip.
238	fn param_present(&self) -> bool {
239		true
240	}
241}
242
243impl Param for u8 {
244	fn param_encode<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
245		match version {
246			// Draft-14/15/16: u8 encoded as varint (cast to u64)
247			Version::Draft14 | Version::Draft15 | Version::Draft16 => (*self as u64).encode(w, version),
248			_ => Encode::encode(self, w, version),
249		}
250	}
251
252	fn param_decode<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
253		match version {
254			Version::Draft14 | Version::Draft15 | Version::Draft16 => {
255				let v = u64::decode(r, version)?;
256				u8::try_from(v).map_err(|_| DecodeError::InvalidValue)
257			}
258			_ => u8::decode(r, version),
259		}
260	}
261}
262
263impl Param for bool {
264	fn param_encode<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
265		match version {
266			// Draft-14/15/16: bool encoded as varint (cast to u64)
267			Version::Draft14 | Version::Draft15 | Version::Draft16 => (*self as u64).encode(w, version),
268			_ => Encode::encode(self, w, version),
269		}
270	}
271
272	fn param_decode<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
273		match version {
274			Version::Draft14 | Version::Draft15 | Version::Draft16 => {
275				let v = u64::decode(r, version)?;
276				match v {
277					0 => Ok(false),
278					1 => Ok(true),
279					_ => Err(DecodeError::InvalidValue),
280				}
281			}
282			_ => bool::decode(r, version),
283		}
284	}
285}
286
287impl Param for u64 {
288	fn param_encode<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
289		self.encode(w, version)
290	}
291
292	fn param_decode<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
293		u64::decode(r, version)
294	}
295}
296
297impl Param for Location {
298	fn param_encode<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
299		match version {
300			Version::Draft14 | Version::Draft15 | Version::Draft16 => {
301				// Length-prefixed bytes containing two QUIC varints
302				let mut buf = Vec::new();
303				self.group.encode(&mut buf, Version::Draft15)?;
304				self.object.encode(&mut buf, Version::Draft15)?;
305				buf.encode(w, version)?;
306				Ok(())
307			}
308			_ => {
309				self.group.encode(w, version)?;
310				self.object.encode(w, version)?;
311				Ok(())
312			}
313		}
314	}
315
316	fn param_decode<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
317		match version {
318			Version::Draft14 | Version::Draft15 | Version::Draft16 => {
319				// Length-prefixed bytes containing two QUIC varints
320				let data = Vec::<u8>::decode(r, version)?;
321				let mut buf = bytes::Bytes::from(data);
322				let group = u64::decode(&mut buf, Version::Draft15)?;
323				let object = u64::decode(&mut buf, Version::Draft15)?;
324				if buf.has_remaining() {
325					return Err(DecodeError::TrailingBytes);
326				}
327				Ok(Location { group, object })
328			}
329			_ => {
330				let group = u64::decode(r, version)?;
331				let object = u64::decode(r, version)?;
332				Ok(Location { group, object })
333			}
334		}
335	}
336}
337
338impl Param for FilterType {
339	fn param_encode<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
340		let mut buf = Vec::new();
341		// Use version-specific varint encoding for the inner value.
342		// Fixes draft-17 interop: inner varints now use leading-ones, not QUIC.
343		let sv = match version {
344			Version::Draft14 | Version::Draft15 | Version::Draft16 => Version::Draft15,
345			_ => version,
346		};
347		self.encode(&mut buf, sv)?;
348		buf.encode(w, version)?;
349		Ok(())
350	}
351
352	fn param_decode<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
353		let data = Vec::<u8>::decode(r, version)?;
354		let mut buf = bytes::Bytes::from(data);
355		let sv = match version {
356			Version::Draft14 | Version::Draft15 | Version::Draft16 => Version::Draft15,
357			_ => version,
358		};
359		let filter = FilterType::decode(&mut buf, sv)?;
360		if buf.has_remaining() {
361			return Err(DecodeError::TrailingBytes);
362		}
363		Ok(filter)
364	}
365}
366
367impl<T: Param> Param for Option<T> {
368	fn param_present(&self) -> bool {
369		self.is_some()
370	}
371
372	fn param_encode<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
373		match self {
374			Some(v) => v.param_encode(w, version),
375			None => Ok(()),
376		}
377	}
378
379	fn param_decode<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
380		Ok(Some(T::param_decode(r, version)?))
381	}
382}
383
384/// Encode message parameters with compile-time sorted keys.
385///
386/// Keys must be listed in ascending order (enforced at compile time).
387/// `Option<T>` values are skipped when `None`.
388///
389/// ```ignore
390/// encode_params!(w, version,
391///     0x10 => self.forward,
392///     0x20 => self.subscriber_priority,
393/// );
394/// ```
395#[macro_export]
396macro_rules! encode_params {
397	($w:expr, $version:expr, $($key:expr => $val:expr),* $(,)?) => {{
398		#[allow(unused_imports)]
399		use $crate::coding::Encode as _;
400
401		#[allow(unused)]
402		const _: () = {
403			let _keys: &[u64] = &[$($key),*];
404			let mut _i = 1;
405			while _i < _keys.len() {
406				assert!(_keys[_i - 1] < _keys[_i], "parameter keys must be in ascending order");
407				_i += 1;
408			}
409		};
410
411		let _version: $crate::ietf::Version = $version;
412
413		#[allow(unused_mut)]
414		let mut _count: usize = 0;
415		$(_count += if $crate::ietf::Param::param_present(&$val) { 1 } else { 0 };)*
416		_count.encode($w, _version)?;
417
418		#[allow(unused_mut, unused_assignments)]
419		let mut _prev_key: u64 = 0;
420		#[allow(unused_mut, unused_assignments)]
421		let mut _first: bool = true;
422		$(
423			if $crate::ietf::Param::param_present(&$val) {
424				let _key: u64 = $key;
425				match _version {
426					$crate::ietf::Version::Draft14 | $crate::ietf::Version::Draft15 => {
427						_key.encode($w, _version)?;
428					}
429					_ => {
430						let _delta = if _first { _key } else { _key - _prev_key };
431						_delta.encode($w, _version)?;
432					}
433				}
434				_prev_key = _key;
435				_first = false;
436				$crate::ietf::Param::param_encode(&$val, $w, _version)?;
437			}
438		)*
439	}};
440}
441
442/// Decode message parameters with compile-time sorted keys.
443///
444/// The declared type is the final type of each variable. Use `Option<T>` for
445/// optional parameters (defaults to `None` when absent) and bare types like `u8`
446/// for parameters where `T::default()` is an acceptable fallback.
447///
448/// Unknown parameters cause `DecodeError::InvalidValue`.
449/// Duplicate parameters cause `DecodeError::Duplicate`.
450///
451/// ```ignore
452/// decode_params!(r, version,
453///     0x10 => forward: Option<bool>,
454///     0x20 => subscriber_priority: Option<u8>,
455/// );
456/// // forward: Option<bool> and subscriber_priority: Option<u8> are now in scope
457/// let subscriber_priority = subscriber_priority.unwrap_or(128);
458/// ```
459#[macro_export]
460macro_rules! decode_params {
461	($r:expr, $version:expr, $($key:expr => $name:ident: $ty:ty),* $(,)?) => {
462		#[allow(unused)]
463		const _: () = {
464			let _keys: &[u64] = &[$($key),*];
465			let mut _i = 1;
466			while _i < _keys.len() {
467				assert!(_keys[_i - 1] < _keys[_i], "parameter keys must be in ascending order");
468				_i += 1;
469			}
470		};
471
472		// Use internal Option wrapper for duplicate detection, then shadow with Default.
473		$(#[allow(unused_mut, non_snake_case)] let mut $name: Option<$ty> = None;)*
474
475		{
476			#[allow(unused_imports)]
477			use $crate::coding::Decode as _;
478
479			let _version: $crate::ietf::Version = $version;
480			let _count = <u64 as $crate::coding::Decode<$crate::ietf::Version>>::decode($r, _version)?;
481			if _count > 64 {
482				return Err($crate::coding::DecodeError::TooMany);
483			}
484
485			#[allow(unused_mut, unused_assignments)]
486			let mut _prev_key: u64 = 0;
487			for _i in 0.._count {
488				let _key: u64 = match _version {
489					$crate::ietf::Version::Draft14 | $crate::ietf::Version::Draft15 => {
490						<u64 as $crate::coding::Decode<$crate::ietf::Version>>::decode($r, _version)?
491					}
492					_ => {
493						let _delta = <u64 as $crate::coding::Decode<$crate::ietf::Version>>::decode($r, _version)?;
494						let _abs = if _i == 0 {
495							_delta
496						} else {
497							_prev_key.checked_add(_delta).ok_or($crate::coding::DecodeError::BoundsExceeded)?
498						};
499						_prev_key = _abs;
500						_abs
501					}
502				};
503
504				match _key {
505					$($key => {
506						if $name.is_some() {
507							return Err($crate::coding::DecodeError::Duplicate);
508						}
509						$name = Some(<$ty as $crate::ietf::Param>::param_decode($r, _version)?);
510					})*
511					_ => return Err($crate::coding::DecodeError::InvalidValue),
512				}
513			}
514		}
515
516		// Shadow with unwrap_or_default: Option<T> defaults to None, T defaults to T::default()
517		$(#[allow(unused_variables)] let $name: $ty = $name.unwrap_or_default();)*
518	};
519}
520
521#[cfg(test)]
522mod tests {
523	use super::*;
524	use bytes::{Buf, BytesMut};
525
526	// ---- Setup Parameters tests (unchanged) ----
527
528	#[test]
529	fn test_parameters_v16_delta_round_trip() {
530		let mut params = Parameters::default();
531		params.set_bytes(ParameterBytes::Path, b"/test".to_vec());
532		params.set_varint(ParameterVarInt::MaxRequestId, 100);
533		params.set_bytes(ParameterBytes::Implementation, b"test-impl".to_vec());
534
535		let mut buf = BytesMut::new();
536		params.encode(&mut buf, Version::Draft16).unwrap();
537
538		let mut bytes = buf.freeze();
539		let decoded = Parameters::decode(&mut bytes, Version::Draft16).unwrap();
540
541		assert_eq!(decoded.get_bytes(ParameterBytes::Path), Some(b"/test".as_ref()));
542		assert_eq!(decoded.get_varint(ParameterVarInt::MaxRequestId), Some(100));
543		assert_eq!(
544			decoded.get_bytes(ParameterBytes::Implementation),
545			Some(b"test-impl".as_ref())
546		);
547	}
548
549	#[test]
550	fn test_parameters_v15_round_trip() {
551		let mut params = Parameters::default();
552		params.set_bytes(ParameterBytes::Path, b"/test".to_vec());
553		params.set_varint(ParameterVarInt::MaxRequestId, 100);
554
555		let mut buf = BytesMut::new();
556		params.encode(&mut buf, Version::Draft15).unwrap();
557
558		let mut bytes = buf.freeze();
559		let decoded = Parameters::decode(&mut bytes, Version::Draft15).unwrap();
560
561		assert_eq!(decoded.get_bytes(ParameterBytes::Path), Some(b"/test".as_ref()));
562		assert_eq!(decoded.get_varint(ParameterVarInt::MaxRequestId), Some(100));
563	}
564
565	#[test]
566	fn test_parameters_v17_round_trip() {
567		let mut params = Parameters::default();
568		params.set_bytes(ParameterBytes::Path, b"/test".to_vec());
569		params.set_varint(ParameterVarInt::MaxAuthTokenCacheSize, 4096);
570		params.set_bytes(ParameterBytes::Implementation, b"test-impl".to_vec());
571
572		let mut buf = BytesMut::new();
573		params.encode(&mut buf, Version::Draft17).unwrap();
574
575		let mut bytes = buf.freeze();
576		let decoded = Parameters::decode(&mut bytes, Version::Draft17).unwrap();
577
578		assert_eq!(decoded.get_bytes(ParameterBytes::Path), Some(b"/test".as_ref()));
579		assert_eq!(decoded.get_varint(ParameterVarInt::MaxAuthTokenCacheSize), Some(4096));
580		assert_eq!(
581			decoded.get_bytes(ParameterBytes::Implementation),
582			Some(b"test-impl".as_ref())
583		);
584		assert!(!bytes.has_remaining());
585	}
586
587	#[test]
588	fn test_parameters_v17_no_count_prefix() {
589		let mut params = Parameters::default();
590		params.set_bytes(ParameterBytes::Path, b"/x".to_vec());
591
592		let mut buf15 = BytesMut::new();
593		params.encode(&mut buf15, Version::Draft15).unwrap();
594
595		let mut buf17 = BytesMut::new();
596		params.encode(&mut buf17, Version::Draft17).unwrap();
597
598		assert!(buf17.len() < buf15.len());
599	}
600
601	// ---- Message Parameter (encode_params!/decode_params!) tests ----
602
603	fn round_trip_params(
604		version: Version,
605		encode_fn: impl FnOnce(&mut BytesMut, Version) -> Result<(), EncodeError>,
606		decode_fn: impl FnOnce(&mut bytes::Bytes, Version) -> Result<(), DecodeError>,
607	) {
608		let mut buf = BytesMut::new();
609		encode_fn(&mut buf, version).unwrap();
610		let mut bytes = buf.freeze();
611		decode_fn(&mut bytes, version).unwrap();
612		assert!(!bytes.has_remaining(), "buffer not fully consumed for {version}");
613	}
614
615	#[test]
616	fn test_param_u8_all_versions() {
617		for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] {
618			round_trip_params(
619				version,
620				|w, v| {
621					encode_params!(w, v, 0x20 => 200u8);
622					Ok(())
623				},
624				|r, v| {
625					decode_params!(r, v, 0x20 => val: Option<u8>);
626					assert_eq!(val, Some(200));
627					Ok(())
628				},
629			);
630		}
631	}
632
633	#[test]
634	fn test_param_bool_all_versions() {
635		for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] {
636			round_trip_params(
637				version,
638				|w, v| {
639					encode_params!(w, v, 0x10 => true);
640					Ok(())
641				},
642				|r, v| {
643					decode_params!(r, v, 0x10 => val: Option<bool>);
644					assert_eq!(val, Some(true));
645					Ok(())
646				},
647			);
648		}
649	}
650
651	#[test]
652	fn test_param_location_all_versions() {
653		let loc = Location { group: 5, object: 3 };
654		for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] {
655			round_trip_params(
656				version,
657				|w, v| {
658					encode_params!(w, v, 0x09 => loc.clone());
659					Ok(())
660				},
661				|r, v| {
662					decode_params!(r, v, 0x09 => val: Option<Location>);
663					assert_eq!(val, Some(Location { group: 5, object: 3 }));
664					Ok(())
665				},
666			);
667		}
668	}
669
670	#[test]
671	fn test_param_filter_type_all_versions() {
672		for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] {
673			round_trip_params(
674				version,
675				|w, v| {
676					encode_params!(w, v, 0x21 => FilterType::LargestObject);
677					Ok(())
678				},
679				|r, v| {
680					decode_params!(r, v, 0x21 => val: Option<FilterType>);
681					assert_eq!(val, Some(FilterType::LargestObject));
682					Ok(())
683				},
684			);
685		}
686	}
687
688	#[test]
689	fn test_param_multiple_delta_encoding() {
690		for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] {
691			round_trip_params(
692				version,
693				|w, v| {
694					encode_params!(w, v,
695						0x10 => true,
696						0x20 => 200u8,
697						0x21 => FilterType::LargestObject,
698						0x22 => 2u8,
699					);
700					Ok(())
701				},
702				|r, v| {
703					decode_params!(r, v,
704						0x10 => forward: Option<bool>,
705						0x20 => sub_pri: Option<u8>,
706						0x21 => filter: Option<FilterType>,
707						0x22 => group_order: Option<u8>,
708					);
709					assert_eq!(forward, Some(true));
710					assert_eq!(sub_pri, Some(200));
711					assert_eq!(filter, Some(FilterType::LargestObject));
712					assert_eq!(group_order, Some(2));
713					Ok(())
714				},
715			);
716		}
717	}
718
719	#[test]
720	fn test_param_empty_set() {
721		for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] {
722			round_trip_params(
723				version,
724				|w, v| {
725					encode_params!(w, v,);
726					Ok(())
727				},
728				|r, v| {
729					decode_params!(r, v,);
730					Ok(())
731				},
732			);
733		}
734	}
735
736	#[test]
737	fn test_param_option_skip_none() {
738		for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] {
739			round_trip_params(
740				version,
741				|w, v| {
742					let loc: Option<Location> = None;
743					encode_params!(w, v,
744						0x09 => loc,
745						0x10 => true,
746					);
747					Ok(())
748				},
749				|r, v| {
750					decode_params!(r, v,
751						0x09 => loc: Option<Location>,
752						0x10 => forward: Option<bool>,
753					);
754					assert_eq!(loc, None);
755					assert_eq!(forward, Some(true));
756					Ok(())
757				},
758			);
759		}
760	}
761
762	#[test]
763	fn test_param_option_encode_some() {
764		for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] {
765			round_trip_params(
766				version,
767				|w, v| {
768					let loc = Some(Location { group: 10, object: 5 });
769					encode_params!(w, v,
770						0x09 => loc,
771						0x10 => true,
772					);
773					Ok(())
774				},
775				|r, v| {
776					decode_params!(r, v,
777						0x09 => loc: Option<Location>,
778						0x10 => forward: Option<bool>,
779					);
780					assert_eq!(loc, Some(Location { group: 10, object: 5 }));
781					assert_eq!(forward, Some(true));
782					Ok(())
783				},
784			);
785		}
786	}
787
788	#[test]
789	fn test_param_bare_type_defaults() {
790		// Bare types use T::default() when the parameter is absent
791		for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] {
792			round_trip_params(
793				version,
794				|w, v| {
795					// Encode only 0x10, not 0x20
796					encode_params!(w, v, 0x10 => true);
797					Ok(())
798				},
799				|r, v| {
800					decode_params!(r, v,
801						0x10 => forward: bool,
802						0x20 => priority: u8,
803					);
804					assert!(forward);
805					assert_eq!(priority, 0); // u8::default()
806					Ok(())
807				},
808			);
809		}
810	}
811
812	#[test]
813	fn test_param_unknown_rejected() {
814		// Manually encode one param at key 0x10, try to decode expecting key 0x20
815		for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] {
816			let mut buf = BytesMut::new();
817			1usize.encode(&mut buf, version).unwrap();
818			0x10u64.encode(&mut buf, version).unwrap();
819			true.param_encode(&mut buf, version).unwrap();
820
821			let mut bytes = buf.freeze();
822			let result: Result<(), DecodeError> = (|| {
823				decode_params!(&mut bytes, version, 0x20 => val: Option<u8>);
824				let _ = val;
825				Ok(())
826			})();
827			assert!(
828				matches!(result, Err(DecodeError::InvalidValue)),
829				"expected InvalidValue for unknown param in {version}"
830			);
831		}
832	}
833
834	#[test]
835	fn test_param_duplicate_rejected() {
836		// Manually construct a buffer with duplicate key 0x20
837		for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] {
838			let mut buf = BytesMut::new();
839			// Encode count = 2
840			2usize.encode(&mut buf, version).unwrap();
841			match version {
842				Version::Draft16 | Version::Draft17 => {
843					// First: key delta=0x20, value=100
844					0x20u64.encode(&mut buf, version).unwrap();
845					100u8.param_encode(&mut buf, version).unwrap();
846					// Second: key delta=0 (same key 0x20), value=200
847					0u64.encode(&mut buf, version).unwrap();
848					200u8.param_encode(&mut buf, version).unwrap();
849				}
850				_ => {
851					// First: key=0x20, value=100
852					0x20u64.encode(&mut buf, version).unwrap();
853					100u8.param_encode(&mut buf, version).unwrap();
854					// Second: key=0x20, value=200
855					0x20u64.encode(&mut buf, version).unwrap();
856					200u8.param_encode(&mut buf, version).unwrap();
857				}
858			}
859
860			let mut bytes = buf.freeze();
861			let result: Result<(), DecodeError> = (|| {
862				decode_params!(&mut bytes, version, 0x20 => val: Option<u8>);
863				let _ = val;
864				Ok(())
865			})();
866			assert!(
867				matches!(result, Err(DecodeError::Duplicate)),
868				"expected Duplicate for {version}"
869			);
870		}
871	}
872}