fswtch
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.
Wrapper API
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:
;
let config = new?;
attach_media_bug?;
Build
Default builds use the bundled FreeSWITCH headers from fswtch-src:
The 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 \
If link metadata is not available through pkg-config, set the library directory explicitly:
FREESWITCH_LIB_DIR=/usr/lib/freeswitch
Set FREESWITCH_NO_PKG_CONFIG=1 to disable pkg-config probing.
Docker Smoke Test
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.
Podman works too:
Successful 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.
Module Shape
A minimal module exports a FreeSWITCH load callback:
module_exports!
Use module_load! to create the typed load callback and register one or more APIs:
module_load!
Examples use fswtch::log_info and fswtch::log_error, which route through FreeSWITCH logging.
Examples
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.
Local AI Example
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.
Production Notes
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.
Repository Layout
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.freeswitch/: vendored upstream FreeSWITCH source context.
The vendored FreeSWITCH trees are third-party inputs. Avoid reformatting or refactoring them unless intentionally updating vendored FreeSWITCH content.
Publishing
Publish crates in dependency order:
Before publishing, run the focused Rust checks above and the Docker smoke test when example behavior changed.