use std::time::Duration;
use arcbox_connect::sandbox_v1;
use arcbox_vm::ExecutionSpec;
use buffa::Message;
use tokio::io::AsyncWrite;
use super::{SandboxService, convert};
use crate::error::SandboxError;
use crate::rpc::{ErrorResponse, MessageType, write_message};
impl SandboxService {
pub async fn start_execution(
&self,
payload: &[u8],
) -> Result<sandbox_v1::Execution, SandboxError> {
let req = sandbox_v1::StartExecutionRequest::decode_from_slice(payload)
.map_err(|e| SandboxError::Decode(e.to_string()))?;
let tty_size = req
.tty_size
.map(|s| {
let width = u16::try_from(s.width).map_err(|_| {
SandboxError::InvalidArgument(format!("invalid tty width {}", s.width))
})?;
let height = u16::try_from(s.height).map_err(|_| {
SandboxError::InvalidArgument(format!("invalid tty height {}", s.height))
})?;
Ok::<_, SandboxError>((width, height))
})
.transpose()?;
let spec = ExecutionSpec {
id: (!req.execution_id.is_empty()).then(|| req.execution_id.clone()),
cmd: req.cmd,
env: req.env.into_iter().collect(),
working_dir: req.working_dir,
user: req.user,
tty: req.tty,
tty_size,
timeout_seconds: req.timeout_seconds,
stdin: req.stdin,
};
let snapshot = self
.manager
.start_execution(&req.sandbox_id, spec)
.await
.map_err(SandboxError::from)?;
Ok(convert::execution_to_proto(&snapshot))
}
pub async fn handle_attach<S>(
&self,
stream: &mut S,
trace_id: &str,
payload: &[u8],
) -> anyhow::Result<()>
where
S: AsyncWrite + Unpin,
{
let req = match sandbox_v1::AttachExecutionRequest::decode_from_slice(payload) {
Ok(r) => r,
Err(e) => {
let err = ErrorResponse::new(400, format!("decode error: {e}"));
write_message(stream, MessageType::Error, trace_id, &err.encode()).await?;
return Ok(());
}
};
let (snapshot, mut rx) = match self.manager.attach_execution(
&req.sandbox_id,
&req.execution_id,
req.stdout_offset,
req.stderr_offset,
) {
Ok(pair) => pair,
Err(e) => {
let e = SandboxError::from(e);
let err = ErrorResponse::new(e.status_code(), e.to_string());
write_message(stream, MessageType::Error, trace_id, &err.encode()).await?;
return Ok(());
}
};
let tty = snapshot.tty;
let started = sandbox_v1::ExecutionEvent {
event: sandbox_v1::ExecutionStarted {
execution: convert::execution_to_proto(&snapshot).into(),
..Default::default()
}
.into(),
..Default::default()
};
write_message(
stream,
MessageType::SandboxExecEvent,
trace_id,
&started.encode_to_vec(),
)
.await?;
while let Some(chunk) = rx.recv().await {
let event = sandbox_v1::ExecutionEvent {
event: sandbox_v1::ExecutionOutput {
channel: convert::channel_to_proto(chunk.channel, tty).into(),
offset: chunk.offset,
data: chunk.data,
..Default::default()
}
.into(),
..Default::default()
};
write_message(
stream,
MessageType::SandboxExecEvent,
trace_id,
&event.encode_to_vec(),
)
.await?;
}
let execution = match self
.manager
.wait_execution(&req.sandbox_id, &req.execution_id, Duration::ZERO)
.await
{
Ok(fin) => convert::execution_to_proto(&fin),
Err(_) => {
let mut expired = convert::execution_to_proto(&snapshot);
expired.state = sandbox_v1::ExecutionState::Exited.into();
expired.error = "execution record expired".into();
expired
}
};
let exited = sandbox_v1::ExecutionEvent {
event: sandbox_v1::ExecutionExited {
execution: execution.into(),
..Default::default()
}
.into(),
..Default::default()
};
write_message(
stream,
MessageType::SandboxExecEvent,
trace_id,
&exited.encode_to_vec(),
)
.await?;
Ok(())
}
pub async fn write_stdin(
&self,
payload: &[u8],
) -> Result<sandbox_v1::StdinStatus, SandboxError> {
let req = sandbox_v1::WriteStdinRequest::decode_from_slice(payload)
.map_err(|e| SandboxError::Decode(e.to_string()))?;
let state = self
.manager
.write_stdin(
&req.sandbox_id,
&req.execution_id,
req.offset,
&req.data,
req.eof,
)
.await
.map_err(SandboxError::from)?;
Ok(convert::stdin_to_proto(state))
}
pub fn stdin_status(&self, payload: &[u8]) -> Result<sandbox_v1::StdinStatus, SandboxError> {
let req = sandbox_v1::GetStdinStatusRequest::decode_from_slice(payload)
.map_err(|e| SandboxError::Decode(e.to_string()))?;
let state = self
.manager
.stdin_status(&req.sandbox_id, &req.execution_id)
.map_err(SandboxError::from)?;
Ok(convert::stdin_to_proto(state))
}
pub async fn signal_execution(&self, payload: &[u8]) -> Result<(), SandboxError> {
let req = sandbox_v1::SignalExecutionRequest::decode_from_slice(payload)
.map_err(|e| SandboxError::Decode(e.to_string()))?;
self.manager
.signal_execution(&req.sandbox_id, &req.execution_id, req.signal.to_i32())
.await
.map_err(SandboxError::from)
}
pub async fn resize_execution(&self, payload: &[u8]) -> Result<(), SandboxError> {
let req = sandbox_v1::ResizeExecutionTtyRequest::decode_from_slice(payload)
.map_err(|e| SandboxError::Decode(e.to_string()))?;
let size = req
.size
.ok_or_else(|| SandboxError::InvalidArgument("missing terminal size".into()))?;
let width = u16::try_from(size.width)
.map_err(|_| SandboxError::InvalidArgument(format!("invalid width {}", size.width)))?;
let height = u16::try_from(size.height).map_err(|_| {
SandboxError::InvalidArgument(format!("invalid height {}", size.height))
})?;
self.manager
.resize_execution(&req.sandbox_id, &req.execution_id, width, height)
.await
.map_err(SandboxError::from)
}
pub async fn wait_execution(
&self,
payload: &[u8],
) -> Result<sandbox_v1::Execution, SandboxError> {
let req = sandbox_v1::WaitExecutionRequest::decode_from_slice(payload)
.map_err(|e| SandboxError::Decode(e.to_string()))?;
let snapshot = self
.manager
.wait_execution(
&req.sandbox_id,
&req.execution_id,
Duration::from_secs(u64::from(req.timeout_seconds)),
)
.await
.map_err(SandboxError::from)?;
Ok(convert::execution_to_proto(&snapshot))
}
}