# fswtch
[](https://crates.io/crates/fswtch)
[](https://crates.io/crates/fswtch-sys)
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 a `ModuleBuilder`.
- `Module::create` builds the loader-owned module interface from raw load callback arguments for lower-level integrations.
- `Module::add_api`, `Module::add_application`, `Module::add_chat_application`, and `Module::add_endpoint` register common FreeSWITCH interfaces without hand-writing interface allocation and field assignment.
- `ModuleBuilder` chains module and interface registration in load callbacks.
- `api_callback!`, `app_callback!`, and `chat_callback!` generate FreeSWITCH ABI callbacks while giving the callback body typed wrapper values.
- `Session` wraps common application callback operations such as answering, sleeping, and file playback.
- `Stream` wraps `switch_stream_handle_t` for byte and string responses.
- `command_text` converts nullable FreeSWITCH callback command pointers into trimmed Rust strings.
- `Event` and `EventRef` wrap 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`, and `status_to_result` convert common FreeSWITCH status handling into Rust `Result` values.
- `LogLevel`, `log`, and convenience helpers such as `log_info`, `log_warning`, `log_error`, and `log_debug1` through `log_debug10` route module logs through FreeSWITCH logging.
- `MediaBugConfig`, `MediaBugFlags`, `MediaBugHandler`, and `attach_media_bug` provide 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`:
```rust
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)?;
```
## Build
Default builds use the bundled FreeSWITCH headers from `fswtch-src`:
```sh
cargo check -p fswtch-sys
cargo check -p fswtch --examples
cargo test --workspace
cargo fmt --all --check
cargo clippy --workspace --all-targets
```
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:
```sh
FREESWITCH_INCLUDE_DIR=/usr/include/freeswitch \
cargo check -p fswtch-sys --no-default-features --features bindgen
```
If link metadata is not available through `pkg-config`, set the library directory explicitly:
```sh
FREESWITCH_LIB_DIR=/usr/lib/freeswitch cargo build
```
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`.
```sh
docker build -t fswtch-freeswitch-smoke .
docker run --rm fswtch-freeswitch-smoke
```
Podman works too:
```sh
podman build -t fswtch-freeswitch-smoke .
podman run --rm fswtch-freeswitch-smoke
```
Successful output ends with:
```text
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:
```rust
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:
```rust
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.
## 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_status`
- `rust_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 to `gpt-5.1`.
- `OPENAI_BASE_URL`: defaults to `https://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_t` outside 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:
```sh
cargo publish -p fswtch-src
cargo publish -p fswtch-sys
cargo publish -p fswtch
```
Before publishing, run the focused Rust checks above and the Docker smoke test when example behavior changed.