Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
target
target# Ignore IDE folders\n.vs/\n.vscode/
10 changes: 9 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ license-file = "LICENSE"
[workspace]
members = [
"rsip-derives",
"rsip-wrapper",
]

[dependencies]
Expand Down
11 changes: 11 additions & 0 deletions rsip-wrapper/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "rsip-wrapper"
version = "0.1.0"
edition = "2018"
description = "Minimal FFI wrapper around rsip: a small transport/UA and C API for integration with FreeSWITCH"
license-file = "../LICENSE"
crate-type = ["cdylib"]

[dependencies]
lazy_static = "1.4"
rsip = { path = ".." }
63 changes: 63 additions & 0 deletions rsip-wrapper/DIAGRAM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
## Incoming call flow — rsip-wrapper + mod_rsip + FreeSWITCH

The diagram below shows the flow for an incoming SIP INVITE when using the `rsip-wrapper` hybrid stack. It highlights the Rust-side listener and parser, the C shim (e.g., `mod_rsip`) which receives events via the FFI callback, and the FreeSWITCH core that creates sessions and handles media.

```mermaid
flowchart LR
%% External UA and network
UA[User Agent SIP endpoint] -->|SIP INVITE| RSIP_WRAPPER[rsip-wrapper UDP/TCP/WS listener]

%% Rust-side processing
RSIP_WRAPPER -->|raw SIP bytes| RSIP_PARSER[rsip parser/types]
RSIP_PARSER -->|event: INVITE parsed| MOD_RSIP[mod_rsip c callback]

%% C-shim translates events into FreeSWITCH API calls
MOD_RSIP -->|create session / new channel| FS_CORE[FreeSWITCH core switch_core_session_*]
MOD_RSIP -->|deliver remote SDP| FS_SDP[switch_sdp]

%% FreeSWITCH negotiates and attaches media
FS_CORE -->|generate local SDP| FS_SDP
FS_CORE -->|attach media| FS_MEDIA[Media Engine / RTP]
FS_MEDIA -->|RTP audio/video| UA

%% Signaling back to UA
MOD_RSIP -->|invoke rsip_send_udp / send response| RSIP_WRAPPER
RSIP_WRAPPER -->|SIP 100/180/200/ACK| UA

%% Mid-call and teardown
UA -->|ACK / in-dialog requests| RSIP_WRAPPER
RSIP_WRAPPER --> MOD_RSIP
UA -->|BYE| RSIP_WRAPPER
RSIP_WRAPPER --> MOD_RSIP
MOD_RSIP -->|call hangup| FS_CORE
FS_CORE -->|release media| FS_MEDIA

%% Grouping boxes
subgraph Rust
RSIP_WRAPPER
RSIP_PARSER
end

subgraph C_Module
MOD_RSIP
end

subgraph FreeSWITCH
FS_CORE
FS_SDP
FS_MEDIA
end

classDef rustfill fill:#E8F1FF,stroke:#5B9BD5;
classDef cfill fill:#FFF4E5,stroke:#E69F00;
classDef fsfill fill:#E8FFE8,stroke:#2E8B57;
class RSIP_WRAPPER,RSIP_PARSER rustfill;
class MOD_RSIP cfill;
class FS_CORE,FS_SDP,FS_MEDIA fsfill;

```

Notes
- The prototype `rsip-wrapper` currently forwards raw SIP datagrams to the C callback; extend `RSIP_PARSER` to emit higher-level events (INVITE/REGISTER/BYE) if desired.
- In an in-process FreeSWITCH module, callbacks from Rust should be marshalled onto FS worker threads before calling core APIs.
- Media (RTP) is typically handled by FreeSWITCH; the diagram assumes FS will terminate or proxy media and negotiate SDP with the remote UA.
63 changes: 63 additions & 0 deletions rsip-wrapper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# rsip-wrapper: transport+UA hybrid (FFI) implementation

This document describes the minimal hybrid approach implemented in this crate: a small Rust "transport & UA" wrapper around `rsip` that exposes a compact C API for integration with FreeSWITCH (or other C hosts).

![diagrm](mermaid-diagram-2025-11-11-120048.png)

## Goals

- Provide a small, safe C ABI surface so a host (FreeSWITCH module) can receive SIP messages from Rust and instruct Rust to send SIP messages.
- Keep the Rust side responsible for networking & protocol parsing. Minimize the FFI surface and make callbacks simple.

## What this prototype does

- Builds a cdylib with a C API.
- Implements a UDP listener that receives raw SIP datagrams and invokes a registered callback with event="sip_rx" and payload containing the raw SIP text.
- Exposes helper functions: init, set/clear callback, start UDP listener, send UDP datagram, shutdown, and a small version string.

## Files added

- `Cargo.toml` - crate manifest (cdylib crate-type).
- `src/lib.rs` - Rust implementation of the FFI API.
- `include/rsip_wrapper.h` - C header describing the API.
- `mod_rsip_example/mod_rsip.c` - small example program that registers a callback and listens on UDP/5060.

