use super::super::super::*;
use super::*;
#[test]
fn handle_device_ready_repeat_ignored() {
let mut dev = VirtioConsole::new();
dev.handle_control_event(VirtioConsoleControl {
id: 0,
event: VIRTIO_CONSOLE_DEVICE_READY,
value: 1,
});
assert!(
dev.device_ready,
"device_ready must be set after first message"
);
let after_first = dev.control_out.len();
assert_eq!(
after_first, NUM_PORTS as usize,
"first DEVICE_READY must enqueue exactly NUM_PORTS PORT_ADD frames",
);
dev.handle_control_event(VirtioConsoleControl {
id: 0,
event: VIRTIO_CONSOLE_DEVICE_READY,
value: 1,
});
assert_eq!(
dev.control_out.len(),
after_first,
"DEVICE_READY repeat must be a no-op — control_out length must \
remain at NUM_PORTS, otherwise a hostile guest can grow it \
unboundedly",
);
assert!(
dev.device_ready,
"device_ready must remain set after repeat"
);
}
#[test]
fn handle_port_ready_repeat_ignored_port0() {
let mut dev = VirtioConsole::new();
dev.handle_control_event(VirtioConsoleControl {
id: 0,
event: VIRTIO_CONSOLE_PORT_READY,
value: 1,
});
assert!(
dev.ports[0].readied,
"port_readied[0] must be set after first PORT_READY"
);
let after_first = dev.control_out.len();
assert_eq!(
after_first, 3,
"first PORT_READY for port 0 must enqueue 3 frames \
(CONSOLE_PORT, PORT_NAME, PORT_OPEN)",
);
dev.handle_control_event(VirtioConsoleControl {
id: 0,
event: VIRTIO_CONSOLE_PORT_READY,
value: 1,
});
assert_eq!(
dev.control_out.len(),
after_first,
"PORT_READY repeat for the same port must be a no-op — \
control_out length must remain at 3, otherwise a hostile \
guest can re-enqueue announce frames unboundedly",
);
}
#[test]
fn handle_port_ready_repeat_ignored_port1() {
let mut dev = VirtioConsole::new();
dev.handle_control_event(VirtioConsoleControl {
id: 1,
event: VIRTIO_CONSOLE_PORT_READY,
value: 1,
});
assert!(dev.ports[1].readied);
let after_first = dev.control_out.len();
assert_eq!(
after_first, 2,
"first PORT_READY for port 1 must enqueue 2 frames (PORT_NAME, PORT_OPEN)",
);
dev.handle_control_event(VirtioConsoleControl {
id: 1,
event: VIRTIO_CONSOLE_PORT_READY,
value: 1,
});
assert_eq!(
dev.control_out.len(),
after_first,
"PORT_READY repeat for port 1 must be a no-op",
);
}
#[test]
fn handle_port_ready_per_port_not_global() {
let mut dev = VirtioConsole::new();
dev.handle_control_event(VirtioConsoleControl {
id: 0,
event: VIRTIO_CONSOLE_PORT_READY,
value: 1,
});
let after_port0 = dev.control_out.len();
assert_eq!(after_port0, 3, "port 0 announce: 3 frames");
dev.handle_control_event(VirtioConsoleControl {
id: 1,
event: VIRTIO_CONSOLE_PORT_READY,
value: 1,
});
assert_eq!(
dev.control_out.len(),
5,
"PORT_READY for port 1 after PORT_READY for port 0 must \
enqueue port 1's announce frames — the gate is per-port",
);
assert!(dev.ports[0].readied);
assert!(dev.ports[1].readied);
}
#[test]
fn handle_port_ready_value_zero_skipped() {
let mut dev = VirtioConsole::new();
dev.handle_control_event(VirtioConsoleControl {
id: 0,
event: VIRTIO_CONSOLE_PORT_READY,
value: 0,
});
assert!(
dev.control_out.is_empty(),
"PORT_READY value=0 must NOT enqueue announce frames",
);
assert!(
!dev.ports[0].readied,
"PORT_READY value=0 must NOT set port_readied — the kernel \
may legitimately retry with value=1 after the host fixes \
the underlying issue",
);
}
#[test]
fn handle_port_ready_value_zero_then_one_completes() {
let mut dev = VirtioConsole::new();
dev.handle_control_event(VirtioConsoleControl {
id: 0,
event: VIRTIO_CONSOLE_PORT_READY,
value: 0,
});
assert!(dev.control_out.is_empty());
assert!(!dev.ports[0].readied);
dev.handle_control_event(VirtioConsoleControl {
id: 0,
event: VIRTIO_CONSOLE_PORT_READY,
value: 1,
});
assert!(dev.ports[0].readied);
assert_eq!(
dev.control_out.len(),
3,
"PORT_READY value=1 after value=0 must enqueue the announce \
— the value=0 path must not poison the per-port gate",
);
}
#[test]
fn handle_port_ready_unknown_port_state_unchanged() {
let mut dev = VirtioConsole::new();
dev.handle_control_event(VirtioConsoleControl {
id: NUM_PORTS, event: VIRTIO_CONSOLE_PORT_READY,
value: 1,
});
assert!(dev.control_out.is_empty());
for p in &dev.ports {
assert!(
!p.readied,
"unknown-port PORT_READY must not flip any port readied flag",
);
}
}
#[test]
fn handle_port_open_unknown_port_ignored() {
let mut dev = VirtioConsole::new();
dev.handle_control_event(VirtioConsoleControl {
id: NUM_PORTS,
event: VIRTIO_CONSOLE_PORT_OPEN,
value: 1,
});
dev.handle_control_event(VirtioConsoleControl {
id: 0xFFFF_FFFF,
event: VIRTIO_CONSOLE_PORT_OPEN,
value: 1,
});
for p in &dev.ports {
assert!(
!p.opened,
"unknown-port PORT_OPEN must not flip any port opened \
flag — the gate prevents out-of-bounds array access on \
a hostile id",
);
}
}
#[test]
fn handle_unhandled_event_absorbed() {
let mut dev = VirtioConsole::new();
let unhandled = [
VIRTIO_CONSOLE_PORT_ADD,
VIRTIO_CONSOLE_PORT_REMOVE,
VIRTIO_CONSOLE_CONSOLE_PORT,
VIRTIO_CONSOLE_RESIZE,
VIRTIO_CONSOLE_PORT_NAME,
0xBEEF,
];
for ev in unhandled {
dev.handle_control_event(VirtioConsoleControl {
id: 0,
event: ev,
value: 1,
});
}
assert!(
dev.control_out.is_empty(),
"unhandled events must NOT enqueue control_out",
);
assert!(
!dev.device_ready,
"unhandled events must NOT flip device_ready",
);
for p in &dev.ports {
assert!(
!p.opened,
"unhandled events must NOT flip any port opened flag"
);
assert!(
!p.readied,
"unhandled events must NOT flip any port readied flag"
);
}
}