Skip to main content

hackrf_nusb/
high_level.rs

1//! High-level owned HackRF device and receive stream.
2
3use std::future::{Future, IntoFuture};
4use std::pin::Pin;
5use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
6use std::task::{Context, Poll};
7use std::time::Duration;
8
9use nusb::MaybeFuture;
10
11use crate::Complex32;
12use crate::config::{
13    Config, ConfigBuilder, validate_frequency, validate_lna_gain, validate_sample_rate,
14    validate_vga_gain,
15};
16use crate::device::{HackRf, shutdown_hardware};
17use crate::errors::{Error, Result};
18use crate::maybe_future::{MaybeFutureExt, ready};
19use crate::streaming::{AsyncDirectRxStream, AsyncStreamingBackend, StreamingStats};
20#[cfg(not(target_arch = "wasm32"))]
21use crate::streaming::{DirectRxStream, StreamingBackend};
22use crate::types::DeviceInfo;
23use crate::usb::{ControlBackend, NusbControl};
24
25#[cfg(not(target_arch = "wasm32"))]
26type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
27#[cfg(target_arch = "wasm32")]
28type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31enum DeviceLifecycle {
32    Open,
33    Closing,
34    DropCleanupPending,
35    Closed,
36}
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39enum ReceiverSlot {
40    Idle,
41    Held,
42    Orphaned,
43}
44
45#[derive(Debug)]
46struct SharedDeviceState {
47    device: DeviceLifecycle,
48    receiver: ReceiverSlot,
49    stream_claimed: bool,
50    bias_tee_enabled: bool,
51}
52
53impl SharedDeviceState {
54    fn new(bias_tee_enabled: bool) -> Self {
55        Self {
56            device: DeviceLifecycle::Open,
57            receiver: ReceiverSlot::Idle,
58            stream_claimed: false,
59            bias_tee_enabled,
60        }
61    }
62}
63
64type SharedState = Arc<Mutex<SharedDeviceState>>;
65
66fn lock_shared(state: &SharedState) -> MutexGuard<'_, SharedDeviceState> {
67    state.lock().unwrap_or_else(PoisonError::into_inner)
68}
69
70fn ensure_device_open(state: &SharedState) -> Result<()> {
71    if lock_shared(state).device == DeviceLifecycle::Open {
72        Ok(())
73    } else {
74        Err(Error::DeviceClosed)
75    }
76}
77
78fn desired_bias_tee(state: &SharedState) -> bool {
79    lock_shared(state).bias_tee_enabled
80}
81
82fn begin_shutdown(state: &SharedState) -> Result<bool> {
83    let mut shared = lock_shared(state);
84    match shared.device {
85        DeviceLifecycle::Closed | DeviceLifecycle::DropCleanupPending => Ok(false),
86        DeviceLifecycle::Closing => Ok(true),
87        DeviceLifecycle::Open => {
88            if shared.receiver == ReceiverSlot::Held {
89                return Err(Error::Busy);
90            }
91            shared.device = DeviceLifecycle::Closing;
92            Ok(true)
93        }
94    }
95}
96
97fn complete_shutdown(state: &SharedState, result: Result<()>) -> Result<()> {
98    if result.is_ok() {
99        let mut shared = lock_shared(state);
100        shared.device = DeviceLifecycle::Closed;
101        shared.receiver = ReceiverSlot::Idle;
102    }
103    result
104}
105
106#[derive(Clone, Copy, Debug, Eq, PartialEq)]
107enum DeviceDropAction {
108    None,
109    Immediate,
110    Deferred,
111}
112
113fn begin_device_drop(state: &SharedState) -> DeviceDropAction {
114    let mut shared = lock_shared(state);
115    match shared.device {
116        DeviceLifecycle::Closed => DeviceDropAction::None,
117        DeviceLifecycle::DropCleanupPending => DeviceDropAction::Deferred,
118        DeviceLifecycle::Open | DeviceLifecycle::Closing => {
119            if shared.receiver == ReceiverSlot::Held {
120                shared.device = DeviceLifecycle::DropCleanupPending;
121                DeviceDropAction::Deferred
122            } else {
123                shared.device = DeviceLifecycle::Closed;
124                shared.receiver = ReceiverSlot::Idle;
125                DeviceDropAction::Immediate
126            }
127        }
128    }
129}
130
131#[derive(Debug)]
132struct DeviceInner<C: ControlBackend = NusbControl> {
133    direct: HackRf<C>,
134    info: DeviceInfo,
135    #[cfg(not(target_arch = "wasm32"))]
136    shutdown_on_drop: bool,
137}
138
139impl<C: ControlBackend> DeviceInner<C> {
140    fn stream_handle(&self) -> Self {
141        Self {
142            direct: self.direct.stream_handle(),
143            info: self.info.clone(),
144            #[cfg(not(target_arch = "wasm32"))]
145            shutdown_on_drop: false,
146        }
147    }
148}
149
150#[cfg(not(target_arch = "wasm32"))]
151impl<C: ControlBackend> Drop for DeviceInner<C> {
152    fn drop(&mut self) {
153        if self.shutdown_on_drop {
154            let _ = shutdown_hardware(&self.direct).wait();
155        }
156    }
157}
158
159/// High-level owned HackRF device handle.
160#[derive(Debug)]
161pub struct Device {
162    inner: DeviceInner<NusbControl>,
163    config: Config,
164    shared: SharedState,
165}
166
167impl Device {
168    /// List visible normal-mode HackRF devices without claiming them.
169    pub fn list() -> impl MaybeFuture<Output = Result<Vec<crate::DeviceDescriptor>>> {
170        crate::discovery::list_devices()
171    }
172
173    /// Start building and opening a HackRF.
174    pub fn builder() -> DeviceBuilder {
175        DeviceBuilder::default()
176    }
177
178    /// Open and configure the first visible HackRF.
179    pub fn open() -> impl MaybeFuture<Output = Result<Self>> {
180        Self::builder().open()
181    }
182
183    /// Open and configure a HackRF by its exact 128-bit serial.
184    pub fn open_serial(serial: u128) -> impl MaybeFuture<Output = Result<Self>> {
185        Self::builder().serial(serial).open()
186    }
187
188    /// Ask a browser user to grant WebUSB access to any supported HackRF.
189    ///
190    /// This must be called from a browser-window user gesture.
191    #[cfg(target_arch = "wasm32")]
192    pub async fn request_permission() -> Result<()> {
193        Self::builder().request_permission().await
194    }
195
196    /// Return metadata collected while opening the device.
197    pub fn info(&self) -> &DeviceInfo {
198        &self.inner.info
199    }
200
201    /// Return the configuration last successfully applied by this driver.
202    pub fn config(&self) -> &Config {
203        &self.config
204    }
205
206    /// Apply a complete validated RX configuration.
207    pub fn configure<'a>(
208        &'a mut self,
209        config: &'a Config,
210    ) -> impl MaybeFuture<Output = Result<()>> + 'a {
211        let lifecycle = ensure_device_open(&self.shared);
212        let applied = config.clone();
213        let active = &mut self.config;
214        let shared = Arc::clone(&self.shared);
215        let operation = self.inner.direct.configure(config);
216        ready(lifecycle)
217            .and_then(move |()| operation)
218            .map(move |result| {
219                if result.is_ok() {
220                    *active = applied;
221                    lock_shared(&shared).bias_tee_enabled = active.bias_tee_enabled();
222                }
223                result
224            })
225    }
226
227    /// Change the center frequency while stopped or receiving.
228    pub fn set_frequency_hz(
229        &mut self,
230        frequency_hz: u64,
231    ) -> impl MaybeFuture<Output = Result<()>> + '_ {
232        let validation =
233            ensure_device_open(&self.shared).and_then(|()| validate_frequency(frequency_hz));
234        let config = &mut self.config;
235        let operation = self.inner.direct.set_frequency(frequency_hz);
236        ready(validation)
237            .and_then(move |()| operation)
238            .map(move |result| {
239                if result.is_ok() {
240                    config.set_frequency_hz_internal(frequency_hz);
241                }
242                result
243            })
244    }
245
246    /// Change the complex sample rate while stopped or receiving.
247    ///
248    /// The driver also requests a baseband filter bandwidth equal to this rate.
249    pub fn set_sample_rate_hz(
250        &mut self,
251        sample_rate_hz: u32,
252    ) -> impl MaybeFuture<Output = Result<()>> + '_ {
253        let validation =
254            ensure_device_open(&self.shared).and_then(|()| validate_sample_rate(sample_rate_hz));
255        let config = &mut self.config;
256        let operation = self.inner.direct.set_sample_rate(sample_rate_hz);
257        ready(validation)
258            .and_then(move |()| operation)
259            .map(move |result| {
260                if result.is_ok() {
261                    config.set_sample_rate_hz_internal(sample_rate_hz);
262                }
263                result
264            })
265    }
266
267    /// Set RX IF/LNA gain in dB.
268    pub fn set_lna_gain_db(&mut self, gain_db: u8) -> impl MaybeFuture<Output = Result<()>> + '_ {
269        let validation = ensure_device_open(&self.shared).and_then(|()| validate_lna_gain(gain_db));
270        let config = &mut self.config;
271        let operation = self.inner.direct.set_lna_gain(gain_db);
272        ready(validation)
273            .and_then(move |()| operation)
274            .map(move |result| {
275                if result.is_ok() {
276                    config.set_lna_gain_db_internal(gain_db);
277                }
278                result
279            })
280    }
281
282    /// Set baseband/VGA gain in dB.
283    pub fn set_vga_gain_db(&mut self, gain_db: u8) -> impl MaybeFuture<Output = Result<()>> + '_ {
284        let validation = ensure_device_open(&self.shared).and_then(|()| validate_vga_gain(gain_db));
285        let config = &mut self.config;
286        let operation = self.inner.direct.set_vga_gain(gain_db);
287        ready(validation)
288            .and_then(move |()| operation)
289            .map(move |result| {
290                if result.is_ok() {
291                    config.set_vga_gain_db_internal(gain_db);
292                }
293                result
294            })
295    }
296
297    /// Enable or disable the RF amplifier.
298    pub fn set_amp_enable(&mut self, enabled: bool) -> impl MaybeFuture<Output = Result<()>> + '_ {
299        let lifecycle = ensure_device_open(&self.shared);
300        let config = &mut self.config;
301        let operation = self.inner.direct.set_amp(enabled);
302        ready(lifecycle)
303            .and_then(move |()| operation)
304            .map(move |result| {
305                if result.is_ok() {
306                    config.set_amp_enabled_internal(enabled);
307                }
308                result
309            })
310    }
311
312    /// Enable or disable antenna-port bias power.
313    ///
314    /// HackRF firmware disables this when reception stops. A later stream start
315    /// automatically reapplies the last successful setting.
316    pub fn set_bias_tee(&mut self, enabled: bool) -> impl MaybeFuture<Output = Result<()>> + '_ {
317        let lifecycle = ensure_device_open(&self.shared);
318        let config = &mut self.config;
319        let shared = Arc::clone(&self.shared);
320        let operation = self.inner.direct.set_bias_tee(enabled);
321        ready(lifecycle)
322            .and_then(move |()| operation)
323            .map(move |result| {
324                if result.is_ok() {
325                    config.set_bias_tee_enabled_internal(enabled);
326                    lock_shared(&shared).bias_tee_enabled = enabled;
327                }
328                result
329            })
330    }
331
332    /// Claim the device's single owned receive stream.
333    ///
334    /// The stream starts dormant. The device remains usable for live setters
335    /// while reception is active. Dropping the stream releases the claim.
336    pub fn rx_stream(&self) -> Result<RxStream> {
337        let claim = RxStreamClaim::acquire(&self.shared)?;
338        Ok(RxStream::new(
339            self.inner.stream_handle(),
340            Arc::clone(&self.shared),
341            claim,
342        ))
343    }
344
345    /// Put the transceiver in off mode and close the logical device session.
346    ///
347    /// Returns [`Error::Busy`] while a receive stream is running. A stopped but
348    /// still-owned stream does not prevent shutdown, but cannot be restarted.
349    #[must_use = "shutdown must be waited or awaited to observe hardware cleanup"]
350    pub fn shutdown(&mut self) -> impl MaybeFuture<Output = Result<()>> + '_ {
351        let decision = begin_shutdown(&self.shared);
352        let shared = Arc::clone(&self.shared);
353        let operation = shutdown_hardware(&self.inner.direct);
354        ready(decision).and_then(move |run| {
355            if run {
356                crate::maybe_future::Either::left(
357                    operation.map(move |result| complete_shutdown(&shared, result)),
358                )
359            } else {
360                crate::maybe_future::Either::right(ready(Ok(())))
361            }
362        })
363    }
364}
365
366impl Drop for Device {
367    fn drop(&mut self) {
368        let action = begin_device_drop(&self.shared);
369        #[cfg(not(target_arch = "wasm32"))]
370        if action != DeviceDropAction::Immediate {
371            self.inner.shutdown_on_drop = false;
372        }
373        #[cfg(target_arch = "wasm32")]
374        if action == DeviceDropAction::Immediate {
375            let direct = self.inner.direct.stream_handle();
376            wasm_bindgen_futures::spawn_local(async move {
377                let _ = shutdown_hardware(&direct).await;
378            });
379        }
380    }
381}
382
383/// Builder for opening and initially configuring a [`Device`].
384#[derive(Clone, Debug, Default)]
385pub struct DeviceBuilder {
386    serial: Option<u128>,
387    config: ConfigBuilder,
388}
389
390impl DeviceBuilder {
391    /// Select a device by its exact full 128-bit serial.
392    pub fn serial(mut self, serial: u128) -> Self {
393        self.serial = Some(serial);
394        self
395    }
396
397    /// Set the initial center frequency.
398    pub fn frequency_hz(mut self, value: u64) -> Self {
399        self.config = self.config.frequency_hz(value);
400        self
401    }
402
403    /// Set the initial complex sample rate.
404    pub fn sample_rate_hz(mut self, value: u32) -> Self {
405        self.config = self.config.sample_rate_hz(value);
406        self
407    }
408
409    /// Set the initial RX IF/LNA gain.
410    pub fn lna_gain_db(mut self, value: u8) -> Self {
411        self.config = self.config.lna_gain_db(value);
412        self
413    }
414
415    /// Set the initial baseband/VGA gain.
416    pub fn vga_gain_db(mut self, value: u8) -> Self {
417        self.config = self.config.vga_gain_db(value);
418        self
419    }
420
421    /// Set the initial RF amplifier state.
422    pub fn amp_enable(mut self, enabled: bool) -> Self {
423        self.config = self.config.amp_enable(enabled);
424        self
425    }
426
427    /// Set the initial antenna-port bias-power state.
428    pub fn bias_tee(mut self, enabled: bool) -> Self {
429        self.config = self.config.bias_tee(enabled);
430        self
431    }
432
433    /// Open the selected device, query metadata, and apply the configuration.
434    pub fn open(self) -> impl MaybeFuture<Output = Result<Device>> {
435        let config = self.config.build();
436        let serial = self.serial;
437        ready(config).and_then(move |config| {
438            HackRf::open(serial).and_then(move |(direct, usb_api_version)| {
439                let info = direct.fetch_device_info(usb_api_version);
440                info.and_then(move |info| {
441                    let applied = config.clone();
442                    direct.configure(&config).map(move |result| {
443                        result?;
444                        Ok(Device {
445                            inner: DeviceInner {
446                                direct,
447                                info,
448                                #[cfg(not(target_arch = "wasm32"))]
449                                shutdown_on_drop: true,
450                            },
451                            shared: Arc::new(Mutex::new(SharedDeviceState::new(
452                                applied.bias_tee_enabled(),
453                            ))),
454                            config: applied,
455                        })
456                    })
457                })
458            })
459        })
460    }
461
462    /// Ask a browser user to grant WebUSB access to this builder's selector.
463    #[cfg(target_arch = "wasm32")]
464    pub async fn request_permission(&self) -> Result<()> {
465        crate::discovery::request_device_permission(self.serial).await
466    }
467}
468
469#[derive(Clone, Copy, Debug, Eq, PartialEq)]
470enum ReceiverState {
471    Stopped,
472    CleanupRequired,
473    Running,
474}
475
476#[cfg(not(target_arch = "wasm32"))]
477struct BlockingRxInner<C: ControlBackend + StreamingBackend> {
478    device: DeviceInner<C>,
479    stream: Option<DirectRxStream<C::BulkIn>>,
480    stats: StreamingStats,
481    state: ReceiverState,
482}
483
484#[cfg(not(target_arch = "wasm32"))]
485impl<C> BlockingRxInner<C>
486where
487    C: ControlBackend + StreamingBackend,
488{
489    fn new(device: DeviceInner<C>) -> Self {
490        Self {
491            device,
492            stream: None,
493            stats: StreamingStats::default(),
494            state: ReceiverState::Stopped,
495        }
496    }
497
498    fn start(&mut self, bias_tee: bool) -> Result<()> {
499        match self.state {
500            ReceiverState::Running => return Ok(()),
501            ReceiverState::CleanupRequired => return Err(Error::Busy),
502            ReceiverState::Stopped => {}
503        }
504        self.state = ReceiverState::CleanupRequired;
505        self.stream = Some(self.device.direct.start_rx_blocking(bias_tee)?);
506        self.state = ReceiverState::Running;
507        Ok(())
508    }
509
510    fn read(&mut self, out: &mut [Complex32], timeout: Duration) -> Result<usize> {
511        if self.state != ReceiverState::Running {
512            return Err(Error::stream_closed("RX stream is stopped"));
513        }
514        let result = self
515            .stream
516            .as_mut()
517            .ok_or(Error::stream_closed("RX stream has no USB queue"))?
518            .read_complex(out, timeout);
519        if result.is_err() {
520            self.state = ReceiverState::CleanupRequired;
521        }
522        result
523    }
524
525    fn stop(&mut self) -> Result<StreamingStats> {
526        if self.state == ReceiverState::Stopped {
527            return Ok(self.stats);
528        }
529        let result = if let Some(stream) = self.stream.take() {
530            let (stats, result) = self.device.direct.stop_rx_blocking(stream);
531            self.stats.accumulate(stats);
532            result
533        } else {
534            shutdown_hardware(&self.device.direct).wait()
535        };
536        match result {
537            Ok(()) => {
538                self.state = ReceiverState::Stopped;
539                Ok(self.stats)
540            }
541            Err(error) => {
542                self.state = ReceiverState::CleanupRequired;
543                Err(error)
544            }
545        }
546    }
547}
548
549struct AsyncRxInner<C: ControlBackend + AsyncStreamingBackend> {
550    device: DeviceInner<C>,
551    stream: Option<AsyncDirectRxStream<C::BulkIn>>,
552    stats: StreamingStats,
553    state: ReceiverState,
554}
555
556impl<C> AsyncRxInner<C>
557where
558    C: ControlBackend + AsyncStreamingBackend,
559{
560    fn new(device: DeviceInner<C>) -> Self {
561        Self {
562            device,
563            stream: None,
564            stats: StreamingStats::default(),
565            state: ReceiverState::Stopped,
566        }
567    }
568
569    async fn start(&mut self, bias_tee: bool) -> Result<()> {
570        match self.state {
571            ReceiverState::Running => return Ok(()),
572            ReceiverState::CleanupRequired => return Err(Error::Busy),
573            ReceiverState::Stopped => {}
574        }
575        self.state = ReceiverState::CleanupRequired;
576        if self.stream.is_some() {
577            self.device.direct.restart_rx_async(bias_tee).await?;
578        } else {
579            self.stream = Some(self.device.direct.start_rx_async(bias_tee).await?);
580        }
581        self.state = ReceiverState::Running;
582        Ok(())
583    }
584
585    fn poll_read(&mut self, out: &mut [Complex32], cx: &mut Context<'_>) -> Poll<Result<usize>> {
586        if self.state != ReceiverState::Running {
587            return Poll::Ready(Err(Error::stream_closed("async RX stream is stopped")));
588        }
589        let result = match self.stream.as_mut() {
590            Some(stream) => stream.poll_read_complex(out, cx),
591            None => Poll::Ready(Err(Error::stream_closed(
592                "async RX stream has no USB queue",
593            ))),
594        };
595        if matches!(result, Poll::Ready(Err(_))) {
596            self.state = ReceiverState::CleanupRequired;
597        }
598        result
599    }
600
601    async fn stop(&mut self) -> Result<StreamingStats> {
602        if self.state == ReceiverState::Stopped {
603            return Ok(self.current_stats());
604        }
605        if let Some(stream) = self.stream.as_mut() {
606            self.device.direct.stop_rx_async(stream).await?;
607        } else {
608            shutdown_hardware(&self.device.direct).await?;
609        }
610        if self
611            .stream
612            .as_ref()
613            .is_some_and(AsyncDirectRxStream::is_closed)
614        {
615            let mut stream = self.stream.take().expect("closed stream checked");
616            self.stats.accumulate(stream.close());
617        }
618        self.state = ReceiverState::Stopped;
619        Ok(self.current_stats())
620    }
621
622    #[cfg(not(target_arch = "wasm32"))]
623    fn stop_on_drop(&mut self) -> Result<StreamingStats> {
624        if self.state == ReceiverState::Stopped {
625            return Ok(self.current_stats());
626        }
627        shutdown_hardware(&self.device.direct).wait()?;
628        if let Some(stream) = self.stream.as_mut() {
629            stream.pause()?;
630        }
631        self.state = ReceiverState::Stopped;
632        Ok(self.current_stats())
633    }
634
635    fn current_stats(&self) -> StreamingStats {
636        self.stream
637            .as_ref()
638            .map_or(self.stats, |stream| self.stats.combined(stream.stats()))
639    }
640}
641
642enum RxStreamState {
643    Dormant(DeviceInner<NusbControl>),
644    Poisoned,
645    #[cfg(not(target_arch = "wasm32"))]
646    Blocking(BlockingRxInner<NusbControl>),
647    Async(AsyncRxInner<NusbControl>),
648}
649
650#[derive(Debug)]
651struct RxStreamClaim {
652    shared: SharedState,
653}
654
655impl RxStreamClaim {
656    fn acquire(shared: &SharedState) -> Result<Self> {
657        let mut state = lock_shared(shared);
658        if state.device != DeviceLifecycle::Open {
659            return Err(Error::DeviceClosed);
660        }
661        if state.stream_claimed || state.receiver != ReceiverSlot::Idle {
662            return Err(Error::Busy);
663        }
664        state.stream_claimed = true;
665        Ok(Self {
666            shared: Arc::clone(shared),
667        })
668    }
669}
670
671impl Drop for RxStreamClaim {
672    fn drop(&mut self) {
673        lock_shared(&self.shared).stream_claimed = false;
674    }
675}
676
677#[derive(Debug)]
678struct ReceiverLease {
679    shared: SharedState,
680    armed: bool,
681}
682
683impl ReceiverLease {
684    fn acquire(shared: &SharedState) -> Result<Self> {
685        let mut state = lock_shared(shared);
686        if state.device != DeviceLifecycle::Open {
687            return Err(Error::DeviceClosed);
688        }
689        if state.receiver != ReceiverSlot::Idle {
690            return Err(Error::Busy);
691        }
692        state.receiver = ReceiverSlot::Held;
693        Ok(Self {
694            shared: Arc::clone(shared),
695            armed: true,
696        })
697    }
698
699    fn release(mut self) {
700        let mut state = lock_shared(&self.shared);
701        if state.receiver == ReceiverSlot::Held {
702            state.receiver = ReceiverSlot::Idle;
703        }
704        if state.device == DeviceLifecycle::DropCleanupPending {
705            state.device = DeviceLifecycle::Closed;
706        }
707        self.armed = false;
708    }
709}
710
711impl Drop for ReceiverLease {
712    fn drop(&mut self) {
713        if self.armed {
714            let mut state = lock_shared(&self.shared);
715            if state.receiver == ReceiverSlot::Held {
716                state.receiver = ReceiverSlot::Orphaned;
717            }
718        }
719    }
720}
721
722/// Owned HackRF receive stream.
723///
724/// Waiting the first [`RxStream::start`] chooses native blocking USB; awaiting
725/// it chooses asynchronous USB. Do not mix the two execution styles on one
726/// stream. The steady-state read path owns its endpoint and never takes the
727/// device lifecycle mutex.
728#[must_use = "RX streams retain the device's exclusive stream claim until dropped"]
729pub struct RxStream {
730    state: RxStreamState,
731    shared: SharedState,
732    receiver: Option<ReceiverLease>,
733    _claim: RxStreamClaim,
734}
735
736impl RxStream {
737    fn new(device: DeviceInner<NusbControl>, shared: SharedState, claim: RxStreamClaim) -> Self {
738        Self {
739            state: RxStreamState::Dormant(device),
740            shared,
741            receiver: None,
742            _claim: claim,
743        }
744    }
745
746    /// Start reception and the persistent USB transfer queue.
747    pub fn start(&mut self) -> impl MaybeFuture<Output = Result<()>> + '_ {
748        StartOperation { stream: self }
749    }
750
751    /// Read normalized complex IQ samples.
752    ///
753    /// Blocking operation fills the output until its total timeout expires;
754    /// `None` waits indefinitely. Awaited operation ignores the timeout and
755    /// consumes buffered data or one new USB completion.
756    pub fn read<'a>(
757        &'a mut self,
758        out: &'a mut [Complex32],
759        timeout: Option<Duration>,
760    ) -> impl MaybeFuture<Output = Result<usize>> + 'a {
761        ReadOperation {
762            stream: self,
763            out,
764            timeout,
765        }
766    }
767
768    /// Stop reception and return accumulated stream counters.
769    pub fn stop(&mut self) -> impl MaybeFuture<Output = Result<StreamingStats>> + '_ {
770        StopOperation { stream: self }
771    }
772
773    fn receiver_state(&self) -> ReceiverState {
774        match &self.state {
775            RxStreamState::Dormant(_) => ReceiverState::Stopped,
776            RxStreamState::Poisoned => ReceiverState::CleanupRequired,
777            #[cfg(not(target_arch = "wasm32"))]
778            RxStreamState::Blocking(stream) => stream.state,
779            RxStreamState::Async(stream) => stream.state,
780        }
781    }
782
783    fn begin_start(&mut self) -> Result<bool> {
784        ensure_device_open(&self.shared)?;
785        match (self.receiver.is_some(), self.receiver_state()) {
786            (true, ReceiverState::Running) => Ok(false),
787            (true, ReceiverState::Stopped | ReceiverState::CleanupRequired)
788            | (false, ReceiverState::Running | ReceiverState::CleanupRequired) => Err(Error::Busy),
789            (false, ReceiverState::Stopped) => {
790                self.receiver = Some(ReceiverLease::acquire(&self.shared)?);
791                Ok(true)
792            }
793        }
794    }
795
796    fn finish_stop(&mut self, result: Result<StreamingStats>) -> Result<StreamingStats> {
797        let stats = result?;
798        if let Some(receiver) = self.receiver.take() {
799            receiver.release();
800        }
801        Ok(stats)
802    }
803
804    #[cfg(not(target_arch = "wasm32"))]
805    fn initialize_blocking(&mut self) -> Result<()> {
806        if matches!(self.state, RxStreamState::Blocking(_)) {
807            return Ok(());
808        }
809        if matches!(self.state, RxStreamState::Async(_)) {
810            return Err(Error::Busy);
811        }
812        let state = core::mem::replace(&mut self.state, RxStreamState::Poisoned);
813        match state {
814            RxStreamState::Dormant(device) => {
815                self.state = RxStreamState::Blocking(BlockingRxInner::new(device));
816                Ok(())
817            }
818            other => {
819                self.state = other;
820                Err(Error::stream_closed("RX stream has no device"))
821            }
822        }
823    }
824
825    fn initialize_async(&mut self) -> Result<()> {
826        if matches!(self.state, RxStreamState::Async(_)) {
827            return Ok(());
828        }
829        #[cfg(not(target_arch = "wasm32"))]
830        if matches!(self.state, RxStreamState::Blocking(_)) {
831            return Err(Error::Busy);
832        }
833        let state = core::mem::replace(&mut self.state, RxStreamState::Poisoned);
834        match state {
835            RxStreamState::Dormant(device) => {
836                self.state = RxStreamState::Async(AsyncRxInner::new(device));
837                Ok(())
838            }
839            other => {
840                self.state = other;
841                Err(Error::stream_closed("RX stream has no device"))
842            }
843        }
844    }
845
846    #[cfg(not(target_arch = "wasm32"))]
847    fn start_blocking(&mut self) -> Result<()> {
848        self.initialize_blocking()?;
849        if !self.begin_start()? {
850            return Ok(());
851        }
852        let bias = desired_bias_tee(&self.shared);
853        match &mut self.state {
854            RxStreamState::Blocking(stream) => stream.start(bias),
855            _ => Err(Error::Busy),
856        }
857    }
858
859    async fn start_async(&mut self) -> Result<()> {
860        self.initialize_async()?;
861        if !self.begin_start()? {
862            return Ok(());
863        }
864        let bias = desired_bias_tee(&self.shared);
865        match &mut self.state {
866            RxStreamState::Async(stream) => stream.start(bias).await,
867            _ => Err(Error::Busy),
868        }
869    }
870
871    #[cfg(not(target_arch = "wasm32"))]
872    fn read_blocking(&mut self, out: &mut [Complex32], timeout: Duration) -> Result<usize> {
873        match &mut self.state {
874            RxStreamState::Blocking(stream) => stream.read(out, timeout),
875            _ => Err(Error::stream_closed(
876                "RX stream is not running synchronously",
877            )),
878        }
879    }
880
881    fn begin_read_async(&mut self) -> Result<()> {
882        match &self.state {
883            RxStreamState::Async(stream) if stream.state == ReceiverState::Running => Ok(()),
884            _ => Err(Error::stream_closed(
885                "RX stream is not running asynchronously",
886            )),
887        }
888    }
889
890    fn poll_read_async(
891        &mut self,
892        out: &mut [Complex32],
893        cx: &mut Context<'_>,
894    ) -> Poll<Result<usize>> {
895        match &mut self.state {
896            RxStreamState::Async(stream) => stream.poll_read(out, cx),
897            _ => Poll::Ready(Err(Error::stream_closed(
898                "RX stream is not running asynchronously",
899            ))),
900        }
901    }
902
903    #[cfg(not(target_arch = "wasm32"))]
904    fn stop_blocking(&mut self) -> Result<StreamingStats> {
905        let result = match &mut self.state {
906            RxStreamState::Dormant(_) => Ok(StreamingStats::default()),
907            RxStreamState::Blocking(stream) => stream.stop(),
908            RxStreamState::Async(_) => Err(Error::Busy),
909            RxStreamState::Poisoned => Err(Error::stream_closed("RX stream has no device")),
910        };
911        self.finish_stop(result)
912    }
913
914    async fn stop_async(&mut self) -> Result<StreamingStats> {
915        let result = match &mut self.state {
916            RxStreamState::Dormant(_) => Ok(StreamingStats::default()),
917            RxStreamState::Async(stream) => stream.stop().await,
918            #[cfg(not(target_arch = "wasm32"))]
919            RxStreamState::Blocking(_) => Err(Error::Busy),
920            RxStreamState::Poisoned => Err(Error::stream_closed("RX stream has no device")),
921        };
922        self.finish_stop(result)
923    }
924
925    #[cfg(target_arch = "wasm32")]
926    fn control_handle(&self) -> Option<HackRf<NusbControl>> {
927        match &self.state {
928            RxStreamState::Dormant(device) => Some(device.direct.stream_handle()),
929            #[cfg(not(target_arch = "wasm32"))]
930            RxStreamState::Blocking(stream) => Some(stream.device.direct.stream_handle()),
931            RxStreamState::Async(stream) => Some(stream.device.direct.stream_handle()),
932            RxStreamState::Poisoned => None,
933        }
934    }
935
936    #[cfg(not(target_arch = "wasm32"))]
937    fn cleanup_on_drop(&mut self) -> Result<()> {
938        match &mut self.state {
939            RxStreamState::Dormant(_) => Ok(()),
940            RxStreamState::Blocking(stream) => stream.stop().map(|_| ()),
941            RxStreamState::Async(stream) => stream.stop_on_drop().map(|_| ()),
942            RxStreamState::Poisoned => Err(Error::stream_closed("RX stream has no device")),
943        }
944    }
945}
946
947impl Drop for RxStream {
948    fn drop(&mut self) {
949        #[cfg(not(target_arch = "wasm32"))]
950        if self.receiver.is_some()
951            && self.cleanup_on_drop().is_ok()
952            && let Some(receiver) = self.receiver.take()
953        {
954            receiver.release();
955        }
956        #[cfg(target_arch = "wasm32")]
957        if let Some(direct) = self.control_handle()
958            && let Some(receiver) = self.receiver.take()
959        {
960            wasm_bindgen_futures::spawn_local(async move {
961                if shutdown_hardware(&direct).await.is_ok() {
962                    receiver.release();
963                }
964            });
965        }
966    }
967}
968
969struct StartOperation<'a> {
970    stream: &'a mut RxStream,
971}
972
973impl<'a> IntoFuture for StartOperation<'a> {
974    type Output = Result<()>;
975    type IntoFuture = BoxFuture<'a, Result<()>>;
976
977    fn into_future(self) -> Self::IntoFuture {
978        Box::pin(async move { self.stream.start_async().await })
979    }
980}
981
982impl MaybeFuture for StartOperation<'_> {
983    #[cfg(not(target_arch = "wasm32"))]
984    fn wait(self) -> Self::Output {
985        self.stream.start_blocking()
986    }
987}
988
989struct StopOperation<'a> {
990    stream: &'a mut RxStream,
991}
992
993impl<'a> IntoFuture for StopOperation<'a> {
994    type Output = Result<StreamingStats>;
995    type IntoFuture = BoxFuture<'a, Result<StreamingStats>>;
996
997    fn into_future(self) -> Self::IntoFuture {
998        Box::pin(async move { self.stream.stop_async().await })
999    }
1000}
1001
1002impl MaybeFuture for StopOperation<'_> {
1003    #[cfg(not(target_arch = "wasm32"))]
1004    fn wait(self) -> Self::Output {
1005        self.stream.stop_blocking()
1006    }
1007}
1008
1009struct ReadOperation<'a> {
1010    stream: &'a mut RxStream,
1011    out: &'a mut [Complex32],
1012    timeout: Option<Duration>,
1013}
1014
1015impl<'a> IntoFuture for ReadOperation<'a> {
1016    type Output = Result<usize>;
1017    type IntoFuture = ReadFuture<'a>;
1018
1019    fn into_future(self) -> Self::IntoFuture {
1020        let _ = self.timeout;
1021        ReadFuture {
1022            stream: self.stream,
1023            out: self.out,
1024            initialized: false,
1025            completed: false,
1026        }
1027    }
1028}
1029
1030impl MaybeFuture for ReadOperation<'_> {
1031    #[cfg(not(target_arch = "wasm32"))]
1032    fn wait(self) -> Self::Output {
1033        self.stream
1034            .read_blocking(self.out, self.timeout.unwrap_or(Duration::MAX))
1035    }
1036}
1037
1038struct ReadFuture<'a> {
1039    stream: &'a mut RxStream,
1040    out: &'a mut [Complex32],
1041    initialized: bool,
1042    completed: bool,
1043}
1044
1045impl Future for ReadFuture<'_> {
1046    type Output = Result<usize>;
1047
1048    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1049        let this = &mut *self;
1050        assert!(!this.completed, "read future polled after completion");
1051        if !this.initialized {
1052            if let Err(error) = this.stream.begin_read_async() {
1053                this.completed = true;
1054                return Poll::Ready(Err(error));
1055            }
1056            this.initialized = true;
1057        }
1058        match this.stream.poll_read_async(this.out, cx) {
1059            Poll::Pending => Poll::Pending,
1060            Poll::Ready(result) => {
1061                this.completed = true;
1062                Poll::Ready(result)
1063            }
1064        }
1065    }
1066}
1067
1068#[cfg(test)]
1069mod tests {
1070    use super::*;
1071
1072    fn shared_state() -> SharedState {
1073        Arc::new(Mutex::new(SharedDeviceState::new(false)))
1074    }
1075
1076    #[test]
1077    fn stream_claim_is_exclusive_and_released_on_drop() {
1078        let shared = shared_state();
1079        let claim = RxStreamClaim::acquire(&shared).unwrap();
1080        assert_eq!(
1081            RxStreamClaim::acquire(&shared).unwrap_err().kind(),
1082            crate::ErrorKind::Busy
1083        );
1084        drop(claim);
1085        assert!(RxStreamClaim::acquire(&shared).is_ok());
1086    }
1087
1088    #[test]
1089    fn shutdown_is_busy_only_while_receiver_lease_is_held() {
1090        let shared = shared_state();
1091        let receiver = ReceiverLease::acquire(&shared).unwrap();
1092        assert_eq!(
1093            begin_shutdown(&shared).unwrap_err().kind(),
1094            crate::ErrorKind::Busy
1095        );
1096        receiver.release();
1097        assert!(begin_shutdown(&shared).unwrap());
1098    }
1099
1100    #[test]
1101    fn device_drop_defers_closed_state_to_active_receiver() {
1102        let shared = shared_state();
1103        let receiver = ReceiverLease::acquire(&shared).unwrap();
1104        assert_eq!(begin_device_drop(&shared), DeviceDropAction::Deferred);
1105        assert_eq!(
1106            lock_shared(&shared).device,
1107            DeviceLifecycle::DropCleanupPending
1108        );
1109        receiver.release();
1110        assert_eq!(lock_shared(&shared).device, DeviceLifecycle::Closed);
1111    }
1112}