Skip to main content

tightbeam/utils/
mod.rs

1//! Utility modules and functions
2
3use crate::error::TightBeamError;
4use crate::{Message, Version};
5
6#[cfg(feature = "builder")]
7use crate::builder::FrameBuilder;
8#[cfg(feature = "compress")]
9use crate::{
10	compress::{Compressor, Inflator},
11	error::CompressionError,
12	CompressedData,
13};
14
15// Submodules
16pub mod basis_points;
17pub mod jitter;
18pub mod math;
19pub mod statistics;
20pub mod task;
21pub mod urn;
22
23pub use basis_points::BasisPoints;
24
25// Re-exports
26#[cfg(feature = "hex")]
27pub use ::hex_literal::hex;
28
29/// Macro to implement From trait for both reference and owned types
30///
31/// This generates:
32/// - `impl From<&TightBeam> for TargetType` that clones or copies the field
33/// - `impl From<TightBeam> for TargetType` that delegates to the reference impl
34#[macro_export]
35macro_rules! impl_from {
36	// Pattern for field extraction from structs
37	($source:ty, $param:ident => $target:ty: $($field:tt)*) => {
38		impl From<&$source> for $target {
39			fn from($param: &$source) -> Self {
40				$($field)*
41			}
42		}
43
44		impl From<$source> for $target {
45			fn from($param: $source) -> Self {
46				(&$param).into()
47			}
48		}
49	};
50
51	// Pattern for error conversions (unconditional)
52	($from_type:ty => $target:ident::$variant:ident) => {
53		impl From<$from_type> for $target {
54			fn from(err: $from_type) -> Self {
55				$target::$variant(err)
56			}
57		}
58	};
59
60	// Pattern for conditional error conversions (with cfg)
61	(#[cfg($feature:meta)] $from_type:ty => $target:ident::$variant:ident) => {
62		#[cfg($feature)]
63		impl From<$from_type> for $target {
64			fn from(err: $from_type) -> Self {
65				$target::$variant(err)
66			}
67		}
68	};
69
70	// Pattern for conditional error conversions via enum wrapper (with cfg)
71	(#[cfg($feature:meta)] $from_type:ty => $target:ident::$variant:ident via $wrapper:path) => {
72		#[cfg($feature)]
73		impl From<$from_type> for $target {
74			fn from(err: $from_type) -> Self {
75				$target::$variant($wrapper(err))
76			}
77		}
78	};
79
80	// Pattern for unconditional error conversions via enum wrapper
81	($from_type:ty => $target:ident::$variant:ident via $wrapper:path) => {
82		impl From<$from_type> for $target {
83			fn from(err: $from_type) -> Self {
84				$target::$variant($wrapper(err))
85			}
86		}
87	};
88
89	// Pattern for extracting inner value from enum variant with fallback
90	($from_type:ty => $target:ident::$variant:ident extract $enum_variant:pat => $inner:ident else $fallback:expr) => {
91		impl From<$from_type> for $target {
92			fn from(err: $from_type) -> Self {
93				match err {
94					$enum_variant => $target::$variant($inner),
95					_ => $target::$variant($fallback),
96				}
97			}
98		}
99	};
100
101	// Pattern for extracting inner value from enum variant with fallback (conditional)
102	(#[cfg($feature:meta)] $from_type:ty => $target:ident::$variant:ident extract $enum_variant:pat => $inner:ident else $fallback:expr) => {
103		#[cfg($feature)]
104		impl From<$from_type> for $target {
105			fn from(err: $from_type) -> Self {
106				match err {
107					$enum_variant => $target::$variant($inner),
108					_ => $target::$variant($fallback),
109				}
110			}
111		}
112	};
113
114	// Pattern for unit variants (discard the error value)
115	($from_type:ty => $target:ident::$variant:ident discard) => {
116		impl From<$from_type> for $target {
117			fn from(_: $from_type) -> Self {
118				$target::$variant
119			}
120		}
121	};
122
123	// Pattern for unit variants with cfg (discard the error value)
124	(#[cfg($feature:meta)] $from_type:ty => $target:ident::$variant:ident discard) => {
125		#[cfg($feature)]
126		impl From<$from_type> for $target {
127			fn from(_: $from_type) -> Self {
128				$target::$variant
129			}
130		}
131	};
132
133	// Pattern for generic types to unit variants (e.g., PoisonError<T>)
134	(<$($gen:ident),+> $from_type:ty => $target:ident::$variant:ident discard) => {
135		impl<$($gen),+> From<$from_type> for $target {
136			fn from(_: $from_type) -> Self {
137				$target::$variant
138			}
139		}
140	};
141
142	// Pattern for generic types to unit variants with cfg
143	(#[cfg($feature:meta)] <$($gen:ident),+> $from_type:ty => $target:ident::$variant:ident discard) => {
144		#[cfg($feature)]
145		impl<$($gen),+> From<$from_type> for $target {
146			fn from(_: $from_type) -> Self {
147				$target::$variant
148			}
149		}
150	};
151
152	// Pattern for transformative conversions (wrap error via expression)
153	($from_type:ty => $target:ident::$variant:ident via |$err:ident| $transform:expr) => {
154		impl From<$from_type> for $target {
155			fn from($err: $from_type) -> Self {
156				$target::$variant($transform)
157			}
158		}
159	};
160
161	// Pattern for transformative conversions with cfg
162	(#[cfg($feature:meta)] $from_type:ty => $target:ident::$variant:ident via |$err:ident| $transform:expr) => {
163		#[cfg($feature)]
164		impl From<$from_type> for $target {
165			fn from($err: $from_type) -> Self {
166				$target::$variant($transform)
167			}
168		}
169	};
170}
171
172/// Macro to implement TryFrom trait for extracting optional fields
173///
174/// This generates:
175/// - `impl TryFrom<TightBeam> for TargetType` that consumes the source
176/// - Uses `take()` for zero-copy extraction when consuming the source
177#[macro_export]
178macro_rules! impl_try_from {
179	// For fields directly on TightBeam
180	($source:ty, $param:ident => $target:ty: $field:ident) => {
181		impl TryFrom<$source> for $target {
182			type Error = $crate::error::TightBeamError;
183
184			fn try_from(mut $param: $source) -> Result<Self, Self::Error> {
185				$param.$field.take().ok_or($crate::error::TightBeamError::InvalidMetadata)
186			}
187		}
188	};
189
190	($source:ty, $param:ident => $target:ty: $field:ident, $error:expr) => {
191		impl TryFrom<$source> for $target {
192			type Error = $crate::error::TightBeamError;
193
194			fn try_from(mut $param: $source) -> core::result::Result<Self, Self::Error> {
195				$param.$field.take().ok_or($error)
196			}
197		}
198	};
199
200	// For fields in metadata
201	($source:ty, $param:ident => $target:ty: metadata.$field:ident) => {
202		impl TryFrom<$source> for $target {
203			type Error = $crate::error::TightBeamError;
204
205			fn try_from(mut $param: $source) -> Result<Self, Self::Error> {
206				$param
207					.metadata
208					.$field
209					.take()
210					.ok_or($crate::error::TightBeamError::InvalidMetadata)
211			}
212		}
213	};
214
215	($source:ty, $param:ident => $target:ty: metadata.$field:ident, $error:expr) => {
216		impl TryFrom<$source> for $target {
217			type Error = $crate::error::TightBeamError;
218
219			fn try_from(mut $param: $source) -> core::result::Result<Self, Self::Error> {
220				$param.metadata.$field.take().ok_or($error)
221			}
222		}
223	};
224}
225
226/// Encode a value to DER format
227#[inline]
228pub fn encode<T: der::Encode>(value: &T) -> Result<Vec<u8>, TightBeamError> {
229	Ok(der::Encode::to_der(value)?)
230}
231
232/// Decode a value from MessageContent
233/// This is used for decoding messages from frame content
234#[inline]
235pub fn decode<'a, T: der::Decode<'a>>(content: &'a impl AsRef<[u8]>) -> Result<T, TightBeamError> {
236	Ok(der::Decode::from_der(content.as_ref())?)
237}
238
239/// Create a new FrameBuilder for the given message type and version
240///
241/// This is a convenience function for creating frames without using the
242/// `compose!` macro. Useful in contexts where macros cannot be used
243/// (e.g., within other macro definitions).
244pub fn compose<T: Message>(version: Version) -> FrameBuilder<T> {
245	FrameBuilder::from(version)
246}
247
248/// Compress data using the specified algorithm.
249#[cfg(feature = "compress")]
250#[inline]
251pub fn compress(
252	data: impl AsRef<[u8]>,
253	compressor: &impl Compressor,
254	content_info: Option<crate::cms::signed_data::EncapsulatedContentInfo>,
255) -> Result<(Vec<u8>, CompressedData), CompressionError> {
256	let data = data.as_ref();
257	compressor.compress(data, content_info)
258}
259
260/// Decompress data using the specified algorithm.
261#[cfg(feature = "compress")]
262#[inline]
263pub fn decompress(data: impl AsRef<[u8]>, inflator: &impl Inflator) -> Result<Vec<u8>, CompressionError> {
264	let data = data.as_ref();
265	inflator.decompress(data)
266}
267
268/// Compute a digest of the provided data using the specified algorithm.
269#[cfg(feature = "digest")]
270#[inline]
271pub fn digest<D: digest::Digest + crate::der::oid::AssociatedOid>(
272	data: impl AsRef<[u8]>,
273) -> Result<crate::asn1::DigestInfo, TightBeamError> {
274	let data = data.as_ref();
275
276	let mut hasher = D::new();
277	hasher.update(data);
278
279	let algorithm = crate::asn1::AlgorithmIdentifier { oid: D::OID, parameters: None };
280	let digest = hasher.finalize();
281	let digest_octet_string = crate::asn1::OctetString::new(&digest[..])?;
282	Ok(crate::asn1::DigestInfo { algorithm, digest: digest_octet_string })
283}
284
285#[cfg(test)]
286mod tests {
287	use super::*;
288
289	use crate::der::asn1::OctetStringRef;
290	use crate::der::oid::ObjectIdentifier;
291
292	#[test]
293	fn data_driven_der_round_trip() -> Result<(), TightBeamError> {
294		// OID cases
295		let oid_cases = [
296			ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1"),
297			ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.3.1"),
298			ObjectIdentifier::new_unwrap("2.5.4.3"),
299		];
300
301		// Octet string cases (varied sizes/patterns)
302		let octet_cases: &[&[u8]] = &[
303			b"",
304			b"a",
305			b"hello",
306			b"The quick brown fox jumps over the lazy dog",
307			&(0u16..64).map(|v| (v % 251) as u8).collect::<Vec<u8>>(),
308		];
309
310		for oid in oid_cases {
311			let enc = encode(&oid)?;
312			assert!(!enc.is_empty());
313			assert_eq!(enc[0], 0x06);
314
315			let dec: ObjectIdentifier = decode(&enc)?;
316			assert_eq!(dec, oid);
317		}
318
319		for data in octet_cases {
320			let oct = OctetStringRef::new(data)?;
321			let enc = encode(&oct)?;
322			assert_eq!(enc[0], 0x04);
323
324			let dec: OctetStringRef<'_> = decode(&enc)?;
325			assert_eq!(dec.as_bytes(), *data);
326		}
327
328		Ok(())
329	}
330
331	#[test]
332	#[cfg(feature = "compress")]
333	fn test_compress_decompress() -> Result<(), CompressionError> {
334		use crate::compress::ZstdCompression;
335
336		// Data-driven cases
337		let patterned = (0u32..2048).map(|i| (i % 251) as u8).collect::<Vec<u8>>();
338		let big_repeat = vec![b'a'; 16 * 1024];
339		let cases: Vec<&[u8]> = vec![
340			b"",
341			b"a",
342			b"hello world",
343			b"The quick brown fox jumps over the lazy dog",
344			&patterned,
345			&big_repeat,
346		];
347
348		let compressor = ZstdCompression;
349		for &data in &cases {
350			let (compressed, _info) = compress(data, &compressor, None)?;
351			let decompressed = decompress(&compressed, &compressor)?;
352			assert_eq!(decompressed, data);
353		}
354
355		Ok(())
356	}
357}