use bytes::Bytes;
use std::cell::RefCell;
use std::sync::{Mutex, OnceLock};
#[derive(Debug, Clone)]
pub enum MessageAction {
Pass,
Drop,
Replace(Bytes),
}
pub type MessageHook = Box<dyn FnMut(&[u8]) -> MessageAction + Send>;
static MESSAGE_HOOK: OnceLock<Mutex<Option<MessageHook>>> = OnceLock::new();
thread_local! {
static LOCAL_MESSAGE_HOOK: RefCell<Option<MessageHook>> = const { RefCell::new(None) };
}
fn hook_slot() -> &'static Mutex<Option<MessageHook>> {
MESSAGE_HOOK.get_or_init(|| Mutex::new(None))
}
pub fn set_message_hook<F>(hook: F)
where
F: FnMut(&[u8]) -> MessageAction + Send + 'static,
{
let mut guard = hook_slot()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
*guard = Some(Box::new(hook));
}
pub fn clear_message_hook() {
let mut guard = hook_slot()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
*guard = None;
}
pub fn set_message_hook_scoped<F>(hook: F) -> MessageHookGuard
where
F: FnMut(&[u8]) -> MessageAction + Send + 'static,
{
LOCAL_MESSAGE_HOOK.with(|slot| {
*slot.borrow_mut() = Some(Box::new(hook));
});
MessageHookGuard { _private: () }
}
pub fn clear_message_hook_scoped() {
LOCAL_MESSAGE_HOOK.with(|slot| {
*slot.borrow_mut() = None;
});
}
pub struct MessageHookGuard {
_private: (),
}
impl Drop for MessageHookGuard {
fn drop(&mut self) {
clear_message_hook_scoped();
}
}
pub(crate) fn intercept_message(bytes: &[u8]) -> MessageAction {
if let Some(action) = LOCAL_MESSAGE_HOOK.with(|slot| {
let mut guard = slot.borrow_mut();
guard.as_mut().map(|hook| hook(bytes))
}) {
return action;
}
let mut guard = hook_slot()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
match guard.as_mut() {
Some(hook) => hook(bytes),
None => MessageAction::Pass,
}
}