Skip to main content

ggwave_rs/
lib.rs

1pub mod ffi;
2
3use libc::{c_int, c_void};
4use std::marker::PhantomData;
5use std::rc::Rc;
6
7pub use ffi::{
8    ggwave_Parameters as Parameters, ggwave_ProtocolId as ProtocolId,
9    ggwave_SampleFormat as SampleFormat, GGWAVE_OPERATING_MODE_RX,
10    GGWAVE_OPERATING_MODE_RX_AND_TX, GGWAVE_OPERATING_MODE_TX,
11    GGWAVE_OPERATING_MODE_TX_ONLY_TONES, GGWAVE_OPERATING_MODE_USE_DSS,
12};
13
14pub const MAX_DATA_SIZE: usize = 256;
15
16#[derive(Debug)]
17pub enum Error {
18    InitFailed,
19    EncodeFailed,
20    DecodeFailed,
21    BufferTooSmall,
22    InvalidInput(&'static str),
23}
24
25impl std::fmt::Display for Error {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            Error::InitFailed => write!(f, "failed to initialize ggwave instance"),
29            Error::EncodeFailed => write!(f, "failed to encode payload"),
30            Error::DecodeFailed => write!(f, "failed to decode waveform"),
31            Error::BufferTooSmall => write!(f, "payload buffer too small"),
32            Error::InvalidInput(msg) => write!(f, "{msg}"),
33        }
34    }
35}
36
37impl std::error::Error for Error {}
38
39/// A ggwave encoder/decoder instance.
40///
41/// # Thread Safety
42///
43/// `GgWave` is intentionally `!Send` and `!Sync`. The upstream C library stores
44/// all instances in a global array (`g_instances[GGWAVE_MAX_INSTANCES]`) without
45/// any synchronization. Concurrent access from multiple threads would cause data
46/// races and undefined behavior. Create and use each instance on a single thread.
47pub struct GgWave {
48    instance: ffi::ggwave_Instance,
49    parameters: Parameters,
50    // PhantomData<Rc<()>> makes this type !Send and !Sync.
51    _not_send_sync: PhantomData<Rc<()>>,
52}
53
54impl GgWave {
55    pub fn new(parameters: Parameters) -> Result<Self, Error> {
56        let instance = unsafe { ffi::ggwave_init(parameters) };
57        if instance < 0 {
58            return Err(Error::InitFailed);
59        }
60
61        Ok(Self {
62            instance,
63            parameters,
64            _not_send_sync: PhantomData,
65        })
66    }
67
68    pub fn parameters(&self) -> &Parameters {
69        &self.parameters
70    }
71
72    pub fn encode(
73        &self,
74        payload: &[u8],
75        protocol: ProtocolId,
76        volume: i32,
77    ) -> Result<Vec<u8>, Error> {
78        if !(0..=100).contains(&volume) {
79            return Err(Error::InvalidInput("volume must be between 0 and 100"));
80        }
81
82        let payload_len = to_c_int(payload.len(), "payload too large")?;
83        let size = unsafe {
84            ffi::ggwave_encode(
85                self.instance,
86                payload.as_ptr() as *const c_void,
87                payload_len,
88                protocol,
89                volume as c_int,
90                std::ptr::null_mut(),
91                1,
92            )
93        };
94
95        if size <= 0 {
96            return Err(Error::EncodeFailed);
97        }
98
99        let mut waveform = vec![0u8; size as usize];
100        let written = unsafe {
101            ffi::ggwave_encode(
102                self.instance,
103                payload.as_ptr() as *const c_void,
104                payload_len,
105                protocol,
106                volume as c_int,
107                waveform.as_mut_ptr() as *mut c_void,
108                0,
109            )
110        };
111
112        if written <= 0 {
113            return Err(Error::EncodeFailed);
114        }
115
116        waveform.truncate(written as usize);
117        Ok(waveform)
118    }
119
120    pub fn decode(&self, waveform: &[u8]) -> Result<Option<Vec<u8>>, Error> {
121        let waveform_len = to_c_int(waveform.len(), "waveform too large")?;
122        let mut payload = vec![0u8; MAX_DATA_SIZE];
123        let decoded = unsafe {
124            ffi::ggwave_ndecode(
125                self.instance,
126                waveform.as_ptr() as *const c_void,
127                waveform_len,
128                payload.as_mut_ptr() as *mut c_void,
129                payload.len() as c_int,
130            )
131        };
132
133        match decoded {
134            0 => Ok(None),
135            -1 => Err(Error::DecodeFailed),
136            -2 => Err(Error::BufferTooSmall),
137            n if n > 0 => {
138                payload.truncate(n as usize);
139                Ok(Some(payload))
140            }
141            _ => Err(Error::DecodeFailed),
142        }
143    }
144
145    pub fn rx_duration_frames(&self) -> i32 {
146        unsafe { ffi::ggwave_rxDurationFrames(self.instance) }
147    }
148}
149
150impl Drop for GgWave {
151    fn drop(&mut self) {
152        unsafe { ffi::ggwave_free(self.instance) };
153    }
154}
155
156pub fn default_parameters() -> Parameters {
157    unsafe { ffi::ggwave_getDefaultParameters() }
158}
159
160pub fn set_rx_protocol_enabled(protocol: ProtocolId, enabled: bool) {
161    unsafe { ffi::ggwave_rxToggleProtocol(protocol, if enabled { 1 } else { 0 }) };
162}
163
164pub fn set_tx_protocol_enabled(protocol: ProtocolId, enabled: bool) {
165    unsafe { ffi::ggwave_txToggleProtocol(protocol, if enabled { 1 } else { 0 }) };
166}
167
168fn to_c_int(value: usize, context: &'static str) -> Result<c_int, Error> {
169    c_int::try_from(value).map_err(|_| Error::InvalidInput(context))
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn roundtrip_encode_decode() {
178        let params = default_parameters();
179        let tx = GgWave::new(params).expect("tx init failed");
180        let waveform = tx
181            .encode(
182                b"ping",
183                ProtocolId::GGWAVE_PROTOCOL_AUDIBLE_FAST,
184                25,
185            )
186            .expect("encode failed");
187
188        let rx = GgWave::new(params).expect("rx init failed");
189        let decoded = rx.decode(&waveform).expect("decode failed");
190        let decoded = decoded.expect("no payload decoded");
191        assert_eq!(decoded, b"ping");
192    }
193}
194