use crate::commands::event_kinds;
use crate::protocol::JdwpResult;
use crate::reader::{read_i32, read_string, read_u64, read_u8};
use crate::types::{FieldId, Location, ObjectId, ReferenceTypeId, ThreadId, Value};
use serde::{Deserialize, Serialize};
use tracing::warn;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventSet {
pub suspend_policy: u8,
pub events: Vec<Event>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
pub kind: u8,
pub request_id: i32,
pub details: EventKind,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum EventKind {
VMStart {
thread: ThreadId,
},
VMDeath,
ThreadStart {
thread: ThreadId,
},
ThreadDeath {
thread: ThreadId,
},
ClassPrepare {
thread: ThreadId,
ref_type: ReferenceTypeId,
signature: String,
status: i32,
},
Breakpoint {
thread: ThreadId,
location: Location,
},
Step {
thread: ThreadId,
location: Location,
},
Exception {
thread: ThreadId,
location: Location,
exception: ObjectId,
catch_location: Option<Location>,
},
MethodExit {
thread: ThreadId,
location: Location,
return_value: Option<Value>,
},
FieldAccess {
field: FieldEvent,
},
FieldModification {
field: FieldEvent,
new_value: Value,
},
MonitorContendedEnter {
monitor: MonitorEvent,
},
MonitorContendedEntered {
monitor: MonitorEvent,
},
MonitorWait {
monitor: MonitorEvent,
timeout: i64,
},
MonitorWaited {
monitor: MonitorEvent,
timed_out: bool,
},
Unknown {
kind: u8,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitorEvent {
pub thread: ThreadId,
pub location: Location,
pub monitor: ObjectId,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldEvent {
pub thread: ThreadId,
pub location: Location,
pub ref_type: ReferenceTypeId,
pub field_id: FieldId,
pub object: ObjectId,
}
#[derive(Debug, Clone)]
pub enum EventModifier {
Count(i32),
ThreadOnly(ThreadId),
ClassOnly(ReferenceTypeId),
ClassMatch(String),
ClassExclude(String),
LocationOnly(Location),
ExceptionOnly { ref_type: ReferenceTypeId, caught: bool, uncaught: bool },
FieldOnly { ref_type: ReferenceTypeId, field_id: FieldId },
Step { thread: ThreadId, size: i32, depth: i32 },
InstanceOnly(ObjectId),
}
pub fn parse_event_packet(data: &[u8]) -> JdwpResult<EventSet> {
let mut buf = data;
let suspend_policy = read_u8(&mut buf)?;
let event_count = read_i32(&mut buf)?;
let mut events = Vec::with_capacity(usize::try_from(event_count).unwrap_or(0));
for _ in 0..event_count {
let kind = read_u8(&mut buf)?;
let request_id = read_i32(&mut buf)?;
let details = parse_event_details(kind, &mut buf)?;
events.push(Event { kind, request_id, details });
}
Ok(EventSet { suspend_policy, events })
}
fn parse_event_details(kind: u8, buf: &mut &[u8]) -> JdwpResult<EventKind> {
if let Some(parsed) = parse_vm_lifecycle_event(kind, buf) {
return parsed;
}
if let Some(parsed) = parse_monitor_event(kind, buf) {
return parsed;
}
match kind {
event_kinds::BREAKPOINT => parse_breakpoint_event(buf),
event_kinds::SINGLE_STEP => parse_step_event(buf),
event_kinds::EXCEPTION => parse_exception_event(buf),
event_kinds::FIELD_ACCESS => parse_field_access_event(buf),
event_kinds::FIELD_MODIFICATION => parse_field_modification_event(buf),
event_kinds::METHOD_EXIT => parse_method_exit_event(buf, false),
event_kinds::METHOD_EXIT_WITH_RETURN_VALUE => parse_method_exit_event(buf, true),
_ => {
warn!("Unsupported event kind: {}", kind);
Ok(EventKind::Unknown { kind })
}
}
}
fn parse_vm_lifecycle_event(kind: u8, buf: &mut &[u8]) -> Option<JdwpResult<EventKind>> {
match kind {
event_kinds::VM_START => Some(parse_vm_start_event(buf)),
event_kinds::VM_DEATH => Some(Ok(EventKind::VMDeath)),
event_kinds::THREAD_START => Some(parse_thread_start_event(buf)),
event_kinds::THREAD_DEATH => Some(parse_thread_death_event(buf)),
event_kinds::CLASS_PREPARE => Some(parse_class_prepare_event(buf)),
_ => None,
}
}
fn parse_monitor_event(kind: u8, buf: &mut &[u8]) -> Option<JdwpResult<EventKind>> {
match kind {
event_kinds::MONITOR_CONTENDED_ENTER => {
Some(parse_monitor_event_head(buf).map(|monitor| EventKind::MonitorContendedEnter { monitor }))
}
event_kinds::MONITOR_CONTENDED_ENTERED => {
Some(parse_monitor_event_head(buf).map(|monitor| EventKind::MonitorContendedEntered { monitor }))
}
event_kinds::MONITOR_WAIT => Some(parse_monitor_wait_event(buf)),
event_kinds::MONITOR_WAITED => Some(parse_monitor_waited_event(buf)),
_ => None,
}
}
fn parse_breakpoint_event(buf: &mut &[u8]) -> JdwpResult<EventKind> {
let thread = read_u64(buf)?;
let location = read_location(buf)?;
Ok(EventKind::Breakpoint { thread, location })
}
fn parse_step_event(buf: &mut &[u8]) -> JdwpResult<EventKind> {
let thread = read_u64(buf)?;
let location = read_location(buf)?;
Ok(EventKind::Step { thread, location })
}
fn parse_vm_start_event(buf: &mut &[u8]) -> JdwpResult<EventKind> {
let thread = read_u64(buf)?;
Ok(EventKind::VMStart { thread })
}
fn parse_thread_start_event(buf: &mut &[u8]) -> JdwpResult<EventKind> {
let thread = read_u64(buf)?;
Ok(EventKind::ThreadStart { thread })
}
fn parse_thread_death_event(buf: &mut &[u8]) -> JdwpResult<EventKind> {
let thread = read_u64(buf)?;
Ok(EventKind::ThreadDeath { thread })
}
fn parse_class_prepare_event(buf: &mut &[u8]) -> JdwpResult<EventKind> {
let thread = read_u64(buf)?;
let _ref_type_tag = read_u8(buf)?;
let ref_type = read_u64(buf)?;
let signature = read_string(buf)?;
let status = read_i32(buf)?;
Ok(EventKind::ClassPrepare { thread, ref_type, signature, status })
}
fn parse_exception_event(buf: &mut &[u8]) -> JdwpResult<EventKind> {
let thread = read_u64(buf)?;
let location = read_location(buf)?;
let _exc_tag = read_u8(buf)?;
let exception = read_u64(buf)?;
let catch = read_location(buf)?;
let catch_location =
if catch.class_id == 0 && catch.method_id == 0 && catch.index == 0 { None } else { Some(catch) };
Ok(EventKind::Exception { thread, location, exception, catch_location })
}
fn parse_field_event_head(buf: &mut &[u8]) -> JdwpResult<FieldEvent> {
let thread = read_u64(buf)?;
let location = read_location(buf)?;
let _ref_type_tag = read_u8(buf)?;
let ref_type = read_u64(buf)?;
let field_id = read_u64(buf)?;
let _obj_tag = read_u8(buf)?;
let object = read_u64(buf)?;
Ok(FieldEvent { thread, location, ref_type, field_id, object })
}
fn parse_field_access_event(buf: &mut &[u8]) -> JdwpResult<EventKind> {
Ok(EventKind::FieldAccess { field: parse_field_event_head(buf)? })
}
fn parse_field_modification_event(buf: &mut &[u8]) -> JdwpResult<EventKind> {
let field = parse_field_event_head(buf)?;
let tag = read_u8(buf)?;
let new_value = Value { tag, data: crate::reader::read_value_by_tag(tag, buf)? };
Ok(EventKind::FieldModification { field, new_value })
}
fn parse_monitor_event_head(buf: &mut &[u8]) -> JdwpResult<MonitorEvent> {
let thread = read_u64(buf)?;
let _monitor_tag = read_u8(buf)?;
let monitor = read_u64(buf)?;
let location = read_location(buf)?;
Ok(MonitorEvent { thread, location, monitor })
}
fn parse_monitor_wait_event(buf: &mut &[u8]) -> JdwpResult<EventKind> {
let monitor = parse_monitor_event_head(buf)?;
let timeout = crate::reader::read_i64(buf)?;
Ok(EventKind::MonitorWait { monitor, timeout })
}
fn parse_monitor_waited_event(buf: &mut &[u8]) -> JdwpResult<EventKind> {
let monitor = parse_monitor_event_head(buf)?;
let timed_out = read_u8(buf)? != 0;
Ok(EventKind::MonitorWaited { monitor, timed_out })
}
fn parse_method_exit_event(buf: &mut &[u8], with_return_value: bool) -> JdwpResult<EventKind> {
let thread = read_u64(buf)?;
let location = read_location(buf)?;
let return_value = if with_return_value {
let tag = read_u8(buf)?;
Some(Value { tag, data: crate::reader::read_value_by_tag(tag, buf)? })
} else {
None
};
Ok(EventKind::MethodExit { thread, location, return_value })
}
fn read_location(buf: &mut &[u8]) -> JdwpResult<Location> {
let type_tag = read_u8(buf)?;
let class_id = read_u64(buf)?;
let method_id = read_u64(buf)?;
let index = read_u64(buf)?;
Ok(Location { type_tag, class_id, method_id, index })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::event_kinds;
fn packet(suspend_policy: u8, events: &[Vec<u8>]) -> Vec<u8> {
let mut out = vec![suspend_policy];
out.extend_from_slice(&i32::try_from(events.len()).unwrap_or(0).to_be_bytes());
for e in events {
out.extend_from_slice(e);
}
out
}
fn location(class: u64, method: u64, index: u64) -> Vec<u8> {
let mut out = vec![1];
out.extend_from_slice(&class.to_be_bytes());
out.extend_from_slice(&method.to_be_bytes());
out.extend_from_slice(&index.to_be_bytes());
out
}
fn breakpoint_event(request_id: i32, thread: u64) -> Vec<u8> {
let mut out = vec![event_kinds::BREAKPOINT];
out.extend_from_slice(&request_id.to_be_bytes());
out.extend_from_slice(&thread.to_be_bytes());
out.extend_from_slice(&location(0x11, 0x22, 3));
out
}
fn field_modification_event(new_value: i32) -> Vec<u8> {
let mut out = vec![event_kinds::FIELD_MODIFICATION];
out.extend_from_slice(&7i32.to_be_bytes()); out.extend_from_slice(&0x1u64.to_be_bytes()); out.extend_from_slice(&location(0x11, 0x22, 3));
out.push(1); out.extend_from_slice(&0x33u64.to_be_bytes()); out.extend_from_slice(&0x44u64.to_be_bytes()); out.push(crate::reader::value_tags::OBJECT); out.extend_from_slice(&0u64.to_be_bytes()); out.push(crate::reader::value_tags::INT);
out.extend_from_slice(&new_value.to_be_bytes());
out
}
fn method_exit_event(with_return_value: bool, returned: i32) -> Vec<u8> {
let mut out = vec![if with_return_value {
event_kinds::METHOD_EXIT_WITH_RETURN_VALUE
} else {
event_kinds::METHOD_EXIT
}];
out.extend_from_slice(&9i32.to_be_bytes()); out.extend_from_slice(&0x1u64.to_be_bytes()); out.extend_from_slice(&location(0x55, 0x66, 12));
if with_return_value {
out.push(crate::reader::value_tags::INT);
out.extend_from_slice(&returned.to_be_bytes());
}
out
}
#[test]
fn method_exit_parses_with_and_without_a_return_value() {
let with = parse_event_packet(&packet(1, &[method_exit_event(true, 42)])).expect("well-formed");
match with.events.first().map(|e| &e.details) {
Some(EventKind::MethodExit { location, return_value: Some(v), .. }) => {
assert_eq!(location.method_id, 0x66, "the return site is the hit location");
assert!(matches!(v.data, crate::types::ValueData::Int(42)), "got {:?}", v.data);
}
other => panic!("expected a method exit with a value, got {other:?}"),
}
let without = parse_event_packet(&packet(1, &[method_exit_event(false, 0)])).expect("well-formed");
assert!(
matches!(
without.events.first().map(|e| &e.details),
Some(EventKind::MethodExit { return_value: None, .. })
),
"kind 41 carries no value, got {:?}",
without.events.first().map(|e| &e.details)
);
let pair = parse_event_packet(&packet(1, &[method_exit_event(true, 7), method_exit_event(true, 8)]))
.expect("well-formed");
assert_eq!(pair.events.len(), 2, "the first event must consume exactly its own bytes");
}
#[test]
fn an_empty_event_set_parses_as_zero_events() {
let set = parse_event_packet(&packet(2, &[])).expect("an empty set is well-formed");
assert_eq!(set.suspend_policy, 2);
assert!(set.events.is_empty());
}
#[test]
fn a_well_formed_set_parses_every_event() {
let wire = packet(1, &[breakpoint_event(5, 0xabc), field_modification_event(42)]);
let set = parse_event_packet(&wire).expect("well-formed");
assert_eq!(set.events.len(), 2);
match &set.events[0].details {
EventKind::Breakpoint { thread, location } => {
assert_eq!(*thread, 0xabc);
assert_eq!(location.method_id, 0x22);
}
other => panic!("expected a breakpoint, got {other:?}"),
}
match &set.events[1].details {
EventKind::FieldModification { field, new_value } => {
assert_eq!(field.field_id, 0x44);
assert!(matches!(new_value.data, crate::types::ValueData::Int(42)));
}
other => panic!("expected a field modification, got {other:?}"),
}
}
#[test]
fn an_unhandled_event_kind_becomes_unknown_rather_than_an_error() {
let mut ev = vec![event_kinds::FRAME_POP];
ev.extend_from_slice(&1i32.to_be_bytes());
let set = parse_event_packet(&packet(0, &[ev])).expect("an unhandled kind is not a parse failure");
assert!(
matches!(set.events.first().map(|e| &e.details), Some(EventKind::Unknown { kind })
if *kind == event_kinds::FRAME_POP),
"expected Unknown, got {:?}",
set.events.first().map(|e| &e.details)
);
}
fn monitor_event(kind: u8, monitor: u64, tail: &[u8]) -> Vec<u8> {
let mut out = vec![kind];
out.extend_from_slice(&11i32.to_be_bytes()); out.extend_from_slice(&0x7fu64.to_be_bytes()); out.push(crate::reader::value_tags::OBJECT); out.extend_from_slice(&monitor.to_be_bytes());
out.extend_from_slice(&location(0x99, 0xaa, 4));
out.extend_from_slice(tail);
out
}
#[test]
fn every_monitor_event_kind_decodes_with_its_own_tail() {
let enter = parse_event_packet(&packet(
1,
&[monitor_event(event_kinds::MONITOR_CONTENDED_ENTER, 0x1234, &[])],
))
.expect("well-formed");
match enter.events.first().map(|e| &e.details) {
Some(EventKind::MonitorContendedEnter { monitor }) => {
assert_eq!(monitor.monitor, 0x1234, "the monitor object, not the location's typeTag");
assert_eq!(monitor.thread, 0x7f);
assert_eq!(monitor.location.method_id, 0xaa);
}
other => panic!("expected a contended enter, got {other:?}"),
}
let pair = parse_event_packet(&packet(
1,
&[
monitor_event(event_kinds::MONITOR_CONTENDED_ENTER, 0x1234, &[]),
monitor_event(event_kinds::MONITOR_CONTENDED_ENTERED, 0x1234, &[]),
],
))
.expect("well-formed");
assert_eq!(pair.events.len(), 2, "an enter must consume exactly its own bytes");
assert!(
matches!(&pair.events[1].details, EventKind::MonitorContendedEntered { monitor } if monitor.monitor == 0x1234),
"got {:?}",
pair.events[1].details
);
let waits = parse_event_packet(&packet(
1,
&[
monitor_event(event_kinds::MONITOR_WAIT, 0x55, &5000i64.to_be_bytes()),
monitor_event(event_kinds::MONITOR_WAITED, 0x55, &[1]),
monitor_event(event_kinds::MONITOR_WAITED, 0x55, &[0]),
],
))
.expect("well-formed");
assert_eq!(waits.events.len(), 3, "each tail must be consumed at its own width");
assert!(
matches!(&waits.events[0].details, EventKind::MonitorWait { timeout: 5000, .. }),
"got {:?}",
waits.events[0].details
);
assert!(
matches!(&waits.events[1].details, EventKind::MonitorWaited { timed_out: true, .. }),
"got {:?}",
waits.events[1].details
);
assert!(
matches!(&waits.events[2].details, EventKind::MonitorWaited { timed_out: false, .. }),
"a notified wait did not time out, got {:?}",
waits.events[2].details
);
}
#[test]
fn every_truncation_of_a_packet_errors_instead_of_panicking() {
for event in [
breakpoint_event(5, 0xabc),
field_modification_event(42),
method_exit_event(true, 42),
monitor_event(event_kinds::MONITOR_WAIT, 0x55, &5000i64.to_be_bytes()),
] {
let wire = packet(1, &[event]);
for keep in 0..wire.len() {
let short = &wire[..keep];
let parsed = parse_event_packet(short);
if let Ok(set) = parsed {
assert!(
set.events.is_empty(),
"{keep} of {} bytes parsed as {} complete event(s)",
wire.len(),
set.events.len()
);
}
}
}
}
#[test]
fn a_lying_event_count_errors_rather_than_over_reading() {
let mut wire = vec![1u8];
wire.extend_from_slice(&1000i32.to_be_bytes());
wire.extend_from_slice(&breakpoint_event(5, 0xabc));
assert!(parse_event_packet(&wire).is_err(), "1000 claimed, 1 supplied");
}
}