Rust bindings and helper APIs for writing FreeSWITCH modules.
This workspace is intentionally split into three crates:
fswtch: safe-ish helpers for module exports, module interface creation, API command registration, stream writes, status conversion, and example logging.fswtch-sys: raw FreeSWITCH ABI bindings generated with bindgen.fswtch-src: packaged FreeSWITCH headers used by default bundled builds.
The fswtch crate provides a small higher-level layer over the raw FreeSWITCH ABI. It focuses on the parts that every module needs first:
module_exports!declares the exported FreeSWITCH module table.module_load!generates the FreeSWITCH load callback while giving the body aModuleBuilder.Module::createbuilds the loader-owned module interface from raw load callback arguments for lower-level integrations.Module::add_api,Module::add_application,Module::add_chat_application, andModule::add_endpointregister common FreeSWITCH interfaces without hand-writing interface allocation and field assignment.ModuleBuilderchains module and interface registration in load callbacks.api_callback!,app_callback!, andchat_callback!generate FreeSWITCH ABI callbacks while giving the callback body typed wrapper values.Sessionwraps common application callback operations such as answering, sleeping, and file playback.Streamwrapsswitch_stream_handle_tfor byte and string responses.command_textconverts nullable FreeSWITCH callback command pointers into trimmed Rust strings.EventandEventRefwrap custom event creation, headers, firing, cleanup, and inbound event header reads while accepting Rust strings.- Module registration, media bug config, session playback, XML helpers, and event helpers convert Rust strings to C strings inside
fswtch. Status,SwitchError, andstatus_to_resultconvert common FreeSWITCH status handling into RustResultvalues.LogLevel,log, and convenience helpers such aslog_info,log_warning,log_error, andlog_debug1throughlog_debug10route module logs through FreeSWITCH logging.MediaBugConfig,MediaBugFlags,MediaBugHandler, andattach_media_bugprovide a higher-level media bug API for bidirectional read/write audio stream callbacks and read/write replacement hooks.
The wrapper does not try to hide the full ABI yet. Examples use fswtch::sys directly where FreeSWITCH exposes interfaces that still need raw pointer setup, such as endpoint I/O routine tables and lifecycle callbacks. Keep those raw calls narrow, document the callback and ownership assumptions, and prefer adding focused helpers to fswtch when the same unsafe pattern appears in more than one module.
Media bug handlers are owned by FreeSWITCH until the close callback. A module can implement MediaBugHandler to observe read and write frames, mutate replacement frames, or pull frames explicitly through MediaBugContext:
struct Meter;
impl fswtch::MediaBugHandler for Meter {
fn on_read(
&mut self,
_ctx: &mut fswtch::MediaBugContext<'_>,
frame: fswtch::MediaFrame<'_>,
) -> fswtch::MediaBugAction {
fswtch::log_debug("mod_meter", format!("read {} bytes", frame.data_len()));
fswtch::MediaBugAction::Continue
}
}
let config = fswtch::MediaBugConfig::new(
"mod_meter",
"read-write",
fswtch::MediaBugFlags::READ_STREAM
| fswtch::MediaBugFlags::WRITE_STREAM
| fswtch::MediaBugFlags::NO_PAUSE,
)?;
fswtch::attach_media_bug(session, config, Meter)?;Default builds use the bundled FreeSWITCH headers from fswtch-src:
cargo check -p fswtch-sys
cargo check -p fswtch --examples
cargo test --workspace
cargo fmt --all --check
cargo clippy --workspace --all-targetsThe default bundled feature only generates Rust bindings from packaged headers. It does not compile or statically link FreeSWITCH.
To generate bindings from a configured local FreeSWITCH install:
FREESWITCH_INCLUDE_DIR=/usr/include/freeswitch \
cargo check -p fswtch-sys --no-default-features --features bindgenIf link metadata is not available through pkg-config, set the library directory explicitly:
FREESWITCH_LIB_DIR=/usr/lib/freeswitch cargo buildSet FREESWITCH_NO_PKG_CONFIG=1 to disable pkg-config probing.
The repository includes a full smoke image that builds FreeSWITCH, builds every Rust example as a cdylib, installs the modules, starts FreeSWITCH, and verifies APIs through fs_cli.
docker build -t fswtch-freeswitch-smoke .
docker run --rm fswtch-freeswitch-smokeSuccessful output ends with:
all fswtch example module checks passed
The smoke script enables FSWTCH_AI_ALLOW_MOCK=1 so the local AI example can run without model files or OpenAI credentials.
A minimal module exports a FreeSWITCH load callback:
fswtch::module_exports! {
module = mod_hello,
load = switch_module_load,
}Use module_load! to create the typed load callback and register one or more APIs:
fswtch::module_load! {
fn switch_module_load(module) for "mod_hello" {
fswtch::log_info("mod_hello", "loading module");
module.api(
"rust_hello",
"prints a Rust greeting",
"rust_hello",
hello_api,
)
}
}Examples use fswtch::log_info and fswtch::log_error, which route through FreeSWITCH logging.
All examples live in crates/fswtch/examples and are compiled as FreeSWITCH modules.
Basic module and API patterns:
mod_hello: minimal API command.mod_api_suite: multiple API commands in one module.mod_stream_tools: stream responses and command argument parsing.mod_lifecycle: load, runtime, and shutdown callbacks.
Operational and integration patterns:
mod_async_job_queue: background worker queue with bounded result history.mod_event_sink: JSON-to-custom-event bridge.mod_http_webhook: queued plain HTTP webhook delivery.mod_registration_check: async registration validation and custom event emission.mod_rate_limiter: token-bucket style API rate limiting with bounded cardinality.mod_metrics: Prometheus-style metrics output with bounded cardinality.mod_config_xml: FreeSWITCH XML config loading and reload.mod_cdr_enricher: CDR JSON enrichment and custom event emission.
FreeSWITCH interface skeletons:
mod_app_playback_control: dialplan application interface that answers and plays a supplied target.mod_media_bug_meter: media bug application that counts observed read/write-stream audio frames.mod_endpoint_skeleton: endpoint interface registration skeleton.mod_chatbot_bridge: chat application interface that emits chatbot bridge events.
AI and media integration:
mod_remote_vad: async websocket VAD worker with custom event reporting.mod_local_ai_bridge: local ASR/TTS integration boundary plus OpenAI Responses API NLP calls.
mod_local_ai_bridge exposes:
rust_local_ai_statusrust_local_asr <pcm16le-file>rust_local_tts <text>rust_local_nlp <prompt>rust_local_nlp_sync <prompt>
Environment variables:
FSWTCH_ASR_ONNX: local ASR ONNX model path.FSWTCH_TTS_ONNX: local TTS ONNX model path.OPENAI_API_KEY: enables OpenAI NLP calls.OPENAI_MODEL: defaults togpt-5.1.OPENAI_BASE_URL: defaults tohttps://api.openai.com/v1.FSWTCH_AI_ALLOW_MOCK=1: allows smoke-test fallback behavior when models or API credentials are absent.
For production, do not set FSWTCH_AI_ALLOW_MOCK; provide real model paths and API credentials. The example isolates the ORT boundary, but real ASR/TTS inference still needs the tensor contracts for the chosen ONNX models.
The examples are production-oriented examples, not drop-in production services. Before deploying a module, review:
- Session lifetime and locking for any work that touches a live
switch_core_session_toutside the original callback. - Backpressure and queue limits for background work.
- Timeout and retry policy for network integrations.
- Secret handling for API keys and webhook credentials.
- Cardinality limits for metrics, rate limiters, and per-call state.
- Cleanup and ownership rules for FreeSWITCH events, media bugs, XML roots, and allocated user data.
- Real model initialization and tensor validation for ORT-backed ASR/TTS.
Unsafe blocks are kept small and local to FFI operations. Public unsafe APIs in the wrapper should document a # Safety contract.
crates/fswtch: wrapper API and compile-checked Rust module examples.crates/fswtch-sys: raw generated FreeSWITCH bindings and bindgen build script.crates/fswtch-src: packaged FreeSWITCH headers.docker/fswtch: smoke-test FreeSWITCH config and verification script.Dockerfile: full FreeSWITCH smoke-test image.
The vendored FreeSWITCH trees are third-party inputs. Avoid reformatting or refactoring them unless intentionally updating vendored FreeSWITCH content.