#![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,
}
}
#[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);
}
}
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) };
match s.listen(addr.to_str().unwrap_or("")) {
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;
}
let frame_ref = unsafe { &*frame };
if unsafe { (*c).state } != client::ClientState::Publishing {
return ErrorCode::Protocol as i32;
}
if frame_ref.size > 0 && frame_ref.data.is_null() {
return ErrorCode::Internal as i32;
}
if frame_ref.size as usize > client::MAX_CLIENT_FRAME_BYTES {
return ErrorCode::Protocol as i32;
}
let payload = if frame_ref.size == 0 || frame_ref.data.is_null() {
Vec::new()
} else {
unsafe { std::slice::from_raw_parts(frame_ref.data, frame_ref.size as usize).to_vec() }
};
match unsafe { (*c).send_frame_payload(frame_ref.frame_type, frame_ref.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()
}