Skip to main content

omp_core/encoding/
hex.rs

1//! Hexadecimal encoding and decoding with streaming support.
2//!
3//! Provides upper/lowercase hex codecs with chunked encoding, streaming
4//! decoders, and utilities for formatting and parsing hex strings.
5
6use core::{mem::MaybeUninit, slice, str};
7use std::{cmp::Ordering, fmt, io, iter::FusedIterator};
8
9use bytes::{BufMut, Bytes};
10
11use super::fixed_arr::serialize;
12pub use super::{Array, ArrayStr, DecodeError, Result};
13
14// ============================================================================
15// HEX DECODER
16// ============================================================================
17
18/// A streaming hex decoder that converts hex characters to bytes.
19///
20/// Supports both even and odd-length hex strings. For odd-length strings,
21/// the first character is treated as a single nibble (0x0-0xF).
22///
23/// # Examples
24/// ```
25/// use omp_core::hex::Decoder;
26/// let hex = b"48656c6c6f"; // "Hello"
27/// let decoded = Decoder::new(hex).into_vec().unwrap();
28/// assert_eq!(decoded, b"Hello");
29/// ```
30#[derive(Debug, Clone)]
31pub struct Decoder<'a> {
32	/// Remaining single nibble for odd-length inputs
33	rem: Option<u8>,
34	/// Source iterator of hex characters
35	src: &'a [[u8; 2]],
36}
37
38impl<'a> Decoder<'a> {
39	/// Creates a new hex decoder from an iterator of bytes (ASCII hex
40	/// characters).
41	///
42	/// If the input has odd length, the first character is consumed and stored
43	/// as a single nibble to be emitted first.
44	pub const fn new(src: &'a [u8]) -> Self {
45		let (rem, src) = src.as_rchunks();
46		Self { rem: rem.first().copied(), src }
47	}
48
49	/// Skips the `0x` or `0X` prefix if present at the current position.
50	///
51	/// Consumes the decoder, skips the prefix if found, and returns a new
52	/// decoder. This method clones the iterator to peek ahead for prefix
53	/// detection.
54	///
55	/// # Examples
56	/// ```
57	/// use omp_core::hex::Decoder;
58	/// let hex = b"0x48656c6c6f";
59	/// let result = Decoder::new(hex).skip_0x().into_vec().unwrap();
60	/// assert_eq!(result, b"Hello");
61	/// ```
62	pub const fn skip_0x(self) -> Self {
63		match (self.rem, self.src) {
64			(Some(b'0'), [[b'x' | b'X', x], rest @ ..]) => Self { rem: Some(*x), src: rest },
65			(None, [[b'0', b'x' | b'X'], rest @ ..]) => Self { rem: None, src: rest },
66			_ => self,
67		}
68	}
69
70	/// Skips leading zero characters from the current position.
71	///
72	/// Consumes the decoder, skips all leading '0' characters, and returns a
73	/// new decoder.
74	///
75	/// # Examples
76	/// ```
77	/// use omp_core::hex::Decoder;
78	/// let hex = b"000048656c6c6f";
79	/// let result = Decoder::new(hex).skip_leading_zeros().into_vec().unwrap();
80	/// assert_eq!(result, b"Hello");
81	/// ```
82	pub const fn skip_leading_zeros(mut self) -> Self {
83		if let Some(v) = self.rem {
84			if v == b'0' {
85				self.rem = None;
86			} else {
87				return self;
88			}
89		}
90
91		loop {
92			match self.src {
93				[[b'0', b'0'], rest @ ..] => {
94					self.src = rest;
95					continue;
96				},
97				[[b'0', x], rest @ ..] => {
98					self.rem = Some(*x);
99					self.src = rest;
100				},
101				_ => {},
102			}
103			break self;
104		}
105	}
106
107	/// Collects the decoded bytes into a `Vec<u8>`.
108	///
109	/// Pre-allocates capacity based on the known decoded length.
110	pub fn into_vec(self) -> Result<Vec<u8>> {
111		let len = self.len();
112		let mut buf = Vec::<u8>::with_capacity(len);
113		let base = buf.spare_capacity_mut();
114		let mut di = 0;
115
116		// Handle odd-length prefix
117		if let Some(rem) = self.rem {
118			let n = parse_nibble(rem).ok_or(DecodeError::InvalidCharacter(rem))?;
119			// SAFETY: di=0 is always in bounds since len = rem.is_some() + src.len() >= 1
120			unsafe { base.get_unchecked_mut(di) }.write(n);
121			di += 1;
122		}
123
124		// HOT LOOP: Process 8 pairs at a time with minimal branching
125		let mut si = 0;
126		while si + 8 <= self.src.len() {
127			for _ in 0..8 {
128				let b = parse_byte(self.src[si])?;
129				// SAFETY: di < len is guaranteed by allocation and loop invariant
130				unsafe { base.get_unchecked_mut(di) }.write(b);
131				di += 1;
132				si += 1;
133			}
134		}
135
136		// Process remaining pairs
137		for &[h, l] in &self.src[si..] {
138			let b = parse_byte([h, l])?;
139			// SAFETY: di < len is guaranteed by allocation
140			unsafe { base.get_unchecked_mut(di) }.write(b);
141			di += 1;
142		}
143
144		debug_assert_eq!(di, len);
145		// SAFETY: We've initialized exactly `len` bytes via MaybeUninit::write:
146		// one nibble if rem was Some, then one byte per element in src.
147		// All writes succeeded (no error returns), so all bytes are valid.
148		unsafe { buf.set_len(len) };
149		Ok(buf)
150	}
151
152	/// Collects the decoded bytes into a `Bytes`.
153	pub fn into_bytes(self) -> Result<Bytes> {
154		self.into_vec().map(Bytes::from)
155	}
156
157	/// Collects the decoded bytes into a slice.
158	pub fn into_slice(mut self, mut buf: &mut [u8]) -> Result<usize> {
159		// Handle odd-length prefix
160		let mut n = if let Some(rem) = self.rem {
161			let Some(d) = buf.split_off_first_mut() else {
162				return Ok(0);
163			};
164			*d = parse_nibble(rem).ok_or(DecodeError::InvalidCharacter(rem))?;
165			1
166		} else {
167			0
168		};
169
170		// Process pairs
171		while let Some(d) = buf.split_off_first_mut()
172			&& let Some(&hl) = self.src.split_off_first()
173		{
174			*d = parse_byte(hl)?;
175			n += 1;
176		}
177		Ok(n)
178	}
179
180	/// Collects the decoded bytes into a `[u8; N]`.
181	pub fn into_array<const K: usize>(self) -> Result<[u8; K]> {
182		let mut buf = [0u8; K];
183		let n = self.into_slice(&mut buf)?;
184		if n != K {
185			return Err(DecodeError::InputTooShort);
186		}
187		Ok(buf)
188	}
189
190	/// Collects the decoded bytes into a `BufMut`.
191	///
192	/// Efficiently writes decoded bytes directly into the buffer's
193	/// uninitialized memory.
194	#[inline]
195	pub fn into_buf<B: BufMut>(mut self, mut buf: B) -> Result<B> {
196		loop {
197			let mut n = 0;
198			// SAFETY: BufMut::chunk_mut() returns a valid uninit slice within the buffer's
199			// capacity. We only write to these bytes via MaybeUninit::write, never read
200			// them.
201			let chunk = unsafe { buf.chunk_mut().as_uninit_slice_mut() };
202			for (b, dst) in self.by_ref().zip(&mut *chunk) {
203				dst.write(b?);
204				n += 1;
205			}
206			let exhausted = n < chunk.len();
207
208			// SAFETY: We have initialized exactly `n` bytes via MaybeUninit::write in the
209			// loop above. BufMut's contract requires we advance by the number of bytes
210			// written.
211			unsafe { buf.advance_mut(n) };
212
213			// If we didn't fill the buffer, we've exhausted the source
214			if exhausted {
215				break Ok(buf);
216			}
217		}
218	}
219
220	/// Extends an existing collection with the decoded bytes.
221	///
222	/// Returns the number of bytes decoded and added to the collection.
223	pub fn extend_into<E: Extend<u8> + ?Sized>(self, buf: &mut E) -> Result<usize> {
224		buf.extend_reserve(self.len());
225		let mut n = 0;
226		for byte in self {
227			let byte = byte?;
228			buf.extend_one(byte);
229			n += 1;
230		}
231		Ok(n)
232	}
233
234	/// Writes the decoded bytes to an `io::Write`.
235	///
236	/// Returns the number of bytes written.
237	pub fn write_into<W: io::Write + ?Sized>(mut self, writer: &mut W) -> io::Result<usize> {
238		let mut buf = [MaybeUninit::<u8>::uninit(); 512];
239		let mut n = 0;
240		let mut done = false;
241		while !done {
242			let mut i = 0;
243			for dst in &mut buf {
244				if let Some(b) = self.next() {
245					dst.write(b.map_err(io::Error::other)?);
246					i += 1;
247				} else {
248					done = true;
249					break;
250				}
251			}
252			n += i;
253
254			if i != 0 {
255				// SAFETY: We've initialized exactly buf[0..i] via MaybeUninit::write in the
256				// loop above. Casting the pointer from MaybeUninit<u8> to u8 is valid for the
257				// first `i` elements, and slice::from_raw_parts creates a valid &[u8] view.
258				unsafe { writer.write_all(slice::from_raw_parts(buf.as_ptr().cast(), i))? };
259			}
260		}
261		Ok(n)
262	}
263}
264
265impl Iterator for Decoder<'_> {
266	type Item = Result<u8>;
267
268	#[inline]
269	fn next(&mut self) -> Option<Self::Item> {
270		// Handle odd-length source: emit single nibble first
271		if let Some(s0) = self.rem.take() {
272			return Some(parse_nibble(s0).ok_or(DecodeError::InvalidCharacter(s0)));
273		}
274
275		// Process two hex characters into one byte
276		match parse_byte(*self.src.split_off_first()?) {
277			Ok(b) => Some(Ok(b)),
278			Err(e) => Some(Err(e)),
279		}
280	}
281
282	#[inline]
283	fn size_hint(&self) -> (usize, Option<usize>) {
284		let n = self.len();
285		(n, Some(n))
286	}
287}
288
289impl DoubleEndedIterator for Decoder<'_> {
290	#[inline]
291	fn next_back(&mut self) -> Option<Self::Item> {
292		// Try to get two characters from the back
293		if let Some(x) = self.src.split_off_last() {
294			return match parse_byte(*x) {
295				Ok(b) => Some(Ok(b)),
296				Err(e) => Some(Err(e)),
297			};
298		}
299
300		// If no pairs left, return the single nibble if present
301		if let Some(s0) = self.rem.take() {
302			return Some(parse_nibble(s0).ok_or(DecodeError::InvalidCharacter(s0)));
303		}
304
305		None
306	}
307}
308
309impl ExactSizeIterator for Decoder<'_> {
310	#[inline]
311	fn len(&self) -> usize {
312		self.src.len() + usize::from(self.rem.is_some())
313	}
314}
315
316impl FusedIterator for Decoder<'_> {}
317
318impl<const N: usize> TryFrom<Decoder<'_>> for [u8; N] {
319	type Error = DecodeError;
320
321	fn try_from(decoder: Decoder<'_>) -> Result<Self> {
322		decoder.into_array()
323	}
324}
325
326impl fmt::Display for Decoder<'_> {
327	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328		for byte in self.clone() {
329			if let Ok(byte) = byte {
330				write!(f, "{byte:02x}")?;
331			} else {
332				write!(f, "??")?;
333			}
334		}
335		Ok(())
336	}
337}
338
339impl PartialEq<[u8]> for Decoder<'_> {
340	fn eq(&self, other: &[u8]) -> bool {
341		self.clone().eq(other.iter().map(|&b| Ok(b)))
342	}
343}
344
345impl PartialOrd<[u8]> for Decoder<'_> {
346	fn partial_cmp(&self, other: &[u8]) -> Option<Ordering> {
347		Some(self.clone().cmp(other.iter().map(|&b| Ok(b))))
348	}
349}
350
351/// Decodes a hex string in-place, writing to a destination buffer.
352///
353/// Returns the number of bytes written.
354/// This is a const function suitable for compile-time evaluation.
355///
356/// # Errors
357/// Returns `Error::InvalidCharacter` if any invalid hex character is
358/// encountered.
359pub const fn decode_mut(src: &[u8], dst: &mut [u8]) -> Result<usize> {
360	let mut i = 0;
361	let mut si = src;
362
363	// Handle odd-length source: decode single nibble first
364	if let [s0, sn @ ..] = si
365		&& sn.len() & 1 == 0
366		&& dst.len() > i
367	{
368		let Some(s0) = parse_nibble(*s0) else {
369			return Err(DecodeError::InvalidCharacter(*s0));
370		};
371		dst[i] = s0;
372		si = sn;
373		i += 1;
374	}
375
376	// Process pairs of hex characters
377	while let [c0, c1, sn @ ..] = si
378		&& dst.len() > i
379	{
380		let c0v = *c0;
381		let c1v = *c1;
382		dst[i] = match parse_byte([c0v, c1v]) {
383			Ok(b) => b,
384			Err(e) => return Err(e),
385		};
386		si = sn;
387		i += 1;
388	}
389
390	Ok(i)
391}
392
393/// Decodes a hex string into a vector of bytes.
394///
395/// # Examples
396/// ```
397/// use omp_core::hex::decode;
398/// let result = decode(b"48656c6c6f").into_vec().unwrap();
399/// assert_eq!(result, b"Hello");
400/// ```
401#[inline]
402pub fn decode<T: AsRef<[u8]> + ?Sized>(src: &T) -> Decoder<'_> {
403	Decoder::new(src.as_ref())
404}
405
406// ============================================================================
407// HEX PREFIX UTILITIES
408// ============================================================================
409
410/// Skips the `0x` or `0X` prefix from a byte slice if present.
411///
412/// # Examples
413/// ```
414/// use omp_core::hex;
415/// let result = hex::Decoder::new(hex::skip_0x(b"0x48656c6c6f"))
416/// 	.into_vec()
417/// 	.unwrap();
418/// assert_eq!(result, b"Hello");
419///
420/// // No prefix - returns original slice
421/// assert_eq!(hex::skip_0x(b"48656c6c6f"), b"48656c6c6f");
422/// ```
423#[inline]
424pub const fn skip_0x(src: &[u8]) -> &[u8] {
425	match src {
426		[b'0', b'x' | b'X', rest @ ..] => rest,
427		_ => src,
428	}
429}
430
431/// Skips leading zero characters from a byte slice.
432///
433/// # Examples
434/// ```
435/// use omp_core::hex;
436/// let result = hex::Decoder::new(hex::skip_leading_zeros(b"000048656c6c6f"))
437/// 	.into_vec()
438/// 	.unwrap();
439/// assert_eq!(result, b"Hello");
440///
441/// // All zeros - returns empty slice
442/// assert_eq!(hex::skip_leading_zeros(b"0000"), b"");
443///
444/// // No leading zeros - returns original slice
445/// assert_eq!(hex::skip_leading_zeros(b"48656c6c6f"), b"48656c6c6f");
446/// ```
447#[inline]
448pub const fn skip_leading_zeros(mut src: &[u8]) -> &[u8] {
449	while let [b'0', rest @ ..] = src {
450		src = rest;
451	}
452	src
453}
454
455// ============================================================================
456// HEX PARSING UTILITIES
457// ============================================================================
458
459/// Lookup table for fast hex character to nibble conversion.
460/// Maps ASCII characters to nibble values (0-15), with invalid chars mapped to
461/// 0x80.
462const DEC_TABLE: [u8; 0x100] = {
463	let mut table = [0x80; 0x100];
464	let mut i = 0;
465
466	// Map '0'-'9' to 1-10 (actual values 0-9)
467	while i <= 0xf {
468		let c = char::from_digit(i as u32, 0x10).unwrap();
469		table[c.to_ascii_lowercase() as usize] = i;
470		table[c.to_ascii_uppercase() as usize] = i;
471		i += 1;
472	}
473
474	table
475};
476
477/// Safely parses a hex character into a nibble (0-15).
478///
479/// Returns `None` for invalid hex characters.
480///
481/// # Examples
482/// ```
483/// use omp_core::hex::parse_nibble;
484/// assert_eq!(parse_nibble(b'A'), Some(10));
485/// assert_eq!(parse_nibble(b'f'), Some(15));
486/// assert_eq!(parse_nibble(b'0'), Some(0));
487/// assert_eq!(parse_nibble(b'g'), None);
488/// ```
489#[inline]
490pub const fn parse_nibble(b: u8) -> Option<u8> {
491	let v = DEC_TABLE[b as usize];
492	if v.cast_signed() >= 0 {
493		Some(v)
494	} else {
495		std::hint::cold_path();
496		None
497	}
498}
499
500/// Parses two hex characters into a byte.
501///
502/// # Examples
503/// ```
504/// use omp_core::hex::parse_byte;
505/// assert_eq!(parse_byte([b'4', b'8']).unwrap(), 0x48);
506/// assert_eq!(parse_byte([b'f', b'f']).unwrap(), 0xff);
507/// ```
508#[inline]
509pub const fn parse_byte([h, l]: [u8; 2]) -> Result<u8> {
510	let hv = DEC_TABLE[h as usize];
511	let lv = DEC_TABLE[l as usize];
512
513	if (hv | lv).cast_signed() >= 0 {
514		Ok((hv << 4) | lv)
515	} else {
516		std::hint::cold_path();
517		let inv = if hv.cast_signed() >= 0 { l } else { h };
518		Err(DecodeError::InvalidCharacter(inv))
519	}
520}
521
522// ============================================================================
523// HEX ENCODER
524// ============================================================================
525
526const ALPHABET: [u8; 32] = *b"0123456789abcdef0123456789ABCDEF";
527
528/// Byte->pair LUT for lowercase hex (256 bytes -> 256 u16 pairs)
529const LUT: [[u16; 256]; 2] = {
530	let mut t = [[0u16; 256]; 2];
531	{
532		let t = t[0].as_mut_slice();
533		let mut i = 0u16;
534		while i < 256 {
535			let b = (i & 0xff) as u8;
536			let h = LOWER.encode_nibble(b >> 4);
537			let l = LOWER.encode_nibble(b & 0x0f);
538			t[i as usize] = u16::from_ne_bytes([h, l]);
539			i += 1;
540		}
541	}
542	{
543		let t = t[1].as_mut_slice();
544		let mut i = 0u16;
545		while i < 256 {
546			let b = (i & 0xff) as u8;
547			let h = UPPER.encode_nibble(b >> 4);
548			let l = UPPER.encode_nibble(b & 0x0f);
549			t[i as usize] = u16::from_ne_bytes([h, l]);
550			i += 1;
551		}
552	}
553	t
554};
555
556/// Character set for hex encoding (uppercase or lowercase).
557#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
558#[repr(u8)]
559pub enum Encoding {
560	/// Lowercase hex digits (a-f)
561	Lowercase = 0,
562	/// Uppercase hex digits (A-F)
563	Uppercase = 1,
564}
565
566/// Standard hex encoding.
567pub const STD: Encoding = Encoding::Lowercase;
568
569/// Lowercase hex digits (a-f)
570pub const LOWER: Encoding = Encoding::Lowercase;
571
572/// Uppercase hex digits (A-F)
573pub const UPPER: Encoding = Encoding::Uppercase;
574
575impl Encoding {
576	/// Converts a nibble (0-15) to a hex character using branchless arithmetic.
577	///
578	/// # Examples
579	/// ```
580	/// use omp_core::hex::{LOWER, UPPER};
581	/// assert_eq!(LOWER.encode_nibble(10), b'a');
582	/// assert_eq!(UPPER.encode_nibble(15), b'F');
583	/// ```
584	#[inline]
585	pub const fn encode_nibble(self, nibble: u8) -> u8 {
586		let idx = (((self as usize) << 4) + (nibble as usize)) & 31;
587		ALPHABET[idx]
588	}
589
590	/// Encodes bytes to hex into a flat byte buffer.
591	///
592	/// Writes ASCII hex characters to `dst` and returns the number of bytes
593	/// written. The output length is `min(dst.len(), 2 * src.len())`.
594	///
595	/// # Examples
596	/// ```
597	/// use omp_core::hex;
598	/// let mut buf = [0u8; 10];
599	/// let n = hex::LOWER.encode_mut(b"Hello", &mut buf);
600	/// assert_eq!(n, 10);
601	/// assert_eq!(&buf, b"48656c6c6f");
602	/// ```
603	#[inline]
604	pub const fn encode_mut(self, src: &[u8], dst: &mut [u8]) -> usize {
605		let lut = self.lut();
606		let mut n = dst.len() >> 1;
607		if n > src.len() {
608			n = src.len();
609		}
610
611		let src = src.as_ptr();
612		let dst = dst.as_mut_ptr();
613		let mut i = 0;
614		while i < n {
615			// SAFETY: we only write within `n * 2` bytes, which is <= dst.len()/2 as well
616			// as <= src.len()
617			unsafe {
618				dst.add(i << 1)
619					.cast::<u16>()
620					.write_unaligned(lut[src.add(i).read() as usize]);
621			}
622			i += 1;
623		}
624		n << 1
625	}
626
627	/// Encodes a single byte to a hex pair.
628	///
629	/// # Examples
630	/// ```
631	/// use omp_core::hex::{LOWER, UPPER};
632	/// assert_eq!(LOWER.encode_byte(0x48), *b"48");
633	/// assert_eq!(UPPER.encode_byte(0xff), *b"FF");
634	/// ```
635	#[inline]
636	pub const fn encode_byte(self, byte: u8) -> [u8; 2] {
637		LUT[self as usize][byte as usize].to_ne_bytes()
638	}
639
640	/// Encodes a byte array to a hex string at compile time.
641	#[inline]
642	pub const fn encode_n<const N: usize>(self, src: &[u8; N]) -> ArrayStr<N> {
643		let mut out = [[0u8; 2]; N];
644		self.encode_mut(src.as_slice(), out.as_flattened_mut());
645		ArrayStr::new(out, N * 2)
646	}
647
648	/// Returns the lookup table for this charset.
649	#[inline]
650	pub const fn lut(self) -> &'static [u16; 256] {
651		&LUT[self as usize]
652	}
653}
654
655/// Iterator that encodes bytes as individual hex ASCII characters.
656///
657/// Each input byte produces two output bytes (hex characters).
658/// Maintains state for bidirectional iteration.
659///
660/// # Examples
661/// ```
662/// use omp_core::hex::Encoder;
663/// let data = b"Hi";
664/// let chars: Vec<u8> = Encoder::new(data).collect();
665/// assert_eq!(chars, b"4869");
666/// ```
667#[derive(Debug, Clone)]
668pub struct Encoder<'a> {
669	/// Source iterator of bytes to encode
670	src:     &'a [u8],
671	/// Output charset (uppercase or lowercase)
672	charset: Encoding,
673	/// Pending low nibble from forward iteration
674	low:     Option<u8>,
675	/// Pending high nibble from backward iteration
676	high:    Option<u8>,
677}
678
679impl<'a> From<&'a [u8]> for Encoder<'a> {
680	fn from(src: &'a [u8]) -> Self {
681		Self { src, charset: LOWER, low: None, high: None }
682	}
683}
684
685impl<'a> Encoder<'a> {
686	/// Creates a new ASCII encoder from an iterator of bytes.
687	pub fn new(src: &'a [u8]) -> Self {
688		src.into()
689	}
690
691	/// Sets the encoder to lowercase mode.
692	pub const fn lower(mut self) -> Self {
693		self.charset = LOWER;
694		self
695	}
696
697	/// Sets the encoder to uppercase mode.
698	pub const fn upper(mut self) -> Self {
699		self.charset = UPPER;
700		self
701	}
702
703	/// Sets the encoder charset.
704	pub const fn with_charset(mut self, charset: Encoding) -> Self {
705		self.charset = charset;
706		self
707	}
708
709	/// Converts to a `CharEncoder` that yields `char` instead of `u8`.
710	#[define_opaque(CharEncoder)]
711	pub fn into_chars(self) -> CharEncoder<'a> {
712		self.map(|x| x as char)
713	}
714
715	/// Collects into a `Vec<u8>`.
716	pub fn into_vec(self) -> Vec<u8> {
717		let lut = self.charset.lut();
718		let out_len = self.len();
719		let mut buf = Vec::<u8>::with_capacity(out_len);
720		if let Some(low) = self.low {
721			buf.push(self.charset.encode_nibble(low));
722		}
723		let pairs_end = buf.len() + 2 * self.src.len();
724		let base = buf.spare_capacity_mut();
725		for (i, &byte) in self.src.iter().enumerate() {
726			// SAFETY: We allocated `out_len >= pairs_end` capacity. Each src byte
727			// at index `i` writes the u16 at offset `i` in spare capacity, staying
728			// within bounds. Writes don't overlap since i is unique.
729			unsafe {
730				base
731					.as_mut_ptr()
732					.cast::<u16>()
733					.add(i)
734					.write_unaligned(lut[byte as usize]);
735			};
736		}
737		// SAFETY: bytes [len, pairs_end) were fully initialized by the loop
738		// above; bytes below len were initialized by the optional push.
739		unsafe { buf.set_len(pairs_end) };
740		// Publish the pairs BEFORE appending the pending high nibble: push
741		// writes at the current length, so it must come after set_len.
742		if let Some(high) = self.high {
743			buf.push(self.charset.encode_nibble(high));
744		}
745		debug_assert_eq!(buf.len(), out_len);
746		buf
747	}
748
749	/// Collects into a `Bytes`.
750	pub fn into_bytes(self) -> Bytes {
751		Bytes::from(self.into_vec())
752	}
753
754	/// Collects into a String.
755	pub fn into_string(self) -> String {
756		super::ascii_to_str_owned(self.into_vec())
757	}
758
759	/// Extends into an existing buffer.
760	pub fn extend_into<E: Extend<u8> + ?Sized>(self, buf: &mut E) {
761		buf.extend(self);
762	}
763
764	/// Collects the encoded output into a `BufMut`.
765	pub fn into_buf<B: BufMut>(mut self, mut buf: B) -> B {
766		loop {
767			let mut n = 0;
768			// SAFETY: BufMut::chunk_mut() returns a valid uninit slice within the buffer's
769			// capacity. We only write to these bytes via MaybeUninit::write, never read
770			// them.
771			let chunk = unsafe { buf.chunk_mut().as_uninit_slice_mut() };
772			for (b, d) in self.by_ref().zip(&mut *chunk) {
773				d.write(b);
774				n += 1;
775			}
776			let exhausted = n < chunk.len();
777
778			// SAFETY: We have initialized exactly `n` bytes via MaybeUninit::write in the
779			// loop above. BufMut's contract requires we advance by the number of bytes
780			// written.
781			unsafe { buf.advance_mut(n) };
782
783			// If we didn't fill the buffer, we've exhausted the source
784			if exhausted {
785				break buf;
786			}
787		}
788	}
789
790	/// Writes to an `io::Write`.
791	pub fn write_into<W: io::Write + ?Sized>(self, writer: &mut W) -> io::Result<usize> {
792		let Self { src: mut it, charset, low, mut high } = self;
793
794		let mut n = 0;
795
796		let mut buf = MaybeUninit::<[[u8; 2]; 64]>::uninit().transpose();
797		if let Some(low) = low {
798			buf[0].write([0, charset.encode_nibble(low)]);
799			n += 1;
800
801			for d in &mut buf[1..] {
802				let Some(&b) = it.split_off_first() else {
803					break;
804				};
805				d.write(charset.encode_byte(b));
806				n += 2;
807			}
808
809			// SAFETY: The loop initialized the flattened byte range 1..=n. The
810			// range is in bounds because `buf` holds 128 bytes and `n <= 127`.
811			let data = unsafe { slice::from_raw_parts(buf.as_ptr().cast::<u8>().add(1), n) };
812			writer.write_all(data)?;
813		}
814
815		while !it.is_empty() {
816			let mut local = 0;
817			for d in &mut buf {
818				let Some(&b) = it.split_off_first() else {
819					if let Some(hi) = high.take() {
820						// Counted via `local` so the flattened write below includes it.
821						d.write([charset.encode_nibble(hi), 0]);
822						local += 1;
823					}
824					break;
825				};
826				d.write(charset.encode_byte(b));
827				local += 2;
828			}
829			// SAFETY: The loop initialized the first `local` flattened bytes, and
830			// `local` cannot exceed the 128-byte capacity of `buf`.
831			let data = unsafe { slice::from_raw_parts(buf.as_ptr().cast::<u8>(), local) };
832			writer.write_all(data)?;
833			n += local;
834		}
835
836		if let Some(high) = high {
837			writer.write_all(&[charset.encode_nibble(high)])?;
838			n += 1;
839		}
840		Ok(n)
841	}
842
843	/// Writes to a `fmt::Write`.
844	pub fn format_into<W: fmt::Write + ?Sized>(self, writer: &mut W) -> fmt::Result {
845		for bytes in self {
846			writer.write_str(super::ascii_to_str(&[bytes]))?;
847		}
848		Ok(())
849	}
850}
851
852impl From<Encoder<'_>> for String {
853	fn from(encoder: Encoder<'_>) -> Self {
854		encoder.into_string()
855	}
856}
857
858impl From<Encoder<'_>> for Bytes {
859	fn from(encoder: Encoder<'_>) -> Self {
860		encoder.into_bytes()
861	}
862}
863
864impl From<Encoder<'_>> for Vec<u8> {
865	fn from(encoder: Encoder<'_>) -> Self {
866		encoder.into_vec()
867	}
868}
869
870impl Iterator for Encoder<'_> {
871	type Item = u8;
872
873	fn next(&mut self) -> Option<Self::Item> {
874		// If we have a pending low nibble, emit it
875		if let Some(low) = self.low.take() {
876			return Some(self.charset.encode_nibble(low));
877		}
878
879		// If we can read from source, get next byte
880		if let Some(byte) = self.src.split_off_first() {
881			let byte = *byte;
882			let high = byte >> 4;
883			let low = byte & 0x0f;
884			self.low = Some(low);
885			return Some(self.charset.encode_nibble(high));
886		}
887
888		// If we have a pending high nibble, emit it
889		if let Some(high) = self.high.take() {
890			return Some(self.charset.encode_nibble(high));
891		}
892
893		None
894	}
895
896	fn size_hint(&self) -> (usize, Option<usize>) {
897		let n = self.len();
898		(n, Some(n))
899	}
900}
901
902impl DoubleEndedIterator for Encoder<'_> {
903	fn next_back(&mut self) -> Option<Self::Item> {
904		// If we have a pending high nibble, emit it
905		if let Some(high) = self.high.take() {
906			return Some(self.charset.encode_nibble(high));
907		}
908
909		// If we can read from source, get next byte
910		if let Some(byte) = self.src.split_off_last() {
911			let byte = *byte;
912			let high = byte >> 4;
913			let low = byte & 0x0f;
914			self.high = Some(high);
915			return Some(self.charset.encode_nibble(low));
916		}
917
918		// If we have a pending low nibble, emit it
919		if let Some(low) = self.low.take() {
920			return Some(self.charset.encode_nibble(low));
921		}
922
923		None
924	}
925}
926
927impl ExactSizeIterator for Encoder<'_> {
928	fn len(&self) -> usize {
929		let rem = self.low.is_some() as usize + self.high.is_some() as usize;
930		(self.src.len() << 1) + rem
931	}
932}
933
934impl FusedIterator for Encoder<'_> {}
935
936impl fmt::Display for Encoder<'_> {
937	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
938		super::format_with_precision(self.clone(), f)
939	}
940}
941
942impl fmt::LowerHex for Encoder<'_> {
943	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
944		fmt::Display::fmt(&self.clone().lower(), f)
945	}
946}
947
948impl fmt::UpperHex for Encoder<'_> {
949	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
950		fmt::Display::fmt(&self.clone().upper(), f)
951	}
952}
953
954impl<'b> PartialEq<Encoder<'b>> for Encoder<'_> {
955	fn eq(&self, other: &Encoder<'b>) -> bool {
956		self.clone().eq(other.clone())
957	}
958}
959
960impl Ord for Encoder<'_> {
961	fn cmp(&self, other: &Self) -> Ordering {
962		self.clone().cmp(other.clone())
963	}
964}
965
966impl<'b> PartialOrd<Encoder<'b>> for Encoder<'_> {
967	fn partial_cmp(&self, other: &Encoder<'b>) -> Option<Ordering> {
968		self.clone().partial_cmp(other.clone())
969	}
970}
971
972impl Eq for Encoder<'_> {}
973
974impl PartialEq<[u8]> for Encoder<'_> {
975	fn eq(&self, other: &[u8]) -> bool {
976		self.clone().eq(other.iter().copied())
977	}
978}
979
980impl PartialEq<str> for Encoder<'_> {
981	fn eq(&self, other: &str) -> bool {
982		self.clone().eq(other.as_bytes().iter().copied())
983	}
984}
985
986impl PartialOrd<[u8]> for Encoder<'_> {
987	fn partial_cmp(&self, other: &[u8]) -> Option<Ordering> {
988		Some(self.clone().cmp(other.iter().copied()))
989	}
990}
991
992impl PartialOrd<str> for Encoder<'_> {
993	fn partial_cmp(&self, other: &str) -> Option<Ordering> {
994		Some(self.clone().cmp(other.as_bytes().iter().copied()))
995	}
996}
997
998impl serde::Serialize for Encoder<'_> {
999	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1000		let len = encode_len(self.src.len());
1001		serialize(serializer, len, |buffer| self.charset.encode_mut(self.src, buffer))
1002	}
1003}
1004
1005/// Iterator that encodes bytes as hex characters (`char` type).
1006///
1007/// This is an opaque type alias that yields `char` values instead of `u8`.
1008/// Use [`Encoder::into_chars`] to create one.
1009pub type CharEncoder<'a> =
1010	impl ExactSizeIterator<Item = char> + DoubleEndedIterator + FusedIterator + 'a;
1011
1012// ============================================================================
1013// CONVENIENCE FUNCTIONS
1014// ============================================================================
1015
1016/// Encodes bytes to lowercase hex, returning an `Encoder`.
1017///
1018/// # Examples
1019/// ```
1020/// use omp_core::hex::encode;
1021/// let hex_string = encode(b"Hello").into_string();
1022/// assert_eq!(hex_string, "48656c6c6f");
1023/// ```
1024pub fn encode<I: AsRef<[u8]> + ?Sized>(src: &I) -> Encoder<'_> {
1025	Encoder::new(src.as_ref())
1026}
1027
1028/// Encodes bytes to lowercase hex into a flat byte buffer.
1029///
1030/// Writes ASCII hex characters to `dst` and returns the number of bytes
1031/// written. The output length is `min(dst.len(), 2 * src.len())`.
1032///
1033/// # Examples
1034/// ```
1035/// use omp_core::hex;
1036/// let mut buf = [0u8; 10];
1037/// let n = hex::encode_mut(b"Hello", &mut buf);
1038/// assert_eq!(n, 10);
1039/// assert_eq!(&buf, b"48656c6c6f");
1040/// ```
1041#[inline]
1042pub const fn encode_mut(src: &[u8], dst: &mut [u8]) -> usize {
1043	LOWER.encode_mut(src, dst)
1044}
1045
1046// ============================================================================
1047// CONST ENCODING/DECODING HELPERS
1048// ============================================================================
1049
1050/// Decodes hex at compile time with validation.
1051///
1052/// # Examples
1053/// ```
1054/// use omp_core::hex::{Array, decode_n};
1055/// let decoded = decode_n(b"48656c6c6f").unwrap();
1056/// assert_eq!(&*decoded, b"Hello");
1057/// ```
1058pub const fn decode_n<const N: usize>(src: &[u8; N]) -> Option<Array<N>> {
1059	let mut out = [0; _];
1060	let Ok(written) = decode_mut(src.as_slice(), out.as_mut_slice()) else {
1061		return None;
1062	};
1063	Some(Array::new(out, written))
1064}
1065
1066/// Encodes bytes to lowercase hex at compile time.
1067///
1068/// # Examples
1069/// ```
1070/// use omp_core::hex::{ArrayStr, encode_n};
1071/// const ENCODED: ArrayStr<5> = encode_n(b"Hello");
1072/// assert_eq!(&*ENCODED, "48656c6c6f");
1073/// ```
1074#[inline]
1075pub const fn encode_n<const N: usize>(src: &[u8; N]) -> ArrayStr<N> {
1076	LOWER.encode_n(src)
1077}
1078
1079/// Returns the exact encoded length for a given source byte length.
1080///
1081/// Each byte encodes to exactly 2 hex characters.
1082///
1083/// # Examples
1084/// ```
1085/// use omp_core::hex::encode_len;
1086/// assert_eq!(encode_len(5), 10);
1087/// assert_eq!(encode_len(0), 0);
1088/// ```
1089#[inline]
1090pub const fn encode_len(src_len: usize) -> usize {
1091	src_len << 1
1092}
1093
1094/// Returns the decoded length for a given hex string length.
1095///
1096/// For even-length inputs, returns `src_len / 2`.
1097/// For odd-length inputs, returns `(src_len + 1) / 2`.
1098///
1099/// # Examples
1100/// ```
1101/// use omp_core::hex::decode_len;
1102/// assert_eq!(decode_len(10), 5);
1103/// assert_eq!(decode_len(9), 5);
1104/// assert_eq!(decode_len(0), 0);
1105/// ```
1106#[inline]
1107pub const fn decode_len(src_len: usize) -> usize {
1108	src_len.div_ceil(2)
1109}