mod agent;
#[cfg(fuzzing)]
mod fuzzed;
mod history;
mod hosted;
mod live;
mod memory;
mod monitor;
mod packet;
mod points;
mod query;
mod registers;
mod threads;
use std::collections::BTreeMap;
use std::io::{self, ErrorKind};
use std::net::{TcpListener, TcpStream};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use super::{Core, Stretch, nanos};
use crate::emulator::{Error, Exit, HaltReason, WalkThreads};
use crate::time::VirtualNanos;
#[cfg(fuzzing)]
pub(crate) use fuzzed::Fuzzed;
use history::{Halted, History, Input};
pub(super) use hosted::Fresult;
use memory::{Reads, binary_write, hex_write, read_memory, span};
use packet::{Carry, Connection, Framed, Negotiated, PACKET_SIZE, hex};
use points::Breakpoint;
pub(super) use points::Watchpoint;
pub(super) use query::Map;
use registers::{
pc_number, register, register_bytes, register_values, register_write, registers, set_registers,
};
use threads::{CORE, PROCESS, Picked, Reason, Stopped, Threads, ours, stop_reply};
const SERVING_STRETCH: u32 = 1 << 20;
const WAITS_BETWEEN_LOOKS: u32 = 1 << 8;
const WAIT_ON_PACKET: Duration = Duration::from_millis(100);
pub(super) struct Debugger<'a> {
listener: TcpListener,
connection: Option<Connection>,
core: Held,
halting: bool,
holding: bool,
breakpoints: BTreeMap<(u64, Breakpoint), points::Agent>,
stopped: Stopped,
resumed: Option<(u64, u64)>,
next_look: u64,
next_warp: u64,
waits: u32,
threads: Threads<'a>,
halt_at: Option<HaltAt>,
reads: Reads,
map: Map,
next: Option<TcpStream>,
interrupted_call: bool,
history: History,
interrupt: Option<&'a AtomicBool>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum HaltAt {
Count(u64),
Instant(VirtualNanos),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Held {
Running,
Halted,
Stepping,
}
#[derive(Debug, PartialEq, Eq)]
enum Asked {
Reply(String),
Continue,
Step,
Back,
Stop(Reason),
Detach,
Kill,
}
impl<'a> Debugger<'a> {
pub(super) fn listen(
listener: &TcpListener,
wait: bool,
map: Map,
kernels: &'a [Box<dyn WalkThreads>],
interrupt: Option<&'a AtomicBool>,
) -> Result<Self, Error> {
let listener = listener.try_clone().map_err(Error::Debugger)?;
listener.set_nonblocking(true).map_err(Error::Debugger)?;
Ok(Self {
listener,
connection: None,
core: if wait { Held::Halted } else { Held::Running },
halting: false,
holding: false,
breakpoints: BTreeMap::new(),
stopped: Stopped {
reason: Reason::Trap,
thread: CORE,
},
resumed: None,
next_look: 0,
next_warp: 0,
waits: 0,
threads: Threads::new(kernels),
halt_at: None,
reads: Reads::Peeks,
map,
next: None,
interrupted_call: false,
history: History::default(),
interrupt,
})
}
fn interrupted(&self) -> bool {
self.interrupt
.is_some_and(|flag| flag.load(Ordering::Relaxed))
}
pub(super) fn hold_interrupts(&mut self, core: &mut Core) {
let stepping = self.core == Held::Stepping;
if self.holding != stepping {
self.holding = stepping;
core.machine.cpu().hold_interrupts(stepping);
}
}
pub(super) fn limit(&self, core: &Core, stretch: Stretch) -> Stretch {
match stretch {
Stretch::Run(_) if self.core == Held::Stepping => Stretch::Run(1),
Stretch::Run(instructions) => {
let left = self.until(core).saturating_sub(core.machine.retired());
Stretch::Run(instructions.min(u32::try_from(left).unwrap_or(u32::MAX)))
}
Stretch::Over(address) => Stretch::Over(address),
}
}
pub(super) fn carry_until(&self, core: &Core) -> Option<u64> {
(self.core != Held::Stepping).then(|| self.until(core))
}
fn until(&self, core: &Core) -> u64 {
let look = core.machine.retired() + u64::from(SERVING_STRETCH);
let halt = match self.halt_at {
Some(HaltAt::Count(count)) => count,
_ => u64::MAX,
};
let input = self.next_input(core).map_or(u64::MAX, |at| at.count);
let back = self.going_until(core).map_or(u64::MAX, |at| at.count);
look.min(halt).min(input).min(back)
}
pub(super) fn serve(&mut self, core: &mut Core) {
self.keep_up(core);
if self.core == Held::Running {
let (retired, warped) = (core.machine.retired(), core.machine.warped);
if retired < self.next_look
&& warped < self.next_warp
&& self.waits < WAITS_BETWEEN_LOOKS
{
return;
}
let stretch = u64::from(SERVING_STRETCH);
self.next_look = retired + stretch;
self.next_warp = warped.saturating_add(nanos(core.machine.rate, stretch));
self.waits = 0;
}
if self.connection.is_some() && self.converse(core).is_err() {
self.disconnect(core);
}
if self.connection.is_some() {
self.refuse_others();
}
while self.connection.is_none() && self.accept() {
if self.converse(core).is_err() {
self.disconnect(core);
}
}
}
fn converse(&mut self, core: &mut Core) -> io::Result<()> {
if self.core == Held::Running {
if self.attached() && !self.non_stop() {
if self.connection()?.interrupted()? {
self.halt_core(core, Reason::Interrupt)?;
}
} else {
for framed in self.connection()?.poll()? {
if self.connection.is_none() {
break;
}
match framed {
Framed::Packet(packet) => self.act(core, &packet)?,
Framed::Interrupt if self.attached() && self.core != Held::Halted => {
self.halt_core(core, Reason::Interrupt)?;
}
_ => {}
}
}
}
}
while self.core == Held::Halted && self.connection.is_some() && !self.interrupted() {
let packet = match self.connection()?.next_packet() {
Err(error)
if matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) =>
{
self.refuse_others();
continue;
}
packet => packet?,
};
self.act(core, &packet)?;
}
Ok(())
}
fn act(&mut self, core: &mut Core, packet: &str) -> io::Result<()> {
if !self.connection()?.taken {
self.take_core(core);
}
match self.answer(core, packet) {
Asked::Reply(text) => self.connection()?.send(&text)?,
asked @ (Asked::Continue | Asked::Step) if self.non_stop() => {
if self.core == Held::Halted && !self.telling() {
self.go(core, asked == Asked::Step);
}
self.connection()?.send("OK")?;
}
Asked::Continue | Asked::Step if std::mem::take(&mut self.connection()?.interrupt) => {
self.halt_core(core, Reason::Interrupt)?;
}
Asked::Continue => self.go(core, false),
Asked::Step => self.go(core, true),
Asked::Stop(reason) => self.stop(core, reason)?,
Asked::Back if self.non_stop() => self.connection()?.send("OK")?,
Asked::Back => {}
Asked::Detach => {
self.connection()?.send("OK")?;
self.connection = None;
}
Asked::Kill => self.connection = None,
}
Ok(())
}
fn connection(&mut self) -> io::Result<&mut Connection> {
self.connection
.as_mut()
.ok_or_else(|| io::Error::from(ErrorKind::NotConnected))
}
fn attached(&self) -> bool {
self.connection
.as_ref()
.is_some_and(|connection| connection.attached)
}
pub(super) fn after(&mut self, core: &mut Core, exit: Exit, ran: u64) {
if ran == 0 {
self.waits = self.waits.saturating_add(1);
}
if !self.attached() {
if exit == Exit::Halted {
self.core = Held::Halted;
self.stopped = Stopped {
reason: Reason::Trap,
thread: CORE,
};
} else {
self.replay(core);
}
return;
}
if self.core == Held::Halted {
self.reached(core);
return;
}
if self.going_back(core, exit, ran) {
return;
}
let stepped = ran > 0 || exit != Exit::Breakpoint;
let reached = self.reached(core);
let at = (core.machine.cpu().pc(), core.machine.retired());
let hit = (exit == Exit::Breakpoint && self.resumed != Some(at))
.then(|| self.breakpoint_at(at.0))
.flatten();
let (reason, halted) =
if let Some((address, watchpoint)) = core.machine.watching.debugger_hit.take() {
let halted = if exit == Exit::Halted {
core.machine.watching.passing = Some((at.0, at.1 + 1));
Halted::AtWatchpoint
} else {
Halted::By(HaltReason::Watchpoint)
};
(Reason::Watch(watchpoint, address), halted)
} else if let Some(kind) = hit
&& self.stops_at(core, at.0)
{
(Reason::Breakpoint(kind), Halted::By(HaltReason::Breakpoint))
} else if hit.is_some() && !reached {
self.pass_own_breakpoint(core);
self.replay(core);
return;
} else if exit == Exit::Halted {
(Reason::Trap, Halted::Itself)
} else if self.core == Held::Stepping && stepped && self.history.replaying && !reached {
self.history.replaying = false;
self.core = Held::Running;
self.replay(core);
return;
} else if (self.core == Held::Stepping && stepped) || reached {
(Reason::Trap, Halted::By(HaltReason::Requested))
} else {
self.replay(core);
return;
};
if self.halt(core, reason, halted).is_err() {
self.disconnect(core);
}
}
pub(super) fn exited(mut self) {
self.exit_reply(0);
}
fn exit_reply(&mut self, code: u32) {
if !self.attached() {
return;
}
let exited = if self.negotiated().multiprocess {
format!("W{code:02x};process:{PROCESS:x}")
} else {
format!("W{code:02x}")
};
if self.non_stop() {
self.tell(&exited).ok();
self.exit_taken();
} else if self.core != Held::Halted
&& let Ok(connection) = self.connection()
{
connection.send(&exited).ok();
}
if let Ok(connection) = self.connection() {
connection.hear_out();
}
}
#[expect(
clippy::disallowed_methods,
reason = "the run waits on a person here, before its core runs again"
)]
fn accept(&mut self) -> bool {
loop {
match self.connecting() {
Some(stream) => {
self.attach(Box::new(stream));
return true;
}
None if self.core == Held::Halted && !self.interrupted() => {
std::thread::sleep(WAIT_ON_PACKET);
}
None => return false,
}
}
}
fn attach(&mut self, stream: Box<dyn Carry>) {
self.connection = Some(Connection::new(stream, (self.core, self.halting)));
self.threads.connected();
self.core = Held::Halted;
}
fn take_core(&mut self, core: &mut Core) {
self.core = Held::Halted;
self.history.stopped();
self.threads.forget();
self.stopped = self.threads.stopped(core, Reason::Trap);
self.set_halting(core, true);
core.machine.cpu().debugger_halted(HaltReason::Requested);
if let Some(connection) = self.connection.as_mut() {
let had = connection.attached && connection.taken;
connection.attached = true;
connection.taken = true;
if !had {
Self::tell_the_run(core, true);
}
}
self.keep_up(core);
}
fn connecting(&mut self) -> Option<TcpStream> {
if let Some(stream) = self.next.take() {
return Self::served(stream);
}
self.listener
.set_nonblocking(self.core != Held::Halted || self.interrupt.is_some())
.ok()?;
let accepted = self.listener.accept();
self.listener.set_nonblocking(true).ok()?;
let (stream, _) = accepted.ok()?;
Self::served(stream)
}
fn served(stream: TcpStream) -> Option<TcpStream> {
stream.set_nonblocking(false).ok()?;
stream.set_nodelay(true).ok()?;
stream.set_read_timeout(Some(WAIT_ON_PACKET)).ok()?;
Some(stream)
}
fn refuse_others(&mut self) {
if self.listener.set_nonblocking(true).is_err() {
return;
}
while let Ok((stream, _)) = self.listener.accept() {
let closed = self.connection.as_mut().is_some_and(Connection::closed);
if closed && self.next.is_none() {
self.next = Some(stream);
}
}
}
fn halt_core(&mut self, core: &mut Core, reason: Reason) -> io::Result<()> {
self.halt(core, reason, Halted::By(HaltReason::Requested))
}
fn hold(&mut self, core: &mut Core, reason: Reason, halted: Halted) {
self.history.stopped();
let changed = match halted {
Halted::By(halt) => core.machine.cpu().debugger_halted(halt),
Halted::Itself | Halted::AtWatchpoint => true,
};
self.note_stop(core, reason, halted, changed);
self.core = Held::Halted;
self.stopped = self.threads.stopped(core, reason);
}
fn halt(&mut self, core: &mut Core, reason: Reason, halted: Halted) -> io::Result<()> {
self.hold(core, reason, halted);
let reply = stop_reply(self.stopped, self.negotiated());
if self.non_stop() {
return self.tell(&reply);
}
self.connection()?.send(&reply)
}
fn negotiated(&self) -> Negotiated {
self.connection
.as_ref()
.map_or_else(Negotiated::default, |connection| connection.negotiated)
}
fn answer(&mut self, core: &mut Core, packet: &str) -> Asked {
let reply = |text: &str| Asked::Reply(text.to_owned());
let (command, rest) = packet.split_at(packet.chars().next().map_or(0, char::len_utf8));
let endian = core.machine.memory.endian();
let negotiated = self.negotiated();
let attached = self.attached();
if !attached
&& !ANSWERED_DETACHED
.iter()
.any(|start| packet.starts_with(start))
{
return reply("E01");
}
if self.refused_running(packet) {
return reply("E01");
}
match command {
"?" if negotiated.non_stop => self.stops_again(),
"?" => reply(&stop_reply(self.stopped, negotiated)),
"g" => {
let picked = self.threads.picked(core);
reply(®isters(core, picked, endian))
}
"G" | "P" if self.threads.picked(core) != Picked::Running => reply("E01"),
"G" | "P" => self.write_registers(core, command == "G", rest),
"p" => {
let picked = self.threads.picked(core);
reply(®ister(core, picked, rest, endian).unwrap_or_else(|| "E01".to_owned()))
}
"m" => self.read(core, rest),
"M" | "X" => self.write(core, command == "M", rest),
"c" | "s" => self.resume_at(core, rest, command == "s"),
"C" | "S" => self.resume_at(
core,
rest.split_once(';').map_or("", |(_, address)| address),
command == "S",
),
"Z" | "z" => reply(self.point(core, command == "Z", rest)),
"D" if rest.is_empty() || ours(rest.strip_prefix(';')) => {
self.release(core, Held::Running);
if negotiated.extended {
reply("OK")
} else {
Asked::Detach
}
}
"D" => reply("E01"),
"k" => {
self.release(core, Held::Running);
Asked::Kill
}
"H" => reply(self.threads.select(core, rest)),
"T" => reply(self.threads.alive(core, rest)),
"!" => {
if let Some(connection) = self.connection.as_mut() {
connection.negotiated.extended = true;
}
reply("OK")
}
"v" if rest == "Run" || rest.starts_with("Run;") => {
self.note(core, Input::Reset, history::reset);
self.disarm(core);
self.take_core(core);
reply(&stop_reply(self.stopped, negotiated))
}
"v" if ours(rest.strip_prefix("Attach;")) && negotiated.non_stop => {
self.take_core(core);
self.core = Held::Running;
Asked::Stop(Reason::Halt)
}
"v" if ours(rest.strip_prefix("Attach;")) => {
self.take_core(core);
reply(&stop_reply(self.stopped, negotiated))
}
"v" if rest.starts_with("Attach;") => reply("E01"),
"v" if rest.starts_with("Kill;")
&& (!negotiated.multiprocess || ours(rest.strip_prefix("Kill;"))) =>
{
if attached {
let then = if negotiated.extended {
Held::Halted
} else {
Held::Running
};
self.release(core, then);
}
reply("OK")
}
"v" if rest.starts_with("Kill;") => reply("E01"),
"v" if rest.starts_with("Flash") => {
self.threads.forget();
reply(self.flash(core, rest))
}
"v" if rest == "Stopped" => self.told(),
"b" if (rest == "s" || rest == "c") && Self::reverses(core) => {
self.threads.forget();
let went = if rest == "s" {
self.step_back(core)
} else {
self.continue_back(core)
};
match went {
Ok(_) => Asked::Back,
Err(_) => reply("E01"),
}
}
"v" if rest == "CtrlC" => Asked::Stop(Reason::Interrupt),
"v" => vee(rest),
"q" => self.query(core, rest),
"Q" if rest == "NonStop:1" || rest == "NonStop:0" => {
self.set_non_stop(core, rest == "NonStop:1")
}
"Q" => self.general_set(rest),
_ => reply(""),
}
}
fn read(&mut self, core: &mut Core, rest: &str) -> Asked {
let reads = if self.core == Held::Halted {
self.reads
} else {
Reads::Peeks
};
let heard = span(rest)
.map(|(address, length)| (address, length.min(PACKET_SIZE as u64 / 2)))
.filter(|&(address, length)| {
reads == Reads::Firmware
&& !(address..address + length).all(|at| core.machine.mapped_byte(at))
});
let read = match heard {
Some((address, length)) => {
let input = Input::Read { address, length };
self.note(core, input, |core| read_memory(core, rest, reads))
}
None => read_memory(core, rest, reads),
};
Asked::Reply(read.unwrap_or_else(|| "E01".to_owned()))
}
fn write(&mut self, core: &mut Core, hex: bool, rest: &str) -> Asked {
self.threads.forget();
let write = if hex {
hex_write(rest)
} else {
binary_write(rest)
};
let Some((address, bytes)) = write else {
return Asked::Reply("E01".to_owned());
};
let input = Input::Write {
address,
bytes: bytes.clone(),
};
let written = self.note(core, input, |core| core.debugger_write(address, &bytes));
Asked::Reply(if written { "OK" } else { "E01" }.to_owned())
}
fn write_registers(&mut self, core: &mut Core, every: bool, rest: &str) -> Asked {
self.threads.forget();
let cpu = core.machine.cpu();
if every {
if register_values(cpu, rest).is_none() {
return Asked::Reply("E01".to_owned());
}
let input = Input::Registers(rest.to_owned());
self.note(core, input, |core| {
set_registers(core.machine.cpu(), rest);
});
} else {
let Some((number, bytes)) = register_write(cpu, rest) else {
return Asked::Reply("E01".to_owned());
};
let input = Input::Register {
number,
bytes: bytes.clone(),
};
self.note(core, input, |core| {
core.machine.cpu().set_gdb_register(number, &bytes);
});
}
Asked::Reply("OK".to_owned())
}
fn resume_at(&mut self, core: &mut Core, address: &str, step: bool) -> Asked {
if !address.is_empty() {
let endian = core.machine.memory.endian();
let cpu = core.machine.cpu();
let Some((number, bytes)) =
hex(address)
.zip(pc_number(cpu.target_xml()))
.and_then(|(value, number)| {
Some((number, register_bytes(cpu, number, value, endian)?))
})
else {
return Asked::Reply("E01".to_owned());
};
let input = Input::Register {
number,
bytes: bytes.clone(),
};
if !self.note(core, input, |core| {
core.machine.cpu().set_gdb_register(number, &bytes)
}) {
return Asked::Reply("E01".to_owned());
}
}
if step { Asked::Step } else { Asked::Continue }
}
fn go(&mut self, core: &mut Core, step: bool) {
let stepped_here = self.put_back_here(core);
if step {
if stepped_here {
self.history_step();
} else {
self.note(core, Input::Stepped, |_| ());
}
} else if stepped_here {
self.history_step();
self.history.replaying = true;
}
self.resume(core, step || self.history.replaying);
self.aim(core);
}
fn resume(&mut self, core: &mut Core, step: bool) {
self.pass_own_breakpoint(core);
self.threads.forget();
self.core = if step { Held::Stepping } else { Held::Running };
}
fn pass_own_breakpoint(&mut self, core: &mut Core) {
let pc = core.machine.cpu().pc();
self.resumed = Some((pc, core.machine.retired()));
if self.breakpoint_at(pc).is_some() && !core.machine.watching.instructions.contains(&pc) {
core.machine.cpu().pass_breakpoint(pc);
}
}
fn release(&mut self, core: &mut Core, then: Held) {
if self.core != Held::Halted && self.attached() {
self.hold(core, Reason::Halt, Halted::By(HaltReason::Requested));
}
self.take_points_out(core);
self.forget_history(core);
self.set_halting(core, false);
self.core = then;
if let Some(connection) = self.connection.as_mut()
&& std::mem::take(&mut connection.attached)
{
Self::tell_the_run(core, false);
}
self.hold_interrupts(core);
}
fn tell_the_run(core: &mut Core, attached: bool) {
let (at, count) = (core.machine.now(), core.machine.retired());
core.driven.debugger_attached(at, count, attached);
}
fn disconnect(&mut self, core: &mut Core) {
let Some(connection) = self.connection.take() else {
return;
};
self.forget_history(core);
if let Some((held, halting)) = connection.unspoken {
self.core = held;
self.set_halting(core, halting);
} else if connection.attached {
self.take_points_out(core);
Self::tell_the_run(core, false);
} else {
self.core = Held::Running;
}
if self.core == Held::Stepping {
self.core = Held::Running;
}
self.hold_interrupts(core);
}
fn take_points_out(&mut self, core: &mut Core) {
for (address, _) in std::mem::take(&mut self.breakpoints).into_keys() {
core.machine.cpu().remove_breakpoint(address);
}
core.machine.debugger_unwatch_all();
self.disarm(core);
}
fn disarm(&mut self, core: &mut Core) {
self.halt_at = None;
self.aim(core);
}
fn arm(&mut self, core: &mut Core, at: HaltAt) {
self.halt_at = Some(at);
self.aim(core);
}
fn aim(&self, core: &mut Core) {
let halt = match self.halt_at {
Some(HaltAt::Instant(instant)) => Some(instant),
_ => None,
};
let input = self.next_input(core).map(|at| at.at);
let back = self
.going_until(core)
.map(|at| at.at)
.filter(|&at| at != VirtualNanos(u64::MAX));
core.machine.horizon = halt.into_iter().chain(input).chain(back).min();
}
fn reached(&mut self, core: &mut Core) -> bool {
let reached = match self.halt_at {
None => false,
Some(HaltAt::Count(count)) => core.machine.retired() >= count,
Some(HaltAt::Instant(instant)) => core.machine.now() >= instant,
};
if reached {
self.disarm(core);
}
reached
}
fn set_halting(&mut self, core: &mut Core, on: bool) {
self.halting = on;
core.machine.cpu().attach_debugger(on);
}
}
const ANSWERED_DETACHED: &[&str] = &[
"vAttach;",
"vRun",
"qfThreadInfo",
"qsThreadInfo",
"!",
"D",
"k",
"vKill;",
"qSupported",
"QStartNoAckMode",
"qXfer:features:read:",
"qXfer:memory-map:read:",
"qSymbol:",
"vCont?",
"vMustReplyEmpty",
"qRcmd,",
];
fn vee(rest: &str) -> Asked {
if rest == "Cont?" {
return Asked::Reply("vCont;c;C;s;S;t".to_owned());
}
let Some(actions) = rest.strip_prefix("Cont;") else {
return Asked::Reply(String::new());
};
match actions.chars().next() {
Some('s' | 'S') => Asked::Step,
Some('c' | 'C') => Asked::Continue,
Some('t') => Asked::Stop(Reason::Halt),
_ => Asked::Reply("E01".to_owned()),
}
}
#[cfg(test)]
mod tests {
use std::io::{Read, Write};
use std::net::Ipv4Addr;
use super::*;
struct Quiet {
closed: bool,
}
impl Read for Quiet {
fn read(&mut self, _bytes: &mut [u8]) -> io::Result<usize> {
if self.closed {
Ok(0)
} else {
Err(io::Error::from(ErrorKind::WouldBlock))
}
}
}
impl Write for Quiet {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Carry for Quiet {
fn set_nonblocking(&self, _nonblocking: bool) -> io::Result<()> {
Ok(())
}
}
#[test]
fn a_debugger_reconnecting_at_once_is_served_and_a_second_one_refused() {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
let address = listener.local_addr().unwrap();
let mut debugger = Debugger::listen(&listener, false, Map::default(), &[], None).unwrap();
let served = |closed| {
Some(Connection::new(
Box::new(Quiet { closed }),
(Held::Running, false),
))
};
debugger.connection = served(true);
let again = TcpStream::connect(address).unwrap();
debugger.refuse_others();
let kept = debugger.connecting().expect("the one that reconnected");
assert_eq!(kept.peer_addr().unwrap(), again.local_addr().unwrap());
debugger.connection = served(false);
let mut second = TcpStream::connect(address).unwrap();
debugger.refuse_others();
assert!(debugger.next.is_none());
second
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
let refused = match second.read(&mut [0]) {
Ok(read) => read == 0,
Err(error) => error.kind() == ErrorKind::ConnectionReset,
};
assert!(refused, "closed at once");
}
#[test]
fn continuing_and_stepping_are_read_from_either_form() {
assert_eq!(vee("Cont;c"), Asked::Continue);
assert_eq!(vee("Cont;s:1;c"), Asked::Step);
assert_eq!(vee("Cont;t:p1.-1"), Asked::Stop(Reason::Halt));
assert_eq!(vee("Cont?"), Asked::Reply("vCont;c;C;s;S;t".to_owned()));
assert_eq!(vee("MustReplyEmpty"), Asked::Reply(String::new()));
}
}