Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting Started

Add asterisk-rs to your project:

[dependencies]
asterisk-rs = "0.8"

Or pick individual protocols:

[dependencies]
asterisk-rs = { version = "0.8", default-features = false, features = ["ami"] }

Or use crates directly:

[dependencies]
asterisk-rs-ami = "0.8"

Protocols

ProtocolPortTransportCrate
AMI5038TCPasterisk-rs-ami
AGI4573TCP (FastAGI)asterisk-rs-agi
ARI8088HTTP + WebSocketasterisk-rs-ari

Domain Types

Common Asterisk constants are available as typed enums in asterisk_rs_core::types: hangup causes, channel states, device states, dial statuses, and more. See Domain Types for the full list.

Requirements

  • Rust 1.86 or newer
  • tokio runtime
  • a C/C++ compiler for the default non-FIPS AWS-LC-backed Rustls provider; CMake, Go, and bindgen are not required for this configuration
  • A running Asterisk instance for integration

This workspace does not expose or test AWS-LC FIPS mode. A downstream FIPS configuration requires CMake and Go, and may also require bindgen plus libclang on targets without pre-generated FIPS bindings; treat that as an unsupported integration until it has its own target-specific CI proof.

Domain types

Shared Asterisk constants are modeled in asterisk_rs_core::types. Rustdoc is the canonical inventory of types, variants, numeric conversions, and string parsing.

Use these types when a protocol boundary has a closed or meaningfully classified value set:

  • HangupCause for Q.850/Q.931 cause codes;
  • ChannelState, DeviceState, and ExtensionState for observed state;
  • DialStatus and CdrDisposition for call outcomes;
  • PeerStatus and QueueStrategy for peer and queue state;
  • AgiStatus for numeric AGI response status.

Unknown or combined wire values are preserved where the protocol requires forward compatibility. Consult each type’s rustdoc for its exact conversion behavior rather than matching a copied table.

AMI (Asterisk Manager Interface)

AMI is a TCP protocol on port 5038 for monitoring and controlling Asterisk. The client handles authentication, reconnection, and event dispatch automatically.

Quick Start

use asterisk_rs_ami::AmiClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = AmiClient::builder()
        .host("10.0.0.1")
        .credentials("admin", "secret")
        .build()
        .await?;

    let resp = client.ping().await?;
    println!("{resp:?}");

    client.disconnect().await?;
    Ok(())
}

Capabilities

  • Typed actions and events for the modeled AMI surface; Asterisk 22 is the live-proven boundary
  • MD5 challenge-response with configurable plaintext fallback; remote transport requires an operator-owned, versioned TLS proxy
  • Automatic reconnection with re-authentication
  • Filtered subscriptions – receive only events you care about
  • Event-collecting actions – send_collecting() gathers multi-event responses
  • Command output capture for Response: Follows
  • Configurable timeouts, backoff, and event buffer size

See Connection & Authentication for setup details, Events for the event system, and API reference for links to the canonical rustdoc inventory.

Connection & Authentication

Builder

use asterisk_rs_ami::AmiClient;
use asterisk_rs_core::config::ReconnectPolicy;
use std::time::Duration;

let client = AmiClient::builder()
    .host("10.0.0.1")
    .port(5038)
    .credentials("admin", "secret")
    .timeout(Duration::from_secs(10))
    .reconnect(ReconnectPolicy::exponential(
        Duration::from_secs(1),
        Duration::from_secs(30),
    ))
    .event_capacity(2048)
    .build()
    .await?;

Authentication

The client tries MD5 challenge-response first. Plaintext fallback is enabled by default for compatibility; set .require_challenge(true) outside an explicitly managed TLS proxy boundary. Version 0.8 owns only the TCP AMI client, so a non-loopback deployment must use a separately versioned and maintained TLS proxy whose listener, certificate verification, access policy, and upgrades are owned by the application operator. Authentication happens automatically during build() and after every reconnect.

Reconnection

When the TCP connection drops, the background task reconnects with exponential backoff and re-authenticates before setting the connection state to Connected.

