Skip to main content

airbender_host/
inputs.rs

1use crate::error::Result;
2use airbender_codec::{AirbenderCodec, AirbenderCodecV0};
3use airbender_core::wire::frame_words_from_bytes;
4use std::fmt::Write as _;
5use std::path::Path;
6
7/// Typed input builder for host-to-guest communication.
8#[derive(Clone, Debug, Default)]
9pub struct Inputs {
10    words: Vec<u32>,
11}
12
13impl Inputs {
14    pub fn new() -> Self {
15        Self { words: Vec::new() }
16    }
17
18    /// Serialize and append a typed input value.
19    pub fn push<T: serde::Serialize>(&mut self, value: &T) -> Result<()> {
20        let bytes = AirbenderCodecV0::encode(value)?;
21        self.push_bytes(&bytes)?;
22        Ok(())
23    }
24
25    /// Append raw bytes as a framed input payload.
26    pub fn push_bytes(&mut self, bytes: &[u8]) -> Result<()> {
27        let words = frame_words_from_bytes(bytes)?;
28        self.words.extend(words);
29        Ok(())
30    }
31
32    /// Access the framed input words.
33    pub fn words(&self) -> &[u32] {
34        &self.words
35    }
36
37    /// Write input words as CLI-compatible hex (`8` hex chars per `u32`).
38    pub fn write_hex_file(&self, path: impl AsRef<Path>) -> Result<()> {
39        let mut hex = String::new();
40        for word in &self.words {
41            writeln!(&mut hex, "{word:08x}").expect("writing to string cannot fail");
42        }
43        std::fs::write(path, hex)?;
44        Ok(())
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::Inputs;
51    use std::fs;
52    use std::path::PathBuf;
53    use std::time::{SystemTime, UNIX_EPOCH};
54
55    #[test]
56    fn writes_hex_file_with_one_word_per_line() {
57        let mut inputs = Inputs::new();
58        inputs.push_bytes(&[0x29]).expect("frame input bytes");
59
60        let file_path = test_file_path("inputs-hex");
61        if let Some(parent) = file_path.parent() {
62            fs::create_dir_all(parent).expect("create test parent directory");
63        }
64
65        inputs
66            .write_hex_file(&file_path)
67            .expect("write input hex file");
68
69        let written = fs::read_to_string(&file_path).expect("read written input hex file");
70        assert_eq!(written, "00000001\n29000000\n");
71
72        fs::remove_file(&file_path).expect("remove input hex file");
73    }
74
75    #[test]
76    fn serializes_small_u32_as_varint_payload() {
77        let mut inputs = Inputs::new();
78        inputs.push(&10u32).expect("frame input value");
79
80        // This fixture documents the format expected by CLI and workflow input
81        // files. Bincode's standard config encodes small integers as varints, so
82        // `10u32` is a one-byte payload rather than a four-byte little-endian word.
83        assert_eq!(inputs.words(), &[1, 0x0a000000]);
84    }
85
86    fn test_file_path(prefix: &str) -> PathBuf {
87        let timestamp = SystemTime::now()
88            .duration_since(UNIX_EPOCH)
89            .expect("system clock should be after unix epoch")
90            .as_nanos();
91        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
92            .join("..")
93            .join("..")
94            .join("tmp")
95            .join(format!("{prefix}-{timestamp}-{}", std::process::id()))
96            .join("input.hex")
97    }
98}