use std::ffi::CString;
use std::marker::PhantomData;
pub use librecast_sys;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Librecast initialization failed: {0}")]
InitializationError(String),
#[error("Invalid configuration: {0}")]
ConfigurationError(String),
#[error("Operation failed: {0}")]
OperationError(String),
#[error("Null pointer encountered")]
NullPointerError,
#[error("String conversion error")]
StringConversionError,
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct Librecast {
ctx: *mut librecast_sys::lc_ctx_t,
}
impl Librecast {
pub fn new() -> Result<Self> {
let ctx = unsafe { librecast_sys::lc_ctx_new() };
if ctx.is_null() {
return Err(Error::InitializationError(
"Failed to create context".to_string(),
));
}
Ok(Self { ctx })
}
pub fn channel_builder(&self) -> ChannelBuilder {
ChannelBuilder::new(self)
}
}
impl Drop for Librecast {
fn drop(&mut self) {
if !self.ctx.is_null() {
unsafe {
librecast_sys::lc_ctx_free(self.ctx);
}
}
}
}
unsafe impl Send for Librecast {}
pub struct ChannelBuilder<'a> {
context: &'a Librecast,
name: Option<String>,
enable_raptorq: bool, enable_loopback: bool, }
impl<'a> ChannelBuilder<'a> {
fn new(context: &'a Librecast) -> Self {
Self {
context,
name: None,
enable_raptorq: false,
enable_loopback: false,
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn enable_raptorq(mut self) -> Self {
self.enable_raptorq = true;
self
}
pub fn enable_loopback(mut self) -> Self {
self.enable_loopback = true;
self
}
pub fn build(self) -> Result<Channel<'a>> {
let name = self
.name
.ok_or_else(|| Error::ConfigurationError("Channel name is required".to_string()))?;
let name_c = CString::new(name).map_err(|_| Error::StringConversionError)?;
let channel = unsafe { librecast_sys::lc_channel_new(self.context.ctx, name_c.into_raw()) };
if channel.is_null() {
return Err(Error::InitializationError(
"Failed to create a channel".to_string(),
));
}
let socket = unsafe { librecast_sys::lc_socket_new(self.context.ctx) };
if socket.is_null() {
unsafe {
librecast_sys::lc_channel_free(channel);
}
return Err(Error::InitializationError(
"Failed to create a socket".to_string(),
));
}
let bind_rc = unsafe { librecast_sys::lc_channel_bind(socket, channel) };
if bind_rc != 0 {
unsafe {
librecast_sys::lc_channel_free(channel);
}
return Err(Error::OperationError(
"Failed to bind a channel to a socket".to_string(),
));
}
if self.enable_raptorq {
let result = unsafe {
librecast_sys::lc_channel_coding_set(
channel,
(librecast_sys::lc_coding_t_LC_CODE_FEC_RQ
| librecast_sys::lc_coding_t_LC_CODE_FEC_OTI)
.try_into()
.unwrap(),
)
};
if result != 40 {
unsafe {
librecast_sys::lc_channel_free(channel);
}
return Err(Error::ConfigurationError(
"Failed to set channel codec".to_string(),
));
}
}
if self.enable_loopback {
let result = unsafe { librecast_sys::lc_socket_loop(socket, 1) };
if result != 0 {
unsafe {
librecast_sys::lc_channel_free(channel);
}
return Err(Error::ConfigurationError(
"Failed to enable loopback mode".to_string(),
));
}
}
Ok(Channel {
channel,
_context: PhantomData, })
}
}
pub struct Channel<'a> {
channel: *mut librecast_sys::lc_channel_t,
_context: PhantomData<&'a Librecast>,
}
impl<'a> Channel<'a> {
pub fn join(&self) -> Result<()> {
let result = unsafe { librecast_sys::lc_channel_join(self.channel) };
if result != 0 {
return Err(Error::OperationError(
"Failed to join a channel".to_string(),
));
}
Ok(())
}
pub fn leave(&self) -> Result<()> {
let result = unsafe { librecast_sys::lc_channel_part(self.channel) };
if result != 0 {
return Err(Error::OperationError(
"Failed to leave a channel".to_string(),
));
}
Ok(())
}
pub fn rate_limit(&self, limit: u32) -> Result<()> {
unsafe { librecast_sys::lc_channel_ratelimit(self.channel, limit as usize, 0) };
Ok(())
}
pub fn send(&self, data: &[u8]) -> Result<()> {
if data.is_empty() {
return Err(Error::OperationError("Data cannot be empty".to_string()));
}
let result = unsafe {
librecast_sys::lc_channel_send(
self.channel,
data.as_ptr() as *const libc::c_void,
data.len(),
0,
)
};
if result == -1 {
return Err(Error::OperationError(
"Failed to send data on a channel".to_string(),
));
}
Ok(())
}
pub fn receive(&self, buffer: &mut [u8]) -> Result<usize> {
if buffer.is_empty() {
return Err(Error::OperationError("Buffer cannot be empty".to_string()));
}
let result = unsafe {
librecast_sys::lc_channel_recv(
self.channel,
buffer.as_mut_ptr() as *mut libc::c_void,
buffer.len(),
0,
)
};
if result == -1 {
return Err(Error::OperationError(
"Failed to receive data on a channel".to_string(),
));
}
Ok(result as usize)
}
}
impl<'a> Drop for Channel<'a> {
fn drop(&mut self) {
if !self.channel.is_null() {
unsafe {
librecast_sys::lc_channel_free(self.channel);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let librecast = Librecast::new().expect("Failed to create test context");
assert!(!librecast.ctx.is_null(), "Context should not be null");
let channel = librecast
.channel_builder()
.name("test_channel")
.enable_loopback() .build()
.expect("Failed to build a test channel");
assert!(!channel.channel.is_null(), "Channel should not be null");
channel.join().expect("Failed to join a test channel");
channel
.rate_limit(104857600) .expect("Failed to set a rate limit for our test channel");
let data = b"Hello, Librecast!";
channel
.send(data)
.expect("Failed to send data on the test channel");
let mut buffer = vec![0u8; 1024]; let received_size = channel
.receive(&mut buffer)
.expect("Failed to receive data on the test channel");
assert!(received_size > 0, "Received size should be greater than 0");
channel.leave().expect("Failed to leave a test channel");
}
}