Rename all crate directories, package names, binary names, proto package/module paths, ALPN strings, env var prefixes, config filenames, mDNS service names, and plugin ABI symbols from quicproquo/qpq to quicprochat/qpc.
163 lines
5.2 KiB
Rust
163 lines
5.2 KiB
Rust
//! Reference quicprochat server plugin: logs all hook events to stderr.
|
|
//!
|
|
//! This plugin demonstrates every hook point in the `HookVTable` API. It
|
|
//! writes a single-line human-readable record to stderr for each server event.
|
|
//! No state is required, so `user_data` is left null.
|
|
//!
|
|
//! # Building
|
|
//!
|
|
//! ```bash
|
|
//! cargo build --release -p logging_plugin
|
|
//! # Output: target/release/liblogging_plugin.so (Linux)
|
|
//! # target/release/liblogging_plugin.dylib (macOS)
|
|
//! ```
|
|
//!
|
|
//! # Deploying
|
|
//!
|
|
//! ```bash
|
|
//! cp target/release/liblogging_plugin.so /etc/qpc/plugins/
|
|
//! qpc-server --plugin-dir /etc/qpc/plugins
|
|
//! ```
|
|
|
|
use std::ffi::c_void;
|
|
use std::slice;
|
|
|
|
use quicprochat_plugin_api::{
|
|
CAuthEvent, CChannelEvent, CFetchEvent, CMessageEvent, HookVTable, HOOK_CONTINUE, PLUGIN_OK,
|
|
};
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
fn hex_prefix(ptr: *const u8, len: usize) -> String {
|
|
if ptr.is_null() || len == 0 {
|
|
return "(none)".to_string();
|
|
}
|
|
let bytes = unsafe { slice::from_raw_parts(ptr, len.min(4)) };
|
|
bytes.iter().map(|b| format!("{:02x}", b)).collect()
|
|
}
|
|
|
|
fn str_from_raw(ptr: *const u8, len: usize) -> &'static str {
|
|
if ptr.is_null() || len == 0 {
|
|
return "";
|
|
}
|
|
// Safety: the server owns the memory and it remains valid for the callback duration.
|
|
let bytes = unsafe { slice::from_raw_parts(ptr, len) };
|
|
std::str::from_utf8(bytes).unwrap_or("<invalid utf8>")
|
|
}
|
|
|
|
// ── Hook callbacks ────────────────────────────────────────────────────────────
|
|
|
|
unsafe extern "C" fn on_message_enqueue(
|
|
_user_data: *mut c_void,
|
|
event: *const CMessageEvent,
|
|
) -> i32 {
|
|
let e = &*event;
|
|
eprintln!(
|
|
"[qpc-plugin:logging] enqueue: recipient={} payload_len={} seq={} has_sender={}",
|
|
hex_prefix(e.recipient_key, e.recipient_key_len),
|
|
e.payload_len,
|
|
e.seq,
|
|
!e.sender_identity.is_null(),
|
|
);
|
|
HOOK_CONTINUE
|
|
}
|
|
|
|
unsafe extern "C" fn on_batch_enqueue(
|
|
_user_data: *mut c_void,
|
|
events: *const CMessageEvent,
|
|
count: usize,
|
|
) {
|
|
eprintln!("[qpc-plugin:logging] batch_enqueue: count={}", count);
|
|
let events = slice::from_raw_parts(events, count);
|
|
for (i, e) in events.iter().enumerate() {
|
|
eprintln!(
|
|
"[qpc-plugin:logging] [{}/{}] recipient={} seq={}",
|
|
i + 1,
|
|
count,
|
|
hex_prefix(e.recipient_key, e.recipient_key_len),
|
|
e.seq,
|
|
);
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn on_auth(_user_data: *mut c_void, event: *const CAuthEvent) {
|
|
let e = &*event;
|
|
let username = str_from_raw(e.username, e.username_len);
|
|
if e.success != 0 {
|
|
eprintln!("[qpc-plugin:logging] auth: user='{}' SUCCESS", username);
|
|
} else {
|
|
let reason = str_from_raw(e.failure_reason, e.failure_reason_len);
|
|
eprintln!(
|
|
"[qpc-plugin:logging] auth: user='{}' FAILURE reason='{}'",
|
|
username, reason
|
|
);
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn on_channel_created(
|
|
_user_data: *mut c_void,
|
|
event: *const CChannelEvent,
|
|
) {
|
|
let e = &*event;
|
|
eprintln!(
|
|
"[qpc-plugin:logging] channel_created: channel={} was_new={} initiator={}",
|
|
hex_prefix(e.channel_id, e.channel_id_len),
|
|
e.was_new != 0,
|
|
hex_prefix(e.initiator_key, e.initiator_key_len),
|
|
);
|
|
}
|
|
|
|
unsafe extern "C" fn on_fetch(_user_data: *mut c_void, event: *const CFetchEvent) {
|
|
let e = &*event;
|
|
if e.message_count > 0 {
|
|
eprintln!(
|
|
"[qpc-plugin:logging] fetch: recipient={} count={}",
|
|
hex_prefix(e.recipient_key, e.recipient_key_len),
|
|
e.message_count,
|
|
);
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn on_user_registered(
|
|
_user_data: *mut c_void,
|
|
username: *const u8,
|
|
username_len: usize,
|
|
identity_key: *const u8,
|
|
identity_key_len: usize,
|
|
) {
|
|
let name = str_from_raw(username, username_len);
|
|
eprintln!(
|
|
"[qpc-plugin:logging] user_registered: user='{}' key={}",
|
|
name,
|
|
hex_prefix(identity_key, identity_key_len),
|
|
);
|
|
}
|
|
|
|
// ── Plugin entry point ────────────────────────────────────────────────────────
|
|
|
|
/// Called by the server once at startup.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// `vtable` must point to a zeroed `HookVTable` as provided by `qpc-server`.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn qpc_plugin_init(vtable: *mut HookVTable) -> i32 {
|
|
if vtable.is_null() {
|
|
return -1;
|
|
}
|
|
let v = &mut *vtable;
|
|
|
|
// user_data is not needed — all callbacks are stateless.
|
|
v.user_data = std::ptr::null_mut();
|
|
v.on_message_enqueue = Some(on_message_enqueue);
|
|
v.on_batch_enqueue = Some(on_batch_enqueue);
|
|
v.on_auth = Some(on_auth);
|
|
v.on_channel_created = Some(on_channel_created);
|
|
v.on_fetch = Some(on_fetch);
|
|
v.on_user_registered = Some(on_user_registered);
|
|
// error_message and destroy not needed (no state, never rejects).
|
|
|
|
eprintln!("[qpc-plugin:logging] initialized");
|
|
PLUGIN_OK
|
|
}
|