use crate::{Decode, Encode, EncodedSize, Error, Result, VarInt};
const MAX_STRING_BYTES: usize = 32767;
impl Encode for String {
fn encode(&self, buf: &mut Vec<u8>) -> Result<()> {
let bytes = self.as_bytes();
if bytes.len() > MAX_STRING_BYTES {
return Err(Error::StringTooLong {
len: bytes.len(),
max: MAX_STRING_BYTES,
});
}
VarInt(bytes.len() as i32).encode(buf)?;
buf.extend_from_slice(bytes);
Ok(())
}
}
impl Decode for String {
fn decode(buf: &mut &[u8]) -> Result<Self> {
let raw_len = VarInt::decode(buf)?.0;
if raw_len < 0 {
return Err(Error::InvalidData(format!(
"negative string length: {raw_len}"
)));
}
let len = raw_len as usize;
if len > MAX_STRING_BYTES {
return Err(Error::StringTooLong {
len,
max: MAX_STRING_BYTES,
});
}
if buf.len() < len {
return Err(Error::BufferUnderflow {
needed: len,
available: buf.len(),
});
}
let (bytes, rest) = buf.split_at(len);
let value = String::from_utf8(bytes.to_vec())?;
*buf = rest;
Ok(value)
}
}
impl EncodedSize for String {
fn encoded_size(&self) -> usize {
let len = self.len();
VarInt(len as i32).encoded_size() + len
}
}
#[cfg(test)]
mod tests {
use super::*;
fn roundtrip(s: &str) {
let original = s.to_string();
let mut buf = Vec::with_capacity(original.encoded_size());
original.encode(&mut buf).unwrap();
assert_eq!(buf.len(), original.encoded_size());
let mut cursor = buf.as_slice();
let decoded = String::decode(&mut cursor).unwrap();
assert!(cursor.is_empty());
assert_eq!(decoded, original);
}
#[test]
fn empty_string() {
roundtrip("");
}
#[test]
fn short_string() {
roundtrip("hello");
}
#[test]
fn unicode_string() {
roundtrip("héllo wörld 🌍");
}
#[test]
fn max_length_string() {
let s = "a".repeat(MAX_STRING_BYTES);
roundtrip(&s);
}
#[test]
fn too_long_encode() {
let s = "a".repeat(MAX_STRING_BYTES + 1);
let mut buf = Vec::new();
assert!(matches!(
s.encode(&mut buf),
Err(Error::StringTooLong { .. })
));
}
#[test]
fn too_long_decode() {
let mut buf = Vec::new();
VarInt(MAX_STRING_BYTES as i32 + 1)
.encode(&mut buf)
.unwrap();
buf.extend_from_slice(&vec![0u8; MAX_STRING_BYTES + 1]);
let mut cursor = buf.as_slice();
assert!(matches!(
String::decode(&mut cursor),
Err(Error::StringTooLong { .. })
));
}
#[test]
fn truncated_buffer() {
let mut buf = Vec::new();
VarInt(10).encode(&mut buf).unwrap();
buf.extend_from_slice(b"short");
let mut cursor = buf.as_slice();
assert!(matches!(
String::decode(&mut cursor),
Err(Error::BufferUnderflow { .. })
));
}
#[test]
fn invalid_utf8() {
let mut buf = Vec::new();
VarInt(2).encode(&mut buf).unwrap();
buf.extend_from_slice(&[0xFF, 0xFE]);
let mut cursor = buf.as_slice();
assert!(matches!(
String::decode(&mut cursor),
Err(Error::InvalidUtf8(_))
));
}
#[test]
fn encoded_size_accounts_for_varint_prefix() {
assert_eq!("".to_string().encoded_size(), 1);
assert_eq!("hi".to_string().encoded_size(), 3);
}
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn string_roundtrip(s in ".{0,1000}") {
roundtrip(&s);
}
}
}
}