use crate::{traits::HintWriterClient, HintReaderServer, PipeHandle};
use alloc::{boxed::Box, string::String, vec};
use anyhow::Result;
use async_trait::async_trait;
use core::future::Future;
use tracing::{error, trace};
#[derive(Debug, Clone, Copy)]
pub struct HintWriter {
pipe_handle: PipeHandle,
}
impl HintWriter {
pub const fn new(pipe_handle: PipeHandle) -> Self {
Self { pipe_handle }
}
}
#[async_trait]
impl HintWriterClient for HintWriter {
async fn write(&self, hint: &str) -> Result<()> {
let mut hint_bytes = vec![0u8; hint.len() + 4];
hint_bytes[0..4].copy_from_slice(u32::to_be_bytes(hint.len() as u32).as_ref());
hint_bytes[4..].copy_from_slice(hint.as_bytes());
trace!(target: "hint_writer", "Writing hint \"{hint}\"");
self.pipe_handle.write(&hint_bytes).await?;
trace!(target: "hint_writer", "Successfully wrote hint");
let mut hint_ack = [0u8; 1];
self.pipe_handle.read_exact(&mut hint_ack).await?;
trace!(target: "hint_writer", "Received hint acknowledgement");
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct HintReader {
pipe_handle: PipeHandle,
}
impl HintReader {
pub fn new(pipe_handle: PipeHandle) -> Self {
Self { pipe_handle }
}
}
#[async_trait]
impl HintReaderServer for HintReader {
async fn next_hint<F, Fut>(&self, mut route_hint: F) -> Result<()>
where
F: FnMut(String) -> Fut + Send,
Fut: Future<Output = Result<()>> + Send,
{
let mut len_buf = [0u8; 4];
self.pipe_handle.read_exact(&mut len_buf).await?;
let len = u32::from_be_bytes(len_buf);
let mut raw_payload = vec![0u8; len as usize];
self.pipe_handle.read_exact(raw_payload.as_mut_slice()).await?;
let payload = String::from_utf8(raw_payload)
.map_err(|e| anyhow::anyhow!("Failed to decode hint payload: {e}"))?;
trace!(target: "hint_reader", "Successfully read hint: \"{payload}\"");
if let Err(e) = route_hint(payload).await {
self.pipe_handle.write(&[0x00]).await?;
error!("Failed to route hint: {e}");
anyhow::bail!("Failed to rout hint: {e}");
}
self.pipe_handle.write(&[0x00]).await?;
trace!(target: "hint_reader", "Successfully routed and acknowledged hint");
Ok(())
}
}
#[cfg(test)]
mod test {
extern crate std;
use super::*;
use alloc::{sync::Arc, vec::Vec};
use core::pin::Pin;
use kona_common::FileDescriptor;
use std::{fs::File, os::fd::AsRawFd};
use tempfile::tempfile;
use tokio::sync::Mutex;
#[derive(Debug)]
struct ClientAndHost {
hint_writer: HintWriter,
hint_reader: HintReader,
_read_file: File,
_write_file: File,
}
fn client_and_host() -> ClientAndHost {
let (read_file, write_file) = (tempfile().unwrap(), tempfile().unwrap());
let (read_fd, write_fd) = (
FileDescriptor::Wildcard(read_file.as_raw_fd().try_into().unwrap()),
FileDescriptor::Wildcard(write_file.as_raw_fd().try_into().unwrap()),
);
let client_handle = PipeHandle::new(read_fd, write_fd);
let host_handle = PipeHandle::new(write_fd, read_fd);
let hint_writer = HintWriter::new(client_handle);
let hint_reader = HintReader::new(host_handle);
ClientAndHost { hint_writer, hint_reader, _read_file: read_file, _write_file: write_file }
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_hint_client_and_host() {
const MOCK_DATA: &str = "test-hint 0xfacade";
let sys = client_and_host();
let (hint_writer, hint_reader) = (sys.hint_writer, sys.hint_reader);
let incoming_hints = Arc::new(Mutex::new(Vec::new()));
let client = tokio::task::spawn(async move { hint_writer.write(MOCK_DATA).await });
let host = tokio::task::spawn({
let incoming_hints_ref = Arc::clone(&incoming_hints);
async move {
let route_hint =
move |hint: String| -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
let hints = Arc::clone(&incoming_hints_ref);
Box::pin(async move {
hints.lock().await.push(hint.clone());
Ok(())
})
};
hint_reader.next_hint(&route_hint).await.unwrap();
let mut hints = incoming_hints.lock().await;
assert_eq!(hints.len(), 1);
hints.remove(0)
}
});
let (_, h) = tokio::join!(client, host);
assert_eq!(h.unwrap(), MOCK_DATA);
}
}