#![no_std]
pub mod cmd_id;
pub mod framing;
#[cfg(feature = "profile")]
pub mod metrics;
#[cfg(feature = "profile")]
pub use metrics::MetricsSnapshot;
use serde::{Deserialize, Serialize};
pub const MAX_PAYLOAD_SIZE: usize = 256;
pub const CMD_ID_DISCOVERY: u16 = 0x0000;
pub const CMD_ID_METRICS: u16 = 0xFFFE;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum PacketType {
Request = 0x01,
Response = 0x02,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum ResponseStatus {
Ok = 0x00,
AppError = 0x01,
SystemError = 0x02,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Request<'a> {
pub kind: PacketType,
pub seq_no: u16,
pub cmd_id: u16,
#[serde(borrow)]
pub args: &'a [u8],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Response<'a> {
pub kind: PacketType,
pub seq_no: u16,
pub status: ResponseStatus,
#[serde(borrow)]
pub payload: &'a [u8],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoveryEntry<'a> {
pub id: u16,
#[serde(borrow)]
pub name: &'a str,
#[serde(borrow)]
pub args_schema: &'a [u8],
#[serde(borrow)]
pub ret_schema: &'a [u8],
#[serde(borrow)]
pub arg_names: &'a str,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct DiscoveryRequest {
pub offset: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoveryPage<'a> {
pub total: u16,
pub offset: u16,
#[serde(borrow)]
pub entries: &'a [u8],
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AppErrorPayload<'a> {
pub code: u16,
#[serde(borrow)]
pub message: &'a str,
}
pub fn encode_app_error(payload: &AppErrorPayload<'_>, out: &mut [u8]) -> Result<usize, WireError> {
let written = postcard::to_slice(payload, out)?;
Ok(written.len())
}
pub fn decode_app_error(bytes: &[u8]) -> Result<AppErrorPayload<'_>, WireError> {
Ok(postcard::from_bytes(bytes)?)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WireError {
PayloadTooLarge,
SerdeError(postcard::Error),
UnknownPacketType,
FramingError,
}
impl From<postcard::Error> for WireError {
fn from(e: postcard::Error) -> Self {
WireError::SerdeError(e)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn packet_type_discriminants() {
assert_eq!(PacketType::Request as u8, 0x01);
assert_eq!(PacketType::Response as u8, 0x02);
}
#[test]
fn response_status_discriminants() {
assert_eq!(ResponseStatus::Ok as u8, 0x00);
assert_eq!(ResponseStatus::AppError as u8, 0x01);
assert_eq!(ResponseStatus::SystemError as u8, 0x02);
}
#[test]
fn cmd_id_discovery_is_zero() {
assert_eq!(CMD_ID_DISCOVERY, 0x0000);
}
#[test]
fn max_payload_size() {
assert_eq!(MAX_PAYLOAD_SIZE, 256);
}
#[test]
fn wire_error_from_postcard_error() {
let pe = postcard::from_bytes::<u32>(&[]).unwrap_err();
let we: WireError = pe.clone().into();
assert_eq!(we, WireError::SerdeError(pe));
}
#[test]
fn app_error_payload_roundtrip() {
let original = AppErrorPayload {
code: 42,
message: "sensor not ready",
};
let mut buf = [0u8; 64];
let n = encode_app_error(&original, &mut buf).expect("encode failed");
let decoded = decode_app_error(&buf[..n]).expect("decode failed");
assert_eq!(decoded.code, original.code);
assert_eq!(decoded.message, original.message);
}
#[test]
fn app_error_payload_wire_layout() {
let payload = AppErrorPayload {
code: 42,
message: "hi",
};
let mut buf = [0u8; 16];
let n = encode_app_error(&payload, &mut buf).expect("encode failed");
assert_eq!(&buf[..n], &[0x2A, 0x02, b'h', b'i']);
}
#[test]
fn app_error_payload_buffer_too_small() {
let payload = AppErrorPayload {
code: 42,
message: "hi",
};
let mut buf = [0u8; 2]; let err = encode_app_error(&payload, &mut buf).unwrap_err();
assert!(matches!(err, WireError::SerdeError(_)));
}
}