use std::convert::Infallible;
use hyper::body::{Bytes, Frame};
use hyper::{Request, Response};
use prost::Message as _;
use tokio::net::TcpStream;
use tokio::sync::mpsc;
use a2a_protocol_types::events::{StreamResponse, TaskStatusUpdateEvent};
use a2a_protocol_types::proto as pb;
use a2a_protocol_types::task::{ContextId, TaskId, TaskState, TaskStatus};
use super::{Script, hold_open};
type BodyFrame = Result<Frame<Bytes>, std::io::Error>;
fn grpc_frame(payload: &[u8]) -> Bytes {
let len = u32::try_from(payload.len()).unwrap_or(u32::MAX);
let mut out = Vec::with_capacity(5 + payload.len());
out.push(0);
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(payload);
Bytes::from(out)
}
fn working_frame() -> Bytes {
let event = StreamResponse::StatusUpdate(TaskStatusUpdateEvent {
task_id: TaskId::new("t"),
context_id: ContextId::new("c"),
status: TaskStatus::new(TaskState::Working),
metadata: None,
});
let encoded = pb::StreamResponse::try_from(event)
.map(|m| m.encode_to_vec())
.unwrap_or_default();
grpc_frame(&encoded)
}
type Body = http_body_util::StreamBody<tokio_stream::wrappers::ReceiverStream<BodyFrame>>;
fn header(resp: &mut Response<Body>, name: &'static str, value: &'static str) {
resp.headers_mut()
.insert(name, hyper::header::HeaderValue::from_static(value));
}
fn respond(script: Script, req: &Request<hyper::body::Incoming>) -> Response<Body> {
let (tx, rx) = mpsc::channel::<BodyFrame>(16);
let mut resp = Response::new(http_body_util::StreamBody::new(
tokio_stream::wrappers::ReceiverStream::new(rx),
));
header(&mut resp, "content-type", "application/grpc");
if script == Script::Unauthorized {
header(&mut resp, "grpc-status", "16");
header(&mut resp, "grpc-message", "unauthorized");
return resp;
}
let streaming = req.uri().path().ends_with("/SendStreamingMessage")
|| req.uri().path().ends_with("/SubscribeToTask");
let after = match script {
Script::Stall { after } | Script::CutOff { after } | Script::MisFrame { after } => after,
Script::Unauthorized => 0,
};
tokio::spawn(async move {
if streaming {
for _ in 0..after {
if tx.send(Ok(Frame::data(working_frame()))).await.is_err() {
return;
}
}
}
match script {
Script::CutOff { .. } => {
}
Script::MisFrame { .. } => {
let _ = tx
.send(Ok(Frame::data(grpc_frame(&[0x0f, 0xff, 0xff]))))
.await;
hold_open().await;
}
_ => hold_open().await,
}
drop(tx);
});
resp
}
pub(super) async fn serve(script: Script, stream: TcpStream) {
let svc = hyper::service::service_fn(move |req: Request<hyper::body::Incoming>| async move {
Ok::<_, Infallible>(respond(script, &req))
});
let _ = hyper::server::conn::http2::Builder::new(hyper_util::rt::TokioExecutor::new())
.serve_connection(hyper_util::rt::TokioIo::new(stream), svc)
.await;
}