#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_imports)]
#![allow(clippy::all)]
pub mod alloc;
pub mod amf;
pub mod buffer;
pub mod bytes;
pub mod chunk;
pub mod client;
pub mod ertmp;
pub mod flv;
pub mod handshake;
pub mod log;
pub mod media;
pub mod message;
pub mod net;
pub mod server;
pub mod session;
pub mod transport;
pub mod types;
pub use types::*;
pub const VERSION_STRING: &str = env!("CARGO_PKG_VERSION");
pub fn version_string() -> &'static str {
VERSION_STRING
}
pub fn version_major() -> i32 {
i32::try_from(types::VERSION_MAJOR).unwrap_or(0)
}
pub fn version_minor() -> i32 {
i32::try_from(types::VERSION_MINOR).unwrap_or(0)
}
pub fn version_patch() -> i32 {
i32::try_from(types::VERSION_PATCH).unwrap_or(0)
}
pub fn tls_supported() -> bool {
transport::tls_available()
}
pub fn error_string(code: ErrorCode) -> &'static str {
code.as_str()
}
fn error_c_string(code: ErrorCode) -> &'static [u8] {
match code {
ErrorCode::Ok => b"OK\0",
ErrorCode::Io => b"I/O error\0",
ErrorCode::Timeout => b"Timeout\0",
ErrorCode::Protocol => b"Protocol error\0",
ErrorCode::Handshake => b"Handshake error\0",
ErrorCode::Chunk => b"Chunk error\0",
ErrorCode::Amf => b"AMF error\0",
ErrorCode::Unsupported => b"Unsupported\0",
ErrorCode::Auth => b"Authentication error\0",
ErrorCode::Internal => b"Internal error\0",
}
}
fn error_code_from_raw(code: i32) -> ErrorCode {
match code {
0 => ErrorCode::Ok,
-1 => ErrorCode::Io,
-2 => ErrorCode::Timeout,
-3 => ErrorCode::Protocol,
-4 => ErrorCode::Handshake,
-5 => ErrorCode::Chunk,
-6 => ErrorCode::Amf,
-7 => ErrorCode::Unsupported,
-8 => ErrorCode::Auth,
-9 => ErrorCode::Internal,
_ => ErrorCode::Internal,
}
}
fn frame_type_from_raw(raw: i32) -> Result<FrameType> {
match raw {
0 => Ok(FrameType::Audio),
1 => Ok(FrameType::Video),
2 => Ok(FrameType::Script),
3 => Ok(FrameType::Metadata),
_ => Err(ErrorCode::Internal),
}
}
#[repr(C)]
struct FrameSendFields {
frame_type: i32,
timestamp: u32,
composition_time: u32,
size: u32,
data: *const u8,
}
const _: [(); std::mem::size_of::<i32>()] = [(); std::mem::size_of::<FrameType>()];
const _: [(); std::mem::align_of::<i32>()] = [(); std::mem::align_of::<FrameType>()];
const _: [(); std::mem::offset_of!(Frame, frame_type)] =
[(); std::mem::offset_of!(FrameSendFields, frame_type)];
const _: [(); std::mem::offset_of!(Frame, timestamp)] =
[(); std::mem::offset_of!(FrameSendFields, timestamp)];
const _: [(); std::mem::offset_of!(Frame, composition_time)] =
[(); std::mem::offset_of!(FrameSendFields, composition_time)];
const _: [(); std::mem::offset_of!(Frame, size)] =
[(); std::mem::offset_of!(FrameSendFields, size)];
const _: [(); std::mem::offset_of!(Frame, data)] =
[(); std::mem::offset_of!(FrameSendFields, data)];
const _: [(); std::mem::size_of::<FrameSendFields>()] =
[(); std::mem::offset_of!(Frame, audio_codec)];
#[cfg(test)]
mod ffi_tests {
use super::*;
use crate::server::Server;
use std::ptr::NonNull;
#[test]
fn server_create_applies_default_max_connections_for_zero_config() {
let config = ServerConfig {
max_connections: 0,
chunk_size: 128,
tls_enabled: 0,
tls_cert_file: std::ptr::null(),
tls_key_file: std::ptr::null(),
tls_ca_file: std::ptr::null(),
tls_insecure: 0,
};
let server = NonNull::new(unsafe { lrtmp2_server_create(&config) })
.expect("lrtmp2_server_create returned null");
unsafe {
assert_eq!(
server.as_ref().config.max_connections,
DEFAULT_FFI_MAX_CONNECTIONS
);
lrtmp2_server_destroy(server.as_ptr());
}
}
#[test]
fn server_create_preserves_negative_max_connections_as_unlimited() {
let config = ServerConfig {
max_connections: -1,
chunk_size: 128,
tls_enabled: 0,
tls_cert_file: std::ptr::null(),
tls_key_file: std::ptr::null(),
tls_ca_file: std::ptr::null(),
tls_insecure: 0,
};
let server = NonNull::new(unsafe { lrtmp2_server_create(&config) })
.expect("lrtmp2_server_create returned null");
unsafe {
assert_eq!(server.as_ref().config.max_connections, -1);
lrtmp2_server_destroy(server.as_ptr());
}
}
#[test]
fn server_new_preserves_zero_max_connections_for_rust_api() {
let config = ServerConfig {
max_connections: 0,
chunk_size: 128,
tls_enabled: 0,
tls_cert_file: std::ptr::null(),
tls_key_file: std::ptr::null(),
tls_ca_file: std::ptr::null(),
tls_insecure: 0,
};
let server = Server::new(config).unwrap();
assert_eq!(server.config.max_connections, 0);
}
#[test]
fn server_listen_rejects_invalid_utf8_bind_addr() {
let config = ServerConfig {
max_connections: 8,
chunk_size: 128,
tls_enabled: 0,
tls_cert_file: std::ptr::null(),
tls_key_file: std::ptr::null(),
tls_ca_file: std::ptr::null(),
tls_insecure: 0,
};
let server = NonNull::new(unsafe { lrtmp2_server_create(&config) })
.expect("lrtmp2_server_create returned null");
let invalid = [0xFF, b':', b'1', b'9', b'3', b'5', 0];
let rc = unsafe { lrtmp2_server_listen(server.as_ptr(), invalid.as_ptr()) };
unsafe {
lrtmp2_server_destroy(server.as_ptr());
}
assert_eq!(rc, ErrorCode::Internal as i32);
}
#[test]
fn frame_type_from_raw_rejects_invalid_discriminants() {
assert_eq!(frame_type_from_raw(0).unwrap(), FrameType::Audio);
assert_eq!(frame_type_from_raw(3).unwrap(), FrameType::Metadata);
assert!(frame_type_from_raw(4).is_err());
assert!(frame_type_from_raw(-1).is_err());
}
#[test]
fn client_send_frame_checks_state_before_reading_frame() {
let client = Box::into_raw(Box::new(client::Client::new()));
let dangling_frame = NonNull::<Frame>::dangling().as_ptr();
let rc = unsafe { lrtmp2_client_send_frame(client, dangling_frame) };
unsafe { lrtmp2_client_destroy(client) };
assert_eq!(rc, ErrorCode::Protocol as i32);
}
}
use std::ffi::c_int;
const DEFAULT_FFI_MAX_CONNECTIONS: c_int = 256;
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_server_create(config: *const ServerConfig) -> *mut server::Server {
if config.is_null() {
return std::ptr::null_mut();
}
let mut cfg = unsafe { *config };
if cfg.max_connections == 0 {
cfg.max_connections = DEFAULT_FFI_MAX_CONNECTIONS;
}
match server::Server::new(cfg) {
Ok(s) => Box::into_raw(Box::new(s)),
Err(_) => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_server_destroy(server: *mut server::Server) {
if !server.is_null() {
drop(unsafe { Box::from_raw(server) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_server_listen(
server: *mut server::Server,
bind_addr: *const u8,
) -> i32 {
if server.is_null() || bind_addr.is_null() {
return ErrorCode::Internal as i32;
}
let s = unsafe { &mut *server };
let addr = unsafe { std::ffi::CStr::from_ptr(bind_addr as *const std::ffi::c_char) };
let addr_str = match addr.to_str() {
Ok(s) => s,
Err(_) => return ErrorCode::Internal as i32,
};
match s.listen(addr_str) {
Ok(()) => 0,
Err(e) => e as i32,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_server_poll(server: *mut server::Server, timeout_ms: i32) -> i32 {
if server.is_null() {
return ErrorCode::Internal as i32;
}
let s = unsafe { &mut *server };
match s.poll(timeout_ms) {
Ok(()) => 0,
Err(e) => e as i32,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_server_stop(server: *mut server::Server) {
if !server.is_null() {
unsafe { (*server).stop() };
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_client_create(config: *const ServerConfig) -> *mut client::Client {
let mut c = client::Client::new();
if !config.is_null() {
let cfg = unsafe { &*config };
let ca_file = if cfg.tls_ca_file.is_null() {
None
} else {
match unsafe { std::ffi::CStr::from_ptr(cfg.tls_ca_file as *const std::ffi::c_char) }
.to_str()
{
Ok(s) => Some(s.to_string()),
Err(_) => return std::ptr::null_mut(),
}
};
c.set_tls_client_config(ca_file, cfg.tls_insecure != 0);
}
Box::into_raw(Box::new(c))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_client_destroy(c: *mut client::Client) {
if !c.is_null() {
drop(unsafe { Box::from_raw(c) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_client_connect(c: *mut client::Client, url: *const u8) -> i32 {
if c.is_null() || url.is_null() {
return ErrorCode::Internal as i32;
}
let client = unsafe { &mut *c };
let url_str = unsafe { std::ffi::CStr::from_ptr(url as *const std::ffi::c_char) };
match client.connect(url_str.to_str().unwrap_or("")) {
Ok(()) => 0,
Err(e) => e as i32,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_client_publish(c: *mut client::Client) -> i32 {
if c.is_null() {
return ErrorCode::Internal as i32;
}
match unsafe { (*c).publish() } {
Ok(()) => 0,
Err(e) => e as i32,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_client_play(c: *mut client::Client) -> i32 {
if c.is_null() {
return ErrorCode::Internal as i32;
}
match unsafe { (*c).play() } {
Ok(()) => 0,
Err(e) => e as i32,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_client_send_frame(
c: *mut client::Client,
frame: *const Frame,
) -> i32 {
if c.is_null() || frame.is_null() {
return ErrorCode::Internal as i32;
}
if unsafe { (*c).state } != client::ClientState::Publishing {
return ErrorCode::Protocol as i32;
}
let fields = unsafe { &*(frame as *const Frame as *const FrameSendFields) };
let frame_type = match frame_type_from_raw(fields.frame_type) {
Ok(t) => t,
Err(e) => return e as i32,
};
if fields.size > 0 && fields.data.is_null() {
return ErrorCode::Internal as i32;
}
if fields.size as usize > client::MAX_CLIENT_FRAME_BYTES {
return ErrorCode::Protocol as i32;
}
let payload = if fields.size == 0 || fields.data.is_null() {
Vec::new()
} else {
unsafe { std::slice::from_raw_parts(fields.data, fields.size as usize).to_vec() }
};
match unsafe { (*c).send_frame_payload(frame_type, fields.timestamp, &payload) } {
Ok(()) => 0,
Err(e) => e as i32,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_client_poll(c: *mut client::Client, timeout_ms: i32) -> i32 {
if c.is_null() {
return ErrorCode::Internal as i32;
}
match unsafe { (*c).poll(timeout_ms) } {
Ok(()) => 0,
Err(e) => e as i32,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_conn_get_fd(_conn: *const session::conn::Conn) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn lrtmp2_tls_supported() -> i32 {
if tls_supported() { 1 } else { 0 }
}
#[unsafe(no_mangle)]
pub extern "C" fn lrtmp2_version_string() -> *const u8 {
concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr()
}
#[unsafe(no_mangle)]
pub extern "C" fn lrtmp2_version_major() -> i32 {
version_major()
}
#[unsafe(no_mangle)]
pub extern "C" fn lrtmp2_version_minor() -> i32 {
version_minor()
}
#[unsafe(no_mangle)]
pub extern "C" fn lrtmp2_version_patch() -> i32 {
version_patch()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lrtmp2_error_string(code: i32) -> *const u8 {
error_c_string(error_code_from_raw(code)).as_ptr()
}