use std::fmt;
use std::io;
use rmp::decode::{self, Bytes, NumValueReadError, ValueReadError};
pub use super::decode::{ArrayReader, Decode, MapReader};
use super::decode::{BytesReaderExt, num_value_read_error, value_read_error};
pub struct RedrawFrame {
bytes: bytes::Bytes,
params_offset: usize,
params_len: u32,
}
pub struct RedrawFrameInfo {
consumed: usize,
params_offset: usize,
params_len: u32,
}
impl RedrawFrameInfo {
pub fn probe(bytes: &[u8]) -> io::Result<Option<Self>> {
let mut reader = Bytes::new(bytes);
let outer_len = match decode::read_array_len(&mut reader) {
Ok(len) => len,
Err(ValueReadError::TypeMismatch(_)) => return Ok(None),
Err(err) => return Err(value_read_error(err)),
};
if outer_len < 3 {
return Ok(None);
}
let msg_type = match decode::read_int::<u8, _>(&mut reader) {
Ok(msg_type) => msg_type,
Err(NumValueReadError::TypeMismatch(_)) | Err(NumValueReadError::OutOfRange) => {
return Ok(None);
}
Err(err) => {
return Err(num_value_read_error(err));
}
};
if msg_type != 2 {
return Ok(None);
}
match reader.read_str_eq("redraw") {
Ok(true) => {}
Ok(false) => return Ok(None),
Err(err) => return Err(err),
}
let params_len = match decode::read_array_len(&mut reader) {
Ok(len) => len,
Err(ValueReadError::TypeMismatch(_)) => {
return Ok(None);
}
Err(err) => {
return Err(value_read_error(err));
}
};
let params_offset = bytes.len() - reader.remaining_slice().len();
reader.skip_values(params_len)?;
for _ in 3..outer_len {
reader.skip_value()?;
}
Ok(Some(Self {
consumed: bytes.len() - reader.remaining_slice().len(),
params_offset,
params_len,
}))
}
#[inline]
#[must_use]
pub fn consumed(&self) -> usize {
self.consumed
}
#[inline]
pub fn frame(&self, bytes: bytes::Bytes) -> RedrawFrame {
debug_assert_eq!(bytes.len(), self.consumed);
RedrawFrame {
bytes,
params_offset: self.params_offset,
params_len: self.params_len,
}
}
}
impl fmt::Debug for RedrawFrame {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("RedrawFrame")
.field("len", &self.bytes.len())
.finish()
}
}
impl RedrawFrame {
pub fn from_slice(bytes: &[u8]) -> io::Result<Self> {
let info = RedrawFrameInfo::probe(bytes)?.expect("redraw frame");
Ok(info.frame(bytes::Bytes::copy_from_slice(&bytes[..info.consumed])))
}
pub fn from_bytes(bytes: bytes::Bytes) -> io::Result<Self> {
let info = RedrawFrameInfo::probe(&bytes)?.expect("redraw frame");
Ok(info.frame(bytes))
}
#[inline]
pub fn consumed(&self) -> usize {
self.bytes.len()
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
#[inline]
pub fn notification(&self) -> io::Result<RedrawNotification<'_>> {
Ok(RedrawNotification::new(ArrayReader {
reader: Bytes::new(&self.bytes[self.params_offset..]),
remaining: self.params_len,
}))
}
}
pub struct RedrawNotification<'de> {
params: ArrayReader<'de>,
}
impl<'de> RedrawNotification<'de> {
#[inline]
#[must_use]
pub(crate) fn new(params: ArrayReader<'de>) -> Self {
Self { params }
}
#[inline]
#[must_use]
pub fn batch_count(&self) -> u32 {
self.params.remaining()
}
pub fn for_each_batch<F>(&mut self, mut f: F) -> io::Result<()>
where
F: FnMut(&mut RedrawBatch<'de>) -> io::Result<bool>,
{
while !self.params.is_empty() {
self.params.ensure_remaining()?;
let mut batch_items = self.params.reader.read_array_reader()?;
self.params.remaining -= 1;
let name = batch_items.read()?;
let args = batch_items;
let mut batch = RedrawBatch { name, args };
let should_continue = f(&mut batch)?;
batch.args.skip_remaining()?;
self.params.reader = batch.args.reader;
if !should_continue {
break;
}
}
Ok(())
}
}
pub struct RedrawBatch<'de> {
pub name: &'de str,
pub args: ArrayReader<'de>,
}
#[cfg(test)]
mod tests {
use std::io::ErrorKind;
use super::*;
use rmp::Marker;
use rmpv::{ValueRef, encode::write_value_ref};
fn encode_value(value: ValueRef<'_>) -> Vec<u8> {
let mut bytes = vec![];
write_value_ref(&mut bytes, &value).unwrap();
bytes
}
fn redraw_notification(params: Vec<ValueRef<'_>>) -> Vec<u8> {
rpc_message(vec![2_u64.into(), "redraw".into(), ValueRef::Array(params)])
}
fn rpc_message(fields: Vec<ValueRef<'_>>) -> Vec<u8> {
encode_value(ValueRef::Array(fields))
}
fn read<'de, D>(reader: &mut Bytes<'de>) -> io::Result<D>
where
D: Decode<'de>,
{
D::decode(reader)
}
fn read_redraw_notification(bytes: &[u8]) -> RedrawNotification<'_> {
let mut reader = Bytes::new(bytes);
decode::read_array_len(&mut reader).unwrap();
reader.skip_value().unwrap();
assert_eq!(read::<&str>(&mut reader).unwrap(), "redraw");
RedrawNotification::new(reader.read_array_reader().unwrap())
}
#[track_caller]
fn assert_reader_error_kind<T>(result: io::Result<T>, kind: ErrorKind) {
let err = result.err().expect("expected reader error");
assert_eq!(err.kind(), kind);
}
#[track_caller]
fn assert_incomplete<T>(result: io::Result<T>) {
assert_reader_error_kind(result, ErrorKind::UnexpectedEof);
}
#[test]
fn redraw_frame_probe_rejects_non_redraw_messages() {
assert!(
RedrawFrameInfo::probe(&encode_value("redraw".into()))
.unwrap()
.is_none()
);
assert!(
RedrawFrameInfo::probe(&rpc_message(vec![]))
.unwrap()
.is_none()
);
assert!(
RedrawFrameInfo::probe(&rpc_message(vec![2_u64.into()]))
.unwrap()
.is_none()
);
let request = rpc_message(vec![
0_u64.into(),
7_u64.into(),
"redraw".into(),
ValueRef::Array(vec![]),
]);
assert!(RedrawFrameInfo::probe(&request).unwrap().is_none());
let non_integer_message_type = rpc_message(vec![
"notification".into(),
"redraw".into(),
ValueRef::Array(vec![]),
]);
assert!(
RedrawFrameInfo::probe(&non_integer_message_type)
.unwrap()
.is_none()
);
let response = rpc_message(vec![
1_u64.into(),
7_u64.into(),
ValueRef::Nil,
ValueRef::Boolean(true),
]);
assert!(RedrawFrameInfo::probe(&response).unwrap().is_none());
let non_redraw = rpc_message(vec![
2_u64.into(),
"not-redraw".into(),
ValueRef::Array(vec![]),
]);
assert!(RedrawFrameInfo::probe(&non_redraw).unwrap().is_none());
let non_string_method =
rpc_message(vec![2_u64.into(), 7_u64.into(), ValueRef::Array(vec![])]);
assert!(
RedrawFrameInfo::probe(&non_string_method)
.unwrap()
.is_none()
);
let method_only = rpc_message(vec![2_u64.into(), "redraw".into()]);
assert!(RedrawFrameInfo::probe(&method_only).unwrap().is_none());
let non_array_params = rpc_message(vec![2_u64.into(), "redraw".into(), "not-array".into()]);
assert!(RedrawFrameInfo::probe(&non_array_params).unwrap().is_none());
}
#[test]
fn redraw_frame_probe_reports_complete_incomplete_and_not_redraw() {
let redraw = redraw_notification(vec![ValueRef::Array(vec!["flush".into()])]);
let incomplete_redraw_prefix = [
Marker::FixArray(3).to_u8(),
2,
Marker::FixStr(6).to_u8(),
b'r',
b'e',
b'd',
b'r',
b'a',
b'w',
];
let request = rpc_message(vec![
0_u64.into(),
1_u64.into(),
"method".into(),
ValueRef::Array(vec![]),
]);
let info = RedrawFrameInfo::probe(&redraw)
.unwrap()
.expect("redraw frame");
assert_eq!(info.consumed(), redraw.len());
let frame = RedrawFrame::from_slice(&redraw).unwrap();
assert_eq!(frame.as_bytes(), redraw.as_slice());
assert_incomplete(RedrawFrameInfo::probe(&[]));
assert_incomplete(RedrawFrameInfo::probe(&incomplete_redraw_prefix));
assert!(RedrawFrameInfo::probe(&request).unwrap().is_none());
}
#[test]
fn redraw_frame_probe_counts_extra_outer_fields() {
let redraw = rpc_message(vec![
2_u64.into(),
"redraw".into(),
ValueRef::Array(vec![ValueRef::Array(vec!["flush".into()])]),
"extra".into(),
]);
let mut bytes = redraw.clone();
bytes.extend_from_slice(&encode_value("tail".into()));
let info = RedrawFrameInfo::probe(&bytes)
.unwrap()
.expect("redraw frame");
assert_eq!(info.consumed(), redraw.len());
}
#[test]
fn redraw_frame_notification_reads_params_from_probe_offset() {
let redraw = redraw_notification(vec![
ValueRef::Array(vec![
"grid_resize".into(),
ValueRef::Array(vec![1_u64.into(), 80_u64.into(), 24_u64.into()]),
]),
ValueRef::Array(vec!["flush".into()]),
]);
let frame = RedrawFrame::from_slice(&redraw).unwrap();
let mut notification = frame.notification().unwrap();
let mut seen = vec![];
assert_eq!(notification.batch_count(), 2);
notification
.for_each_batch(|batch| {
seen.push(batch.name.to_owned());
if batch.name == "grid_resize" {
batch.args.read_array(|args| {
assert_eq!(args.read::<u64>()?, 1);
assert_eq!(args.read::<u64>()?, 80);
assert_eq!(args.read::<u64>()?, 24);
Ok(true)
})?;
} else {
assert!(batch.args.is_empty());
}
Ok(true)
})
.unwrap();
assert_eq!(seen, vec!["grid_resize", "flush"]);
}
#[test]
fn redraw_frame_probe_reports_malformed_method_payloads() {
let invalid_utf8_method = vec![
Marker::FixArray(3).to_u8(),
2,
Marker::Str8.to_u8(),
1,
0xff,
Marker::FixArray(0).to_u8(),
];
assert_reader_error_kind(
RedrawFrameInfo::probe(&invalid_utf8_method),
ErrorKind::InvalidData,
);
let incomplete_method = vec![
Marker::FixArray(3).to_u8(),
2,
Marker::Str8.to_u8(),
2,
b'a',
];
assert_incomplete(RedrawFrameInfo::probe(&incomplete_method));
let incomplete_first_item = vec![Marker::FixArray(3).to_u8(), Marker::U16.to_u8(), 1];
assert_incomplete(RedrawFrameInfo::probe(&incomplete_first_item));
}
#[test]
fn redraw_notification_reads_batches_and_args() {
let bytes = redraw_notification(vec![
ValueRef::Array(vec![
"grid_line".into(),
ValueRef::Array(vec![1_u64.into(), (-2_i64).into(), ValueRef::Boolean(true)]),
]),
ValueRef::Array(vec!["flush".into()]),
]);
let mut redraw = read_redraw_notification(&bytes);
let mut seen = vec![];
assert_eq!(redraw.batch_count(), 2);
redraw
.for_each_batch(|batch| {
seen.push(batch.name.to_owned());
if batch.name == "grid_line" {
assert_eq!(batch.args.remaining(), 1);
while !batch.args.is_empty() {
batch.args.read_array(|args| {
assert_eq!(args.read::<u64>()?, 1);
assert_eq!(args.read::<i64>()?, -2);
assert!(args.read::<bool>()?);
Ok(())
})?;
}
} else {
assert!(batch.args.is_empty());
}
Ok(true)
})
.unwrap();
assert_eq!(seen, vec!["grid_line", "flush"]);
}
#[test]
fn redraw_notification_reports_invalid_batch_name() {
let bytes = redraw_notification(vec![ValueRef::Array(vec![1_u64.into()])]);
let mut redraw = read_redraw_notification(&bytes);
assert_reader_error_kind(
redraw.for_each_batch(|_| unreachable!()),
ErrorKind::InvalidData,
);
}
#[test]
fn redraw_batch_skips_unread_args() {
let bytes = redraw_notification(vec![
ValueRef::Array(vec![
"grid_line".into(),
ValueRef::Array(vec![1_u64.into()]),
ValueRef::Array(vec![2_u64.into()]),
]),
ValueRef::Array(vec!["flush".into()]),
]);
let mut redraw = read_redraw_notification(&bytes);
let mut names = vec![];
redraw
.for_each_batch(|batch| {
names.push(batch.name.to_owned());
Ok(true)
})
.unwrap();
assert_eq!(names, vec!["grid_line", "flush"]);
}
#[test]
fn redraw_notification_stops_when_batch_callback_returns_false() {
let bytes = redraw_notification(vec![
ValueRef::Array(vec![
"grid_line".into(),
ValueRef::Array(vec![1_u64.into()]),
]),
ValueRef::Array(vec!["flush".into()]),
]);
let mut redraw = read_redraw_notification(&bytes);
let mut names = vec![];
redraw
.for_each_batch(|batch| {
names.push(batch.name.to_owned());
Ok(false)
})
.unwrap();
assert_eq!(names, vec!["grid_line"]);
assert_eq!(redraw.batch_count(), 1);
redraw
.for_each_batch(|batch| {
names.push(batch.name.to_owned());
Ok(true)
})
.unwrap();
assert_eq!(names, vec!["grid_line", "flush"]);
assert_eq!(redraw.batch_count(), 0);
}
}