1#![allow(dead_code)]
7#![allow(unused_variables)]
8#![allow(unused_imports)]
9#![allow(clippy::all)]
10
11pub mod alloc;
12pub mod amf;
13pub mod buffer;
14pub mod bytes;
15pub mod chunk;
16pub mod client;
17pub mod ertmp;
18pub mod flv;
19pub mod handshake;
20pub mod log;
21pub mod message;
22pub mod net;
23pub mod server;
24pub mod session;
25pub mod transport;
26pub mod types;
27
28pub use types::*;
30
31pub const VERSION_STRING: &str = env!("CARGO_PKG_VERSION");
33
34pub fn version_string() -> &'static str {
36 VERSION_STRING
37}
38
39pub fn version_major() -> i32 {
41 i32::try_from(types::VERSION_MAJOR).unwrap_or(0)
42}
43
44pub fn version_minor() -> i32 {
46 i32::try_from(types::VERSION_MINOR).unwrap_or(0)
47}
48
49pub fn version_patch() -> i32 {
51 i32::try_from(types::VERSION_PATCH).unwrap_or(0)
52}
53
54pub fn tls_supported() -> bool {
56 transport::tls_available()
57}
58
59pub fn error_string(code: ErrorCode) -> &'static str {
61 code.as_str()
62}
63
64fn error_c_string(code: ErrorCode) -> &'static [u8] {
65 match code {
66 ErrorCode::Ok => b"OK\0",
67 ErrorCode::Io => b"I/O error\0",
68 ErrorCode::Timeout => b"Timeout\0",
69 ErrorCode::Protocol => b"Protocol error\0",
70 ErrorCode::Handshake => b"Handshake error\0",
71 ErrorCode::Chunk => b"Chunk error\0",
72 ErrorCode::Amf => b"AMF error\0",
73 ErrorCode::Unsupported => b"Unsupported\0",
74 ErrorCode::Auth => b"Authentication error\0",
75 ErrorCode::Internal => b"Internal error\0",
76 }
77}
78
79fn error_code_from_raw(code: i32) -> ErrorCode {
86 match code {
87 0 => ErrorCode::Ok,
88 -1 => ErrorCode::Io,
89 -2 => ErrorCode::Timeout,
90 -3 => ErrorCode::Protocol,
91 -4 => ErrorCode::Handshake,
92 -5 => ErrorCode::Chunk,
93 -6 => ErrorCode::Amf,
94 -7 => ErrorCode::Unsupported,
95 -8 => ErrorCode::Auth,
96 -9 => ErrorCode::Internal,
97 _ => ErrorCode::Internal,
98 }
99}
100
101#[cfg(test)]
102mod ffi_tests {
103 use super::*;
104 use crate::server::Server;
105 use std::ptr::NonNull;
106
107 #[test]
108 fn server_create_applies_default_max_connections_for_zero_config() {
109 let config = ServerConfig {
110 max_connections: 0,
111 chunk_size: 128,
112 tls_enabled: 0,
113 tls_cert_file: std::ptr::null(),
114 tls_key_file: std::ptr::null(),
115 tls_ca_file: std::ptr::null(),
116 tls_insecure: 0,
117 };
118 let server = NonNull::new(unsafe { lrtmp2_server_create(&config) })
119 .expect("lrtmp2_server_create returned null");
120 unsafe {
121 assert_eq!(
122 server.as_ref().config.max_connections,
123 DEFAULT_FFI_MAX_CONNECTIONS
124 );
125 lrtmp2_server_destroy(server.as_ptr());
126 }
127 }
128
129 #[test]
130 fn server_create_preserves_negative_max_connections_as_unlimited() {
131 let config = ServerConfig {
132 max_connections: -1,
133 chunk_size: 128,
134 tls_enabled: 0,
135 tls_cert_file: std::ptr::null(),
136 tls_key_file: std::ptr::null(),
137 tls_ca_file: std::ptr::null(),
138 tls_insecure: 0,
139 };
140 let server = NonNull::new(unsafe { lrtmp2_server_create(&config) })
141 .expect("lrtmp2_server_create returned null");
142 unsafe {
143 assert_eq!(server.as_ref().config.max_connections, -1);
147 lrtmp2_server_destroy(server.as_ptr());
148 }
149 }
150
151 #[test]
152 fn server_new_preserves_zero_max_connections_for_rust_api() {
153 let config = ServerConfig {
154 max_connections: 0,
155 chunk_size: 128,
156 tls_enabled: 0,
157 tls_cert_file: std::ptr::null(),
158 tls_key_file: std::ptr::null(),
159 tls_ca_file: std::ptr::null(),
160 tls_insecure: 0,
161 };
162 let server = Server::new(config).unwrap();
163 assert_eq!(server.config.max_connections, 0);
164 }
165}
166
167use std::ffi::c_int;
168
169const DEFAULT_FFI_MAX_CONNECTIONS: c_int = 256;
176
177#[unsafe(no_mangle)]
179pub unsafe extern "C" fn lrtmp2_server_create(config: *const ServerConfig) -> *mut server::Server {
180 if config.is_null() {
181 return std::ptr::null_mut();
182 }
183 let mut cfg = unsafe { *config };
184 if cfg.max_connections == 0 {
185 cfg.max_connections = DEFAULT_FFI_MAX_CONNECTIONS;
186 }
187 match server::Server::new(cfg) {
188 Ok(s) => Box::into_raw(Box::new(s)),
189 Err(_) => std::ptr::null_mut(),
190 }
191}
192
193#[unsafe(no_mangle)]
195pub unsafe extern "C" fn lrtmp2_server_destroy(server: *mut server::Server) {
196 if !server.is_null() {
197 drop(unsafe { Box::from_raw(server) });
198 }
199}
200
201#[unsafe(no_mangle)]
203pub unsafe extern "C" fn lrtmp2_server_listen(
204 server: *mut server::Server,
205 bind_addr: *const u8,
206) -> i32 {
207 if server.is_null() || bind_addr.is_null() {
208 return ErrorCode::Internal as i32;
209 }
210 let s = unsafe { &mut *server };
211 let addr = unsafe { std::ffi::CStr::from_ptr(bind_addr as *const std::ffi::c_char) };
212 match s.listen(addr.to_str().unwrap_or("")) {
213 Ok(()) => 0,
214 Err(e) => e as i32,
215 }
216}
217
218#[unsafe(no_mangle)]
220pub unsafe extern "C" fn lrtmp2_server_poll(server: *mut server::Server, timeout_ms: i32) -> i32 {
221 if server.is_null() {
222 return ErrorCode::Internal as i32;
223 }
224 let s = unsafe { &mut *server };
225 match s.poll(timeout_ms) {
226 Ok(()) => 0,
227 Err(e) => e as i32,
228 }
229}
230
231#[unsafe(no_mangle)]
233pub unsafe extern "C" fn lrtmp2_server_stop(server: *mut server::Server) {
234 if !server.is_null() {
235 unsafe { (*server).stop() };
236 }
237}
238
239#[unsafe(no_mangle)]
247pub unsafe extern "C" fn lrtmp2_client_create(config: *const ServerConfig) -> *mut client::Client {
248 let mut c = client::Client::new();
249 if !config.is_null() {
250 let cfg = unsafe { &*config };
251 let ca_file = if cfg.tls_ca_file.is_null() {
252 None
253 } else {
254 match unsafe { std::ffi::CStr::from_ptr(cfg.tls_ca_file as *const std::ffi::c_char) }
255 .to_str()
256 {
257 Ok(s) => Some(s.to_string()),
258 Err(_) => return std::ptr::null_mut(),
259 }
260 };
261 c.set_tls_client_config(ca_file, cfg.tls_insecure != 0);
262 }
263 Box::into_raw(Box::new(c))
264}
265
266#[unsafe(no_mangle)]
268pub unsafe extern "C" fn lrtmp2_client_destroy(c: *mut client::Client) {
269 if !c.is_null() {
270 drop(unsafe { Box::from_raw(c) });
271 }
272}
273
274#[unsafe(no_mangle)]
276pub unsafe extern "C" fn lrtmp2_client_connect(c: *mut client::Client, url: *const u8) -> i32 {
277 if c.is_null() || url.is_null() {
278 return ErrorCode::Internal as i32;
279 }
280 let client = unsafe { &mut *c };
281 let url_str = unsafe { std::ffi::CStr::from_ptr(url as *const std::ffi::c_char) };
282 match client.connect(url_str.to_str().unwrap_or("")) {
283 Ok(()) => 0,
284 Err(e) => e as i32,
285 }
286}
287
288#[unsafe(no_mangle)]
290pub unsafe extern "C" fn lrtmp2_client_publish(c: *mut client::Client) -> i32 {
291 if c.is_null() {
292 return ErrorCode::Internal as i32;
293 }
294 match unsafe { (*c).publish() } {
295 Ok(()) => 0,
296 Err(e) => e as i32,
297 }
298}
299
300#[unsafe(no_mangle)]
302pub unsafe extern "C" fn lrtmp2_client_play(c: *mut client::Client) -> i32 {
303 if c.is_null() {
304 return ErrorCode::Internal as i32;
305 }
306 match unsafe { (*c).play() } {
307 Ok(()) => 0,
308 Err(e) => e as i32,
309 }
310}
311
312#[unsafe(no_mangle)]
314pub unsafe extern "C" fn lrtmp2_client_send_frame(
315 c: *mut client::Client,
316 frame: *const Frame,
317) -> i32 {
318 if c.is_null() || frame.is_null() {
319 return ErrorCode::Internal as i32;
320 }
321 let frame_ref = unsafe { &*frame };
322 if unsafe { (*c).state } != client::ClientState::Publishing {
323 return ErrorCode::Protocol as i32;
324 }
325 if frame_ref.size > 0 && frame_ref.data.is_null() {
326 return ErrorCode::Internal as i32;
327 }
328 if frame_ref.size as usize > client::MAX_CLIENT_FRAME_BYTES {
329 return ErrorCode::Protocol as i32;
330 }
331 let payload = if frame_ref.size == 0 || frame_ref.data.is_null() {
332 Vec::new()
333 } else {
334 unsafe {
335 std::slice::from_raw_parts(frame_ref.data, frame_ref.size as usize).to_vec()
336 }
337 };
338 match unsafe {
339 (*c).send_frame_payload(
340 frame_ref.frame_type,
341 frame_ref.timestamp,
342 &payload,
343 )
344 } {
345 Ok(()) => 0,
346 Err(e) => e as i32,
347 }
348}
349
350#[unsafe(no_mangle)]
352pub unsafe extern "C" fn lrtmp2_client_poll(c: *mut client::Client, timeout_ms: i32) -> i32 {
353 if c.is_null() {
354 return ErrorCode::Internal as i32;
355 }
356 match unsafe { (*c).poll(timeout_ms) } {
357 Ok(()) => 0,
358 Err(e) => e as i32,
359 }
360}
361
362#[unsafe(no_mangle)]
364pub unsafe extern "C" fn lrtmp2_conn_get_fd(_conn: *const session::conn::Conn) -> i32 {
365 -1
366}
367
368#[unsafe(no_mangle)]
370pub extern "C" fn lrtmp2_tls_supported() -> i32 {
371 if tls_supported() {
372 1
373 } else {
374 0
375 }
376}
377
378#[unsafe(no_mangle)]
380pub extern "C" fn lrtmp2_version_string() -> *const u8 {
381 concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr()
382}
383
384#[unsafe(no_mangle)]
386pub extern "C" fn lrtmp2_version_major() -> i32 {
387 version_major()
388}
389
390#[unsafe(no_mangle)]
392pub extern "C" fn lrtmp2_version_minor() -> i32 {
393 version_minor()
394}
395
396#[unsafe(no_mangle)]
398pub extern "C" fn lrtmp2_version_patch() -> i32 {
399 version_patch()
400}
401
402#[unsafe(no_mangle)]
409pub unsafe extern "C" fn lrtmp2_error_string(code: i32) -> *const u8 {
410 error_c_string(error_code_from_raw(code)).as_ptr()
411}