rigless 0.0.2

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model, and unit of virtual time in Rust; needs the rigless-sidecar package from rigless-labs/qemu
Documentation
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: Apache-2.0
//! The core's side of the exchange over any control block: the handshake, the run loop, and the shutdown message.

use crate::sidecar::backend::{Access, AccessKind, BackendError, Reply, Stop};
use crate::sidecar::layout::{ControlBlock, InterruptLevels, Version};
use crate::sidecar::message::{CoreMessage, SidecarMessage};
use crate::sidecar::turn::{Turn, WaitError};
use crate::time::VirtualNanos;

/// The core's side of the exchange over any control block, whoever the peer is: [`crate::sidecar::Sidecar`] drives one
/// over its mapping, and a test drives one over a block shared with a scripted thread.
#[derive(Debug)]
pub struct Link<'a, Check> {
    block: &'a ControlBlock,
    check: Check,
}

impl<'a, Check: FnMut() -> Result<(), WaitError>> Link<'a, Check> {
    /// Speaks over `block`; `check` is consulted while a wait for the peer outlasts the spin window, and the error
    /// it returns ends the wait.
    pub fn new(block: &'a ControlBlock, check: Check) -> Self {
        Self { block, check }
    }

    /// Waits for `Hello` and compares its version with `expected`; another version is told to shut down.
    pub fn handshake(&mut self, expected: Version) -> Result<(), BackendError> {
        match self.receive()? {
            SidecarMessage::Hello { version } if version == expected => Ok(()),
            SidecarMessage::Hello { version } => {
                self.send(CoreMessage::Shutdown);
                Err(BackendError::Version {
                    sidecar: version,
                    expected,
                })
            }
            message => Err(BackendError::Unexpected { message }),
        }
    }

    /// Sends `RunUntil` and serves every access through `on_access` until the peer stops.
    pub fn run_until(
        &mut self,
        deadline: VirtualNanos,
        on_access: &mut dyn FnMut(Access, &InterruptLevels) -> Reply,
    ) -> Result<Stop, BackendError> {
        self.send(CoreMessage::RunUntil {
            deadline_ns: deadline.0,
        });
        loop {
            let access = match self.receive()? {
                SidecarMessage::Read {
                    address,
                    width,
                    instruction_count,
                    program_counter,
                    virtual_time_ns,
                } => Access {
                    kind: AccessKind::Read,
                    address,
                    width,
                    value: 0,
                    instruction_count,
                    virtual_time: VirtualNanos(virtual_time_ns),
                    program_counter,
                },
                SidecarMessage::Write {
                    address,
                    width,
                    value,
                    instruction_count,
                    program_counter,
                    virtual_time_ns,
                } => Access {
                    kind: AccessKind::Write,
                    address,
                    width,
                    value,
                    instruction_count,
                    virtual_time: VirtualNanos(virtual_time_ns),
                    program_counter,
                },
                SidecarMessage::Stopped {
                    instruction_count,
                    virtual_time_ns,
                    reason,
                } => {
                    return Ok(Stop {
                        at: VirtualNanos(virtual_time_ns),
                        instruction_count,
                        reason,
                    });
                }
                message @ SidecarMessage::Hello { .. } => {
                    return Err(BackendError::Unexpected { message });
                }
            };
            let reply = on_access(access, &self.block.interrupt_levels);
            self.send(CoreMessage::Reply {
                value: reply.value,
                deadline_ns: reply.next_deadline.0,
            });
        }
    }

    /// Sends `Shutdown`.
    pub fn shutdown(&mut self) {
        self.send(CoreMessage::Shutdown);
    }

    fn receive(&mut self) -> Result<SidecarMessage, BackendError> {
        self.block.turn.wait_for(Turn::Core, &mut self.check)?;
        Ok(SidecarMessage::decode(&self.block.sidecar_slot)?)
    }

    fn send(&self, message: CoreMessage) {
        message.encode(&self.block.core_slot);
        self.block.turn.hand_over(Turn::Sidecar);
    }
}