## Design notes and safety

- The callback has signature `void(*cb)(const char* event, const char* payload)` and is called synchronously from the Rust listener thread. The strings are only valid for the duration of the callback; the callee must copy them if it needs to persist the data.
- We use `lazy_static`-backed `Mutex` and an `AtomicBool` to store the callback, the thread handle, and a running flag.
- We intentionally keep the API small to reduce cross-language ownership complexity.
- The Rust side currently performs no full SIP transaction or dialog management — it only receives raw SIP datagrams and forwards them. `rsip` (the dependency) can be used inside the listener to parse/validate messages if you extend the implementation.

## Integration with FreeSWITCH (next steps)

1. In-process approach (advanced): write a FreeSWITCH module `mod_rsip.c` that dynamically loads the `rsip-wrapper` DLL (or links against it) and registers a callback. The module should translate events into FS session actions (create session, set remote SDP, answer, bridge). Ensure thread-safety: many FS APIs must be called from FS worker threads or using FS-provided async mechanisms.
2. Hybrid (recommended incremental): run the `rsip-wrapper` as an external process or simple native binary and communicate via network (SIP) or FSMQ/ESL. Use FreeSWITCH `sofia` profiles to talk to your process as a gateway.

## Build notes (Windows PowerShell examples)

1. Build the Rust cdylib (MSVC toolchain recommended if FreeSWITCH is built with MSVC):

cd C:\Users\altan\Downloads\rsip\rsip-wrapper
cargo build --release

2. The produced dynamic library will be at `target\release\rsip_wrapper.dll` (name may vary depending on platform). Use `cbindgen` or the provided header `include/rsip_wrapper.h` to include definitions in C code.

3. Example: compile the example shim (adjust to your compiler):

# If using MSVC: cl.exe /EHsc mod_rsip.c /I..\include

# If using gcc: gcc mod_rsip.c -I../include -o mod_rsip_example.exe -L../target/release -lrsip_wrapper

Note: linking against the produced rsip_wrapper library on Windows may require generating an import library or loading the DLL dynamically.

## Limitations & next steps

- The prototype only handles UDP datagrams; you should add TCP/TLS/WS transports for production SIP.
- Add proper SIP transaction, dialog, and timer handling (retransmits, forking, PRACK, etc.) by implementing those layers on top of `rsip` parsing.
- For in-process modules, design a small, robust event model that allows the FS module to ask Rust to perform actions synchronously or asynchronously. Carefully manage the async runtime lifecycle (spawn a dedicated runtime thread inside Rust and do not block FS threads).
- Use `cbindgen` to generate headers automatically and include tests that validate FFI linkage.

## Contact & follow-up

I can flesh out a `mod_rsip.c` FreeSWITCH module example that calls `switch_core_session_*` APIs and maps events into FS sessions if you want to proceed with an in-process integration. I can also extend `rsip-wrapper` to parse SIP via `rsip::message` and expose higher-level events (INVITE, BYE, REGISTER) rather than raw SIP strings.
168 changes: 168 additions & 0 deletions rsip-wrapper/TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Testing Guide for rsip-wrapper

This document describes the unit and integration tests available for the `rsip-wrapper` crate, and how to run them locally.

## Test Structure

### Unit Tests (in `src/lib.rs`)

The unit tests cover the core FFI API and internal state management:

- `test_rsip_init()` — Verifies `rsip_init()` initializes state correctly.
- `test_rsip_version()` — Tests the `rsip_version()` helper function returns the correct version string.
- `test_callback_registration()` — Validates callback registration, clearing, and state.
- `test_udp_send_with_null_pointers()` — Ensures `rsip_send_udp()` rejects null pointers safely.
- `test_udp_send_invalid_address()` — Tests behavior with invalid IP addresses.
- `test_listener_already_running()` — Verifies that starting a listener twice fails (prevents races).
- `test_shutdown_clears_state()` — Confirms `rsip_shutdown()` cleanly resets all state.

### Integration Tests (in `tests/integration_test.rs`)

The integration tests validate the complete FFI linkage and runtime behavior:

- `test_ffi_version_linkage()` — Confirms the library links correctly and exports the version symbol.
- `test_ffi_init_and_shutdown()` — Tests FFI init/shutdown lifecycle across the C boundary.
- `test_ffi_callback_registration()` — Validates callback registration from C side.
- `test_ffi_send_udp()` — Tests the `rsip_send_udp()` FFI function with a real UDP send.
- `test_ffi_listener_lifecycle()` — Starts a listener on port 15060, sends a test SIP message, and verifies the listener receives it and invokes the callback.
- `test_ffi_multiple_lifecycle()` — Stress-tests multiple init/shutdown cycles to ensure no resource leaks.

## Running Tests Locally

### Prerequisites

