Skip to main content

omp_core/encoding/
fixed_arr.rs

1//! Fixed-size array wrappers and formatting utilities for encoded strings.
2//!
3//! Provides [`Array`] and [`ArrayStr`] wrappers that efficiently store
4//! encoded/decoded data with dynamic length tracking while maintaining
5//! stack-allocated storage. These types support zero-copy access via `Deref`
6//! and include specialized formatting implementations for hex and base-N
7//! encodings.
8
9use std::{
10	borrow::{Borrow, BorrowMut},
11	fmt::{self, Write},
12	ops::{Deref, DerefMut},
13};
14
15/// A byte array wrapper that derefs to the decoded portion.
16#[derive(Debug, Clone, Copy)]
17pub struct Array<const N: usize>([u8; N], u16);
18
19impl<const N: usize> Default for Array<N> {
20	fn default() -> Self {
21		Self([0u8; N], 0)
22	}
23}
24
25impl<const N: usize> Array<N> {
26	/// Creates a new `Array` wrapping the given data with a specified valid
27	/// length.
28	pub(crate) const fn new(data: [u8; N], len: usize) -> Self {
29		debug_assert!(len <= N);
30		debug_assert!(len <= u16::MAX as usize);
31		Self(data, len as u16)
32	}
33
34	/// Returns the decoded bytes as a slice.
35	#[inline(always)]
36	pub const fn as_bytes(&self) -> &[u8] {
37		// SAFETY: self.1 <= N is guaranteed by the constructor assert.
38		unsafe { self.0.split_at_unchecked(self.1 as usize).0 }
39	}
40
41	/// Returns the decoded bytes as a mutable slice.
42	#[inline(always)]
43	pub const fn as_bytes_mut(&mut self) -> &mut [u8] {
44		// SAFETY: self.1 <= N is guaranteed by the constructor assert.
45		unsafe { self.0.split_at_mut_unchecked(self.1 as usize).0 }
46	}
47}
48
49impl<const N: usize> Deref for Array<N> {
50	type Target = [u8];
51
52	#[inline]
53	fn deref(&self) -> &Self::Target {
54		self.as_bytes()
55	}
56}
57
58impl<const N: usize> DerefMut for Array<N> {
59	#[inline]
60	fn deref_mut(&mut self) -> &mut Self::Target {
61		self.as_bytes_mut()
62	}
63}
64
65impl<const N: usize> AsRef<[u8]> for Array<N> {
66	#[inline]
67	fn as_ref(&self) -> &[u8] {
68		self.as_bytes()
69	}
70}
71
72impl<const N: usize> AsMut<[u8]> for Array<N> {
73	#[inline]
74	fn as_mut(&mut self) -> &mut [u8] {
75		self.as_bytes_mut()
76	}
77}
78
79/// An encoded string wrapper that stores the result.
80#[derive(Clone, Copy)]
81pub struct ArrayStr<const N: usize>([[u8; 2]; N], u16);
82
83impl<const N: usize> Default for ArrayStr<N> {
84	fn default() -> Self {
85		Self([[0u8; 2]; N], 0)
86	}
87}
88
89impl<const N: usize> ArrayStr<N> {
90	/// Creates a new `ArrayStr` wrapping the given data with a specified valid
91	/// length.
92	pub(crate) const fn new(data: [[u8; 2]; N], len: usize) -> Self {
93		debug_assert!(len <= N * 2, "Invalid length");
94		debug_assert!(len <= u16::MAX as usize, "Invalid length");
95		debug_assert!(data.as_flattened().split_at(len).0.is_ascii(), "Invalid ASCII");
96		Self(data, len as u16)
97	}
98
99	/// Truncates the `ArrayStr` to the specified length.
100	/// Does nothing if `new_len` is greater than the current length of the
101	/// `ArrayStr`.
102	pub const fn truncate(&mut self, new_len: usize) {
103		if new_len <= self.1 as usize {
104			self.1 = new_len as u16;
105		}
106	}
107
108	/// Returns the encoded string as bytes.
109	#[inline(always)]
110	pub const fn as_bytes(&self) -> &[u8] {
111		// SAFETY: self.1 <= N * 2 is guaranteed by the constructor assert.
112		unsafe { self.0.as_flattened().split_at_unchecked(self.1 as usize).0 }
113	}
114
115	/// Returns the encoded string as bytes mut.
116	///
117	/// # Safety
118	/// Caller must ensure that any modifications maintain valid base-N ASCII
119	/// characters.
120	#[inline(always)]
121	pub const unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
122		// SAFETY: self.1 <= N * 2 is guaranteed by the constructor assert.
123		unsafe {
124			self
125				.0
126				.as_flattened_mut()
127				.split_at_mut_unchecked(self.1 as usize)
128				.0
129		}
130	}
131
132	/// Returns the encoded string as a str.
133	#[inline(always)]
134	pub const fn as_str(&self) -> &str {
135		// SAFETY: Encoder produces only valid base-N ASCII characters, which are all
136		// valid UTF-8.
137		ascii_to_str(self.as_bytes())
138	}
139
140	/// Returns the encoded string as a mutable str.
141	#[inline]
142	pub const fn as_str_mut(&mut self) -> &mut str {
143		// SAFETY: We will only return a mutable str instance so cannot become invalid
144		// UTF-8.
145		ascii_to_str_mut(unsafe { self.as_bytes_mut() })
146	}
147}
148
149impl<const N: usize> Deref for ArrayStr<N> {
150	type Target = str;
151
152	#[inline]
153	fn deref(&self) -> &Self::Target {
154		self.as_str()
155	}
156}
157
158impl<const N: usize> DerefMut for ArrayStr<N> {
159	#[inline]
160	fn deref_mut(&mut self) -> &mut Self::Target {
161		self.as_str_mut()
162	}
163}
164
165impl<const N: usize> AsRef<[u8]> for ArrayStr<N> {
166	#[inline]
167	fn as_ref(&self) -> &[u8] {
168		self.as_bytes()
169	}
170}
171
172impl<const N: usize> AsRef<str> for ArrayStr<N> {
173	#[inline]
174	fn as_ref(&self) -> &str {
175		self.as_str()
176	}
177}
178
179impl<const N: usize> AsMut<str> for ArrayStr<N> {
180	#[inline]
181	fn as_mut(&mut self) -> &mut str {
182		self.as_str_mut()
183	}
184}
185
186impl<const N: usize> Borrow<str> for ArrayStr<N> {
187	#[inline]
188	fn borrow(&self) -> &str {
189		self.as_str()
190	}
191}
192
193impl<const N: usize> BorrowMut<str> for ArrayStr<N> {
194	#[inline]
195	fn borrow_mut(&mut self) -> &mut str {
196		self.as_str_mut()
197	}
198}
199
200impl<const N: usize> fmt::Debug for ArrayStr<N> {
201	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202		f.write_str("\"")?;
203		fmt::Display::fmt(self, f)?;
204		f.write_str("\"")?;
205		Ok(())
206	}
207}
208
209impl<const N: usize> fmt::Display for ArrayStr<N> {
210	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211		if self.1 as usize == N * 2 && f.alternate() {
212			f.write_str("0x")?;
213		}
214		format_with_precision(self.as_bytes().iter().copied(), f)
215	}
216}
217
218impl<const N: usize> fmt::LowerHex for ArrayStr<N> {
219	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220		// If this is a hex array, it will be entirely fiilled
221		if self.1 as usize == N * 2 {
222			if f.alternate() {
223				f.write_str("0x")?;
224			}
225			format_with_precision(
226				self
227					.as_bytes()
228					.iter()
229					.copied()
230					.map(|b| b.to_ascii_lowercase()),
231				f,
232			)
233		} else {
234			format_with_precision(self.as_bytes().iter().copied(), f)
235		}
236	}
237}
238
239impl<const N: usize> fmt::UpperHex for ArrayStr<N> {
240	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241		// If this is a hex array, it will be entirely fiilled
242		if self.1 as usize == N * 2 {
243			if f.alternate() {
244				f.write_str("0x")?;
245			}
246			format_with_precision(
247				self
248					.as_bytes()
249					.iter()
250					.copied()
251					.map(|b| b.to_ascii_uppercase()),
252				f,
253			)
254		} else {
255			format_with_precision(self.as_bytes().iter().copied(), f)
256		}
257	}
258}
259
260// ============================================================================
261// SHARED FORMATTING UTILITY
262// ============================================================================
263
264/// Converts ASCII bytes to a string slice without validation.
265///
266/// # Safety
267/// Must only be called on ASCII hex character bytes.
268#[inline(always)]
269pub const fn ascii_to_str(b: &[u8]) -> &str {
270	debug_assert!(b.is_ascii(), "Invalid ASCII");
271	// SAFETY: This function is only called on byte slices produced by the hex
272	// encoders, which only generate ASCII hex characters ('0'-'9', 'a'-'f',
273	// 'A'-'F'). All ASCII is valid UTF-8.
274	unsafe { str::from_utf8_unchecked(b) }
275}
276
277/// Converts ASCII bytes to a mutable string slice without validation.
278///
279/// # Safety
280/// Must only be called on ASCII hex character bytes.
281#[inline(always)]
282pub const fn ascii_to_str_mut(b: &mut [u8]) -> &mut str {
283	debug_assert!(b.is_ascii(), "Invalid ASCII");
284	// SAFETY: This function is only called on byte slices produced by the hex
285	// encoders, which only generate ASCII hex characters ('0'-'9', 'a'-'f',
286	// 'A'-'F'). All ASCII is valid UTF-8.
287	unsafe { str::from_utf8_unchecked_mut(b) }
288}
289
290/// Converts ASCII bytes to an owned string without validation.
291///
292/// # Safety
293/// Must only be called on ASCII hex character bytes.
294#[inline(always)]
295pub fn ascii_to_str_owned(b: Vec<u8>) -> String {
296	debug_assert!(b.as_slice().is_ascii(), "Invalid ASCII");
297	// SAFETY: This function is only called on byte slices produced by the hex
298	// encoders, which only generate ASCII hex characters ('0'-'9', 'a'-'f',
299	// 'A'-'F'). All ASCII is valid UTF-8.
300	unsafe { String::from_utf8_unchecked(b) }
301}
302
303/// Formats an iterator with optional precision and alignment.
304///
305/// Used internally by `Display`, `LowerHex`, and `UpperHex` implementations.
306pub fn format_with_precision(
307	mut it: impl ExactSizeIterator<Item = u8>,
308	f: &mut fmt::Formatter<'_>,
309) -> fmt::Result {
310	let Some(mut prec) = f.precision() else {
311		for item in it {
312			f.write_str(ascii_to_str(&[item]))?;
313		}
314		return Ok(());
315	};
316
317	let (align, fill) = match (f.align(), f.fill()) {
318		(Some(align), ' ') => (align, '…'),
319		(Some(align), fill) => (align, fill),
320		(None, _) => (fmt::Alignment::Center, '…'),
321	};
322
323	let len = it.len();
324	match align {
325		fmt::Alignment::Left => {
326			for item in it.by_ref() {
327				let Some(pnew) = prec.checked_sub(1) else {
328					f.write_char(fill)?;
329					break;
330				};
331				prec = pnew;
332				f.write_str(ascii_to_str(&[item]))?;
333			}
334		},
335		fmt::Alignment::Right => {
336			// If exact size iter:
337			let skip_count = len.saturating_sub(prec);
338			if skip_count > 0 {
339				f.write_char(fill)?;
340			}
341			for i in it.skip(skip_count) {
342				f.write_str(ascii_to_str(&[i]))?;
343			}
344		},
345		fmt::Alignment::Center => {
346			// If exact size iter:
347			let (mut l, mut r) = (len, 0);
348			if prec < l {
349				r = prec >> 1;
350				l = prec - r;
351			}
352
353			for i in (&mut it).take(l) {
354				f.write_str(ascii_to_str(&[i]))?;
355			}
356			if r > 0 {
357				f.write_char(fill)?;
358				for i in it.skip(len - r - l) {
359					f.write_str(ascii_to_str(&[i]))?;
360				}
361			}
362		},
363	}
364	Ok(())
365}
366
367pub fn serialize<S, F>(serializer: S, n: usize, wr: F) -> Result<S::Ok, S::Error>
368where
369	S: serde::Serializer,
370	F: FnOnce(&mut [u8]) -> usize,
371{
372	let mut stack = [0u8; 1024];
373	let mut heap = Vec::new();
374	let buffer = if let Some(slice) = stack.get_mut(..n) {
375		slice
376	} else {
377		heap.resize(n, 0);
378		&mut heap
379	};
380
381	let written = wr(buffer);
382	serializer.serialize_str(ascii_to_str(&buffer[..written]))
383}