File
Blob: firmware/crates/esp32-radio/src/platform/http.rs
| 1 | //! Blocking certificate-verified HTTPS, owned exclusively by signaling. |
| 2 | use super::ffi; |
| 3 | use crate::error::{Error, Result}; |
| 4 | use std::{ |
| 5 | ffi::{CStr, c_void}, |
| 6 | marker::PhantomData, |
| 7 | ptr::{self, NonNull}, |
| 8 | rc::Rc, |
| 9 | sync::atomic::{AtomicBool, Ordering}, |
| 10 | }; |
| 11 | |
| 12 | static HTTP_TAKEN: AtomicBool = AtomicBool::new(false); |
| 13 | const RESPONSE_LIMIT: usize = 24_576; |
| 14 | |
| 15 | #[derive(Debug)] |
| 16 | pub(crate) struct Http { |
| 17 | handle: NonNull<c_void>, |
| 18 | _task: PhantomData<Rc<()>>, |
| 19 | } |
| 20 | |
| 21 | impl Http { |
| 22 | pub(crate) fn open() -> Result<Self> { |
| 23 | if HTTP_TAKEN.swap(true, Ordering::AcqRel) { |
| 24 | return Err(Error::new("HTTP task already initialized")); |
| 25 | } |
| 26 | // SAFETY: C returns a fresh client owning all callback buffers. It uses |
| 27 | // compiled credentials internally; no secret enters Rust diagnostics. |
| 28 | let handle = NonNull::new(unsafe { ffi::radio_http_open() }) |
| 29 | .ok_or(Error::new("HTTPS initialization failed"))?; |
| 30 | Ok(Self { |
| 31 | handle, |
| 32 | _task: PhantomData, |
| 33 | }) |
| 34 | } |
| 35 | /// The response borrows this client, excluding another post or its cleanup. |
| 36 | pub(crate) fn post(&mut self, path: &CStr, body: &[u8]) -> Result<(i32, &[u8])> { |
| 37 | let mut response = ptr::null(); |
| 38 | let mut used = 0; |
| 39 | // SAFETY: all arguments are live for this blocking call, and &mut self |
| 40 | // excludes prior response borrows. C replaces the borrowed POST field with |
| 41 | // static storage before return; response callbacks have finished writing. |
| 42 | let status = unsafe { |
| 43 | ffi::radio_http_post( |
| 44 | self.handle.as_ptr(), |
| 45 | path.as_ptr(), |
| 46 | body.as_ptr(), |
| 47 | body.len(), |
| 48 | &mut response, |
| 49 | &mut used, |
| 50 | ) |
| 51 | }; |
| 52 | if used > RESPONSE_LIMIT || (used > 0 && response.is_null()) { |
| 53 | return Err(Error::new("invalid HTTPS response length")); |
| 54 | } |
| 55 | let bytes = if used == 0 { |
| 56 | &[] |
| 57 | } else { |
| 58 | // SAFETY: C returns exactly `used` initialized bytes from this |
| 59 | // client's live allocation, bounded above. The returned borrow is |
| 60 | // tied to &mut self, so neither another post nor Drop can mutate or |
| 61 | // free it. Http cannot cross tasks; C has no asynchronous callback. |
| 62 | unsafe { std::slice::from_raw_parts(response, used) } |
| 63 | }; |
| 64 | Ok((status, bytes)) |
| 65 | } |
| 66 | } |
| 67 | impl Drop for Http { |
| 68 | fn drop(&mut self) { |
| 69 | // SAFETY: all blocking calls/callbacks have returned; same task and sole |
| 70 | // owner. C cleans up the client before freeing its callback context. |
| 71 | unsafe { ffi::radio_http_free(self.handle.as_ptr()) }; |
| 72 | } |
| 73 | } |