- Rust toolchain (install from https://rustup.rs/)
- On Windows, MSVC or GNU toolchain (MSVC recommended if you're building against MSVC libraries)

### Build the crate

```powershell
cd C:\Users\altan\Downloads\rsip\rsip-wrapper
cargo build
```

This produces `target\debug\rsip_wrapper.dll` (or `.a` / `.so` depending on your platform).

### Run unit tests

```powershell
cargo test --lib
```

Example output:
```
running 7 tests
test tests::test_rsip_init ... ok
test tests::test_rsip_version ... ok
test tests::test_callback_registration ... ok
test tests::test_udp_send_with_null_pointers ... ok
test tests::test_udp_send_invalid_address ... ok
test tests::test_listener_already_running ... ok
test tests::test_shutdown_clears_state ... ok

test result: ok. 7 passed
```

### Run integration tests

```powershell
cargo test --test integration_test
```

This runs the FFI linkage tests. Note: the integration tests use `extern "C"` to declare the FFI functions, so Cargo must link against the compiled cdylib. Rust's `cargo test` automatically links the library for integration tests.

Example output (with some listener/network delays):
```
running 6 tests
test test_ffi_version_linkage ... ok
test test_ffi_init_and_shutdown ... ok
test test_ffi_callback_registration ... ok
test test_ffi_send_udp ... ok
test test_ffi_listener_lifecycle ... ok (may take a few hundred milliseconds)
test test_ffi_multiple_lifecycle ... ok

test result: ok. 6 passed
```

### Run all tests

```powershell
cargo test
```

This runs both unit and integration tests in sequence.

### Run with output

To see println! output from tests (useful for debugging):

```powershell
cargo test -- --nocapture
```

### Run a specific test

```powershell
cargo test test_ffi_listener_lifecycle -- --nocapture
```

## Test Expectations

### What the tests validate

1. **API correctness**: init, set/clear callback, send, shutdown behave as documented.
2. **Thread safety**: the listener can be started and stopped cleanly; multiple cycles don't leak state.
3. **FFI safety**: null pointer checks, CString conversions, and callback invocations don't crash.
4. **UDP transport**: datagrams are sent and received correctly; callbacks are invoked when data arrives.

### Known limitations

- Tests use localhost (127.0.0.1) and high ports (15060+) to avoid conflicts with running services.
- The listener test (`test_ffi_listener_lifecycle`) sends a raw SIP-like string; the current implementation does not parse it with `rsip`, only forwards it to the callback.
- On slow systems or under high load, timing-sensitive tests may occasionally flake. Increase sleep durations in the test if needed.

## Next Steps for Production Testing

1. **Extend rsip parsing**: add tests that verify SIP message parsing with `rsip::message` inside the listener.
2. **Add transport variants**: test TCP, TLS, and WebSocket transports.
3. **Add transaction tests**: verify that retransmit timers, INVITE/ACK flow, and dialog state are handled correctly.
4. **Add benchmarks**: measure throughput and latency with high-volume SIP message injection.
5. **Add C/FFI tests**: write C or C++ tests that link the library dynamically and test from that side (good for validating compatibility with FreeSWITCH modules).

## Troubleshooting

### `cargo test` fails with "cannot find library"

Ensure the crate is built first:
```powershell
cargo build
cargo test
```

### `test_ffi_listener_lifecycle` times out or hangs

This may happen if port 15060 is already in use. Try:
- Changing the port number in the test.
- Checking if another service is listening: `netstat -an | findstr 15060`

### Tests panic with "thread 'test-...' panicked"

Check the panic message carefully. Common issues:
- Null pointer access in FFI functions.
- CString validation failure (non-UTF8 strings).
- Listener thread not starting (port already in use).

## Continuous Integration

For CI/CD pipelines (GitHub Actions, Azure Pipelines, etc.), add a step:

```yaml
- name: Run tests
run: |
cd rsip-wrapper
cargo test --lib
cargo test --test integration_test
```

This will catch regressions early.

37 changes: 37 additions & 0 deletions rsip-wrapper/include/rsip_wrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#ifndef RSIP_WRAPPER_H
#define RSIP_WRAPPER_H

#include <stdbool.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

// Initialize internal structures. Call before other APIs.
bool rsip_init(void);

// Set a callback to receive events from the Rust side. The callback is called
// synchronously from the Rust listener thread. The strings are valid only for
// the duration of the callback and will be freed after the call returns.
void rsip_set_event_callback(void (*cb)(const char* event, const char* payload));
void rsip_clear_event_callback(void);

// Start a UDP listener on the given port. Received datagrams trigger the
// registered callback with event="sip_rx" and payload being the raw SIP text.
bool rsip_start_udp_listener(uint16_t port);

// Send a raw UDP datagram to dest_ip:dest_port with data being a C string.
bool rsip_send_udp(const char* dest_ip, uint16_t dest_port, const char* data);

// Shutdown listener and clean up.
void rsip_shutdown(void);

// Return an informational static string (leaked pointer) for testing linkage.
const char* rsip_version(void);

#ifdef __cplusplus
}
#endif

#endif // RSIP_WRAPPER_H
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading