use std::fmt;
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, BufReader};
pub const MAX_TUPLE_DEPTH: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Item {
Word(String),
Number(u64),
Str(Vec<u8>),
List(Vec<Item>),
}
impl Item {
pub fn head(&self) -> Option<&Item> {
match self {
Item::List(items) => items.first(),
_ => None,
}
}
pub fn to_json(&self) -> serde_json::Value {
match self {
Item::Word(w) => serde_json::Value::String(w.clone()),
Item::Number(n) => serde_json::Value::from(*n),
Item::Str(bytes) => serde_json::Value::String(String::from_utf8_lossy(bytes).into()),
Item::List(items) => {
serde_json::Value::Array(items.iter().map(Item::to_json).collect())
}
}
}
}
impl fmt::Display for Item {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Item::Word(w) => f.write_str(w),
Item::Number(n) => write!(f, "{n}"),
Item::Str(bytes) => {
write!(f, "{}:{}", bytes.len(), String::from_utf8_lossy(bytes))
}
Item::List(items) => {
f.write_str("(")?;
for item in items {
write!(f, " {item}")?;
}
f.write_str(" )")
}
}
}
}
#[derive(Debug)]
pub enum WireError {
UnexpectedEof,
TooDeep,
TooLarge { limit: u64 },
Malformed(&'static str),
Io(std::io::Error),
}
impl fmt::Display for WireError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WireError::UnexpectedEof => f.write_str("connection closed mid-tuple"),
WireError::TooDeep => {
write!(f, "tuple nested deeper than {MAX_TUPLE_DEPTH} lists")
}
WireError::TooLarge { limit } => {
write!(f, "tuple larger than the {limit}-byte message cap")
}
WireError::Malformed(why) => write!(f, "malformed ra_svn tuple: {why}"),
WireError::Io(e) => write!(f, "read error: {e}"),
}
}
}
impl From<std::io::Error> for WireError {
fn from(e: std::io::Error) -> Self {
WireError::Io(e)
}
}
fn is_space(b: u8) -> bool {
matches!(b, b' ' | b'\n' | b'\r' | b'\t' | 0x0b | 0x0c)
}
pub struct ItemReader<R> {
inner: BufReader<R>,
pending: Option<u8>,
}
impl<R: AsyncRead + Unpin> ItemReader<R> {
pub fn new(reader: R) -> Self {
Self {
inner: BufReader::new(reader),
pending: None,
}
}
async fn byte(&mut self) -> Result<Option<u8>, WireError> {
if let Some(b) = self.pending.take() {
return Ok(Some(b));
}
let mut one = [0u8; 1];
match self.inner.read_exact(&mut one).await {
Ok(_) => Ok(Some(one[0])),
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Ok(None),
Err(e) => Err(WireError::Io(e)),
}
}
pub async fn read_item(&mut self, max_bytes: u64) -> Result<Option<(Item, u64)>, WireError> {
let mut consumed: u64 = 0;
let mut stack: Vec<Vec<Item>> = Vec::new();
loop {
let Some(b) = self.byte().await? else {
return if stack.is_empty() && consumed == 0 {
Ok(None)
} else {
Err(WireError::UnexpectedEof)
};
};
consumed += 1;
if consumed > max_bytes {
return Err(WireError::TooLarge { limit: max_bytes });
}
if is_space(b) {
continue;
}
let item = match b {
b'(' => {
if stack.len() >= MAX_TUPLE_DEPTH {
return Err(WireError::TooDeep);
}
stack.push(Vec::new());
continue;
}
b')' => {
let Some(items) = stack.pop() else {
return Err(WireError::Malformed("closing paren with no list open"));
};
Item::List(items)
}
b'0'..=b'9' => self.number_or_string(b, &mut consumed, max_bytes).await?,
_ => self.word(b, &mut consumed, max_bytes).await?,
};
match stack.last_mut() {
Some(open) => open.push(item),
None => {
consumed += self.drain_buffered_whitespace();
return Ok(Some((item, consumed)));
}
}
}
}
fn drain_buffered_whitespace(&mut self) -> u64 {
let mut drained = 0u64;
if let Some(b) = self.pending {
if is_space(b) {
self.pending = None;
drained += 1;
} else {
return 0;
}
}
loop {
let take = self
.inner
.buffer()
.iter()
.take_while(|b| is_space(**b))
.count();
if take == 0 {
return drained;
}
self.inner.consume(take);
drained += take as u64;
}
}
async fn number_or_string(
&mut self,
first: u8,
consumed: &mut u64,
max_bytes: u64,
) -> Result<Item, WireError> {
let mut value: u64 = (first - b'0') as u64;
let mut digits = 1usize;
loop {
let Some(b) = self.byte().await? else {
return Ok(Item::Number(value));
};
*consumed += 1;
if *consumed > max_bytes {
return Err(WireError::TooLarge { limit: max_bytes });
}
match b {
b'0'..=b'9' => {
digits += 1;
if digits > 20 {
return Err(WireError::Malformed("number too long"));
}
value = value
.checked_mul(10)
.and_then(|v| v.checked_add((b - b'0') as u64))
.ok_or(WireError::Malformed("number overflows 64 bits"))?;
}
b':' => {
let remaining = max_bytes.saturating_sub(*consumed);
if value > remaining {
return Err(WireError::TooLarge { limit: max_bytes });
}
let len = value as usize;
let mut bytes = vec![0u8; len];
if len > 0 {
debug_assert!(self.pending.is_none());
match self.inner.read_exact(&mut bytes).await {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
return Err(WireError::UnexpectedEof)
}
Err(e) => return Err(WireError::Io(e)),
}
}
*consumed += value;
return Ok(Item::Str(bytes));
}
other => {
self.pending = Some(other);
*consumed -= 1;
return Ok(Item::Number(value));
}
}
}
}
async fn word(
&mut self,
first: u8,
consumed: &mut u64,
max_bytes: u64,
) -> Result<Item, WireError> {
let mut bytes = vec![first];
loop {
let Some(b) = self.byte().await? else {
break;
};
if is_space(b) || b == b'(' || b == b')' {
self.pending = Some(b);
break;
}
*consumed += 1;
if *consumed > max_bytes {
return Err(WireError::TooLarge { limit: max_bytes });
}
bytes.push(b);
}
Ok(Item::Word(String::from_utf8_lossy(&bytes).into_owned()))
}
}