use std::io::{self, ErrorKind};
use super::super::Core;
use super::history::Input;
use super::packet::{PACKET_SIZE, hex, to_hex};
use super::threads::Reason;
use super::{Debugger, Held};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(in super::super) struct Fresult {
pub(in super::super) result: i64,
pub(in super::super) errno: Option<u64>,
}
impl Debugger<'_> {
pub(in super::super) fn waits_on_core(&self) -> bool {
self.attached() && self.core != Held::Halted && !self.non_stop()
}
pub(in super::super) fn console(&mut self, core: &mut Core, bytes: &[u8]) {
if !self.waits_on_core() {
return;
}
let sent = bytes
.chunks((PACKET_SIZE - 1) / 2)
.try_for_each(|chunk| self.connection()?.send(&format!("O{}", to_hex(chunk))));
if sent.is_err() {
self.disconnect(core);
}
}
pub(in super::super) fn file_call(
&mut self,
core: &mut Core,
request: &str,
) -> Option<Fresult> {
if let Some(answer) = self.answered_here(core) {
return answer;
}
let here = self.new_input(core);
let answer = if self.waits_on_core() {
let answer = self.converse_call(core, request);
if answer.is_err() {
self.disconnect(core);
}
answer.ok().flatten()
} else {
None
};
self.noted(here, Input::HostCall(answer));
answer
}
fn converse_call(&mut self, core: &mut Core, request: &str) -> io::Result<Option<Fresult>> {
self.connection()?.send(request)?;
loop {
let packet = match self.connection()?.next_packet() {
Err(error)
if matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) =>
{
self.refuse_others();
continue;
}
packet => packet?,
};
if let Some(reply) = packet.strip_prefix('F') {
let Some((answer, interrupted)) = f_reply(reply) else {
return Ok(None);
};
self.interrupted_call =
interrupted || std::mem::take(&mut self.connection()?.interrupt);
return Ok(Some(answer));
}
let reply = match packet.chars().next() {
Some('m' | 'M' | 'X') => match self.answer(core, &packet) {
super::Asked::Reply(reply) => reply,
_ => String::new(),
},
_ => String::new(),
};
self.connection()?.send(&reply)?;
}
}
pub(in super::super) fn after_file_call(&mut self, core: &mut Core) {
if std::mem::take(&mut self.interrupted_call)
&& self.halt_core(core, Reason::Interrupt).is_err()
{
self.disconnect(core);
}
}
pub(in super::super) fn ended(mut self, code: u32) {
self.exit_reply(code & 0xFF);
}
}
fn f_reply(reply: &str) -> Option<(Fresult, bool)> {
let (fields, _attachment) = reply.split_once(';').unwrap_or((reply, ""));
let mut fields = fields.split(',');
let code = fields.next()?;
let result = match code.strip_prefix('-') {
Some(magnitude) => -i64::try_from(hex(magnitude)?).ok()?,
None => i64::try_from(hex(code)?).ok()?,
};
let errno = match fields.next() {
Some(errno) => Some(hex(errno)?),
None => None,
};
let interrupted = match fields.next() {
Some("C") => true,
Some(_) => return None,
None => false,
};
Some((Fresult { result, errno }, interrupted))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_f_reply_reads_its_code_errno_and_ctrl_c() {
let reply = |result, errno| Fresult { result, errno };
assert_eq!(f_reply("6"), Some((reply(6, None), false)));
assert_eq!(f_reply("-1,9"), Some((reply(-1, Some(9)), false)));
assert_eq!(f_reply("-1,4,C"), Some((reply(-1, Some(4)), true)));
assert_eq!(f_reply("0,0,C;ignored"), Some((reply(0, Some(0)), true)));
assert_eq!(f_reply("1f"), Some((reply(0x1f, None), false)));
for bad in ["", "x", "1,y", "1,2,D", "-"] {
assert_eq!(f_reply(bad), None, "{bad}");
}
}
}