Policies:

  • ReconnectPolicy::exponential(initial, max) — doubling delay with jitter
  • ReconnectPolicy::fixed(interval) — constant delay
  • ReconnectPolicy::none() — no retry
  • .with_max_retries(n) — cap attempts

Connection State

Monitor connection health:

use asterisk_rs_core::config::ConnectionState;

let state = client.connection_state();
match state {
    ConnectionState::Connected => { /* ready */ }
    ConnectionState::Reconnecting => { /* waiting */ }
    _ => { /* down */ }
}

Disconnect

client.disconnect().await?;

Sends a Logoff action before closing the TCP connection.

Events

AMI delivers real-time events as things happen in Asterisk. Events are parsed into typed AmiEvent variants. See API reference for canonical rustdoc links.

Subscribing

let mut sub = client.subscribe();

while let Some(event) = sub.recv().await {
    println!("{}: {}", event.event_name(), event.channel().unwrap_or("n/a"));
}

Filtered Subscriptions

Subscribe to specific event types without processing every event:

let mut hangups = client.subscribe_filtered(|e| {
    e.event_name() == "Hangup"
});

while let Some(event) = hangups.recv().await {
    if let AmiEvent::Hangup { channel, cause, cause_txt, .. } = event {
        println!("hangup on {channel}: {cause} ({cause_txt})");
    }
}

Event-Generating Actions

Actions like Status, CoreShowChannels, and QueueStatus return results as a sequence of events. Use send_collecting to gather them:

use asterisk_rs_ami::action::StatusAction;

let result = client.send_collecting(&StatusAction { channel: None }).await?;
println!("got {} channel status events", result.events.len());
for event in &result.events {
    println!("  {}", event.event_name());
}

Common Accessors

Every AmiEvent has:

  • event_name() — the raw event name string
  • channel() — the associated channel name, if any
  • unique_id() — the unique channel identifier, if any

Unknown Events

Events not covered by typed variants arrive as AmiEvent::Unknown:

if let AmiEvent::Unknown { event_name, headers } = event {
    println!("unhandled: {event_name}");
    for (k, v) in &headers {
        println!("  {k}: {v}");
    }
}

AMI API reference

The public Rust API is documented in asterisk_rs_ami. Rustdoc is the canonical inventory of actions, events, builders, errors, and methods; this guide explains how those APIs fit together without duplicating a generated symbol table.

Find the right API

  • AmiClient owns connection, authentication, subscriptions, action correlation, and shutdown.
  • action contains typed AMI actions. Use send for one response and send_collecting for Asterisk actions that terminate with a completion event.
  • AmiEvent contains modeled events and retains unknown events for forward-compatible handling.
  • AmiResponse exposes parsed response fields and output from Response: Follows.

Start with connection and authentication, then use events for subscription and lag behavior. The crate examples show complete Tokio programs.

AGI (Asterisk Gateway Interface)

AGI allows external programs to control Asterisk dialplan execution. This crate implements a FastAGI TCP server that accepts connections from Asterisk and dispatches them to a handler.

Quick Start

use asterisk_rs_agi::{AgiServer, AgiHandler, AgiRequest, AgiChannel};

struct MyHandler;

impl AgiHandler for MyHandler {
    async fn handle(&self, request: AgiRequest, mut channel: AgiChannel)
        -> asterisk_rs_agi::error::Result<()>
    {
        channel.answer().await?;
        channel.stream_file("hello-world", "").await?;
        channel.hangup(None).await?;
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (server, _shutdown) = AgiServer::builder()
        .bind("0.0.0.0:4573")
        // expose only on an isolated/private FastAGI network
        .allow_external_bind(true)
        .handler(MyHandler)
        .max_connections(100)
        .build()
        .await?;

    server.run().await?;
    Ok(())
}

FastAGI does not authenticate peers. External binds require an explicit opt-in and must be isolated with a private network, firewall allowlist, or authenticated TLS proxy. Prefer the default loopback bind when Asterisk runs on the same host.

Capabilities

