use crate::error::LabelStringError;
pub fn byte_string_to_labels<L: From<u8>>(bytes: &[u8], labels: &mut Vec<L>) {
labels.reserve(bytes.len());
labels.extend(bytes.iter().copied().map(L::from));
}
pub fn utf8_string_to_labels<L>(bytes: &[u8], labels: &mut Vec<L>) -> Result<(), LabelStringError>
where
L: TryFrom<u32>,
{
let mut i = 0;
while i < bytes.len() {
let lead = bytes[i];
let offset = i;
i += 1;
if lead & 0x80 == 0 {
push_label(labels, u32::from(lead), offset)?;
continue;
}
if lead & 0xc0 == 0x80 {
return Err(LabelStringError::ContinuationAsLeadByte { byte: lead, offset });
}
let count = u32::from(lead >= 0xc0)
+ u32::from(lead >= 0xe0)
+ u32::from(lead >= 0xf0)
+ u32::from(lead >= 0xf8)
+ u32::from(lead >= 0xfc);
let mut label = u32::from(lead) & ((1u32 << (6 - count)) - 1);
for _ in 0..count {
if i == bytes.len() {
return Err(LabelStringError::TruncatedSequence { offset });
}
let byte = bytes[i];
i += 1;
if byte & 0xc0 != 0x80 {
return Err(LabelStringError::MissingContinuationByte {
byte,
offset: i - 1,
});
}
label = (label << 6) | u32::from(byte & 0x3f);
}
push_label(labels, label, offset)?;
}
Ok(())
}
pub fn labels_to_byte_string<L>(labels: &[L], bytes: &mut Vec<u8>) -> Result<(), LabelStringError>
where
L: Copy + Into<i64>,
{
bytes.reserve(labels.len());
for (index, &label) in labels.iter().enumerate() {
let label = label.into();
match label {
0 => continue,
v if v < 0 => return Err(LabelStringError::NegativeLabel { label: v, index }),
v if v > u8::MAX as i64 => {
return Err(LabelStringError::LabelOutOfRange { label: v, index });
}
v => bytes.push(v as u8),
}
}
Ok(())
}
pub fn labels_to_utf8_string<L>(labels: &[L], bytes: &mut Vec<u8>) -> Result<(), LabelStringError>
where
L: Copy + Into<i64>,
{
bytes.reserve(labels.len());
for (index, &label) in labels.iter().enumerate() {
let label = label.into();
if label == 0 {
continue;
}
if label < 0 {
return Err(LabelStringError::NegativeLabel { label, index });
}
if label > i64::from(MAX_LABEL) {
return Err(LabelStringError::LabelOutOfRange { label, index });
}
encode_one(label as u32, bytes);
}
Ok(())
}
pub const MAX_LABEL: u32 = 0x7fff_ffff;
fn encode_one(label: u32, bytes: &mut Vec<u8>) {
const CONT: u32 = 0x80;
match label {
0..=0x7f => bytes.push(label as u8),
0x80..=0x7ff => {
bytes.push(((label >> 6) | 0xc0) as u8);
bytes.push(((label & 0x3f) | CONT) as u8);
}
0x800..=0xffff => {
bytes.push(((label >> 12) | 0xe0) as u8);
bytes.push((((label >> 6) & 0x3f) | CONT) as u8);
bytes.push(((label & 0x3f) | CONT) as u8);
}
0x1_0000..=0x1f_ffff => {
bytes.push(((label >> 18) | 0xf0) as u8);
bytes.push((((label >> 12) & 0x3f) | CONT) as u8);
bytes.push((((label >> 6) & 0x3f) | CONT) as u8);
bytes.push(((label & 0x3f) | CONT) as u8);
}
0x20_0000..=0x3ff_ffff => {
bytes.push(((label >> 24) | 0xf8) as u8);
bytes.push((((label >> 18) & 0x3f) | CONT) as u8);
bytes.push((((label >> 12) & 0x3f) | CONT) as u8);
bytes.push((((label >> 6) & 0x3f) | CONT) as u8);
bytes.push(((label & 0x3f) | CONT) as u8);
}
_ => {
bytes.push(((label >> 30) | 0xfc) as u8);
bytes.push((((label >> 24) & 0x3f) | CONT) as u8);
bytes.push((((label >> 18) & 0x3f) | CONT) as u8);
bytes.push((((label >> 12) & 0x3f) | CONT) as u8);
bytes.push((((label >> 6) & 0x3f) | CONT) as u8);
bytes.push(((label & 0x3f) | CONT) as u8);
}
}
}
#[inline]
fn push_label<L: TryFrom<u32>>(
labels: &mut Vec<L>,
label: u32,
index: usize,
) -> Result<(), LabelStringError> {
match L::try_from(label) {
Ok(label) => {
labels.push(label);
Ok(())
}
Err(_) => Err(LabelStringError::LabelOutOfRange {
label: i64::from(label),
index,
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(bytes: &[u8]) -> Result<Vec<i32>, LabelStringError> {
let mut labels = Vec::new();
utf8_string_to_labels(bytes, &mut labels)?;
Ok(labels)
}
fn serialize(labels: &[i32]) -> Result<Vec<u8>, LabelStringError> {
let mut bytes = Vec::new();
labels_to_utf8_string(labels, &mut bytes)?;
Ok(bytes)
}
#[test]
fn byte_strings_map_one_label_per_byte() {
let mut labels: Vec<i32> = Vec::new();
byte_string_to_labels(b"a\xffb", &mut labels);
assert_eq!(labels, vec![0x61, 0xff, 0x62]);
}
#[test]
fn parses_standard_utf8() {
assert_eq!(parse(b"hello").unwrap(), b"hello".map(i32::from).to_vec());
assert_eq!(parse("é".as_bytes()).unwrap(), vec![0xe9]);
assert_eq!(parse("日".as_bytes()).unwrap(), vec![0x65e5]);
assert_eq!(parse("😀".as_bytes()).unwrap(), vec![0x1f600]);
assert_eq!(
parse("aé日😀".as_bytes()).unwrap(),
vec![0x61, 0xe9, 0x65e5, 0x1f600]
);
}
#[test]
fn parses_sequences_standard_utf8_rejects() {
assert_eq!(
parse(&[0xf8, 0x88, 0x80, 0x80, 0x80]).unwrap(),
vec![0x20_0000]
);
assert_eq!(
parse(&[0xfc, 0x84, 0x80, 0x80, 0x80, 0x80]).unwrap(),
vec![0x400_0000]
);
assert_eq!(
parse(&[0xfd, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf]).unwrap(),
vec![MAX_LABEL as i32]
);
assert_eq!(parse(&[0xed, 0xa0, 0x80]).unwrap(), vec![0xd800]);
assert_eq!(parse(&[0xc1, 0x81]).unwrap(), vec![0x41]);
let surrogate: Vec<u8> = vec![0xed, 0xa0, 0x80];
assert!(std::str::from_utf8(&surrogate).is_err());
}
#[test]
fn rejects_malformed_sequences() {
assert!(matches!(
parse(&[0x80]),
Err(LabelStringError::ContinuationAsLeadByte {
byte: 0x80,
offset: 0
})
));
assert!(matches!(
parse(&[0xe6, 0x97]),
Err(LabelStringError::TruncatedSequence { offset: 0 })
));
assert!(matches!(
parse(&[0xe6, 0x97, 0x41]),
Err(LabelStringError::MissingContinuationByte {
byte: 0x41,
offset: 2
})
));
}
#[test]
fn rejects_labels_that_do_not_fit() {
let mut labels: Vec<u8> = Vec::new();
assert!(matches!(
utf8_string_to_labels("日".as_bytes(), &mut labels),
Err(LabelStringError::LabelOutOfRange { label: 0x65e5, .. })
));
}
#[test]
fn serializes_back_to_the_same_bytes() {
for text in ["", "hello", "é", "日本語", "a😀b"] {
let labels = parse(text.as_bytes()).unwrap();
assert_eq!(serialize(&labels).unwrap(), text.as_bytes(), "{text}");
}
}
#[test]
fn epsilon_labels_are_skipped() {
assert_eq!(serialize(&[0x61, 0, 0x62]).unwrap(), b"ab");
let mut bytes = Vec::new();
labels_to_byte_string(&[0x61, 0, 0x62], &mut bytes).unwrap();
assert_eq!(bytes, b"ab");
}
#[test]
fn byte_string_output_rejects_what_upstream_would_silently_mangle() {
let mut bytes = Vec::new();
assert!(matches!(
labels_to_byte_string(&[0x61, 256], &mut bytes),
Err(LabelStringError::LabelOutOfRange {
label: 256,
index: 1
})
));
bytes.clear();
assert!(matches!(
labels_to_byte_string(&[300], &mut bytes),
Err(LabelStringError::LabelOutOfRange { label: 300, .. })
));
bytes.clear();
assert!(matches!(
labels_to_byte_string(&[-1], &mut bytes),
Err(LabelStringError::NegativeLabel { label: -1, .. })
));
}
#[test]
fn utf8_output_rejects_negative_and_oversized_labels() {
assert!(matches!(
serialize(&[-5]),
Err(LabelStringError::NegativeLabel { label: -5, .. })
));
let mut bytes = Vec::new();
assert!(matches!(
labels_to_utf8_string(&[i64::from(MAX_LABEL) + 1], &mut bytes),
Err(LabelStringError::LabelOutOfRange { .. })
));
}
#[test]
fn round_trips_every_sequence_length() {
let boundaries = [
1, 0x7f, 0x80, 0x7ff, 0x800, 0xffff, 0x1_0000, 0x1f_ffff, 0x20_0000, 0x3ff_ffff,
0x400_0000, MAX_LABEL,
];
let expected_len = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6];
for (&label, &len) in boundaries.iter().zip(expected_len.iter()) {
let mut bytes = Vec::new();
labels_to_utf8_string(&[label as i64], &mut bytes).unwrap();
assert_eq!(bytes.len(), len, "label {label:#x} encoded as {bytes:02x?}");
assert_eq!(
parse(&bytes).unwrap(),
vec![label as i32],
"label {label:#x}"
);
}
}
#[test]
fn round_trips_random_label_sequences() {
let mut state = 0x9E37_79B9_7F4A_7C15u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for _ in 0..200 {
let labels: Vec<i32> = (0..64)
.map(|_| (next() % u64::from(MAX_LABEL)) as i32 + 1)
.collect();
let bytes = serialize(&labels).unwrap();
assert_eq!(parse(&bytes).unwrap(), labels);
}
}
}