#![no_std]
use core::mem::{offset_of, size_of};
pub const TOCAT_ABI_VERSION: u32 = 2;
pub const TOCAT_OUTBOX_LEN: u32 = 48;
pub const TOCAT_LOG_RECORD_LEN: u32 = 12;
pub const TOCAT_EMIT_PENDING: u32 = 0;
pub const TOCAT_EMIT_PASSTHROUGH: u32 = 1;
pub const TOCAT_EMIT_BUFFERED: u32 = 2;
pub const TOCAT_FLAG_REARM: u32 = 1 << 0;
pub const TOCAT_FLAG_HALT: u32 = 1 << 1;
pub const TOCAT_FLAG_PACE: u32 = 1 << 2;
pub const TOCAT_FLAG_ERROR: u32 = 1 << 3;
pub const TOCAT_BOUNDARIES_MASK: u32 = 0b11;
pub const TOCAT_BOUNDARIES_FUSE: u32 = 0;
pub const TOCAT_BOUNDARIES_PRESERVE: u32 = 1;
pub const TOCAT_BOUNDARIES_SEAL: u32 = 2;
pub const TOCAT_BOUNDARIES_SPLIT: u32 = 3;
pub const TOCAT_NEEDS_MASK: u32 = 0b1100;
pub const TOCAT_NEEDS_NOTHING: u32 = 0;
pub const TOCAT_NEEDS_UPSTREAM: u32 = 1 << 2;
pub const TOCAT_NEEDS_DOWNSTREAM: u32 = 1 << 3;
pub const TOCAT_NEEDS_BOTH: u32 = TOCAT_NEEDS_UPSTREAM | TOCAT_NEEDS_DOWNSTREAM;
pub const TOCAT_TRACE: u32 = 0;
pub const TOCAT_DEBUG: u32 = 1;
pub const TOCAT_INFO: u32 = 2;
pub const TOCAT_WARN: u32 = 3;
pub const TOCAT_ERROR: u32 = 4;
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct LogRecord {
pub level: u32,
pub ptr: u32,
pub len: u32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Outbox {
pub emit: u32,
pub bytes_ptr: u32,
pub bytes_len: u32,
pub bounds_ptr: u32,
pub bounds_len: u32,
pub flags: u32,
pub message_ptr: u32,
pub message_len: u32,
pub pace_ns: u64,
pub logs_ptr: u32,
pub logs_len: u32,
}
const _: () = {
assert!(size_of::<Outbox>() == TOCAT_OUTBOX_LEN as usize);
assert!(offset_of!(Outbox, emit) == 0);
assert!(offset_of!(Outbox, bytes_ptr) == 4);
assert!(offset_of!(Outbox, bytes_len) == 8);
assert!(offset_of!(Outbox, bounds_ptr) == 12);
assert!(offset_of!(Outbox, bounds_len) == 16);
assert!(offset_of!(Outbox, flags) == 20);
assert!(offset_of!(Outbox, message_ptr) == 24);
assert!(offset_of!(Outbox, message_len) == 28);
assert!(offset_of!(Outbox, pace_ns) == 32);
assert!(offset_of!(Outbox, logs_ptr) == 40);
assert!(offset_of!(Outbox, logs_len) == 44);
assert!(size_of::<LogRecord>() == TOCAT_LOG_RECORD_LEN as usize);
};
impl Outbox {
pub const fn new() -> Self {
Self {
emit: TOCAT_EMIT_PENDING,
bytes_ptr: 0,
bytes_len: 0,
bounds_ptr: 0,
bounds_len: 0,
flags: 0,
message_ptr: 0,
message_len: 0,
pace_ns: 0,
logs_ptr: 0,
logs_len: 0,
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
pub const fn emit(&self) -> Option<Emit> {
Emit::from_u32(self.emit)
}
pub const fn set_emit(&mut self, emit: Emit) {
self.emit = emit.as_u32();
}
pub const fn has(&self, flag: u32) -> bool {
self.flags & flag != 0
}
pub const fn set(&mut self, flag: u32) {
self.flags |= flag;
}
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Emit {
#[default]
Pending = TOCAT_EMIT_PENDING,
Passthrough = TOCAT_EMIT_PASSTHROUGH,
Buffered = TOCAT_EMIT_BUFFERED,
}
impl Emit {
pub const fn from_u32(value: u32) -> Option<Self> {
match value {
TOCAT_EMIT_PENDING => Some(Self::Pending),
TOCAT_EMIT_PASSTHROUGH => Some(Self::Passthrough),
TOCAT_EMIT_BUFFERED => Some(Self::Buffered),
_ => None,
}
}
pub const fn as_u32(self) -> u32 {
self as u32
}
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Boundaries {
#[default]
Fuse = TOCAT_BOUNDARIES_FUSE,
Preserve = TOCAT_BOUNDARIES_PRESERVE,
Seal = TOCAT_BOUNDARIES_SEAL,
Split = TOCAT_BOUNDARIES_SPLIT,
}
impl Boundaries {
pub const fn from_u32(value: u32) -> Option<Self> {
match value {
TOCAT_BOUNDARIES_FUSE => Some(Self::Fuse),
TOCAT_BOUNDARIES_PRESERVE => Some(Self::Preserve),
TOCAT_BOUNDARIES_SEAL => Some(Self::Seal),
TOCAT_BOUNDARIES_SPLIT => Some(Self::Split),
_ => None,
}
}
pub const fn as_u32(self) -> u32 {
self as u32
}
pub const fn preserves_messages(self) -> bool {
matches!(self, Self::Preserve | Self::Seal)
}
pub const fn passes_downstream(self) -> bool {
matches!(self, Self::Preserve)
}
pub const fn passes_upstream(self) -> bool {
matches!(self, Self::Preserve | Self::Seal)
}
pub const fn satisfies_downstream(self) -> bool {
matches!(self, Self::Seal)
}
pub const fn satisfies_upstream(self) -> bool {
matches!(self, Self::Split)
}
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Needs {
#[default]
Nothing = TOCAT_NEEDS_NOTHING,
Upstream = TOCAT_NEEDS_UPSTREAM,
Downstream = TOCAT_NEEDS_DOWNSTREAM,
Both = TOCAT_NEEDS_BOTH,
}
impl Needs {
pub const fn from_u32(value: u32) -> Option<Self> {
match value {
TOCAT_NEEDS_NOTHING => Some(Self::Nothing),
TOCAT_NEEDS_UPSTREAM => Some(Self::Upstream),
TOCAT_NEEDS_DOWNSTREAM => Some(Self::Downstream),
TOCAT_NEEDS_BOTH => Some(Self::Both),
_ => None,
}
}
pub const fn as_u32(self) -> u32 {
self as u32
}
pub const fn upstream(self) -> bool {
matches!(self, Self::Upstream | Self::Both)
}
pub const fn downstream(self) -> bool {
matches!(self, Self::Downstream | Self::Both)
}
}
pub const fn pack_boundaries(boundaries: Boundaries, needs: Needs) -> u32 {
boundaries.as_u32() | needs.as_u32()
}
pub const fn unpack_boundaries(value: u32) -> Option<(Boundaries, Needs)> {
if value & !(TOCAT_BOUNDARIES_MASK | TOCAT_NEEDS_MASK) != 0 {
return None;
}
match (
Boundaries::from_u32(value & TOCAT_BOUNDARIES_MASK),
Needs::from_u32(value & TOCAT_NEEDS_MASK),
) {
(Some(boundaries), Some(needs)) => Some((boundaries, needs)),
_ => None,
}
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Level {
Trace = TOCAT_TRACE,
Debug = TOCAT_DEBUG,
#[default]
Info = TOCAT_INFO,
Warn = TOCAT_WARN,
Error = TOCAT_ERROR,
}
impl Level {
pub const fn from_u32(value: u32) -> Self {
match value {
TOCAT_TRACE => Self::Trace,
TOCAT_DEBUG => Self::Debug,
TOCAT_WARN => Self::Warn,
TOCAT_ERROR => Self::Error,
_ => Self::Info,
}
}
pub const fn as_u32(self) -> u32 {
self as u32
}
}
pub mod exports {
pub const MEMORY: &str = "memory";
pub const ABI_VERSION: &str = "tocat_abi_version";
pub const OUTBOX: &str = "tocat_outbox";
pub const ALLOC: &str = "tocat_alloc";
pub const INIT: &str = "tocat_init";
pub const ON_BYTES: &str = "tocat_on_bytes";
pub const ON_EOF: &str = "tocat_on_eof";
pub const ON_TICK: &str = "tocat_on_tick";
pub const TICK_INTERVAL_NS: &str = "tocat_tick_interval_ns";
pub const BOUNDARIES: &str = "tocat_boundaries";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_enums_are_the_constants() {
assert_eq!(Emit::Buffered.as_u32(), TOCAT_EMIT_BUFFERED);
assert_eq!(Level::Warn.as_u32(), TOCAT_WARN);
assert_eq!(
Emit::from_u32(TOCAT_EMIT_PASSTHROUGH),
Some(Emit::Passthrough)
);
assert_eq!(Emit::from_u32(3), None);
assert_eq!(Level::from_u32(99), Level::Info);
assert_eq!(Boundaries::Seal.as_u32(), TOCAT_BOUNDARIES_SEAL);
assert_eq!(Needs::Downstream.as_u32(), TOCAT_NEEDS_DOWNSTREAM);
}
#[test]
fn boundaries_round_trip_through_one_word() {
for boundaries in [
Boundaries::Fuse,
Boundaries::Preserve,
Boundaries::Seal,
Boundaries::Split,
] {
for needs in [
Needs::Nothing,
Needs::Upstream,
Needs::Downstream,
Needs::Both,
] {
let packed = pack_boundaries(boundaries, needs);
assert_eq!(unpack_boundaries(packed), Some((boundaries, needs)));
}
}
}
#[test]
fn zero_claims_nothing_and_asks_for_nothing() {
assert_eq!(
unpack_boundaries(0),
Some((Boundaries::Fuse, Needs::Nothing))
);
}
#[test]
fn an_unknown_bit_is_refused() {
assert_eq!(unpack_boundaries(1 << 4), None);
assert_eq!(unpack_boundaries(u32::MAX), None);
}
#[test]
fn an_outbox_starts_and_resets_empty() {
let mut outbox = Outbox::new();
assert_eq!(outbox.emit(), Some(Emit::Pending));
outbox.set(TOCAT_FLAG_HALT);
outbox.set_emit(Emit::Buffered);
assert!(outbox.has(TOCAT_FLAG_HALT));
outbox.reset();
assert_eq!(outbox, Outbox::new());
assert!(!outbox.has(TOCAT_FLAG_HALT));
}
}