#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(
nonstandard_style,
rust_2018_idioms,
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links
)]
#![forbid(non_ascii_idents, unsafe_code)]
#![warn(
deprecated_in_future,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
unreachable_pub,
unused_import_braces,
unused_labels,
unused_lifetimes,
unused_qualifications,
unused_results
)]
#![allow(clippy::uninlined_format_args)]
use std::{ops::Deref, str::Utf8Error};
use thiserror::Error;
#[derive(Debug)]
pub struct NetstringParser {
buf: Vec<u8>,
len: usize,
}
impl NetstringParser {
pub fn new(buf_size: usize) -> Self {
Self {
buf: vec![0; buf_size],
len: 0,
}
}
pub fn available_buffer(&mut self) -> &mut [u8] {
&mut self.buf[self.len..]
}
pub fn advance(&mut self, count: usize) {
self.len += count;
}
pub fn write(&mut self, data: &[u8]) -> Result<(), WriteError> {
let remaining = self.buf.len() - self.len;
if data.len() <= remaining {
self.buf[self.len..self.len + data.len()].copy_from_slice(data);
self.len += data.len();
Ok(())
} else {
Err(WriteError::BufferTooSmall)
}
}
pub fn is_buffer_full(&self) -> bool {
self.len >= self.buf.len()
}
pub fn is_buffer_empty(&self) -> bool {
self.len == 0
}
pub fn parse_next<'a>(&'a mut self) -> Result<Option<Netstring<'a>>, NetstringError> {
match parse_length(&self.buf[..self.len])? {
None => Ok(None),
Some((len, rest)) => {
if rest.len() < len + 1 {
return Ok(None); }
if rest[len] != b',' {
return Err(NetstringError::MissingComma);
}
let offset = self.len - rest.len();
Ok(Some(Netstring {
parser: self,
offset,
length: len,
}))
}
}
}
pub fn clear(&mut self) {
self.len = 0;
}
fn discard(&mut self, count: usize) {
self.buf.copy_within(count..self.len, 0);
self.len = self.len.saturating_sub(count);
}
}
#[derive(Debug, Error, Copy, Clone)]
pub enum NetstringError {
#[error("String too long")]
StringTooLong,
#[error("Invalid data")]
InvalidData,
#[error("No colon found")]
NoColonFound,
#[error("Missing comma")]
MissingComma,
#[error("Invalid length")]
InvalidLength,
}
#[derive(Debug, Error, Copy, Clone)]
pub enum WriteError {
#[error("Buffer too small")]
BufferTooSmall,
}
pub struct Netstring<'a> {
parser: &'a mut NetstringParser,
offset: usize,
length: usize,
}
impl Netstring<'_> {
pub fn to_str(&self) -> Result<&str, Utf8Error> {
std::str::from_utf8(self)
}
pub fn as_bytes(&self) -> &[u8] {
self
}
}
impl<'a> std::fmt::Debug for Netstring<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Netstring").field(&self.as_bytes()).finish()
}
}
impl<'a> std::fmt::Display for Netstring<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.to_str() {
Ok(s) => f.write_str(s),
Err(_) => write!(f, "<invalid utf-8: {:?}>", self.as_bytes()),
}
}
}
impl<'a> Deref for Netstring<'a> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.parser.buf[self.offset..self.offset + self.length]
}
}
impl<'a> Drop for Netstring<'a> {
fn drop(&mut self) {
self.parser.discard(self.offset + self.length + 1);
}
}
fn parse_length(input: &[u8]) -> Result<Option<(usize, &[u8])>, NetstringError> {
let Some(colon_pos) = input.iter().position(|&b| b == b':') else {
if input.len() > 20 {
return Err(NetstringError::NoColonFound);
}
return Ok(None);
};
let len = &input[..colon_pos];
let rest = &input[colon_pos + 1..];
let Ok(len) = std::str::from_utf8(len) else {
return Err(NetstringError::InvalidLength);
};
let Ok(len) = len.parse::<usize>() else {
return Err(NetstringError::InvalidLength);
};
Ok(Some((len, rest)))
}