use crate::{DEFAULT_MAX_MESSAGE, PREFIX, ST};
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct Params(Vec<(String, String)>);
impl Params {
pub fn new() -> Self {
Self::default()
}
pub fn parse(s: &str) -> Self {
Params(s.split(',').filter_map(|kv| kv.split_once('=')).map(|(k, v)| (k.to_string(), v.to_string())).collect())
}
pub fn with(mut self, key: impl Into<String>, value: impl ToString) -> Self {
self.push(key, value);
self
}
pub fn push(&mut self, key: impl Into<String>, value: impl ToString) {
self.0.push((key.into(), value.to_string()));
}
pub fn get(&self, key: &str) -> Option<&str> {
self.0.iter().rev().find(|(k, _)| k == key).map(|(_, v)| v.as_str())
}
pub fn num(&self, key: &str) -> Option<u32> {
let v = self.get(key)?;
if v.is_empty() || v.len() > 10 || !v.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
v.parse().ok()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.0.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
pub fn encode(&self) -> String {
let mut s = String::new();
for (i, (k, v)) in self.0.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push_str(k);
s.push('=');
s.push_str(v);
}
s
}
}
impl<K: Into<String>, V: ToString> FromIterator<(K, V)> for Params {
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
Params(iter.into_iter().map(|(k, v)| (k.into(), v.to_string())).collect())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Message {
pub subsystem: u8,
pub verb: u8,
pub params: Params,
pub payload: Option<Vec<u8>>,
}
impl Message {
pub fn new(subsystem: u8, verb: u8, params: Params) -> Self {
Message { subsystem, verb, params, payload: None }
}
pub fn with_payload(subsystem: u8, verb: u8, params: Params, payload: Vec<u8>) -> Self {
Message { subsystem, verb, params, payload: Some(payload) }
}
pub fn is(&self, subsystem: u8, verb: u8) -> bool {
self.subsystem == subsystem && self.verb == verb
}
pub fn encode(&self) -> Vec<u8> {
let mut o = PREFIX.to_vec();
o.push(self.subsystem);
o.push(self.verb);
o.push(b';');
o.extend_from_slice(self.params.encode().as_bytes());
if let Some(p) = &self.payload {
o.push(b':');
o.extend_from_slice(p);
}
o.extend_from_slice(ST);
o
}
pub fn encode_string(&self) -> String {
String::from_utf8_lossy(&self.encode()).into_owned()
}
pub fn from_body(body: &[u8]) -> Option<Self> {
if body.len() < 2 || !body[0].is_ascii_alphabetic() || !body[1].is_ascii_alphabetic() {
return None;
}
let rest = &body[2..];
let rest = rest.strip_prefix(b";").unwrap_or(rest);
if let Some(i) = rest.iter().position(|&b| b == b':') {
let head = Params::parse(std::str::from_utf8(&rest[..i]).ok()?);
if head.get("len").is_some() {
return Some(Message { subsystem: body[0], verb: body[1], params: head, payload: Some(rest[i + 1..].to_vec()) });
}
}
let params = Params::parse(std::str::from_utf8(rest).ok()?);
let payload = None;
Some(Message { subsystem: body[0], verb: body[1], params, payload })
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
match parse(bytes) {
Parse::Complete { value, len } if len == bytes.len() => Some(value),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Parse<T> {
Complete {
value: T,
len: usize,
},
Incomplete,
Invalid {
len: usize,
},
NotFuzzball,
}
impl<T> Parse<T> {
pub fn map<U>(self, f: impl FnOnce(T) -> Option<U>) -> Parse<U> {
match self {
Parse::Complete { value, len } => match f(value) {
Some(value) => Parse::Complete { value, len },
None => Parse::Invalid { len },
},
Parse::Incomplete => Parse::Incomplete,
Parse::Invalid { len } => Parse::Invalid { len },
Parse::NotFuzzball => Parse::NotFuzzball,
}
}
}
pub fn parse(buf: &[u8]) -> Parse<Message> {
parse_with_limit(buf, DEFAULT_MAX_MESSAGE)
}
pub fn parse_with_limit(buf: &[u8], max: usize) -> Parse<Message> {
if buf.len() < PREFIX.len() {
return if PREFIX.starts_with(buf) { Parse::Incomplete } else { Parse::NotFuzzball };
}
if !buf.starts_with(PREFIX) {
return Parse::NotFuzzball;
}
let body_start = PREFIX.len();
let st = find(buf, body_start, ST);
let colon = buf[body_start..].iter().position(|&b| b == b':').map(|i| i + body_start);
if let Some(c) = colon.filter(|&c| st.is_none_or(|s| c < s)) {
let head_start = (body_start + 2).min(c);
let head = &buf[head_start..c];
let head = Params::parse(&String::from_utf8_lossy(head.strip_prefix(b";").unwrap_or(head)));
if head.get("enc") == Some("raw") {
let Some(n) = head.num("len").map(|n| n as usize) else {
return invalid_until_st(buf, st, max);
};
let end = c + 1 + n;
if end + 2 > max {
return Parse::Invalid { len: buf.len().min(end + 2) };
}
if buf.len() < end + 2 {
return Parse::Incomplete;
}
if &buf[end..end + 2] != ST {
return Parse::Invalid { len: end };
}
return match Message::from_body(&buf[body_start..end]) {
Some(value) => Parse::Complete { value, len: end + 2 },
None => Parse::Invalid { len: end + 2 },
};
}
}
match st {
Some(s) if s + 2 > max => Parse::Invalid { len: s + 2 },
Some(s) => match Message::from_body(&buf[body_start..s]) {
Some(value) => Parse::Complete { value, len: s + 2 },
None => Parse::Invalid { len: s + 2 },
},
None if buf.len() > max => Parse::Invalid { len: buf.len() },
None => Parse::Incomplete,
}
}
fn invalid_until_st(buf: &[u8], st: Option<usize>, max: usize) -> Parse<Message> {
match st {
Some(s) => Parse::Invalid { len: s + 2 },
None if buf.len() > max => Parse::Invalid { len: buf.len() },
None => Parse::Incomplete,
}
}
fn find(buf: &[u8], from: usize, needle: &[u8]) -> Option<usize> {
buf.get(from..)?.windows(needle.len()).position(|w| w == needle).map(|i| i + from)
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Item {
Message(Message),
Text(Vec<u8>),
Invalid(Vec<u8>),
}
#[derive(Debug, Clone)]
pub struct Decoder {
buf: Vec<u8>,
max: usize,
}
impl Default for Decoder {
fn default() -> Self {
Self::new()
}
}
impl Decoder {
pub fn new() -> Self {
Self::with_limit(DEFAULT_MAX_MESSAGE)
}
pub fn with_limit(max: usize) -> Self {
Decoder { buf: Vec::new(), max }
}
pub fn push(&mut self, bytes: &[u8]) {
self.buf.extend_from_slice(bytes);
}
pub fn pending(&self) -> &[u8] {
&self.buf
}
pub fn flush(&mut self) -> Option<Item> {
(!self.buf.is_empty()).then(|| Item::Text(std::mem::take(&mut self.buf)))
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Option<Item> {
if self.buf.is_empty() {
return None;
}
match parse_with_limit(&self.buf, self.max) {
Parse::Complete { value, len } => {
self.buf.drain(..len);
Some(Item::Message(value))
}
Parse::Invalid { len } => Some(Item::Invalid(self.buf.drain(..len).collect())),
Parse::Incomplete => None,
Parse::NotFuzzball => {
let mut end = self.buf.len();
for i in 1..self.buf.len() {
if self.buf[i] == 0x1b {
let rest = &self.buf[i..];
if rest.starts_with(PREFIX) || PREFIX.starts_with(rest) {
end = i;
break;
}
}
}
Some(Item::Text(self.buf.drain(..end).collect()))
}
}
}
}