Skip to content
File

Blob: firmware/crates/esp32-radio/src/platform/http.rs

rust74 lines
1//! Blocking certificate-verified HTTPS, owned exclusively by signaling.
2use super::ffi;
3use crate::error::{Error, Result};
4use std::{
5 ffi::{CStr, c_void},
6 marker::PhantomData,
7 ptr::{self, NonNull},
8 rc::Rc,
9 sync::atomic::{AtomicBool, Ordering},
10};
11 
12static HTTP_TAKEN: AtomicBool = AtomicBool::new(false);
13const RESPONSE_LIMIT: usize = 24_576;
14 
15#[derive(Debug)]
16pub(crate) struct Http {
17 handle: NonNull<c_void>,
18 _task: PhantomData<Rc<()>>,
19}
20 
21impl 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}
67impl 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}