- Expand crate-level docs for quicprochat-rpc (architecture, wire format, module map) and quicprochat-sdk (connection lifecycle, event subscription, module descriptions). - Add /// doc comments to all undocumented pub fn/struct/enum items in server domain services (keys, channels, devices, users, account, p2p, blobs) and domain types. - Fix rustdoc broken intra-doc links in plugin-api (HookResult, qpc_plugin_init), federation/mod.rs (Store), and client main.rs (unescaped brackets).
209 lines
7.5 KiB
Rust
209 lines
7.5 KiB
Rust
//! quicprochat server plugin API — C-ABI vtable.
|
|
//!
|
|
//! # Overview
|
|
//!
|
|
//! Every plugin is a `cdylib` that exports one symbol:
|
|
//!
|
|
//! ```c
|
|
//! extern "C" int32_t qpc_plugin_init(HookVTable *vtable);
|
|
//! ```
|
|
//!
|
|
//! The server passes a zeroed [`HookVTable`] to the init function. The plugin
|
|
//! fills in whichever function pointers it cares about and returns `0` on
|
|
//! success or a negative error code on failure. Unused slots remain null and
|
|
//! the server treats them as no-ops.
|
|
//!
|
|
//! # Wire types
|
|
//!
|
|
//! All event structs are passed by const pointer across the FFI boundary. The
|
|
//! server owns the memory; plugin code must not store these pointers beyond the
|
|
//! duration of the callback.
|
|
//!
|
|
//! # Thread safety
|
|
//!
|
|
//! Hook callbacks are called from the Tokio worker thread that handles the RPC.
|
|
//! Plugins must be `Send + Sync` in practice (the server wraps them in `Arc`).
|
|
//! Global plugin state should be guarded with `Mutex` or `RwLock` if mutable.
|
|
//!
|
|
//! # Return values
|
|
//!
|
|
//! Hooks that can reject an operation return an `i32` result code. The server maps
|
|
//! [`HOOK_CONTINUE`] to allow and any other value to reject, reading the reason
|
|
//! string from [`HookVTable::error_message`].
|
|
|
|
#![no_std]
|
|
|
|
/// Plugin init returned success.
|
|
pub const PLUGIN_OK: i32 = 0;
|
|
|
|
/// Hook should allow the operation to proceed.
|
|
pub const HOOK_CONTINUE: i32 = 0;
|
|
|
|
/// Hook wants to reject the operation. Fill [`HookVTable::error_message`] with
|
|
/// a null-terminated reason before returning this.
|
|
pub const HOOK_REJECT: i32 = 1;
|
|
|
|
// ── Event structs (C-compatible) ─────────────────────────────────────────────
|
|
|
|
/// Event data for message enqueue operations.
|
|
///
|
|
/// Passed by pointer to [`HookVTable::on_message_enqueue`].
|
|
/// All pointer fields are valid for the duration of the callback only.
|
|
#[repr(C)]
|
|
pub struct CMessageEvent {
|
|
/// Sender's Ed25519 identity key (32 bytes), or null if sealed sender.
|
|
pub sender_identity: *const u8,
|
|
/// Length of `sender_identity`; 0 when null.
|
|
pub sender_identity_len: usize,
|
|
/// Recipient's Ed25519 identity key (32 bytes).
|
|
pub recipient_key: *const u8,
|
|
pub recipient_key_len: usize,
|
|
/// Channel ID (16 bytes).
|
|
pub channel_id: *const u8,
|
|
pub channel_id_len: usize,
|
|
/// Length of the encrypted payload.
|
|
pub payload_len: usize,
|
|
/// Server-assigned sequence number.
|
|
pub seq: u64,
|
|
}
|
|
|
|
/// Event data for authentication operations.
|
|
#[repr(C)]
|
|
pub struct CAuthEvent {
|
|
/// Null-terminated username string.
|
|
pub username: *const u8,
|
|
pub username_len: usize,
|
|
/// Non-zero on success.
|
|
pub success: i32,
|
|
/// Null-terminated failure reason (empty on success).
|
|
pub failure_reason: *const u8,
|
|
pub failure_reason_len: usize,
|
|
}
|
|
|
|
/// Event data for channel creation operations.
|
|
#[repr(C)]
|
|
pub struct CChannelEvent {
|
|
pub channel_id: *const u8,
|
|
pub channel_id_len: usize,
|
|
pub initiator_key: *const u8,
|
|
pub initiator_key_len: usize,
|
|
pub peer_key: *const u8,
|
|
pub peer_key_len: usize,
|
|
/// Non-zero if this is a freshly created channel.
|
|
pub was_new: i32,
|
|
}
|
|
|
|
/// Event data for message fetch operations.
|
|
#[repr(C)]
|
|
pub struct CFetchEvent {
|
|
pub recipient_key: *const u8,
|
|
pub recipient_key_len: usize,
|
|
pub channel_id: *const u8,
|
|
pub channel_id_len: usize,
|
|
pub message_count: usize,
|
|
}
|
|
|
|
// ── HookVTable ────────────────────────────────────────────────────────────────
|
|
|
|
/// C-ABI function-pointer table filled by the plugin's `qpc_plugin_init` export.
|
|
///
|
|
/// All fields default to null (no-op). The server only calls a slot when its
|
|
/// pointer is non-null. The `user_data` field is passed as the first argument
|
|
/// to every hook; use it to thread plugin-private state without global variables.
|
|
#[repr(C)]
|
|
pub struct HookVTable {
|
|
/// Opaque pointer to plugin-private state. The server passes this as the
|
|
/// first argument to every hook callback. May be null.
|
|
pub user_data: *mut core::ffi::c_void,
|
|
|
|
/// Called before a message is stored. Return [`HOOK_CONTINUE`] or
|
|
/// [`HOOK_REJECT`]. On reject, populate `error_message`.
|
|
pub on_message_enqueue: Option<
|
|
unsafe extern "C" fn(
|
|
user_data: *mut core::ffi::c_void,
|
|
event: *const CMessageEvent,
|
|
) -> i32,
|
|
>,
|
|
|
|
/// Called after a batch of messages is enqueued (fire-and-forget, no return value).
|
|
pub on_batch_enqueue: Option<
|
|
unsafe extern "C" fn(
|
|
user_data: *mut core::ffi::c_void,
|
|
events: *const CMessageEvent,
|
|
count: usize,
|
|
),
|
|
>,
|
|
|
|
/// Called after a login attempt (fire-and-forget).
|
|
pub on_auth: Option<
|
|
unsafe extern "C" fn(
|
|
user_data: *mut core::ffi::c_void,
|
|
event: *const CAuthEvent,
|
|
),
|
|
>,
|
|
|
|
/// Called after a channel is created or looked up (fire-and-forget).
|
|
pub on_channel_created: Option<
|
|
unsafe extern "C" fn(
|
|
user_data: *mut core::ffi::c_void,
|
|
event: *const CChannelEvent,
|
|
),
|
|
>,
|
|
|
|
/// Called after messages are fetched (fire-and-forget).
|
|
pub on_fetch: Option<
|
|
unsafe extern "C" fn(
|
|
user_data: *mut core::ffi::c_void,
|
|
event: *const CFetchEvent,
|
|
),
|
|
>,
|
|
|
|
/// Called after a user completes OPAQUE registration (fire-and-forget).
|
|
pub on_user_registered: Option<
|
|
unsafe extern "C" fn(
|
|
user_data: *mut core::ffi::c_void,
|
|
username: *const u8,
|
|
username_len: usize,
|
|
identity_key: *const u8,
|
|
identity_key_len: usize,
|
|
),
|
|
>,
|
|
|
|
/// When a hook returns [`HOOK_REJECT`], the server calls this to retrieve
|
|
/// the null-terminated rejection reason. The returned pointer must remain
|
|
/// valid until the next call on the same `user_data`. May be null (server
|
|
/// will use a generic message).
|
|
pub error_message: Option<
|
|
unsafe extern "C" fn(user_data: *mut core::ffi::c_void) -> *const u8,
|
|
>,
|
|
|
|
/// Called by the server when it is done with this plugin (shutdown).
|
|
/// Release resources / join threads here. May be null.
|
|
pub destroy: Option<unsafe extern "C" fn(user_data: *mut core::ffi::c_void)>,
|
|
|
|
/// Called when the server is shutting down, before connections are closed.
|
|
/// Plugins can use this to flush buffers, close external connections, etc.
|
|
/// May be null (server treats it as a no-op).
|
|
pub on_shutdown: Option<unsafe extern "C" fn(user_data: *mut core::ffi::c_void)>,
|
|
}
|
|
|
|
// SAFETY: `HookVTable` contains raw pointers (`user_data`, function pointers)
|
|
// which are not inherently `Send`/`Sync`. These impls are sound because:
|
|
//
|
|
// 1. `user_data` is an opaque pointer managed entirely by the plugin. The plugin
|
|
// contract (documented in the module-level doc comment) requires that plugins
|
|
// use interior mutability (Mutex/RwLock) if `user_data` is mutated through
|
|
// callbacks. The server wraps each loaded plugin in an `Arc<HookVTable>` and
|
|
// may invoke hooks from any Tokio worker thread.
|
|
//
|
|
// 2. All function pointers are `unsafe extern "C" fn` — they are plain addresses
|
|
// with no captured state. The code they point to must be thread-safe per the
|
|
// plugin contract.
|
|
//
|
|
// 3. The server guarantees that `destroy` is called exactly once during shutdown,
|
|
// after which no further hook calls are made on the vtable.
|
|
#[allow(unsafe_code)]
|
|
unsafe impl Send for HookVTable {}
|
|
#[allow(unsafe_code)]
|
|
unsafe impl Sync for HookVTable {}
|