use crate::protocol::{JdwpError, JdwpResult};
use crate::types::ValueData;
use bytes::Buf;
pub mod value_tags {
pub const BYTE: u8 = 66; pub const CHAR: u8 = 67; pub const OBJECT: u8 = 76; pub const FLOAT: u8 = 70; pub const DOUBLE: u8 = 68; pub const INT: u8 = 73; pub const LONG: u8 = 74; pub const SHORT: u8 = 83; pub const VOID: u8 = 86; pub const BOOLEAN: u8 = 90; pub const STRING: u8 = 115; pub const THREAD: u8 = 116; pub const THREAD_GROUP: u8 = 103; pub const CLASS_LOADER: u8 = 108; pub const CLASS_OBJECT: u8 = 99; pub const ARRAY: u8 = 91; }
fn ensure(buf: &[u8], n: usize, what: &str) -> JdwpResult<()> {
if buf.remaining() < n {
return Err(JdwpError::Protocol(format!(
"Not enough data for {what}: need {n} byte(s), have {}",
buf.remaining()
)));
}
Ok(())
}
fn value_width(tag: u8) -> JdwpResult<usize> {
use value_tags as t;
Ok(match tag {
t::VOID => 0,
t::BYTE | t::BOOLEAN => 1,
t::CHAR | t::SHORT => 2,
t::FLOAT | t::INT => 4,
t::DOUBLE
| t::LONG
| t::OBJECT
| t::STRING
| t::THREAD
| t::THREAD_GROUP
| t::CLASS_LOADER
| t::CLASS_OBJECT
| t::ARRAY => 8,
_ => return Err(JdwpError::Protocol(format!("Unknown value tag: {tag}"))),
})
}
pub fn read_value_by_tag(tag: u8, buf: &mut &[u8]) -> JdwpResult<ValueData> {
use value_tags as t;
let width = value_width(tag)?;
ensure(buf, width, "value")?;
Ok(match tag {
t::BYTE => ValueData::Byte(buf.get_i8()),
t::CHAR => ValueData::Char(buf.get_u16()),
t::DOUBLE => ValueData::Double(buf.get_f64()),
t::FLOAT => ValueData::Float(buf.get_f32()),
t::INT => ValueData::Int(buf.get_i32()),
t::LONG => ValueData::Long(buf.get_i64()),
t::SHORT => ValueData::Short(buf.get_i16()),
t::BOOLEAN => ValueData::Boolean(buf.get_u8() != 0),
t::VOID => ValueData::Void,
_ => ValueData::Object(buf.get_u64()),
})
}
pub fn read_string(buf: &mut &[u8]) -> JdwpResult<String> {
ensure(buf, 4, "string length")?;
let len = buf.get_u32() as usize;
ensure(buf, len, "string body")?;
let bytes = buf
.get(..len)
.ok_or_else(|| JdwpError::Protocol("String shorter than its length prefix".to_string()))?
.to_vec();
buf.advance(len);
String::from_utf8(bytes).map_err(|e| JdwpError::Protocol(format!("Invalid UTF-8 in string: {e}")))
}
pub fn read_u32(buf: &mut &[u8]) -> JdwpResult<u32> {
ensure(buf, 4, "u32")?;
Ok(buf.get_u32())
}
pub fn read_i32(buf: &mut &[u8]) -> JdwpResult<i32> {
ensure(buf, 4, "i32")?;
Ok(buf.get_i32())
}
pub fn read_u8(buf: &mut &[u8]) -> JdwpResult<u8> {
ensure(buf, 1, "u8")?;
Ok(buf.get_u8())
}
pub fn read_u64(buf: &mut &[u8]) -> JdwpResult<u64> {
ensure(buf, 8, "u64")?;
Ok(buf.get_u64())
}
pub fn read_i64(buf: &mut &[u8]) -> JdwpResult<i64> {
ensure(buf, 8, "i64")?;
Ok(buf.get_i64())
}
#[must_use]
pub fn some_if_present(s: String) -> Option<String> {
(!s.is_empty()).then_some(s)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eval::write_untagged_value;
use crate::types::Value;
fn sample_values() -> Vec<(u8, ValueData)> {
use value_tags as t;
vec![
(t::BYTE, ValueData::Byte(-7)),
(t::CHAR, ValueData::Char(u16::from(b'Q'))),
(t::SHORT, ValueData::Short(-300)),
(t::INT, ValueData::Int(i32::MIN)),
(t::LONG, ValueData::Long(i64::MAX)),
(t::FLOAT, ValueData::Float(1.5)),
(t::DOUBLE, ValueData::Double(-2.25)),
(t::BOOLEAN, ValueData::Boolean(true)),
(t::OBJECT, ValueData::Object(0xdead_beef)),
]
}
#[test]
fn every_value_variant_round_trips_through_write_then_read() {
let mut bytes = Vec::new();
for (tag, data) in sample_values() {
bytes.clear();
write_untagged_value(&mut bytes, &Value { tag, data: data.clone() });
assert_eq!(
bytes.len(),
value_width(tag).expect("sample tags are all known"),
"tag {tag} wrote {} bytes, width table says otherwise",
bytes.len()
);
let mut buf = bytes.as_slice();
let read = read_value_by_tag(tag, &mut buf).expect("round trip");
assert_eq!(format!("{read:?}"), format!("{data:?}"), "tag {tag} changed on the way back");
assert!(buf.is_empty(), "tag {tag} left {} unread byte(s)", buf.len());
}
}
#[test]
fn a_truncated_buffer_errors_for_every_value_tag() {
let mut full = Vec::new();
for (tag, data) in sample_values() {
full.clear();
write_untagged_value(&mut full, &Value { tag, data });
for keep in 0..full.len() {
let mut buf = &full[..keep];
let err = read_value_by_tag(tag, &mut buf);
assert!(
err.is_err(),
"tag {tag} accepted {keep} of {} byte(s) instead of erroring",
full.len()
);
}
}
}
#[test]
fn void_reads_from_an_empty_buffer_and_consumes_nothing() {
let mut buf: &[u8] = &[];
assert!(matches!(read_value_by_tag(value_tags::VOID, &mut buf), Ok(ValueData::Void)));
assert!(buf.is_empty());
}
#[test]
fn an_unknown_value_tag_errors_without_consuming_input() {
let mut buf: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8];
let err = read_value_by_tag(b'?', &mut buf).expect_err("'?' is not a value tag");
assert!(format!("{err}").contains("Unknown value tag"), "unhelpful error: {err}");
assert_eq!(buf.len(), 8, "a rejected tag must not advance the buffer");
}
#[test]
fn each_fixed_width_reader_errors_on_a_short_buffer() {
assert!(read_u8(&mut &[][..]).is_err());
assert!(read_u32(&mut &[0u8; 3][..]).is_err());
assert!(read_i32(&mut &[0u8; 3][..]).is_err());
assert!(read_u64(&mut &[0u8; 7][..]).is_err());
assert_eq!(read_u8(&mut &[9u8][..]).expect("u8"), 9);
assert_eq!(read_u32(&mut &[0, 0, 1, 0][..]).expect("u32"), 256);
assert_eq!(read_i32(&mut &[0xff, 0xff, 0xff, 0xff][..]).expect("i32"), -1);
assert_eq!(read_u64(&mut &[0, 0, 0, 0, 0, 0, 0, 5][..]).expect("u64"), 5);
}
#[test]
fn read_string_handles_empty_truncated_and_lying_lengths() {
let mut wire = 5u32.to_be_bytes().to_vec();
wire.extend_from_slice(b"hello");
wire.extend_from_slice(b"tail");
let mut buf = wire.as_slice();
assert_eq!(read_string(&mut buf).expect("string"), "hello");
assert_eq!(buf, b"tail", "read_string must consume exactly the string");
assert_eq!(read_string(&mut &0u32.to_be_bytes()[..]).expect("empty"), "");
assert!(read_string(&mut &[0u8, 0, 5][..]).is_err());
let mut lying = 99u32.to_be_bytes().to_vec();
lying.extend_from_slice(b"short");
assert!(read_string(&mut lying.as_slice()).is_err(), "a length past the end must error");
let mut bad = 2u32.to_be_bytes().to_vec();
bad.extend_from_slice(&[0xff, 0xfe]);
assert!(read_string(&mut bad.as_slice()).is_err());
}
}