use melin_app::encoder::ResponseEncoder;
use melin_transport_core::pipeline::OutputPayload;
use melin_wire_protocol::control::TransportResponse;
use melin_wire_protocol::control_codec::{self, TAG_APP, TAG_LEN};
pub const MAX_RESPONSE_BODY: usize = 512;
const HEADER_LEN: usize = 4 + TAG_LEN;
pub(crate) const MAX_APP_FRAME: usize = HEADER_LEN + MAX_RESPONSE_BODY;
pub(crate) type EncodeBuf = [u8; MAX_APP_FRAME];
#[inline]
pub(crate) fn frame_app_response(
buf: &mut EncodeBuf,
encode: impl FnOnce(&mut [u8]) -> Result<usize, &'static str>,
) -> Result<usize, &'static str> {
let len = encode(&mut buf[HEADER_LEN..])?;
if len > MAX_RESPONSE_BODY {
return Err("encoder reported a body longer than its buffer");
}
let payload_len = (TAG_LEN + len) as u32;
buf[..4].copy_from_slice(&payload_len.to_le_bytes());
buf[4] = TAG_APP;
Ok(HEADER_LEN + len)
}
#[inline]
pub(crate) fn encode_slot_payload<R: Copy, Q: Copy>(
payload: &OutputPayload<R, Q>,
encoder: &dyn ResponseEncoder<Report = R, Query = Q>,
buf: &mut EncodeBuf,
) -> Option<Result<usize, &'static str>> {
match payload {
OutputPayload::Report(report) => Some(frame_app_response(buf, |body| {
encoder.encode_report(report, body)
})),
OutputPayload::QueryResponse(q) => Some(frame_app_response(buf, |body| {
encoder.encode_query(q, body)
})),
OutputPayload::EngineError => Some(
control_codec::encode_transport_response(&TransportResponse::EngineError, buf)
.map_err(|_| "encode error"),
),
OutputPayload::BatchEnd => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
struct ByteEncoder;
impl ResponseEncoder for ByteEncoder {
type Report = u8;
type Query = u16;
fn encode_report(&self, report: &u8, buf: &mut [u8]) -> Result<usize, &'static str> {
buf[0] = *report;
Ok(1)
}
fn encode_query(&self, query: &u16, buf: &mut [u8]) -> Result<usize, &'static str> {
buf[..2].copy_from_slice(&query.to_le_bytes());
Ok(2)
}
}
#[test]
fn a_report_goes_through_the_report_encoder() {
let mut buf = [0u8; MAX_APP_FRAME];
let n = encode_slot_payload(&OutputPayload::Report(7), &ByteEncoder, &mut buf);
assert_eq!(n, Some(Ok(6)));
assert_eq!(&buf[..6], &[2, 0, 0, 0, TAG_APP, 7]);
}
#[test]
fn a_query_response_goes_through_the_query_encoder() {
let mut buf = [0u8; MAX_APP_FRAME];
let n = encode_slot_payload(
&OutputPayload::<u8, u16>::QueryResponse(0x0201),
&ByteEncoder,
&mut buf,
);
assert_eq!(n, Some(Ok(7)));
assert_eq!(&buf[..7], &[3, 0, 0, 0, TAG_APP, 1, 2]);
}
#[test]
fn an_engine_error_is_the_transport_frame() {
let mut buf = [0u8; MAX_APP_FRAME];
let n = encode_slot_payload(
&OutputPayload::<u8, u16>::EngineError,
&ByteEncoder,
&mut buf,
)
.expect("EngineError has a body")
.expect("EngineError encodes");
let mut expected = [0u8; 16];
let m = control_codec::encode_transport_response(
&TransportResponse::EngineError,
&mut expected,
)
.expect("EngineError encodes");
assert_eq!(&buf[..n], &expected[..m]);
}
#[test]
fn a_batch_end_has_no_payload_frame() {
let mut buf = [0u8; MAX_APP_FRAME];
let n = encode_slot_payload(&OutputPayload::<u8, u16>::BatchEnd, &ByteEncoder, &mut buf);
assert_eq!(n, None);
}
fn frame(
encode: impl FnOnce(&mut [u8]) -> Result<usize, &'static str>,
) -> (Result<usize, &'static str>, EncodeBuf) {
let mut buf = [0u8; MAX_APP_FRAME];
let result = frame_app_response(&mut buf, encode);
(result, buf)
}
#[test]
fn the_header_goes_in_front_of_the_body() {
let (n, buf) = frame(|body| {
body[..3].copy_from_slice(b"abc");
Ok(3)
});
assert_eq!(n, Ok(8));
assert_eq!(&buf[..8], &[4, 0, 0, 0, TAG_APP, b'a', b'b', b'c']);
}
#[test]
fn an_empty_body_is_a_tag_only_frame() {
let (n, buf) = frame(|_| Ok(0));
assert_eq!(n, Ok(5));
assert_eq!(&buf[..5], &[1, 0, 0, 0, TAG_APP]);
}
#[test]
fn a_body_may_start_with_any_byte() {
for first in [0x00, 0x01, 0x02, TAG_APP, 0xFF] {
let (n, buf) = frame(|body| {
body[0] = first;
Ok(1)
});
assert_eq!(n, Ok(6));
assert_eq!(&buf[..6], &[2, 0, 0, 0, TAG_APP, first]);
}
}
#[test]
fn the_encoder_is_given_the_whole_body_bound() {
let (n, _) = frame(|body| {
assert_eq!(body.len(), MAX_RESPONSE_BODY);
Ok(MAX_RESPONSE_BODY)
});
assert_eq!(n, Ok(MAX_APP_FRAME));
}
#[test]
fn a_length_past_the_body_is_refused() {
let (n, _) = frame(|_| Ok(MAX_RESPONSE_BODY + 1));
assert!(n.is_err());
}
#[test]
fn an_encode_error_passes_through() {
let (n, _) = frame(|_| Err("no"));
assert_eq!(n, Err("no"));
}
}