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
39pub struct GgWave {
48 instance: ffi::ggwave_Instance,
49 parameters: Parameters,
50 _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