  • Every AGI command with typed async methods
  • Handler trait using native async fn (RPITIT, no macro needed)
  • Request environment parsing from Asterisk
  • Configurable concurrency limits
  • Optional command deadline, disabled by default for long-running operations
  • Graceful shutdown via ShutdownHandle

See FastAGI Server for server details and API reference for the canonical rustdoc command inventory.

FastAGI Server

Binding

The server binds a TCP listener and dispatches each connection to your handler. Asterisk connects via the AGI() dialplan application:

exten => 100,1,AGI(agi://your-server:4573)

The builder defaults to 127.0.0.1:4573. FastAGI has no native peer authentication, so an external bind is rejected unless allow_external_bind(true) is explicit. External listeners must be isolated with a private network, firewall allowlist, or authenticated TLS proxy.

Handler Trait

pub trait AgiHandler: Send + Sync + 'static {
    fn handle(&self, request: AgiRequest, channel: AgiChannel)
        -> impl Future<Output = Result<()>> + Send;
}

The handler receives the AGI request (parsed environment variables from Asterisk) and a channel for sending commands back.

Request Environment

AgiRequest contains the agi_* variables sent by Asterisk at connection start: channel name, caller ID, called extension, context, language, etc.

Channel Commands

AgiChannel provides typed async methods for every AGI command: answer, hangup, stream_file, get_data, say_digits, record_file, database_get, speech_create, and more. See API reference for canonical rustdoc links.

Command round trips have no deadline by default because WAIT FOR DIGIT -1, dial applications, recording, and speech operations can wait indefinitely. Applications that need a bound can call channel.set_command_timeout(Some(duration)); expiry poisons the channel because a late response cannot be correlated safely. Pass None to disable the deadline again.

Concurrency

Limit concurrent connections with max_connections:

let (server, _shutdown) = AgiServer::builder()
    .bind("0.0.0.0:4573")
    .allow_external_bind(true)
    .handler(MyHandler)
    .max_connections(50)
    .build()
    .await?;

Graceful Shutdown

build() returns a ShutdownHandle that stops the accept loop:

let (server, shutdown) = AgiServer::builder()
    .bind("0.0.0.0:4573")
    .allow_external_bind(true)
    .handler(MyHandler)
    .build()
    .await?;

// stop accepting after ctrl-c
tokio::spawn(async move {
    tokio::signal::ctrl_c().await.ok();
    shutdown.shutdown();
});

server.run().await?;

AGI API reference

The public Rust API is documented in asterisk_rs_agi. Rustdoc is the canonical inventory of channel commands, request accessors, server configuration, and errors.

Find the right API

  • AgiServer accepts bounded FastAGI sessions and coordinates shutdown.
  • AgiHandler is the application callback for one parsed request and channel session.
  • AgiRequest provides typed access to the FastAGI prelude.
  • AgiChannel owns the command/response exchange. Its rustdoc lists every supported typed command and exact signature.

See FastAGI server for admission, bind, timeout, and shutdown behavior. The crate examples are the source of complete runnable server programs.

ARI (Asterisk REST Interface)

ARI provides full call control through a REST API combined with a WebSocket event stream for Stasis applications.

Cleartext HTTP/WebSocket is allowed by default only on loopback. Remote cleartext requires .allow_insecure_remote(true); prefer .secure(true). Private PKI deployments can add a PEM CA bundle with .private_ca_pem(...), which augments platform trust for both HTTPS and WSS.

Quick Start

use asterisk_rs_ari::AriClient;
use asterisk_rs_ari::config::AriConfigBuilder;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = AriConfigBuilder::new("my-app")
        .host("10.0.0.1")
        .secure(true)
        .username("asterisk")
        .password("secret")
        .build()?;

    let client = AriClient::connect(config).await?;
    let mut events = client.subscribe();

    while let Some(msg) = events.recv().await {
        println!("[{}] {:?}", msg.application, msg.event);
    }

    Ok(())
}

