use std::fmt;
use crate::frame_v1 as backend;
pub const DAEMON_FRAME_V1_VERSION: u8 = backend::ENVELOPE_VERSION;
pub const DAEMON_FRAME_V1_MAX_BODY_BYTES: usize = backend::MAX_FRAME_BYTES;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DaemonFrame {
envelope_version: u32,
kind: i32,
payload_protocol: u32,
payload: Vec<u8>,
request_id: u64,
payload_encoding: i32,
deadline_unix_ms: u64,
traceparent: String,
tracestate: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DaemonFrameKind {
Request,
Response,
Event,
Cancel,
Unknown(i32),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DaemonPayloadEncoding {
None,
Zstd,
Snappy,
Lz4,
Unknown(i32),
}
impl DaemonFrame {
#[must_use]
pub fn request(payload_protocol: u32, payload: Vec<u8>) -> Self {
Self::from_backend(backend::Frame::request(payload_protocol, payload))
}
#[must_use]
pub fn response_to(request: &Self, payload: Vec<u8>) -> Self {
Self::from_backend(backend::Frame::response_to(&request.to_backend(), payload))
}
#[must_use]
pub fn with_request_id(mut self, request_id: u64) -> Self {
self.request_id = request_id;
self
}
#[must_use]
pub fn with_raw_kind(mut self, kind: i32) -> Self {
self.kind = kind;
self
}
#[must_use]
pub fn with_raw_payload_encoding(mut self, payload_encoding: i32) -> Self {
self.payload_encoding = payload_encoding;
self
}
#[must_use]
pub fn with_deadline_unix_ms(mut self, deadline_unix_ms: u64) -> Self {
self.deadline_unix_ms = deadline_unix_ms;
self
}
#[must_use]
pub fn with_trace_context(
mut self,
trace_id: impl AsRef<str>,
span_id: impl AsRef<str>,
) -> Self {
self.traceparent = format!("00-{}-{}-01", trace_id.as_ref(), span_id.as_ref());
self
}
#[must_use]
pub fn with_trace_state(mut self, trace_state: impl Into<String>) -> Self {
self.tracestate = trace_state.into();
self
}
#[must_use]
pub fn envelope_version(&self) -> u32 {
self.envelope_version
}
#[must_use]
pub fn kind(&self) -> i32 {
self.kind
}
#[must_use]
pub fn kind_classification(&self) -> DaemonFrameKind {
match self.kind {
value if value == backend::FrameKind::Request as i32 => DaemonFrameKind::Request,
value if value == backend::FrameKind::Response as i32 => DaemonFrameKind::Response,
value if value == backend::FrameKind::Event as i32 => DaemonFrameKind::Event,
value if value == backend::FrameKind::Cancel as i32 => DaemonFrameKind::Cancel,
value => DaemonFrameKind::Unknown(value),
}
}
#[must_use]
pub fn payload_protocol(&self) -> u32 {
self.payload_protocol
}
#[must_use]
pub fn payload(&self) -> &[u8] {
&self.payload
}
#[must_use]
pub fn request_id(&self) -> u64 {
self.request_id
}
#[must_use]
pub fn payload_encoding(&self) -> i32 {
self.payload_encoding
}
#[must_use]
pub fn payload_encoding_classification(&self) -> DaemonPayloadEncoding {
match self.payload_encoding {
value if value == backend::PayloadEncoding::None as i32 => DaemonPayloadEncoding::None,
value if value == backend::PayloadEncoding::Zstd as i32 => DaemonPayloadEncoding::Zstd,
value if value == backend::PayloadEncoding::Snappy as i32 => {
DaemonPayloadEncoding::Snappy
}
value if value == backend::PayloadEncoding::Lz4 as i32 => DaemonPayloadEncoding::Lz4,
value => DaemonPayloadEncoding::Unknown(value),
}
}
#[must_use]
pub fn deadline_unix_ms(&self) -> u64 {
self.deadline_unix_ms
}
#[must_use]
pub fn trace_id(&self) -> Option<&str> {
trace_ids(&self.traceparent).map(|(trace_id, _)| trace_id)
}
#[must_use]
pub fn span_id(&self) -> Option<&str> {
trace_ids(&self.traceparent).map(|(_, span_id)| span_id)
}
#[must_use]
pub fn trace_state(&self) -> &str {
&self.tracestate
}
fn from_backend(frame: backend::Frame) -> Self {
Self {
envelope_version: frame.envelope_version,
kind: frame.kind,
payload_protocol: frame.payload_protocol,
payload: frame.payload,
request_id: frame.request_id,
payload_encoding: frame.payload_encoding,
deadline_unix_ms: frame.deadline_unix_ms,
traceparent: frame.traceparent,
tracestate: frame.tracestate,
}
}
fn to_backend(&self) -> backend::Frame {
backend::Frame {
envelope_version: self.envelope_version,
kind: self.kind,
payload_protocol: self.payload_protocol,
payload: self.payload.clone(),
request_id: self.request_id,
payload_encoding: self.payload_encoding,
deadline_unix_ms: self.deadline_unix_ms,
traceparent: self.traceparent.clone(),
tracestate: self.tracestate.clone(),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DaemonFrameCodec;
impl DaemonFrameCodec {
pub fn encode(frame: &DaemonFrame) -> Result<Vec<u8>, DaemonFrameError> {
backend::encode_framed(&frame.to_backend()).map_err(DaemonFrameError::from_backend)
}
pub fn encode_request(
payload_protocol: u32,
payload: Vec<u8>,
request_id: u64,
) -> Result<Vec<u8>, DaemonFrameError> {
Self::encode(&DaemonFrame::request(payload_protocol, payload).with_request_id(request_id))
}
pub fn encode_response_to(
request: &DaemonFrame,
payload: Vec<u8>,
) -> Result<Vec<u8>, DaemonFrameError> {
Self::encode(&DaemonFrame::response_to(request, payload))
}
pub fn decode(buffer: &[u8]) -> Result<DaemonFrameDecode, DaemonFrameError> {
match backend::try_decode_framed(buffer).map_err(DaemonFrameError::from_backend)? {
Some(decoded) => Ok(DaemonFrameDecode::Frame {
frame: DaemonFrame::from_backend(decoded.frame),
consumed: decoded.consumed,
}),
None => Ok(DaemonFrameDecode::NeedMoreBytes),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DaemonFrameDecode {
NeedMoreBytes,
Frame {
frame: DaemonFrame,
consumed: usize,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DaemonFrameError {
UnsupportedFrameVersion {
received: u8,
expected: u8,
},
FrameTooLarge {
body_length: usize,
maximum: usize,
},
MalformedFrame,
}
impl DaemonFrameError {
fn from_backend(error: backend::FramingError) -> Self {
match error {
backend::FramingError::UnsupportedFramingVersion { got, expected } => {
Self::UnsupportedFrameVersion {
received: got,
expected,
}
}
backend::FramingError::FrameTooLarge { body_length, cap } => Self::FrameTooLarge {
body_length,
maximum: cap,
},
backend::FramingError::UnexpectedEof { .. }
| backend::FramingError::Io(_)
| backend::FramingError::Decode(_) => Self::MalformedFrame,
}
}
}
impl fmt::Display for DaemonFrameError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnsupportedFrameVersion { received, expected } => {
write!(
formatter,
"unsupported daemon-frame version {received}; expected {expected}"
)
}
Self::FrameTooLarge {
body_length,
maximum,
} => write!(
formatter,
"daemon-frame body {body_length} exceeds maximum {maximum}"
),
Self::MalformedFrame => formatter.write_str("malformed daemon-frame body"),
}
}
}
impl std::error::Error for DaemonFrameError {}
#[doc(hidden)]
pub const fn is_first_party_payload_protocol(payload_protocol: u32) -> bool {
backend::registry::is_first_party(payload_protocol)
}
#[doc(hidden)]
pub const fn is_registered_payload_protocol(payload_protocol: u32) -> bool {
backend::registry::is_registered_consumer_id(payload_protocol)
}
#[doc(hidden)]
pub const fn is_private_payload_protocol(payload_protocol: u32) -> bool {
backend::registry::is_private_use_id(payload_protocol)
}
#[macro_export]
macro_rules! register_daemon_frame_payload_protocol {
($(#[$meta:meta])* $vis:vis const $name:ident: u32 = $value:expr;) => {
$(#[$meta])*
$vis const $name: u32 = $value;
const _: () = {
assert!(
!$crate::daemon_frame_v1::is_first_party_payload_protocol($name),
concat!(
stringify!($name),
" collides with a first-party daemon-frame payload protocol",
),
);
assert!(
$crate::daemon_frame_v1::is_registered_payload_protocol($name)
|| $crate::daemon_frame_v1::is_private_payload_protocol($name),
concat!(
stringify!($name),
" must lie in the registered-consumer range (0x7000..=0x7EFF) ",
"or the private-use range (0xF000..=0xFFFF)",
),
);
};
};
}
fn trace_ids(traceparent: &str) -> Option<(&str, &str)> {
let mut fields = traceparent.split('-');
let _version = fields.next()?;
let trace_id = fields.next()?;
let span_id = fields.next()?;
let _flags = fields.next()?;
if fields.next().is_some() || trace_id.is_empty() || span_id.is_empty() {
return None;
}
Some((trace_id, span_id))
}
#[cfg(test)]
mod compatibility_tests {
use super::*;
#[test]
fn request_matches_literal_consumer_wire_fixture() {
let request =
DaemonFrame::request(0x7A63, b"ping".to_vec()).with_request_id(0x0102_0304_0506_0708);
assert_eq!(
DaemonFrameCodec::encode(&request).unwrap(),
[
0x01, 0x16, 0, 0, 0, 0x08, 0x01, 0x18, 0xE3, 0xF4, 0x01, 0x22, 0x04, b'p', b'i',
b'n', b'g', 0x28, 0x88, 0x8E, 0x98, 0xA8, 0xC0, 0xE0, 0x80, 0x81, 0x01,
]
);
}
#[test]
fn decoded_trace_headers_are_not_reconstructed_from_semantic_ids() {
let mut raw = backend::Frame::request(0x7A63, vec![0, 255]);
raw.traceparent = "01-ABCDEF0123456789ABCDEF0123456789-ABCDEF0123456789-fe".into();
raw.tracestate = "vendor=One,another=Two".into();
let wire = backend::encode_framed(&raw).unwrap();
let DaemonFrameDecode::Frame { frame, .. } = DaemonFrameCodec::decode(&wire).unwrap()
else {
panic!("complete frame")
};
assert_eq!(DaemonFrameCodec::encode(&frame).unwrap(), wire);
let reply = DaemonFrame::response_to(&frame, Vec::new()).to_backend();
assert_eq!(reply.traceparent, raw.traceparent);
assert_eq!(reply.tracestate, raw.tracestate);
}
#[test]
fn raw_unknown_enums_trace_and_correlation_round_trip_in_frozen_bytes() {
let frame = DaemonFrame::request(0x7A63, b"payload".to_vec())
.with_request_id(77)
.with_raw_kind(37)
.with_raw_payload_encoding(91)
.with_trace_context("0123456789abcdef0123456789abcdef", "0123456789abcdef")
.with_trace_state("vendor=value");
let wire = DaemonFrameCodec::encode(&frame).expect("encode");
let DaemonFrameDecode::Frame {
frame: decoded,
consumed,
} = DaemonFrameCodec::decode(&wire).expect("decode")
else {
panic!("complete")
};
assert_eq!(consumed, wire.len());
assert_eq!(decoded.kind_classification(), DaemonFrameKind::Unknown(37));
assert_eq!(
decoded.payload_encoding_classification(),
DaemonPayloadEncoding::Unknown(91)
);
assert_eq!(decoded.request_id(), 77);
assert_eq!(decoded.trace_id(), Some("0123456789abcdef0123456789abcdef"));
assert_eq!(DaemonFrameCodec::encode(&decoded).expect("reencode"), wire);
}
#[test]
fn incremental_and_frozen_error_mapping_are_stable() {
assert_eq!(
DaemonFrameCodec::decode(&[]).expect("partial"),
DaemonFrameDecode::NeedMoreBytes
);
assert!(matches!(
DaemonFrameCodec::decode(&[2]),
Err(DaemonFrameError::UnsupportedFrameVersion { .. })
));
assert!(matches!(
DaemonFrameCodec::decode(&[1, 1, 0, 0, 0, 0xff]),
Err(DaemonFrameError::MalformedFrame)
));
}
}