use std::{
fmt,
panic::{AssertUnwindSafe, catch_unwind},
};
use tracing::trace;
use super::MAX_RAW_REPORT_LENGTH;
#[cfg(test)]
mod tests;
#[derive(Clone, PartialEq, Eq)]
pub struct ObservedReport {
bytes: [u8; MAX_RAW_REPORT_LENGTH],
len: usize,
}
impl ObservedReport {
pub(super) fn from_bytes(bytes: &[u8]) -> Self {
debug_assert!(bytes.len() <= MAX_RAW_REPORT_LENGTH);
let mut owned = [0; MAX_RAW_REPORT_LENGTH];
owned[..bytes.len()].copy_from_slice(bytes);
Self {
bytes: owned,
len: bytes.len(),
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl AsRef<[u8]> for ObservedReport {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl fmt::Debug for ObservedReport {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("ObservedReport")
.field(&self.as_bytes())
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RequestOutcome {
Succeeded,
TimedOut,
WriteFailed,
NoResponse,
Cancelled,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChannelObservation {
OutgoingReport {
request_id: Option<u64>,
report: ObservedReport,
},
IncomingReport {
request_id: Option<u64>,
report: ObservedReport,
},
MalformedIncomingReport {
report: ObservedReport,
},
RequestOutcome {
request_id: u64,
outcome: RequestOutcome,
},
}
pub trait ChannelObserver: Send + Sync + 'static {
fn observe(&self, observation: ChannelObservation);
}
impl<F> ChannelObserver for F
where
F: Fn(ChannelObservation) + Send + Sync + 'static,
{
fn observe(&self, observation: ChannelObservation) {
self(observation);
}
}
pub(super) fn emit(observer: &dyn ChannelObserver, observation: ChannelObservation) {
if catch_unwind(AssertUnwindSafe(|| observer.observe(observation))).is_err() {
trace!("channel observer panicked — observation ignored");
}
}
pub(super) fn emit_report(
observer: Option<&dyn ChannelObserver>,
bytes: &[u8],
observation: impl FnOnce(ObservedReport) -> ChannelObservation,
) {
if let Some(observer) = observer {
emit(observer, observation(ObservedReport::from_bytes(bytes)));
}
}
pub(super) struct RequestObservation<'a> {
observer: Option<&'a dyn ChannelObserver>,
request_id: u64,
completed: bool,
}
impl<'a> RequestObservation<'a> {
pub(super) fn new(observer: Option<&'a dyn ChannelObserver>, request_id: u64) -> Self {
Self {
observer,
request_id,
completed: false,
}
}
pub(super) fn complete(&mut self, outcome: RequestOutcome) {
self.completed = true;
if let Some(observer) = self.observer {
emit(
observer,
ChannelObservation::RequestOutcome {
request_id: self.request_id,
outcome,
},
);
}
}
}
impl Drop for RequestObservation<'_> {
fn drop(&mut self) {
if !self.completed
&& let Some(observer) = self.observer
{
emit(
observer,
ChannelObservation::RequestOutcome {
request_id: self.request_id,
outcome: RequestOutcome::Cancelled,
},
);
}
}
}