Skip to main content

tuwunel_core/utils/
bytes.rs

1//! Byte-size parsing, display, and integer encoding helpers.
2//!
3//! The module handles human-readable sizes alongside fixed-width big-endian
4//! counters. Its size deserializers integrate human-readable values with Serde.
5
6#[cfg(test)]
7mod tests;
8
9use bytesize::ByteSize;
10use serde::{Deserialize, Deserializer, de};
11
12use crate::{Result, at, err};
13
14/// Accepts an integer byte count or a string with SI/IEC suffix (e.g. "24 MiB")
15/// and returns a `usize`.
16pub fn deserialize_bytesize_usize<'de, D>(de: D) -> Result<usize, D::Error>
17where
18	D: Deserializer<'de>,
19{
20	ByteSize::deserialize(de)
21		.map(at!(0))
22		.map(usize::try_from)?
23		.map_err(de::Error::custom)
24}
25
26/// Accepts an integer byte count or a string with SI/IEC suffix (e.g. "32 MiB")
27/// and returns a `u64`.
28pub fn deserialize_bytesize_u64<'de, D>(de: D) -> Result<u64, D::Error>
29where
30	D: Deserializer<'de>,
31{
32	ByteSize::deserialize(de).map(at!(0))
33}
34
35/// Parse a human-writable size string w/ si-unit suffix into integer
36#[inline]
37pub fn from_str(str: &str) -> Result<usize> {
38	let bytes: ByteSize = str
39		.parse()
40		.map_err(|e| err!(Arithmetic("Failed to parse byte size: {e}")))?;
41
42	let bytes: usize = bytes
43		.as_u64()
44		.try_into()
45		.map_err(|e| err!(Arithmetic("Failed to convert u64 to usize: {e}")))?;
46
47	Ok(bytes)
48}
49
50/// Output a human-readable size string w/ iec-unit suffix
51#[inline]
52#[must_use]
53pub fn pretty(bytes: usize) -> String {
54	let bytes: u64 = bytes
55		.try_into()
56		.expect("failed to convert usize to u64");
57
58	ByteSize::b(bytes).display().iec().to_string()
59}
60
61/// Increments an optional big-endian counter with wrapping arithmetic.
62///
63/// Missing or malformed input is treated as zero. The returned array contains
64/// the incremented value in big-endian byte order.
65#[inline]
66#[must_use]
67pub fn increment(old: Option<&[u8]>) -> [u8; 8] {
68	old.map_or(0_u64, |bytes| u64_from_bytes(bytes).unwrap_or(0))
69		.wrapping_add(1)
70		.to_be_bytes()
71}
72
73/// Parses 8 big-endian bytes into an u64; panic on invalid argument
74#[inline]
75#[must_use]
76pub fn u64_from_u8(bytes: &[u8]) -> u64 {
77	u64_from_bytes(bytes).expect("must slice at least 8 bytes")
78}
79
80/// Parses the big-endian bytes into an u64.
81#[inline]
82pub fn u64_from_bytes(bytes: &[u8]) -> Result<u64> { Ok(u64::from_be_bytes(bytes.try_into()?)) }
83
84/// Returns the least byte prefix strictly after every key with this prefix.
85///
86/// Trailing maximum bytes are removed before the remaining last byte advances.
87/// An empty or all-maximum prefix has no successor. The owned buffer is reused.
88#[must_use]
89pub fn prefix_successor(mut prefix: Vec<u8>) -> Option<Vec<u8>> {
90	let index = prefix.iter().rposition(|byte| *byte != u8::MAX)?;
91
92	prefix.truncate(index.saturating_add(1));
93	prefix[index] = prefix[index].saturating_add(1);
94	Some(prefix)
95}