Skip to main content

jdwp_client/
reader.rs

1// Helper functions for reading JDWP data types from buffers
2//
3// Every reader here checks the buffer before it reads. `bytes::Buf::get_*` PANICS on a short buffer,
4// and these run inside the event-loop task: a panic there kills the connection instead of surfacing
5// as an error the caller can report. A truncated or malformed reply is not hypothetical — it is what
6// a version-skewed JVM, a half-closed socket, or a bug in our own request framing produces.
7//
8// # Every JDWP id is read as 8 bytes, and that is ASSUMED — nothing checks it
9//
10// `objectID`, `referenceTypeID`, `methodID`, `fieldID` and `frameID` are all read with [`read_u64`],
11// and [`value_width`] gives every reference tag a width of 8. The JDWP spec does not fix those widths:
12// a VM declares them in `VirtualMachine.IDSizes`, and this crate never asks. It holds on every 64-bit
13// `HotSpot`, which is what this tool attaches to.
14//
15// The assumption is deliberate and **unvalidated**. A wrapper for `IDSizes` existed and was deleted by
16// CLEAN-1 (#27) precisely because it was never called: an uncalled command made the widths look
17// checked. On a VM that reported narrower ids, every read after the first id would be misaligned and
18// the failure would surface as garbled values or an `Unknown value tag`, not as a clear mismatch. If
19// that ever needs guarding, the fix is a real check at attach time that refuses the session — not a
20// function nobody calls.
21
22use crate::protocol::{JdwpError, JdwpResult};
23use crate::types::ValueData;
24use bytes::Buf;
25
26/// JDWP value tags (JDWP spec, `Value` / `TaggedObjectID`). Each is the ASCII code of the JNI type
27/// signature character, which is why they look arbitrary as numbers.
28pub mod value_tags {
29    pub const BYTE: u8 = 66; // 'B'
30    pub const CHAR: u8 = 67; // 'C'
31    pub const OBJECT: u8 = 76; // 'L'
32    pub const FLOAT: u8 = 70; // 'F'
33    pub const DOUBLE: u8 = 68; // 'D'
34    pub const INT: u8 = 73; // 'I'
35    pub const LONG: u8 = 74; // 'J'
36    pub const SHORT: u8 = 83; // 'S'
37    pub const VOID: u8 = 86; // 'V'
38    pub const BOOLEAN: u8 = 90; // 'Z'
39    pub const STRING: u8 = 115; // 's'
40    pub const THREAD: u8 = 116; // 't'
41    pub const THREAD_GROUP: u8 = 103; // 'g'
42    pub const CLASS_LOADER: u8 = 108; // 'l'
43    pub const CLASS_OBJECT: u8 = 99; // 'c'
44    pub const ARRAY: u8 = 91; // '['
45}
46
47/// Error unless `buf` holds at least `n` more bytes. `what` names the thing being read, so a
48/// truncated reply says which field ran out rather than just that something did.
49fn ensure(buf: &[u8], n: usize, what: &str) -> JdwpResult<()> {
50    if buf.remaining() < n {
51        return Err(JdwpError::Protocol(format!(
52            "Not enough data for {what}: need {n} byte(s), have {}",
53            buf.remaining()
54        )));
55    }
56    Ok(())
57}
58
59/// How many bytes a value of this tag occupies, or `Err` for a tag we don't know.
60///
61/// Resolving the width up front is what makes [`read_value_by_tag`] total: an unknown tag fails
62/// before the buffer is touched, and a known one is bounds-checked once instead of per branch.
63fn value_width(tag: u8) -> JdwpResult<usize> {
64    use value_tags as t;
65    Ok(match tag {
66        t::VOID => 0,
67        t::BYTE | t::BOOLEAN => 1,
68        t::CHAR | t::SHORT => 2,
69        t::FLOAT | t::INT => 4,
70        // 8 covers both the 64-bit primitives and every reference kind, which is an objectID.
71        t::DOUBLE
72        | t::LONG
73        | t::OBJECT
74        | t::STRING
75        | t::THREAD
76        | t::THREAD_GROUP
77        | t::CLASS_LOADER
78        | t::CLASS_OBJECT
79        | t::ARRAY => 8,
80        _ => return Err(JdwpError::Protocol(format!("Unknown value tag: {tag}"))),
81    })
82}
83
84/// Read an untagged value whose type is named by a JDWP value `tag`.
85///
86/// The one implementation of this: it used to be copied into `eval`, `stackframe` and `object`, all
87/// three of which read the buffer without checking it first.
88///
89/// # Errors
90/// Returns a [`JdwpError`] for an unknown tag, or if the buffer is too short for the value.
91pub fn read_value_by_tag(tag: u8, buf: &mut &[u8]) -> JdwpResult<ValueData> {
92    use value_tags as t;
93    let width = value_width(tag)?;
94    ensure(buf, width, "value")?;
95    // Checked above, so every `get_*` below is infallible.
96    Ok(match tag {
97        t::BYTE => ValueData::Byte(buf.get_i8()),
98        t::CHAR => ValueData::Char(buf.get_u16()),
99        t::DOUBLE => ValueData::Double(buf.get_f64()),
100        t::FLOAT => ValueData::Float(buf.get_f32()),
101        t::INT => ValueData::Int(buf.get_i32()),
102        t::LONG => ValueData::Long(buf.get_i64()),
103        t::SHORT => ValueData::Short(buf.get_i16()),
104        t::BOOLEAN => ValueData::Boolean(buf.get_u8() != 0),
105        t::VOID => ValueData::Void,
106        _ => ValueData::Object(buf.get_u64()),
107    })
108}
109
110/// Read a JDWP string (4-byte length prefix + UTF-8 bytes)
111///
112/// # Errors
113/// Returns a [`JdwpError`] if the buffer does not contain enough bytes or is malformed.
114pub fn read_string(buf: &mut &[u8]) -> JdwpResult<String> {
115    ensure(buf, 4, "string length")?;
116    let len = buf.get_u32() as usize;
117    // The length comes off the wire, so a corrupt one can claim gigabytes. Checking it against what is
118    // actually left is the whole defence; `ensure` first so the error names the shortfall, then a
119    // checked slice so this can't panic even if the two ever disagreed.
120    ensure(buf, len, "string body")?;
121    let bytes = buf
122        .get(..len)
123        .ok_or_else(|| JdwpError::Protocol("String shorter than its length prefix".to_string()))?
124        .to_vec();
125    buf.advance(len);
126
127    String::from_utf8(bytes).map_err(|e| JdwpError::Protocol(format!("Invalid UTF-8 in string: {e}")))
128}
129
130/// Read a u32
131///
132/// # Errors
133/// Returns a [`JdwpError`] if the buffer does not contain enough bytes or is malformed.
134pub fn read_u32(buf: &mut &[u8]) -> JdwpResult<u32> {
135    ensure(buf, 4, "u32")?;
136    Ok(buf.get_u32())
137}
138
139/// Read a i32
140///
141/// # Errors
142/// Returns a [`JdwpError`] if the buffer does not contain enough bytes or is malformed.
143pub fn read_i32(buf: &mut &[u8]) -> JdwpResult<i32> {
144    ensure(buf, 4, "i32")?;
145    Ok(buf.get_i32())
146}
147
148/// Read a u8
149///
150/// # Errors
151/// Returns a [`JdwpError`] if the buffer does not contain enough bytes or is malformed.
152pub fn read_u8(buf: &mut &[u8]) -> JdwpResult<u8> {
153    ensure(buf, 1, "u8")?;
154    Ok(buf.get_u8())
155}
156
157/// Read a u64
158///
159/// # Errors
160/// Returns a [`JdwpError`] if the buffer does not contain enough bytes or is malformed.
161pub fn read_u64(buf: &mut &[u8]) -> JdwpResult<u64> {
162    ensure(buf, 8, "u64")?;
163    Ok(buf.get_u64())
164}
165
166/// Read an i64
167///
168/// # Errors
169/// Returns a [`JdwpError`] if the buffer does not contain enough bytes or is malformed.
170pub fn read_i64(buf: &mut &[u8]) -> JdwpResult<i64> {
171    ensure(buf, 8, "i64")?;
172    Ok(buf.get_i64())
173}
174
175/// A JDWP string that means "there is none" when it is empty.
176///
177/// The generic-signature commands (DISC-12, #95) answer with an **empty string** for a member whose class
178/// file carries no `Signature` attribute, rather than with an error or an absent field. That is the ordinary
179/// case by a wide margin, and left as `Some("")` it would travel all the way to a caller as a blank type.
180/// Normalising it here means every consumer sees the same `None` the absence deserves.
181#[must_use]
182pub fn some_if_present(s: String) -> Option<String> {
183    (!s.is_empty()).then_some(s)
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use crate::eval::write_untagged_value;
190    use crate::types::Value;
191
192    /// One value of every `ValueData` variant, with its tag. `Void` has no bytes, so it is exercised
193    /// separately rather than round-tripped.
194    fn sample_values() -> Vec<(u8, ValueData)> {
195        use value_tags as t;
196        vec![
197            (t::BYTE, ValueData::Byte(-7)),
198            (t::CHAR, ValueData::Char(u16::from(b'Q'))),
199            (t::SHORT, ValueData::Short(-300)),
200            (t::INT, ValueData::Int(i32::MIN)),
201            (t::LONG, ValueData::Long(i64::MAX)),
202            (t::FLOAT, ValueData::Float(1.5)),
203            (t::DOUBLE, ValueData::Double(-2.25)),
204            (t::BOOLEAN, ValueData::Boolean(true)),
205            (t::OBJECT, ValueData::Object(0xdead_beef)),
206        ]
207    }
208
209    /// The writer and the reader have to agree byte for byte — they are the two halves of every
210    /// `SetValues` / `GetValues` round trip, and a mismatch would show up as a plausible wrong number
211    /// rather than an error.
212    #[test]
213    fn every_value_variant_round_trips_through_write_then_read() {
214        let mut bytes = Vec::new();
215        for (tag, data) in sample_values() {
216            bytes.clear();
217            write_untagged_value(&mut bytes, &Value { tag, data: data.clone() });
218            assert_eq!(
219                bytes.len(),
220                value_width(tag).expect("sample tags are all known"),
221                "tag {tag} wrote {} bytes, width table says otherwise",
222                bytes.len()
223            );
224
225            let mut buf = bytes.as_slice();
226            let read = read_value_by_tag(tag, &mut buf).expect("round trip");
227            assert_eq!(format!("{read:?}"), format!("{data:?}"), "tag {tag} changed on the way back");
228            assert!(buf.is_empty(), "tag {tag} left {} unread byte(s)", buf.len());
229        }
230    }
231
232    /// A short buffer must be an error, never a panic: these run in the event-loop task, where a panic
233    /// takes the connection down instead of being reported.
234    #[test]
235    fn a_truncated_buffer_errors_for_every_value_tag() {
236        let mut full = Vec::new();
237        for (tag, data) in sample_values() {
238            full.clear();
239            write_untagged_value(&mut full, &Value { tag, data });
240            // Every length short of complete, including empty.
241            for keep in 0..full.len() {
242                let mut buf = &full[..keep];
243                let err = read_value_by_tag(tag, &mut buf);
244                assert!(
245                    err.is_err(),
246                    "tag {tag} accepted {keep} of {} byte(s) instead of erroring",
247                    full.len()
248                );
249            }
250        }
251    }
252
253    #[test]
254    fn void_reads_from_an_empty_buffer_and_consumes_nothing() {
255        let mut buf: &[u8] = &[];
256        assert!(matches!(read_value_by_tag(value_tags::VOID, &mut buf), Ok(ValueData::Void)));
257        assert!(buf.is_empty());
258    }
259
260    /// An unrecognised tag is refused before the buffer is touched, so a bogus tag can't be read as
261    /// whatever happens to follow it.
262    #[test]
263    fn an_unknown_value_tag_errors_without_consuming_input() {
264        let mut buf: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8];
265        let err = read_value_by_tag(b'?', &mut buf).expect_err("'?' is not a value tag");
266        assert!(format!("{err}").contains("Unknown value tag"), "unhelpful error: {err}");
267        assert_eq!(buf.len(), 8, "a rejected tag must not advance the buffer");
268    }
269
270    #[test]
271    fn each_fixed_width_reader_errors_on_a_short_buffer() {
272        assert!(read_u8(&mut &[][..]).is_err());
273        assert!(read_u32(&mut &[0u8; 3][..]).is_err());
274        assert!(read_i32(&mut &[0u8; 3][..]).is_err());
275        assert!(read_u64(&mut &[0u8; 7][..]).is_err());
276        // And each succeeds at exactly its width, so the checks aren't off by one.
277        assert_eq!(read_u8(&mut &[9u8][..]).expect("u8"), 9);
278        assert_eq!(read_u32(&mut &[0, 0, 1, 0][..]).expect("u32"), 256);
279        assert_eq!(read_i32(&mut &[0xff, 0xff, 0xff, 0xff][..]).expect("i32"), -1);
280        assert_eq!(read_u64(&mut &[0, 0, 0, 0, 0, 0, 0, 5][..]).expect("u64"), 5);
281    }
282
283    #[test]
284    fn read_string_handles_empty_truncated_and_lying_lengths() {
285        // A well-formed string, and the buffer left positioned after it.
286        let mut wire = 5u32.to_be_bytes().to_vec();
287        wire.extend_from_slice(b"hello");
288        wire.extend_from_slice(b"tail");
289        let mut buf = wire.as_slice();
290        assert_eq!(read_string(&mut buf).expect("string"), "hello");
291        assert_eq!(buf, b"tail", "read_string must consume exactly the string");
292
293        // Empty string: a length of 0 is legal, not a truncation.
294        assert_eq!(read_string(&mut &0u32.to_be_bytes()[..]).expect("empty"), "");
295
296        // Truncated length prefix, and a length that overruns the body.
297        assert!(read_string(&mut &[0u8, 0, 5][..]).is_err());
298        let mut lying = 99u32.to_be_bytes().to_vec();
299        lying.extend_from_slice(b"short");
300        assert!(read_string(&mut lying.as_slice()).is_err(), "a length past the end must error");
301
302        // Invalid UTF-8 is an error, not a lossy string: a mangled class signature would be worse
303        // than a reported failure.
304        let mut bad = 2u32.to_be_bytes().to_vec();
305        bad.extend_from_slice(&[0xff, 0xfe]);
306        assert!(read_string(&mut bad.as_slice()).is_err());
307    }
308}