use tokio::io::{AsyncRead, AsyncReadExt};
const WINDOW: usize = 0x200;
const MIN_LEN: usize = 4;
pub(crate) trait IsPrintable {
fn is_printable(&self) -> bool;
}
impl IsPrintable for u8 {
#[inline]
fn is_printable(&self) -> bool {
(0x20..=0x7e).contains(self)
}
}
pub(crate) struct Strings<'a, R: AsyncRead + Unpin> {
buf: &'a mut R,
window: [u8; WINDOW],
filled: usize,
pos: usize,
}
impl<'a, R: AsyncRead + Unpin> Strings<'a, R> {
pub(crate) fn from_reader(buf: &'a mut R) -> Self {
Strings {
buf,
window: [0; WINDOW],
filled: 0,
pos: 0,
}
}
pub(crate) async fn next_string(&mut self) -> Option<String> {
let mut stanza = String::new();
loop {
if self.pos == self.filled {
self.filled = self.buf.read(&mut self.window).await.ok()?;
self.pos = 0;
if self.filled == 0 {
return if stanza.len() >= MIN_LEN {
Some(stanza)
} else {
None
};
}
}
let byte = self.window[self.pos];
self.pos += 1;
if byte.is_printable() {
stanza.push(char::from(byte));
} else if stanza.len() >= MIN_LEN {
return Some(stanza);
} else {
stanza.clear();
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::Cursor;
#[test]
fn printable() {
assert!(!b'\0'.is_printable());
assert!(!b'\t'.is_printable());
assert!(!b'\n'.is_printable());
assert!(!b'\r'.is_printable());
assert!(!b'\x1b'.is_printable());
assert!(b'a'.is_printable());
assert!(b'B'.is_printable());
assert!(b'x'.is_printable());
assert!(b'~'.is_printable());
}
#[tokio::test]
async fn runs_between_unprintable_bytes() {
let mut bytes = Cursor::new(b"\0\tfoobar\r\tbarfoo".to_vec());
let mut strings = Strings::from_reader(&mut bytes);
assert_eq!(Some("foobar".to_string()), strings.next_string().await);
assert_eq!(Some("barfoo".to_string()), strings.next_string().await);
assert_eq!(None, strings.next_string().await);
}
#[tokio::test]
async fn runs_shorter_than_the_minimum_are_dropped() {
let mut bytes = Cursor::new(b"\0abc\0abcd\0".to_vec());
let mut strings = Strings::from_reader(&mut bytes);
assert_eq!(Some("abcd".to_string()), strings.next_string().await);
assert_eq!(None, strings.next_string().await);
}
#[tokio::test]
async fn run_spanning_several_windows() {
let run = "A".repeat(2 * WINDOW + 16);
let mut data = vec![0];
data.extend_from_slice(run.as_bytes());
data.push(0);
let mut bytes = Cursor::new(data);
let mut strings = Strings::from_reader(&mut bytes);
assert_eq!(Some(run), strings.next_string().await);
assert_eq!(None, strings.next_string().await);
}
}