Capabilities

  • REST/WebSocket clients for the modeled ARI surface, with Asterisk 22 as the live-proven boundary
  • Typed events with metadata (application, timestamp, asterisk_id)
  • Filtered subscriptions – receive only events you care about
  • Resource handles for channels, bridges, playbacks, recordings
  • System management – modules, logging, config, global variables
  • URL-safe query encoding, HTTP timeouts, WebSocket lifecycle management

See Stasis Applications for the event model, Resources for the handle pattern, and API reference for links to the canonical rustdoc inventory.

Stasis Applications

ARI routes calls to your application via the Stasis() dialplan application:

exten => 100,1,Stasis(my-app,arg1,arg2)

Event Stream

Events arrive via WebSocket as AriMessage structs containing metadata and a typed AriEvent:

use asterisk_rs_ari::event::{AriEvent, AriMessage};

let mut events = client.subscribe();
while let Some(msg) = events.recv().await {
    println!("app={} time={}", msg.application, msg.timestamp);
    match msg.event {
        AriEvent::StasisStart { channel, args, .. } => {
            println!("call from {} with args {:?}", channel.name, args);
        }
        AriEvent::StasisEnd { channel } => {
            println!("call ended: {}", channel.name);
        }
        _ => {}
    }
}

Filtered Subscriptions

let mut calls = client.subscribe_filtered(|msg| {
    matches!(msg.event, AriEvent::StasisStart { .. } | AriEvent::StasisEnd { .. })
});

Event Metadata

Every AriMessage carries:

  • application — the Stasis app that received the event
  • timestamp — ISO 8601 when the event was created
  • asterisk_id — unique Asterisk instance ID (for clusters)
  • event — the typed AriEvent payload

See API reference for the canonical rustdoc event inventory.

Resources

ARI resources are managed through handle objects that bundle a resource ID with a client reference.

Handle Pattern

use asterisk_rs_ari::resources::channel::{ChannelHandle, originate, OriginateParams};

// originate a channel
let params = OriginateParams {
    endpoint: "PJSIP/100".into(),
    app: Some("my-app".into()),
    ..Default::default()
};
let channel = originate(&client, &params).await?;

// wrap in a handle for operations
let handle = ChannelHandle::new(channel.id, client.clone());
handle.answer().await?;
handle.play("sound:hello-world").await?;
handle.hangup(None).await?;

Available Handles

HandleResourceKey Operations
ChannelHandleChannelanswer, hangup, play, record, hold, mute, dtmf, dial, snoop
BridgeHandleBridgeadd/remove channel, play, record, moh, video source
PlaybackHandlePlaybackcontrol, stop
RecordingHandleRecordingstop, pause, mute

Module Functions

Each resource module also provides free functions for list/get/create:

use asterisk_rs_ari::resources::channel;
use asterisk_rs_ari::resources::bridge;

let channels = channel::list(&client).await?;
let bridges = bridge::list(&client).await?;

Asterisk System

The asterisk resource module provides system management:

use asterisk_rs_ari::resources::asterisk;

let info = asterisk::info(&client, None).await?;
let pong = asterisk::ping(&client).await?;
asterisk::reload_module(&client, "res_pjsip.so").await?;

See API reference for the canonical rustdoc resource inventory.

ARI API reference

The public Rust API is documented in asterisk_rs_ari. Rustdoc is the canonical inventory of resources, handles, events, transports, media types, and server APIs.

Find the right API

  • AriClient combines REST or unified WebSocket requests with the event stream.
  • resources groups operations by Asterisk resource. Handles bind an ID to a client for follow-up operations.
  • pending contains subscribe-before-create flows for channels, bridges, and playbacks.
  • AriEvent contains modeled Stasis events plus forward-compatible unknown-event retention.
  • media owns chan_websocket control and audio exchange.
  • server owns Asterisk 22 outbound WebSocket sessions.

Use resources for the handle pattern and Stasis applications for event delivery. Exact operations and signatures belong only in rustdoc so source-module splits cannot silently stale this guide.