use crate::{Packet, PacketParseError, ProtocolVersion};
use futures_util::{Stream, StreamExt};
use bytes::Buf;
use http_body::Body;
use http_body_util::BodyStream;
use std::io::BufRead;
use super::buf::BufList;
struct Payload<B: Body + Unpin> {
body: BodyStream<B>,
buffer: BufList<B::Data>,
end_of_stream: bool,
current_payload_size: u64,
}
impl<B: Body + Unpin> Payload<B> {
fn new(body: B) -> Self {
Self {
body: BodyStream::new(body),
buffer: BufList::new(),
end_of_stream: false,
current_payload_size: 0,
}
}
}
async fn poll_body<B, E>(state: &mut Payload<B>, max_payload: u64) -> Result<(), PacketParseError>
where
B: Body<Error = E> + Unpin,
E: std::fmt::Debug,
{
let data = match state.body.next().await.transpose() {
Ok(Some(frame)) if frame.is_data() => Ok(frame
.into_data()
.unwrap_or_else(|_| unreachable!("frame.is_data() is true"))),
Ok(_) => {
state.end_of_stream = true;
return Ok(());
}
Err(_e) => {
#[cfg(feature = "tracing")]
tracing::debug!("error reading body stream: {:?}", _e);
Err(PacketParseError::InvalidPacketPayload)
}
}?;
if state.current_payload_size + (data.remaining() as u64) <= max_payload {
state.current_payload_size += data.remaining() as u64;
state.buffer.push(data);
Ok(())
} else {
Err(PacketParseError::PayloadTooLarge { max: max_payload })
}
}
pub fn v4_decoder<B, E>(
body: B,
max_payload: u64,
) -> impl Stream<Item = Result<Packet, PacketParseError>>
where
B: Body<Error = E> + Unpin,
E: std::fmt::Debug,
{
use super::PACKET_SEPARATOR_V4;
#[cfg(feature = "tracing")]
tracing::debug!("decoding payload with v4 decoder");
let state = Payload::new(body);
futures_util::stream::unfold(state, move |mut state| async move {
let mut packet_buf: Vec<u8> = Vec::new();
loop {
if !state.end_of_stream
&& let Err(e) = poll_body(&mut state, max_payload).await
{
break Some((Err(e), state));
}
if let Err(_err) = (&mut state.buffer)
.reader()
.read_until(PACKET_SEPARATOR_V4, &mut packet_buf)
{
#[cfg(feature = "tracing")]
tracing::debug!("failed to read packet payload: {_err}");
break Some((Err(PacketParseError::InvalidPacketPayload), state));
}
let separator_found = packet_buf.ends_with(&[PACKET_SEPARATOR_V4]);
if separator_found {
packet_buf.pop(); }
if separator_found
|| (state.end_of_stream && state.buffer.remaining() == 0 && !packet_buf.is_empty())
{
let packet = String::from_utf8(packet_buf)
.map_err(PacketParseError::from)
.and_then(|v| Packet::parse(ProtocolVersion::V4, v)); break Some((packet, state)); } else if state.end_of_stream && state.buffer.remaining() == 0 {
break None; }
}
})
}
#[cfg(feature = "v3")]
pub fn v3_binary_decoder<B, E>(
body: B,
max_payload: u64,
) -> impl Stream<Item = Result<Packet, PacketParseError>>
where
B: Body<Error = E> + Unpin,
E: std::fmt::Debug,
{
use std::io::Read;
use crate::payload::{
BINARY_PACKET_IDENTIFIER_V3, BINARY_PACKET_SEPARATOR_V3, STRING_PACKET_IDENTIFIER_V3,
};
let state = Payload::new(body);
#[cfg(feature = "tracing")]
tracing::debug!("decoding payload with v3 binary decoder");
futures_util::stream::unfold(state, move |mut state| async move {
let mut packet_buf: Vec<u8> = Vec::new();
let mut packet_type: Option<u8> = None;
let mut packet_size: u64 = 0;
loop {
if !state.end_of_stream
&& let Err(e) = poll_body(&mut state, max_payload).await
{
break Some((Err(e), state));
}
if packet_type.is_none() && state.buffer.remaining() > 0 {
if let Err(_err) = (&mut state.buffer)
.reader()
.read_until(BINARY_PACKET_SEPARATOR_V3, &mut packet_buf)
{
#[cfg(feature = "tracing")]
tracing::debug!("failed to read packet payload: {_err}");
break Some((Err(PacketParseError::InvalidPacketPayload), state));
}
if packet_buf.ends_with(&[BINARY_PACKET_SEPARATOR_V3]) {
packet_buf.pop();
match packet_buf.first() {
Some(&BINARY_PACKET_IDENTIFIER_V3) => {
packet_type = Some(BINARY_PACKET_IDENTIFIER_V3)
}
Some(&STRING_PACKET_IDENTIFIER_V3) => {
packet_type = Some(STRING_PACKET_IDENTIFIER_V3)
}
_ => break Some((Err(PacketParseError::InvalidPacketLen), state)),
}
if packet_buf.len() > 9 {
break Some((Err(PacketParseError::InvalidPacketLen), state));
}
let size_str = &packet_buf[1..]
.iter()
.map(|b| (b.wrapping_add(b'0')) as char)
.collect::<String>();
if let Ok(size) = size_str.parse() {
packet_size = size;
} else {
break Some((Err(PacketParseError::InvalidPacketLen), state));
}
packet_buf.clear();
}
} else if packet_size > 0 && state.buffer.remaining() >= packet_size as usize {
let mut reader = (&mut state.buffer).reader().take(packet_size);
if packet_type.unwrap() == BINARY_PACKET_IDENTIFIER_V3 {
reader.consume(1);
}
reader.read_to_end(&mut packet_buf).unwrap();
let packet = match packet_type.unwrap() {
STRING_PACKET_IDENTIFIER_V3 => String::from_utf8(packet_buf)
.map_err(PacketParseError::from)
.and_then(|v| Packet::parse(ProtocolVersion::V3, v)), BINARY_PACKET_IDENTIFIER_V3 => Ok(Packet::BinaryV3(packet_buf.into())),
_ => Err(PacketParseError::InvalidPacketLen),
};
break Some((packet, state));
} else if state.end_of_stream && state.buffer.remaining() == 0 {
break None;
} else if state.end_of_stream {
break Some((Err(PacketParseError::InvalidPacketLen), state));
}
}
})
}
#[cfg(feature = "v3")]
fn utf16_byte_offset(s: &str, target: usize) -> Option<usize> {
if target == 0 {
return Some(0);
}
let mut count = 0usize;
for (i, ch) in s.char_indices() {
count += ch.len_utf16();
if count >= target {
return Some(i + ch.len_utf8());
}
}
None
}
#[cfg(feature = "v3")]
fn utf16_len(s: &str) -> usize {
s.encode_utf16().count()
}
#[cfg(feature = "v3")]
pub fn v3_string_decoder(
body: impl Body<Error = impl std::fmt::Debug> + Unpin,
max_payload: u64,
) -> impl Stream<Item = Result<Packet, PacketParseError>> {
use std::io::ErrorKind;
use crate::payload::STRING_PACKET_SEPARATOR_V3;
#[cfg(feature = "tracing")]
tracing::debug!("decoding payload with v3 string decoder");
let state = Payload::new(body);
futures_util::stream::unfold(state, move |mut state| async move {
let mut packet_buf: Vec<u8> = Vec::new();
let mut packet_utf16_len: usize = 0;
loop {
if !state.end_of_stream
&& let Err(e) = poll_body(&mut state, max_payload).await
{
break Some((Err(e), state));
}
if state.end_of_stream && state.buffer.remaining() == 0 {
if !packet_buf.is_empty() {
break Some((Err(PacketParseError::InvalidPacketLen), state));
}
break None; }
let mut reader = (&mut state.buffer).reader();
if packet_utf16_len == 0 {
loop {
let (done, used) = {
let remaining = reader.get_ref().remaining();
let available = match reader.fill_buf() {
Ok(n) => n,
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(_err) => {
#[cfg(feature = "tracing")]
tracing::debug!("failed to read packet payload: {_err}");
return Some((Err(PacketParseError::InvalidPacketPayload), state));
}
};
let old_len = packet_buf.len();
packet_buf.extend_from_slice(available);
match memchr::memchr(STRING_PACKET_SEPARATOR_V3, &packet_buf) {
Some(i) => {
packet_utf16_len = match std::str::from_utf8(&packet_buf[..i])
.map_err(PacketParseError::from)
.and_then(|s| {
s.parse::<usize>()
.map_err(|_| PacketParseError::InvalidPacketLen)
}) {
Ok(size) => size,
Err(e) => return Some((Err(e), state)),
};
packet_buf.clear();
(true, i + 1 - old_len) }
None if state.end_of_stream && remaining - available.len() == 0 => {
return Some((Err(PacketParseError::InvalidPacketLen), state));
} None => (false, available.len()), }
};
reader.consume(used); if done || used == 0 {
break; }
}
}
if packet_utf16_len == 0 {
continue; }
let data: &[u8] = reader.fill_buf().unwrap();
let old_len = packet_buf.len();
packet_buf.extend_from_slice(data);
let truncate_to = |s: &str| utf16_byte_offset(s, packet_utf16_len);
let byte_read = match std::str::from_utf8(&packet_buf) {
Ok(fulldata) => match truncate_to(fulldata) {
Some(i) => {
packet_buf.truncate(i);
packet_buf.len() - old_len
}
None => data.len(), },
Err(e) => {
let chunk =
unsafe { std::str::from_utf8_unchecked(&packet_buf[..e.valid_up_to()]) };
match truncate_to(chunk) {
Some(i) => {
packet_buf.truncate(i);
packet_buf.len() - old_len
}
None => data.len(),
}
}
};
reader.consume(byte_read);
if let Ok(packet) = std::str::from_utf8(&packet_buf) {
if utf16_len(packet) == packet_utf16_len {
let packet = unsafe { String::from_utf8_unchecked(packet_buf) };
let packet = Packet::parse(ProtocolVersion::V3, packet)
.map_err(|_| PacketParseError::InvalidPacketLen);
break Some((packet, state)); }
} else if state.end_of_stream && state.buffer.remaining() == 0 {
break None; }
}
})
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use futures_util::StreamExt;
use http_body::Frame;
use http_body_util::{Full, StreamBody};
use super::*;
const MAX_PAYLOAD: u64 = 100_000;
#[tokio::test]
async fn payload_iterator_v4() {
let data = Full::new(Bytes::from("4foo\x1e4€f\x1e4f"));
let payload = v4_decoder(data, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "foo"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "€f"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "f"
));
assert!(payload.next().await.is_none());
}
#[tokio::test]
async fn payload_stream_v4() {
const DATA: &[u8] = "4foo\x1e4€f\x1e4fo".as_bytes();
for i in 1..DATA.len() {
let stream = StreamBody::new(futures_util::stream::iter(
DATA.chunks(i)
.map(Frame::data)
.map(Ok::<_, std::convert::Infallible>),
));
let payload = v4_decoder(stream, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "foo"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "€f"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "fo"
));
assert!(payload.next().await.is_none());
}
}
#[tokio::test]
async fn max_payload_v4() {
const DATA: &[u8] = "4foo\x1e4€f\x1e4fo".as_bytes();
const MAX_PAYLOAD: u64 = 3;
for i in 1..DATA.len() {
let stream = StreamBody::new(futures_util::stream::iter(
DATA.chunks(i)
.map(Frame::data)
.map(Ok::<_, std::convert::Infallible>),
));
let payload = v4_decoder(stream, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
let packet = payload.next().await.unwrap();
assert!(matches!(
packet,
Err(PacketParseError::PayloadTooLarge { max: MAX_PAYLOAD })
));
}
}
#[cfg(feature = "v3")]
#[tokio::test]
async fn string_payload_v3_utf16_length() {
let data = Full::new(Bytes::from("3:4𝕊3:4ab"));
let payload = v3_string_decoder(data, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "𝕊"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "ab"
));
assert!(payload.next().await.is_none());
}
#[cfg(feature = "v3")]
#[tokio::test]
async fn string_payload_iterator_v3() {
let data = Full::new(Bytes::from("4:4foo3:4€f11:4faaaaaaaaa"));
let payload = v3_string_decoder(data, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "foo"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "€f"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "faaaaaaaaa"
));
assert!(payload.next().await.is_none());
}
#[cfg(feature = "v3")]
#[tokio::test]
async fn binary_payload_iterator_v3() {
const PAYLOAD: &[u8] = &[
0, 9, 255, 52, 104, 101, 108, 108, 111, 226, 130, 172, 1, 5, 255, 4, 1, 2, 3, 4,
];
const BINARY_PAYLOAD: &[u8] = &[1, 2, 3, 4];
let data = Full::new(Bytes::from(PAYLOAD));
let payload = v3_binary_decoder(data, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "hello€"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::BinaryV3(msg) if msg == BINARY_PAYLOAD
));
assert!(payload.next().await.is_none());
}
#[cfg(feature = "v3")]
#[tokio::test]
async fn string_payload_stream_v3() {
const DATA: &[u8] = "4:4foo3:4€f11:4baaaaaaaar".as_bytes();
for i in 1..DATA.len() {
let stream = StreamBody::new(futures_util::stream::iter(
DATA.chunks(i)
.map(Frame::data)
.map(Ok::<_, std::convert::Infallible>),
));
let payload = v3_string_decoder(stream, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
let packet = payload.next().await.unwrap().unwrap();
assert!(matches!(
packet,
Packet::Message(msg) if msg == "foo"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "€f"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "baaaaaaaar"
));
assert!(payload.next().await.is_none());
}
}
#[cfg(feature = "v3")]
#[tokio::test]
async fn string_invalid_packet_format_v3() {
let data = Full::new(Bytes::from_static(b"abc"));
let payload = v3_string_decoder(data, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
assert!(matches!(
payload.next().await,
Some(Err(crate::PacketParseError::InvalidPacketLen))
));
}
#[cfg(feature = "v3")]
#[tokio::test]
async fn binary_payload_stream_v3() {
const PAYLOAD: &[u8] = &[
0, 9, 255, 52, 104, 101, 108, 108, 111, 226, 130, 172, 1, 5, 255, 4, 1, 2, 3, 4,
];
const BINARY_PAYLOAD: &[u8] = &[1, 2, 3, 4];
for i in 1..PAYLOAD.len() {
let stream = StreamBody::new(futures_util::stream::iter(
PAYLOAD
.chunks(i)
.map(Frame::data)
.map(Ok::<_, std::convert::Infallible>),
));
let payload = v3_binary_decoder(stream, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "hello€"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::BinaryV3(msg) if msg == BINARY_PAYLOAD
));
assert!(payload.next().await.is_none());
}
}
#[cfg(feature = "v3")]
#[tokio::test]
async fn binary_payload_truncated_v3() {
const TRUNCATED: &[u8] = &[1, 5, 255, 4, 1, 2];
let data = Full::new(Bytes::from(TRUNCATED));
let payload = v3_binary_decoder(data, MAX_PAYLOAD);
let result = tokio::time::timeout(std::time::Duration::from_millis(200), async {
futures_util::pin_mut!(payload);
payload.next().await
})
.await
.expect("v3_binary_decoder hung on truncated packet");
assert!(matches!(result, Some(Err(_)) | None));
}
#[cfg(feature = "v3")]
#[tokio::test]
async fn max_payload_v3() {
const DATA: &[u8] = "4:4foo3:4€f11:4baaaaaaaar".as_bytes();
const MAX_PAYLOAD: u64 = 3;
for i in 1..DATA.len() {
let stream = StreamBody::new(futures_util::stream::iter(
DATA.chunks(i)
.map(Frame::data)
.map(Ok::<_, std::convert::Infallible>),
));
let payload = v3_binary_decoder(stream, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
let packet = payload.next().await.unwrap();
assert!(matches!(
packet,
Err(PacketParseError::PayloadTooLarge { max: MAX_PAYLOAD })
));
}
for i in 1..DATA.len() {
let stream = StreamBody::new(futures_util::stream::iter(
DATA.chunks(i)
.map(Frame::data)
.map(Ok::<_, std::convert::Infallible>),
));
let payload = v3_string_decoder(stream, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
let packet = payload.next().await.unwrap();
assert!(matches!(
packet,
Err(PacketParseError::PayloadTooLarge { max: MAX_PAYLOAD })
));
}
}
#[cfg(feature = "v3")]
#[tokio::test]
async fn string_payload_stream_v3_utf16_length() {
const DATA: &[u8] = "3:4𝕊3:4ab".as_bytes();
for i in 1..DATA.len() {
let stream = StreamBody::new(futures_util::stream::iter(
DATA.chunks(i)
.map(Frame::data)
.map(Ok::<_, std::convert::Infallible>),
));
let payload = v3_string_decoder(stream, MAX_PAYLOAD);
futures_util::pin_mut!(payload);
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "𝕊"
));
assert!(matches!(
payload.next().await.unwrap().unwrap(),
Packet::Message(msg) if msg == "ab"
));
assert!(payload.next().await.is_none());
}
}
#[cfg(feature = "v3")]
#[tokio::test]
async fn string_payload_v3_surrogate_straddle_rejected() {
let data = Full::new(Bytes::from("2:4𝕊"));
let payload = v3_string_decoder(data, MAX_PAYLOAD);
let result = tokio::time::timeout(std::time::Duration::from_millis(200), async {
futures_util::pin_mut!(payload);
payload.next().await
})
.await
.expect("v3_string_decoder hung on surrogate-straddling length");
assert!(matches!(result, Some(Err(_)) | None));
}
#[cfg(feature = "v3")]
#[test]
fn utf16_byte_offset_unit() {
assert_eq!(utf16_byte_offset("", 0), Some(0));
assert_eq!(utf16_byte_offset("abc", 0), Some(0));
assert_eq!(utf16_byte_offset("abc", 2), Some(2));
assert_eq!(utf16_byte_offset("abc", 3), Some(3));
assert_eq!(utf16_byte_offset("abc", 5), None);
assert_eq!(utf16_byte_offset("€", 1), Some(3));
assert_eq!(utf16_byte_offset("𝕊", 2), Some(4));
assert_eq!(utf16_byte_offset("𝕊", 1), Some(4));
assert_eq!(utf16_byte_offset("4𝕊", 3), Some(5));
assert_eq!(utf16_byte_offset("4𝕊", 2), Some(5)); assert_eq!(utf16_byte_offset("4𝕊", 10), None); }
#[cfg(feature = "v3")]
#[tokio::test]
async fn string_payload_truncated_v3() {
let data = Full::new(Bytes::from("10:4ab"));
let payload = v3_string_decoder(data, MAX_PAYLOAD);
let result = tokio::time::timeout(std::time::Duration::from_millis(200), async {
futures_util::pin_mut!(payload);
payload.next().await
})
.await
.expect("v3_string_decoder hung on truncated packet");
assert!(matches!(result, Some(Err(_)) | None));
}
}