use std::ops::Range;
#[derive(Clone)]
pub struct TxtRecord(Vec<u8>);
impl TxtRecord {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn parse_vec(data: Vec<u8>) -> Option<Self> {
if data.len() == 1 && data[0] == 0 {
let mut data = data;
data.clear();
return Some(Self(data));
}
let mut pos = 0;
while pos < data.len() {
let len = data[pos] as usize;
let new_pos = pos + 1 + len;
if new_pos > data.len() {
return None;
}
pos = new_pos;
}
Some(Self(data))
}
pub fn parse(data: &[u8]) -> Option<Self> {
Self::parse_vec(data.into())
}
pub fn with_capacity(capacity: usize) -> Self {
Self(Vec::with_capacity(capacity))
}
pub fn reserve(&mut self, additional: usize) {
self.0.reserve(additional);
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn clear(&mut self) {
self.0.clear();
}
pub fn data(&self) -> &[u8] {
&self.0
}
pub fn rdata(&self) -> &[u8] {
if self.0.is_empty() {
&[0x00] } else {
&self.0
}
}
fn _position_keys(&self) -> PositionKeyIter<'_> {
PositionKeyIter {
pos: 0,
data: &self.0,
}
}
pub fn iter(&self) -> TxtRecordIter<'_> {
TxtRecordIter {
pos: 0,
data: &self.0,
}
}
#[allow(clippy::option_option)]
pub fn get(&self, key: &[u8]) -> Option<Option<&[u8]>> {
self.iter().find(|&(k, _)| key == k).map(|(_, value)| value)
}
pub fn remove(&mut self, key: &[u8]) {
if let Some((loc, _)) = self._position_keys().find(|&(_, k)| key == k) {
self.0.drain(loc);
}
}
pub fn set(&mut self, key: &[u8], value: Option<&[u8]>) -> Result<(), TxtRecordError> {
for &k in key {
if k == b'=' || !(0x20..=0x7e).contains(&k) {
return Err(TxtRecordError::InvalidKey);
}
}
let entry_len = key.len() + value.map(|v| v.len() + 1).unwrap_or(0);
if entry_len > 255 {
return Err(TxtRecordError::EntryTooLong);
}
self.remove(key);
self.0.push(entry_len as u8);
self.0.extend_from_slice(key);
if let Some(value) = value {
self.0.push(b'=');
self.0.extend_from_slice(value);
}
Ok(())
}
pub fn set_no_value(&mut self, key: &[u8]) -> Result<(), TxtRecordError> {
self.set(key, None)
}
pub fn set_value(&mut self, key: &[u8], value: &[u8]) -> Result<(), TxtRecordError> {
self.set(key, Some(value))
}
}
impl Default for TxtRecord {
fn default() -> Self {
Self::new()
}
}
impl<'a> IntoIterator for &'a TxtRecord {
type IntoIter = TxtRecordIter<'a>;
type Item = (&'a [u8], Option<&'a [u8]>);
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum TxtRecordError {
InvalidKey,
EntryTooLong,
}
struct PositionKeyIter<'a> {
pos: usize,
data: &'a [u8],
}
impl<'a> Iterator for PositionKeyIter<'a> {
type Item = (Range<usize>, &'a [u8]);
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
let len = self.data[0] as usize;
let entry_pos = self.pos;
let entry = &self.data[1..][..len];
self.data = &self.data[len + 1..];
self.pos += len + 1;
Some(match entry.iter().position(|&b| b == b'=') {
Some(pos) => (entry_pos..self.pos, &entry[..pos]),
None => (entry_pos..self.pos, entry),
})
}
}
pub struct TxtRecordIter<'a> {
pos: usize,
data: &'a [u8],
}
impl<'a> Iterator for TxtRecordIter<'a> {
type Item = (&'a [u8], Option<&'a [u8]>);
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
let len = self.data[0] as usize;
let entry = &self.data[1..][..len];
self.data = &self.data[len + 1..];
self.pos += len + 1;
Some(match entry.iter().position(|&b| b == b'=') {
Some(pos) => (&entry[..pos], Some(&entry[pos + 1..])),
None => (entry, None),
})
}
}
#[cfg(test)]
mod tests {
use super::TxtRecord;
#[test]
fn modifications() {
let mut r = TxtRecord::new();
assert!(r.is_empty());
assert_eq!(r.data(), b"");
assert_eq!(r.rdata(), b"\x00");
r.set(b"foo", Some(b"bar")).unwrap();
assert!(!r.is_empty());
assert_eq!(r.data(), b"\x07foo=bar");
assert_eq!(r.rdata(), b"\x07foo=bar");
r.set(b"u", Some(b"vw")).unwrap();
assert!(!r.is_empty());
assert_eq!(r.data(), b"\x07foo=bar\x04u=vw");
assert_eq!(r.rdata(), b"\x07foo=bar\x04u=vw");
assert_eq!(
r.iter().collect::<Vec<_>>(),
vec![
(b"foo" as &[u8], Some(b"bar" as &[u8])),
(b"u", Some(b"vw")),
]
);
r.set(b"foo", None).unwrap();
assert!(!r.is_empty());
assert_eq!(r.data(), b"\x04u=vw\x03foo");
assert_eq!(r.rdata(), b"\x04u=vw\x03foo");
assert_eq!(
r.iter().collect::<Vec<_>>(),
vec![(b"u" as &[u8], Some(b"vw" as &[u8])), (b"foo", None),]
);
r.set(b"foo", Some(b"bar")).unwrap();
assert!(!r.is_empty());
assert_eq!(r.data(), b"\x04u=vw\x07foo=bar");
assert_eq!(r.rdata(), b"\x04u=vw\x07foo=bar");
r.remove(b"foo");
assert!(!r.is_empty());
assert_eq!(r.data(), b"\x04u=vw");
assert_eq!(r.rdata(), b"\x04u=vw");
}
}