Skip to content
File

Blob: archive/fft-benchmark/firmware/crates/esp32-radio/src/platform/peer.rs

rust114 lines
1//! Task-affine peer. Native callbacks terminate in C-owned bounded queues.
2use super::ffi;
3use crate::error::{Error, Result, check};
4use std::{ffi::c_void, marker::PhantomData, ptr::NonNull, rc::Rc};
5 
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub(crate) enum PeerState {
8 Connecting,
9 Connected,
10 Lost,
11}
12 
13/// C owns this single peer until reboot. Intentionally no close-on-Drop: vendor
14/// teardown currently hangs. The private raw handle and Rc marker forbid sharing
15/// or moving it to another task; no Rust allocation is retained by its callbacks.
16#[derive(Debug)]
17pub(crate) struct Peer {
18 handle: NonNull<c_void>,
19 _task: PhantomData<Rc<()>>,
20}
21 
22impl Peer {
23 pub(crate) fn open() -> Result<Self> {
24 // SAFETY: C enforces single creation, owns all callback contexts and
25 // configuration until reboot, and returns either null or a live handle.
26 let handle = NonNull::new(unsafe { ffi::radio_peer_open() })
27 .ok_or(Error::new("peer initialization failed"))?;
28 Ok(Self {
29 handle,
30 _task: PhantomData,
31 })
32 }
33 pub(crate) fn poll(&mut self) {
34 // SAFETY: private live handle, same task; callbacks access C queues only.
35 unsafe { ffi::radio_peer_poll(self.handle.as_ptr()) };
36 }
37 pub(crate) fn state(&self) -> PeerState {
38 // SAFETY: C performs an atomic read on its live callback state.
39 match unsafe { ffi::radio_peer_state(self.handle.as_ptr()) } {
40 1 => PeerState::Connected,
41 -1 => PeerState::Lost,
42 _ => PeerState::Connecting,
43 }
44 }
45 pub(crate) fn offer(&mut self, out: &mut [u8]) -> Result<usize> {
46 // SAFETY: C copies at most out.len() bytes while holding its offer lock;
47 // it retains no pointer to out. The handle remains on its creating task.
48 let length =
49 unsafe { ffi::radio_peer_offer(self.handle.as_ptr(), out.as_mut_ptr(), out.len()) };
50 bounded_length(length, out.len())
51 }
52 pub(crate) fn answer(&mut self, sdp: &str) -> Result<()> {
53 check(
54 // SAFETY: C copies the bytes into its persistent answer storage before
55 // invoking the vendor. It rejects oversized and repeated answers.
56 unsafe { ffi::radio_peer_answer(self.handle.as_ptr(), sdp.as_ptr(), sdp.len()) },
57 "peer rejected answer",
58 )
59 }
60 pub(crate) fn create_channels(&mut self) -> Result<()> {
61 check(
62 // SAFETY: sole task owner, C validates state and stores configs for life.
63 unsafe { ffi::radio_peer_channels(self.handle.as_ptr()) },
64 "channel creation failed",
65 )
66 }
67 pub(crate) fn command(&mut self, out: &mut [u8]) -> Result<usize> {
68 // SAFETY: C copies a single bounded queue item into caller-owned output
69 // and never retains out. Callback and consumer synchronize via the queue.
70 let length =
71 unsafe { ffi::radio_peer_command(self.handle.as_ptr(), out.as_mut_ptr(), out.len()) };
72 bounded_length(length, out.len())
73 }
74 pub(crate) fn dropped(&self) -> u32 {
75 // SAFETY: atomic read from a live, process-lifetime C context.
76 unsafe { ffi::radio_peer_dropped(self.handle.as_ptr()) }
77 }
78 pub(crate) fn audio(&mut self, pts: u32, bytes: &[u8]) -> Result<()> {
79 check(
80 // SAFETY: the shim bounds-checks and copies input to C-owned storage.
81 // No vendor code receives a pointer into this Rust slice; see FFI contract
82 // for the pinned encoder's synchronous packet/cache copy behavior.
83 unsafe {
84 ffi::radio_peer_audio(self.handle.as_ptr(), pts, bytes.as_ptr(), bytes.len())
85 },
86 "audio send failed",
87 )
88 }
89 pub(crate) fn data(&mut self, stream: u16, binary: bool, bytes: &[u8]) -> Result<()> {
90 check(
91 // SAFETY: the shim validates stream/length and copies to C-owned storage;
92 // SCTP copies into its bounded cache before returning. No Rust pointer is retained.
93 unsafe {
94 ffi::radio_peer_data(
95 self.handle.as_ptr(),
96 stream,
97 i32::from(binary),
98 bytes.as_ptr(),
99 bytes.len(),
100 )
101 },
102 "data send failed",
103 )
104 }
105}
106 
107fn bounded_length(length: i32, capacity: usize) -> Result<usize> {
108 let length = usize::try_from(length).map_err(|_| Error::new("invalid peer response length"))?;
109 if length > capacity {
110 return Err(Error::new("oversized peer response"));
111 }
112 Ok(length)
113}