pub const NAME_FLAG_EXPORTED: u8 = 1 << 0;
pub const NAME_FLAG_HAS_TAG: u8 = 1 << 1;
pub const NAME_FLAG_HAS_PKG_PATH: u8 = 1 << 2;
pub const NAME_FLAG_EMBEDDED: u8 = 1 << 3;
pub fn decode_name(data: &[u8], legacy: bool) -> Option<&str> {
decode_name_with_flags(data, legacy).map(|(name, _)| name)
}
pub fn decode_name_with_flags(data: &[u8], legacy: bool) -> Option<(&str, u8)> {
let flags = *data.first()?;
let (pos, name_len) = if legacy {
let hi = *data.get(1)? as usize;
let lo = *data.get(2)? as usize;
(3usize, (hi << 8) | lo)
} else {
let mut name_len: usize = 0;
let mut shift: u32 = 0;
let mut pos: usize = 1;
loop {
let b = *data.get(pos)?;
name_len |= ((b & 0x7f) as usize).checked_shl(shift)?;
pos = pos.checked_add(1)?;
if b & 0x80 == 0 {
break;
}
shift = shift.checked_add(7)?;
if shift > 35 {
return None;
}
}
(pos, name_len)
};
let end = pos.checked_add(name_len)?;
let bytes = data.get(pos..end)?;
let name = std::str::from_utf8(bytes).ok()?;
Some((name, flags))
}
pub fn decode_name_and_tag(data: &[u8], legacy: bool) -> Option<(&str, u8, Option<&str>)> {
let flags = *data.first()?;
let read_len = |start: usize| -> Option<(usize, usize)> {
if legacy {
let hi = *data.get(start)? as usize;
let lo = *data.get(start.checked_add(1)?)? as usize;
Some((start.checked_add(2)?, (hi << 8) | lo))
} else {
let mut len: usize = 0;
let mut shift: u32 = 0;
let mut pos = start;
loop {
let b = *data.get(pos)?;
len |= ((b & 0x7f) as usize).checked_shl(shift)?;
pos = pos.checked_add(1)?;
if b & 0x80 == 0 {
break;
}
shift = shift.checked_add(7)?;
if shift > 35 {
return None;
}
}
Some((pos, len))
}
};
let (name_start, name_len) = read_len(1)?;
let name_end = name_start.checked_add(name_len)?;
let name = std::str::from_utf8(data.get(name_start..name_end)?).ok()?;
let tag = if flags & NAME_FLAG_HAS_TAG != 0 {
let (tag_start, tag_len) = read_len(name_end)?;
let tag_end = tag_start.checked_add(tag_len)?;
std::str::from_utf8(data.get(tag_start..tag_end)?).ok()
} else {
None
};
Some((name, flags, tag))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_simple_name() {
let data = [0x01, 0x05, b'H', b'e', b'l', b'l', b'o'];
assert_eq!(decode_name(&data, false), Some("Hello"));
}
#[test]
fn decode_empty_name() {
let data = [0x00, 0x00];
assert_eq!(decode_name(&data, false), Some(""));
}
#[test]
fn decode_varint_length() {
let mut data = vec![0x00, 0x80, 0x01];
data.extend(std::iter::repeat_n(b'x', 128));
assert_eq!(decode_name(&data, false), Some(&"x".repeat(128)[..]));
}
#[test]
fn decode_legacy_big_endian_length() {
let data = [0x01, 0x00, 0x05, b'H', b'e', b'l', b'l', b'o'];
assert_eq!(decode_name(&data, true), Some("Hello"));
let mut big = vec![0x00, 0x01, 0x2c];
big.extend(std::iter::repeat_n(b'z', 300));
assert_eq!(decode_name(&big, true), Some(&"z".repeat(300)[..]));
}
#[test]
fn decode_tag_varint() {
let tag = b"json:\"id\"";
let mut data = vec![NAME_FLAG_HAS_TAG, 0x02, b'I', b'D', 0x09];
data.extend_from_slice(tag);
let (name, _flags, decoded_tag) = decode_name_and_tag(&data, false).unwrap();
assert_eq!(name, "ID");
assert_eq!(decoded_tag, Some("json:\"id\""));
}
#[test]
fn decode_no_tag_when_flag_unset() {
let data = [0x01, 0x02, b'I', b'D'];
let (name, _f, tag) = decode_name_and_tag(&data, false).unwrap();
assert_eq!(name, "ID");
assert_eq!(tag, None);
}
#[test]
fn decode_tag_legacy() {
let mut data = vec![NAME_FLAG_HAS_TAG, 0x00, 0x02, b'I', b'D', 0x00, 0x04];
data.extend_from_slice(b"db:x");
let (name, _f, tag) = decode_name_and_tag(&data, true).unwrap();
assert_eq!(name, "ID");
assert_eq!(tag, Some("db:x"));
}
#[test]
fn decode_empty_data() {
assert_eq!(decode_name(&[], false), None);
}
#[test]
fn decode_truncated() {
let data = [0x00, 0x0A, b'H', b'e', b'l', b'l', b'o'];
assert_eq!(decode_name(&data, false), None);
}
#[test]
fn decode_invalid_utf8() {
let data = [0x00, 0x02, 0xFF, 0xFE];
assert_eq!(decode_name(&data, false), None);
}
}