Skip to main content

esp_idf_svc/
tls.rs

1//! Type safe abstraction for esp-tls
2
3#[cfg(all(esp_idf_esp_tls_psk_verification, feature = "alloc"))]
4use core::convert::TryFrom;
5use core::fmt::Debug;
6
7use crate::private::cstr::{c_char, CStr};
8#[cfg(all(esp_idf_esp_tls_psk_verification, feature = "alloc"))]
9use crate::sys::EspError;
10
11#[cfg(all(
12    esp_idf_comp_esp_tls_enabled,
13    any(esp_idf_esp_tls_using_mbedtls, esp_idf_esp_tls_using_wolfssl)
14))]
15pub use self::esptls::*;
16
17#[derive(Copy, Clone, Eq, PartialEq)]
18pub struct Psk<'a> {
19    pub key: &'a [u8],
20    pub hint: &'a str,
21}
22
23impl Debug for Psk<'_> {
24    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
25        f.debug_struct("Psk")
26            .field("hint", &self.hint)
27            .finish_non_exhaustive()
28    }
29}
30
31/// Helper for holding PSK data for lately initialized TLS connections.
32///
33/// It could be easily converted from the public `Psk` configuration and holds the `psk_hint_key_t`
34/// along with its (string) data as this data typically needs to be around after initializing a TLS
35/// client until it has been started.
36#[cfg(all(esp_idf_esp_tls_psk_verification, feature = "alloc"))]
37pub(crate) struct TlsPsk {
38    pub(crate) psk: alloc::boxed::Box<crate::hal::sys::psk_hint_key_t>,
39    pub(crate) _cstrs: crate::private::cstr::RawCstrs,
40}
41/// Dummy for maintaining the same internal interface whether TLS PSK support is enabled or not.
42#[cfg(not(all(esp_idf_esp_tls_psk_verification, feature = "alloc")))]
43#[allow(dead_code)]
44pub(crate) struct TlsPsk {}
45
46#[cfg(all(esp_idf_esp_tls_psk_verification, feature = "alloc"))]
47impl Debug for TlsPsk {
48    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
49        f.debug_struct("TlsPsk")
50            .field("psk", &self.psk)
51            .finish_non_exhaustive()
52    }
53}
54
55#[cfg(all(esp_idf_esp_tls_psk_verification, feature = "alloc"))]
56impl<'a> TryFrom<&'a Psk<'a>> for TlsPsk {
57    type Error = EspError;
58
59    fn try_from(conf: &Psk) -> Result<Self, EspError> {
60        let mut cstrs = crate::private::cstr::RawCstrs::new();
61        let psk = alloc::boxed::Box::new(crate::hal::sys::psk_hint_key_t {
62            key: conf.key.as_ptr(),
63            key_size: conf.key.len(),
64            hint: cstrs.as_ptr(conf.hint)?,
65        });
66
67        Ok(TlsPsk { psk, _cstrs: cstrs })
68    }
69}
70
71#[derive(Copy, Clone, Eq, PartialEq)]
72pub struct X509<'a>(&'a [u8]);
73
74impl<'a> X509<'a> {
75    pub fn pem(cstr: &'a CStr) -> Self {
76        Self(cstr.to_bytes_with_nul())
77    }
78
79    pub const fn pem_until_nul(bytes: &'a [u8]) -> Self {
80        // TODO: replace with `CStr::from_bytes_until_nul` when stabilized
81        let mut nul_pos = 0;
82        while nul_pos < bytes.len() {
83            if bytes[nul_pos] == 0 {
84                // TODO: replace with `<[u8]>::split_at(nul_pos + 1)` when const stabilized
85                let slice = unsafe { core::slice::from_raw_parts(bytes.as_ptr(), nul_pos + 1) };
86                return Self(slice);
87            }
88            nul_pos += 1;
89        }
90        panic!("PEM certificates should end with a NIL (`\\0`) ASCII character.")
91    }
92
93    pub const fn der(bytes: &'a [u8]) -> Self {
94        Self(bytes)
95    }
96
97    pub fn data(&self) -> &[u8] {
98        self.0
99    }
100
101    #[allow(unused)]
102    pub(crate) fn as_esp_idf_raw_ptr(&self) -> *const c_char {
103        self.data().as_ptr().cast()
104    }
105
106    #[allow(unused)]
107    pub(crate) fn as_esp_idf_raw_len(&self) -> usize {
108        self.data().len()
109    }
110}
111
112impl Debug for X509<'_> {
113    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
114        f.debug_struct("X509").finish_non_exhaustive()
115    }
116}
117
118#[cfg(all(
119    esp_idf_comp_esp_tls_enabled,
120    any(esp_idf_esp_tls_using_mbedtls, esp_idf_esp_tls_using_wolfssl)
121))]
122mod esptls {
123    use core::ffi::c_char;
124    #[cfg(esp_idf_esp_tls_server_cert_select_hook)]
125    use core::ffi::c_int;
126    use core::task::{Context, Poll};
127    use core::time::Duration;
128    #[allow(unused_imports)]
129    use core::{pin::Pin, task::ready};
130
131    use embedded_svc::io;
132
133    use super::X509;
134
135    use crate::{
136        io::EspIOError,
137        private::cstr::{cstr_arr_from_str_slice, cstr_from_str_truncating, CStr},
138        sys::{
139            self, EspError, ESP_ERR_NO_MEM, ESP_FAIL, ESP_TLS_ERR_SSL_WANT_READ,
140            ESP_TLS_ERR_SSL_WANT_WRITE, EWOULDBLOCK,
141        },
142    };
143
144    /// see https://www.ietf.org/rfc/rfc3280.txt ub-common-name-length
145    const MAX_COMMON_NAME_LENGTH: usize = 64;
146
147    pub struct Config<'a> {
148        /// up to 9 ALPNs allowed, with avg 10 bytes for each name
149        pub alpn_protos: Option<&'a [&'a str]>,
150        pub ca_cert: Option<X509<'a>>,
151        pub client_cert: Option<X509<'a>>,
152        pub client_key: Option<X509<'a>>,
153        pub client_key_password: Option<&'a str>,
154        pub non_block: bool,
155        pub use_secure_element: bool,
156        pub timeout_ms: u32,
157        pub use_global_ca_store: bool,
158        pub common_name: Option<&'a str>,
159        pub skip_common_name: bool,
160        pub keep_alive_cfg: Option<KeepAliveConfig>,
161        pub psk_hint_key: Option<PskHintKey<'a>>,
162        /// whether to use esp_crt_bundle_attach, see <https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/protocols/esp_crt_bundle.html>
163        #[cfg(esp_idf_mbedtls_certificate_bundle)]
164        pub use_crt_bundle_attach: bool,
165        // TODO ds_data not implemented
166        pub is_plain_tcp: bool,
167    }
168
169    impl Config<'_> {
170        pub const fn new() -> Self {
171            Self {
172                alpn_protos: None,
173                ca_cert: None,
174                client_cert: None,
175                client_key: None,
176                client_key_password: None,
177                non_block: false,
178                use_secure_element: false,
179                timeout_ms: 4000,
180                use_global_ca_store: false,
181                common_name: None,
182                skip_common_name: false,
183                keep_alive_cfg: None,
184                psk_hint_key: None,
185                #[cfg(esp_idf_mbedtls_certificate_bundle)]
186                use_crt_bundle_attach: true,
187                is_plain_tcp: false,
188            }
189        }
190
191        fn try_into_raw(&self, bufs: &mut RawConfigBufs) -> Result<sys::esp_tls_cfg, EspError> {
192            let mut rcfg: sys::esp_tls_cfg = Default::default();
193
194            if let Some(ca_cert) = self.ca_cert {
195                rcfg.__bindgen_anon_1.cacert_buf = ca_cert.data().as_ptr();
196                rcfg.__bindgen_anon_2.cacert_bytes = ca_cert.data().len() as u32;
197            }
198
199            if let Some(client_cert) = self.client_cert {
200                rcfg.__bindgen_anon_3.clientcert_buf = client_cert.data().as_ptr();
201                rcfg.__bindgen_anon_4.clientcert_bytes = client_cert.data().len() as u32;
202            }
203
204            if let Some(client_key) = self.client_key {
205                rcfg.__bindgen_anon_5.clientkey_buf = client_key.data().as_ptr();
206                rcfg.__bindgen_anon_6.clientkey_bytes = client_key.data().len() as u32;
207            }
208
209            if let Some(ckp) = self.client_key_password {
210                rcfg.clientkey_password = ckp.as_ptr();
211                rcfg.clientkey_password_len = ckp.len() as u32;
212            }
213
214            // allow up to 9 protocols
215            if let Some(protos) = self.alpn_protos {
216                bufs.alpn_protos = cstr_arr_from_str_slice(protos, &mut bufs.alpn_protos_cbuf)?;
217                rcfg.alpn_protos = bufs.alpn_protos.as_mut_ptr();
218            }
219
220            rcfg.non_block = self.non_block;
221            rcfg.use_secure_element = self.use_secure_element;
222            rcfg.timeout_ms = self.timeout_ms as i32;
223            rcfg.use_global_ca_store = self.use_global_ca_store;
224
225            if let Some(common_name) = self.common_name {
226                rcfg.common_name =
227                    cstr_from_str_truncating(common_name, &mut bufs.common_name_buf).as_ptr();
228            }
229
230            rcfg.skip_common_name = self.skip_common_name;
231
232            let mut raw_kac: sys::tls_keep_alive_cfg;
233            if let Some(kac) = &self.keep_alive_cfg {
234                raw_kac = sys::tls_keep_alive_cfg {
235                    keep_alive_enable: kac.enable,
236                    keep_alive_idle: kac.idle.as_secs() as i32,
237                    keep_alive_interval: kac.interval.as_secs() as i32,
238                    keep_alive_count: kac.count as i32,
239                };
240                rcfg.keep_alive_cfg = &mut raw_kac as *mut _;
241            }
242
243            #[cfg(any(
244                esp_idf_esp_tls_psk_verification,
245                esp_idf_version_major = "4",
246                esp_idf_version = "5.0",
247                esp_idf_version = "5.1",
248                esp_idf_version = "5.2",
249                esp_idf_version = "5.3",
250                esp_idf_version = "5.4",
251            ))]
252            {
253                let mut raw_psk: sys::psk_key_hint;
254                if let Some(psk) = &self.psk_hint_key {
255                    raw_psk = sys::psk_key_hint {
256                        key: psk.key.as_ptr(),
257                        key_size: psk.key.len(),
258                        hint: psk.hint.as_ptr(),
259                    };
260                    rcfg.psk_hint_key = &mut raw_psk as *mut _;
261                }
262            }
263
264            #[cfg(esp_idf_mbedtls_certificate_bundle)]
265            if self.use_crt_bundle_attach {
266                rcfg.crt_bundle_attach = Some(sys::esp_crt_bundle_attach);
267            }
268
269            rcfg.is_plain_tcp = self.is_plain_tcp;
270
271            #[cfg(esp_idf_comp_lwip_enabled)]
272            {
273                rcfg.if_name = core::ptr::null_mut();
274            }
275
276            Ok(rcfg)
277        }
278    }
279
280    impl Default for Config<'_> {
281        fn default() -> Self {
282            Self::new()
283        }
284    }
285
286    struct RawConfigBufs {
287        alpn_protos: [*const c_char; 10],
288        alpn_protos_cbuf: [u8; 99],
289        common_name_buf: [u8; MAX_COMMON_NAME_LENGTH + 1],
290    }
291
292    unsafe impl Send for RawConfigBufs {}
293
294    impl Default for RawConfigBufs {
295        fn default() -> Self {
296            RawConfigBufs {
297                alpn_protos: [core::ptr::null(); 10],
298                alpn_protos_cbuf: [0; 99],
299                common_name_buf: [0; MAX_COMMON_NAME_LENGTH + 1],
300            }
301        }
302    }
303
304    type AlpnBuf = [u8; 16];
305
306    #[derive(Clone, Default)]
307    pub struct CompletedHandshake {
308        alpn: AlpnBuf,
309    }
310
311    impl CompletedHandshake {
312        pub fn alpn_proto(&self) -> Option<&str> {
313            let p = CStr::from_bytes_until_nul(self.alpn.as_slice()).unwrap();
314            // Safety: the bytes always come from a user supplied &str.
315            let p = unsafe { core::str::from_utf8_unchecked(p.to_bytes()) };
316
317            // A valid protocol is never empty.
318            if !p.is_empty() {
319                Some(p)
320            } else {
321                None
322            }
323        }
324
325        // Safety: Must be called while the configured ALPN protocol strings are valid.
326        unsafe fn extract(raw: *mut sys::esp_tls) -> CompletedHandshake {
327            CompletedHandshake {
328                alpn: unsafe { Self::extract_alpn(raw) }.unwrap_or_default(),
329            }
330        }
331
332        #[cfg(not(all(
333            not(esp_idf_version_major = "4"),
334            esp_idf_comp_esp_tls_enabled,
335            esp_idf_esp_tls_using_mbedtls,
336            esp_idf_mbedtls_ssl_alpn
337        )))]
338        unsafe fn extract_alpn(_raw: *mut sys::esp_tls) -> Option<AlpnBuf> {
339            None
340        }
341
342        #[cfg(all(
343            not(esp_idf_version_major = "4"),
344            esp_idf_comp_esp_tls_enabled,
345            esp_idf_esp_tls_using_mbedtls,
346            esp_idf_mbedtls_ssl_alpn
347        ))]
348        #[warn(unsafe_op_in_unsafe_fn)]
349        unsafe fn extract_alpn(raw: *mut sys::esp_tls) -> Option<AlpnBuf> {
350            let raw: *mut sys::mbedtls_ssl_context =
351                unsafe { sys::esp_tls_get_ssl_context(raw) }.cast();
352
353            if raw.is_null() {
354                return None;
355            }
356
357            let chosen = unsafe { sys::mbedtls_ssl_get_alpn_protocol(raw) };
358            if chosen.is_null() {
359                return None;
360            }
361
362            let mut proto = AlpnBuf::default();
363            let chosen = unsafe { CStr::from_ptr(chosen) };
364            let chosen_bytes = chosen.to_bytes_with_nul();
365            if chosen_bytes.len() > proto.len() {
366                return None;
367            }
368
369            proto[..chosen_bytes.len()].copy_from_slice(chosen_bytes);
370
371            Some(proto)
372        }
373    }
374
375    #[derive(Clone, Debug)]
376    pub struct KeepAliveConfig {
377        /// Enable keep-alive timeout
378        pub enable: bool,
379        /// Keep-alive idle time (second)
380        pub idle: Duration,
381        /// Keep-alive interval time (second)
382        pub interval: Duration,
383        /// Keep-alive packet retry send count
384        pub count: u32,
385    }
386
387    pub struct PskHintKey<'a> {
388        pub key: &'a [u8],
389        pub hint: &'a CStr,
390    }
391
392    #[cfg(any(
393        esp_idf_esp_tls_server,
394        all(esp_idf_version_at_least_5_3_0, esp_idf_esp_tls_using_mbedtls),
395    ))]
396    pub struct ServerConfig<'a> {
397        /// up to 9 ALPNs allowed, with avg 10 bytes for each name
398        pub alpn_protos: Option<&'a [&'a str]>,
399        pub ca_cert: Option<X509<'a>>,
400        pub server_cert: Option<X509<'a>>,
401        pub server_key: Option<X509<'a>>,
402        pub server_key_password: Option<&'a str>,
403        pub use_secure_element: bool,
404        /// Overall TLS handshake timeout in milliseconds.
405        ///
406        /// `0` means the ESP-TLS default (10 seconds).
407        ///
408        /// Only honored by the blocking [`EspTls::negotiate_server`] path.
409        /// The non-blocking [`EspTls::negotiate_server_init`] /
410        /// [`EspTls::negotiate_server_continue`] path (and thus
411        /// `EspAsyncTls::negotiate_server`) is not bounded by it, so callers
412        /// there should enforce their own deadline.
413        ///
414        /// Only available on ESP-IDF >= 5.5.0 (`esp_tls_cfg_server::tls_handshake_timeout_ms`).
415        #[cfg(esp_idf_version_at_least_5_5_0)]
416        pub tls_handshake_timeout_ms: u32,
417        #[cfg(esp_idf_esp_tls_server_cert_select_hook)]
418        pub handshake_callback: Option<extern "C" fn(*mut sys::mbedtls_ssl_context) -> c_int>,
419    }
420
421    #[cfg(any(
422        esp_idf_esp_tls_server,
423        all(esp_idf_version_at_least_5_3_0, esp_idf_esp_tls_using_mbedtls),
424    ))]
425    impl<'a> ServerConfig<'a> {
426        pub const fn new() -> Self {
427            Self {
428                alpn_protos: None,
429                ca_cert: None,
430                server_cert: None,
431                server_key: None,
432                server_key_password: None,
433                use_secure_element: false,
434                #[cfg(esp_idf_version_at_least_5_5_0)]
435                tls_handshake_timeout_ms: 0,
436                #[cfg(esp_idf_esp_tls_server_cert_select_hook)]
437                handshake_callback: None,
438            }
439        }
440
441        fn try_into_raw(
442            &self,
443            bufs: &mut RawConfigBufs,
444        ) -> Result<sys::esp_tls_cfg_server, EspError> {
445            let mut rcfg: sys::esp_tls_cfg_server = Default::default();
446
447            if let Some(ca_cert) = self.ca_cert {
448                rcfg.__bindgen_anon_1.cacert_buf = ca_cert.data().as_ptr();
449                rcfg.__bindgen_anon_2.cacert_bytes = ca_cert.data().len() as u32;
450            }
451
452            if let Some(server_cert) = self.server_cert {
453                rcfg.__bindgen_anon_3.servercert_buf = server_cert.data().as_ptr();
454                rcfg.__bindgen_anon_4.servercert_bytes = server_cert.data().len() as u32;
455            }
456
457            if let Some(server_key) = self.server_key {
458                rcfg.__bindgen_anon_5.serverkey_buf = server_key.data().as_ptr();
459                rcfg.__bindgen_anon_6.serverkey_bytes = server_key.data().len() as u32;
460            }
461
462            if let Some(ckp) = self.server_key_password {
463                rcfg.serverkey_password = ckp.as_ptr();
464                rcfg.serverkey_password_len = ckp.len() as u32;
465            }
466
467            // allow up to 9 protocols
468            if let Some(protos) = self.alpn_protos {
469                bufs.alpn_protos = cstr_arr_from_str_slice(protos, &mut bufs.alpn_protos_cbuf)?;
470                rcfg.alpn_protos = bufs.alpn_protos.as_mut_ptr();
471            }
472
473            rcfg.use_secure_element = self.use_secure_element;
474            #[cfg(esp_idf_version_at_least_5_5_0)]
475            {
476                rcfg.tls_handshake_timeout_ms = self.tls_handshake_timeout_ms;
477            }
478
479            #[cfg(esp_idf_esp_tls_server_cert_select_hook)]
480            if let Some(cb) = self.handshake_callback {
481                rcfg.cert_select_cb = cb;
482            }
483
484            Ok(rcfg)
485        }
486    }
487
488    #[cfg(any(
489        esp_idf_esp_tls_server,
490        all(esp_idf_version_at_least_5_3_0, esp_idf_esp_tls_using_mbedtls),
491    ))]
492    impl<'a> Default for ServerConfig<'a> {
493        fn default() -> Self {
494            Self::new()
495        }
496    }
497
498    pub trait Socket {
499        /// Returns the integer FD.
500        fn handle(&self) -> i32;
501        /// This is called before cleaning up the the tls context and is responsible
502        /// for essentially giving up ownership of the socket such that it can safely
503        /// be closed by the ESP IDF.
504        fn release(&mut self) -> Result<(), EspError>;
505    }
506
507    pub trait PollableSocket: Socket {
508        fn poll_readable(&self, ctx: &mut Context) -> Poll<Result<(), EspError>>;
509        fn poll_writable(&self, ctx: &mut Context) -> Poll<Result<(), EspError>>;
510    }
511
512    pub struct InternalSocket(());
513
514    impl Socket for InternalSocket {
515        fn handle(&self) -> i32 {
516            unreachable!()
517        }
518
519        fn release(&mut self) -> Result<(), EspError> {
520            Ok(())
521        }
522    }
523
524    /// Wrapper for `esp-tls` module. Only supports synchronous operation for now.
525    pub struct EspTls<S>
526    where
527        S: Socket,
528    {
529        raw: *mut sys::esp_tls,
530        socket: S,
531        #[cfg(any(
532            esp_idf_esp_tls_server,
533            all(esp_idf_version_at_least_5_3_0, esp_idf_esp_tls_using_mbedtls,),
534        ))]
535        server_session: bool,
536    }
537
538    // A single Mbed TLS context itself is safe to send across threads.
539    // Require the threading implementation to be enabled since a shared context such as RSA or X509 could be used by multiple threads at once.
540    // See https://mbed-tls.readthedocs.io/en/latest/kb/development/thread-safety-and-multi-threading/
541    #[cfg(all(
542        esp_idf_comp_esp_tls_enabled,
543        esp_idf_esp_tls_using_mbedtls,
544        esp_idf_mbedtls_threading_c
545    ))]
546    unsafe impl<S> Send for EspTls<S> where S: Send + Socket {}
547
548    impl EspTls<InternalSocket> {
549        /// Create a new `EspTls` instance using internally-managed socket.
550        ///
551        /// # Errors
552        ///
553        /// * `ESP_ERR_NO_MEM` if not enough memory to create the TLS connection
554        pub fn new() -> Result<Self, EspError> {
555            let raw = unsafe { sys::esp_tls_init() };
556            if !raw.is_null() {
557                Ok(Self {
558                    raw,
559                    socket: InternalSocket(()),
560                    #[cfg(any(
561                        esp_idf_esp_tls_server,
562                        all(esp_idf_version_at_least_5_3_0, esp_idf_esp_tls_using_mbedtls,),
563                    ))]
564                    server_session: false,
565                })
566            } else {
567                Err(EspError::from_infallible::<ESP_ERR_NO_MEM>())
568            }
569        }
570
571        /// Establish a TLS/SSL connection with the specified host and port, using an internally-managed socket.
572        ///
573        /// # Errors
574        ///
575        /// * `ESP_ERR_INVALID_SIZE` if `cfg.alpn_protos` exceeds 9 elements or avg 10 bytes/ALPN
576        /// * `ESP_FAIL` if connection could not be established
577        /// * `ESP_TLS_ERR_SSL_WANT_READ` if the socket is in non-blocking mode and it is not ready for reading
578        /// * `ESP_TLS_ERR_SSL_WANT_WRITE` if the socket is in non-blocking mode and it is not ready for writing
579        /// * `EWOULDBLOCK` if the socket is in non-blocking mode and it is not ready either for reading or writing (a peculiarity/bug of the `esp-tls` C module)
580        pub fn connect(
581            &mut self,
582            host: &str,
583            port: u16,
584            cfg: &Config,
585        ) -> Result<CompletedHandshake, EspError> {
586            let mut bufs = RawConfigBufs::default();
587            let rcfg = cfg.try_into_raw(&mut bufs)?;
588
589            let res = self.internal_connect(host, port, cfg.non_block, &rcfg);
590
591            // Make sure buffers are held long enough
592            #[allow(clippy::drop_non_drop)]
593            drop(bufs);
594
595            res
596        }
597    }
598
599    impl<S> EspTls<S>
600    where
601        S: Socket,
602    {
603        /// Create a new `EspTls` instance adopting the supplied socket.
604        /// The socket should be in a connected state.
605        ///
606        /// # Errors
607        ///
608        /// * `ESP_ERR_NO_MEM` if not enough memory to create the TLS connection
609        #[cfg(all(
610            not(esp_idf_version_major = "4"),
611            any(not(esp_idf_version_major = "5"), not(esp_idf_version_minor = "0"))
612        ))]
613        pub fn adopt(socket: S) -> Result<Self, EspError> {
614            let raw = unsafe { sys::esp_tls_init() };
615            if !raw.is_null() {
616                sys::esp!(unsafe { sys::esp_tls_set_conn_sockfd(raw, socket.handle()) })?;
617
618                sys::esp!(unsafe {
619                    sys::esp_tls_set_conn_state(raw, sys::esp_tls_conn_state_ESP_TLS_CONNECTING)
620                })?;
621
622                Ok(Self {
623                    raw,
624                    socket,
625                    #[cfg(any(
626                        esp_idf_esp_tls_server,
627                        all(esp_idf_version_at_least_5_3_0, esp_idf_esp_tls_using_mbedtls,),
628                    ))]
629                    server_session: false,
630                })
631            } else {
632                Err(EspError::from_infallible::<ESP_ERR_NO_MEM>())
633            }
634        }
635
636        /// Establish a TLS/SSL connection using the adopted socket.
637        ///
638        /// # Errors
639        ///
640        /// * `ESP_ERR_INVALID_SIZE` if `cfg.alpn_protos` exceeds 9 elements or avg 10 bytes/ALPN
641        /// * `ESP_FAIL` if connection could not be established
642        /// * `ESP_TLS_ERR_SSL_WANT_READ` if the socket is in non-blocking mode and it is not ready for reading
643        /// * `ESP_TLS_ERR_SSL_WANT_WRITE` if the socket is in non-blocking mode and it is not ready for writing
644        /// * `EWOULDBLOCK` if the socket is in non-blocking mode and it is not ready either for reading or writing (a peculiarity/bug of the `esp-tls` C module)
645        #[cfg(all(
646            not(esp_idf_version_major = "4"),
647            any(not(esp_idf_version_major = "5"), not(esp_idf_version_minor = "0"))
648        ))]
649        pub fn negotiate(
650            &mut self,
651            host: &str,
652            cfg: &Config,
653        ) -> Result<CompletedHandshake, EspError> {
654            let mut bufs = RawConfigBufs::default();
655            let rcfg = cfg.try_into_raw(&mut bufs)?;
656
657            let res = self.internal_connect(host, 0, cfg.non_block, &rcfg);
658
659            // Make sure buffers are held long enough
660            #[allow(clippy::drop_non_drop)]
661            drop(bufs);
662
663            res
664        }
665
666        /// Establish a TLS/SSL connection using the adopted connection, acting as the server.
667        ///
668        /// This call is **blocking**. On ESP-IDF >= 5.5.0 the duration is bounded by
669        /// `cfg.tls_handshake_timeout_ms` (or the ESP-TLS default of 10 s when that
670        /// field is `0`). When the caller cannot afford to stall, prefer
671        /// `EspAsyncTls::negotiate_server`, or drive a non-blocking socket manually
672        /// with `negotiate_server_init` + `negotiate_server_continue`
673        /// (both ESP-IDF >= 5.5.0).
674        ///
675        /// # Errors
676        ///
677        /// * `ESP_FAIL` if connection could not be established
678        #[cfg(any(
679            esp_idf_esp_tls_server,
680            all(esp_idf_version_at_least_5_3_0, esp_idf_esp_tls_using_mbedtls),
681        ))]
682        pub fn negotiate_server(&mut self, cfg: &ServerConfig) -> Result<(), EspError> {
683            let mut bufs = RawConfigBufs::default();
684            let mut rcfg = cfg.try_into_raw(&mut bufs)?;
685
686            unsafe {
687                let error =
688                    sys::esp_tls_server_session_create(&mut rcfg, self.socket.handle(), self.raw);
689                if error != 0 {
690                    log::error!("failed to create tls server session (error {error})");
691                    return Err(EspError::from_infallible::<ESP_FAIL>());
692                }
693            }
694            self.server_session = true;
695
696            // Make sure buffers are held long enough
697            #[allow(clippy::drop_non_drop)]
698            drop(bufs);
699
700            Ok(())
701        }
702
703        /// Begin a server-side TLS handshake on an already-adopted socket.
704        ///
705        /// Requires ESP-IDF >= 5.5.0 with mbedTLS. The socket should typically be
706        /// non-blocking. Complete the handshake by repeatedly calling
707        /// [`Self::negotiate_server_continue`] until it returns `Ok(())`.
708        /// Certificate material referenced by `cfg` must remain valid for the
709        /// duration of this call (mbedTLS parses it here).
710        ///
711        /// # Errors
712        ///
713        /// * `ESP_FAIL` / other ESP-TLS errors if session setup fails
714        #[cfg(all(esp_idf_version_at_least_5_5_0, esp_idf_esp_tls_using_mbedtls,))]
715        pub fn negotiate_server_init(&mut self, cfg: &ServerConfig) -> Result<(), EspError> {
716            let mut bufs = RawConfigBufs::default();
717            let mut rcfg = cfg.try_into_raw(&mut bufs)?;
718
719            // esp_tls_server_session_init returns esp_err_t (0 on success).
720            sys::esp!(unsafe {
721                sys::esp_tls_server_session_init(&mut rcfg, self.socket.handle(), self.raw)
722            })?;
723            self.server_session = true;
724
725            #[allow(clippy::drop_non_drop)]
726            drop(bufs);
727
728            Ok(())
729        }
730
731        /// Continue a server-side handshake started with [`Self::negotiate_server_init`].
732        ///
733        /// Requires ESP-IDF >= 5.5.0 with mbedTLS.
734        ///
735        /// Returns `Ok(())` once the handshake is complete and the session is
736        /// ready for read/write.
737        ///
738        /// # Errors
739        ///
740        /// * `ESP_TLS_ERR_SSL_WANT_READ` if the socket is in non-blocking mode and it is not ready for reading
741        /// * `ESP_TLS_ERR_SSL_WANT_WRITE` if the socket is in non-blocking mode and it is not ready for writing
742        /// * `ESP_FAIL` if the handshake failed; drop the connection
743        #[allow(clippy::unnecessary_cast)]
744        #[cfg(all(esp_idf_version_at_least_5_5_0, esp_idf_esp_tls_using_mbedtls,))]
745        pub fn negotiate_server_continue(&mut self) -> Result<(), EspError> {
746            let ret = unsafe { sys::esp_tls_server_session_continue_async(self.raw) };
747
748            match ret {
749                0 => Ok(()),
750                ESP_TLS_ERR_SSL_WANT_READ => Err(EspError::from_infallible::<
751                    { ESP_TLS_ERR_SSL_WANT_READ as i32 },
752                >()),
753                ESP_TLS_ERR_SSL_WANT_WRITE => Err(EspError::from_infallible::<
754                    { ESP_TLS_ERR_SSL_WANT_WRITE as i32 },
755                >()),
756                _ => {
757                    log::error!("TLS server handshake continue failed (error {ret})");
758                    Err(EspError::from_infallible::<ESP_FAIL>())
759                }
760            }
761        }
762
763        #[allow(clippy::unnecessary_cast)]
764        fn internal_connect(
765            &mut self,
766            host: &str,
767            port: u16,
768            asynch: bool,
769            cfg: &sys::esp_tls_cfg,
770        ) -> Result<CompletedHandshake, EspError> {
771            let ret = unsafe {
772                if asynch {
773                    sys::esp_tls_conn_new_async(
774                        host.as_bytes().as_ptr() as *const c_char,
775                        host.len() as i32,
776                        port as i32,
777                        cfg,
778                        self.raw,
779                    )
780                } else {
781                    sys::esp_tls_conn_new_sync(
782                        host.as_bytes().as_ptr() as *const c_char,
783                        host.len() as i32,
784                        port as i32,
785                        cfg,
786                        self.raw,
787                    )
788                }
789            };
790
791            match ret {
792                1 => Ok(unsafe { CompletedHandshake::extract(self.raw) }),
793                ESP_TLS_ERR_SSL_WANT_READ => Err(EspError::from_infallible::<
794                    { ESP_TLS_ERR_SSL_WANT_READ as i32 },
795                >()),
796                ESP_TLS_ERR_SSL_WANT_WRITE => Err(EspError::from_infallible::<
797                    { ESP_TLS_ERR_SSL_WANT_WRITE as i32 },
798                >()),
799                0 => Err(EspError::from_infallible::<{ EWOULDBLOCK as i32 }>()),
800                _ => Err(EspError::from_infallible::<ESP_FAIL>()),
801            }
802        }
803
804        /// Read in the supplied buffer. Returns the number of bytes read.
805        ///
806        ///
807        /// # Errors
808        /// * `ESP_TLS_ERR_SSL_WANT_READ` if the socket is in non-blocking mode and it is not ready for reading
809        /// * `ESP_TLS_ERR_SSL_WANT_WRITE` if the socket is in non-blocking mode and it is not ready for writing
810        /// * Any other `EspError` for a general error
811        pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, EspError> {
812            if buf.is_empty() {
813                return Ok(0);
814            }
815
816            let ret = self.read_raw(buf);
817            // ESP docs treat 0 as error, but in Rust it's common to return 0 from `Read::read` to indicate eof
818            if ret >= 0 {
819                Ok(ret as usize)
820            } else {
821                Err(EspError::from(ret as i32).unwrap())
822            }
823        }
824
825        #[cfg(esp_idf_version_major = "4")]
826        fn read_raw(&mut self, buf: &mut [u8]) -> isize {
827            // cannot call esp_tls_conn_read bc it's inline in v4
828            let esp_tls = unsafe { core::ptr::read_unaligned(self.raw) };
829            let read_func = esp_tls.read.unwrap();
830            unsafe { read_func(self.raw, buf.as_mut_ptr() as *mut c_char, buf.len()) }
831        }
832
833        #[cfg(not(esp_idf_version_major = "4"))]
834        fn read_raw(&mut self, buf: &mut [u8]) -> isize {
835            use core::ffi::c_void;
836
837            unsafe { sys::esp_tls_conn_read(self.raw, buf.as_mut_ptr() as *mut c_void, buf.len()) }
838        }
839
840        /// Write the supplied buffer. Returns the number of bytes written.
841        ///
842        /// # Errors
843        /// * `ESP_TLS_ERR_SSL_WANT_READ` if the socket is in non-blocking mode and it is not ready for reading
844        /// * `ESP_TLS_ERR_SSL_WANT_WRITE` if the socket is in non-blocking mode and it is not ready for writing
845        /// * Any other `EspError` for a general error
846        pub fn write(&mut self, buf: &[u8]) -> Result<usize, EspError> {
847            if buf.is_empty() {
848                return Ok(0);
849            }
850
851            let ret = self.write_raw(buf);
852            if ret >= 0 {
853                Ok(ret as usize)
854            } else {
855                Err(EspError::from(ret as i32).unwrap())
856            }
857        }
858
859        pub fn write_all(&mut self, buf: &[u8]) -> Result<(), EspError> {
860            let mut buf = buf;
861
862            while !buf.is_empty() {
863                match self.write(buf) {
864                    Ok(0) => panic!("zero-length write."),
865                    Ok(n) => buf = &buf[n..],
866                    Err(e) => return Err(e),
867                }
868            }
869
870            Ok(())
871        }
872
873        #[cfg(esp_idf_version_major = "4")]
874        fn write_raw(&mut self, buf: &[u8]) -> isize {
875            // cannot call esp_tls_conn_write bc it's inline
876            let esp_tls = unsafe { core::ptr::read_unaligned(self.raw) };
877            let write_func = esp_tls.write.unwrap();
878            unsafe { write_func(self.raw, buf.as_ptr() as *const c_char, buf.len()) }
879        }
880
881        #[cfg(not(esp_idf_version_major = "4"))]
882        fn write_raw(&mut self, buf: &[u8]) -> isize {
883            use core::ffi::c_void;
884
885            unsafe { sys::esp_tls_conn_write(self.raw, buf.as_ptr() as *const c_void, buf.len()) }
886        }
887
888        pub fn context_handle(&self) -> *mut sys::esp_tls {
889            self.raw
890        }
891    }
892
893    impl<S> Drop for EspTls<S>
894    where
895        S: Socket,
896    {
897        fn drop(&mut self) {
898            let _ = self.socket.release();
899
900            unsafe {
901                // use esp_tls_conn_destroy for both client and server
902                sys::esp_tls_conn_destroy(self.raw);
903            }
904        }
905    }
906
907    impl<S> io::ErrorType for EspTls<S>
908    where
909        S: Socket,
910    {
911        type Error = EspIOError;
912    }
913
914    impl<S> io::Read for EspTls<S>
915    where
916        S: Socket,
917    {
918        fn read(&mut self, buf: &mut [u8]) -> Result<usize, EspIOError> {
919            EspTls::read(self, buf).map_err(EspIOError)
920        }
921    }
922
923    impl<S> io::Write for EspTls<S>
924    where
925        S: Socket,
926    {
927        fn write(&mut self, buf: &[u8]) -> Result<usize, EspIOError> {
928            EspTls::write(self, buf).map_err(EspIOError)
929        }
930
931        fn flush(&mut self) -> Result<(), EspIOError> {
932            Ok(())
933        }
934    }
935    #[cfg(all(
936        not(esp_idf_version_major = "4"),
937        any(not(esp_idf_version_major = "5"), not(esp_idf_version_minor = "0"))
938    ))]
939    pub struct EspAsyncTls<S>(crate::private::mutex::Mutex<EspTls<S>>)
940    where
941        S: PollableSocket;
942
943    #[cfg(all(
944        not(esp_idf_version_major = "4"),
945        any(not(esp_idf_version_major = "5"), not(esp_idf_version_minor = "0"))
946    ))]
947    impl<S> EspAsyncTls<S>
948    where
949        S: PollableSocket,
950    {
951        /// Create a new `AsyncEspTls` instance adopting the supplied socket.
952        /// The socket should be in a connected state.
953        ///
954        /// # Errors
955        ///
956        /// * `ESP_ERR_NO_MEM` if not enough memory to create the TLS connection
957        pub fn adopt(socket: S) -> Result<Self, EspError> {
958            Ok(Self(crate::private::mutex::Mutex::new(EspTls::adopt(
959                socket,
960            )?)))
961        }
962
963        /// Establish a TLS/SSL connection using the adopted socket.
964        ///
965        /// # Errors
966        ///
967        /// * `ESP_ERR_INVALID_SIZE` if `cfg.alpn_protos` exceeds 9 elements or avg 10 bytes/ALPN
968        /// * `ESP_FAIL` if connection could not be established
969        pub async fn negotiate(
970            &mut self,
971            hostname: &str,
972            cfg: &Config<'_>,
973        ) -> Result<CompletedHandshake, EspError> {
974            struct AssertSend<T>(T);
975            unsafe impl<T> Send for AssertSend<T> {}
976
977            let mut bufs = RawConfigBufs::default();
978            let mut rcfg: AssertSend<sys::esp_tls_cfg> = AssertSend(cfg.try_into_raw(&mut bufs)?);
979
980            // It is a bit unintuitive, but when an async socket is being adopted, `non_block` should be set to false.
981            //
982            // Background:
983            // `non_block = true` is only used at one place in the ESP IDF code and that is to run
984            // a check - with `select` - whether the socket is really connected.
985            // However, we want to avoid the `select()` call, as
986            // (a) It won't work, because we jump directly into the ESP_TLS_CONNECTING state as we adopt a socket.
987            //.    As a side effect, the select() call is not properly initialized.
988            // (b) The adopted socket might be registered in a select() loop already.
989            //
990            // Avoiding the connectivity check with `select()` should be fine, as the adopted socket
991            // must be already connected anyway (API requirement).
992            rcfg.0.non_block = false;
993
994            let res = loop {
995                let res = self
996                    .0
997                    .get_mut()
998                    .internal_connect(hostname, 0, true, &rcfg.0);
999
1000                match res {
1001                    Err(e) => self.wait(e).await?,
1002                    other => break other,
1003                }
1004            };
1005
1006            // Make sure buffers are held long enough
1007            #[allow(clippy::drop_non_drop)]
1008            drop(bufs);
1009
1010            res
1011        }
1012
1013        /// Establish a TLS/SSL connection using the adopted socket, acting as the server.
1014        ///
1015        /// Requires ESP-IDF >= 5.5.0 with mbedTLS.
1016        ///
1017        /// Note that the handshake is not bounded in time (`cfg.tls_handshake_timeout_ms`
1018        /// is only honored by the blocking `EspTls::negotiate_server`), so a
1019        /// misbehaving peer can keep the negotiation going indefinitely.
1020        /// Callers which cannot afford that should race this future against a timer.
1021        ///
1022        /// # Errors
1023        ///
1024        /// * `ESP_FAIL` if the connection could not be established
1025        #[cfg(all(esp_idf_version_at_least_5_5_0, esp_idf_esp_tls_using_mbedtls))]
1026        pub async fn negotiate_server(&mut self, cfg: &ServerConfig<'_>) -> Result<(), EspError> {
1027            self.0.get_mut().negotiate_server_init(cfg)?;
1028
1029            loop {
1030                let res = self.0.get_mut().negotiate_server_continue();
1031
1032                match res {
1033                    Err(e) => self.wait(e).await?,
1034                    Ok(()) => break Ok(()),
1035                }
1036            }
1037        }
1038
1039        /// Read in the supplied buffer. Returns the number of bytes read.
1040        pub async fn read(&self, buf: &mut [u8]) -> Result<usize, EspError> {
1041            core::future::poll_fn(|ctx| self.poll_read(ctx, buf)).await
1042        }
1043
1044        pub fn poll_read(
1045            &self,
1046            ctx: &mut Context<'_>,
1047            buf: &mut [u8],
1048        ) -> Poll<Result<usize, EspError>> {
1049            loop {
1050                let res = self.0.lock().read(buf);
1051
1052                match res {
1053                    Err(e) => ready!(self.poll_wait(ctx, e))?,
1054                    Ok(n) => break Poll::Ready(Ok(n)),
1055                }
1056            }
1057        }
1058
1059        /// Write the supplied buffer. Returns the number of bytes written.
1060        pub async fn write(&self, buf: &[u8]) -> Result<usize, EspError> {
1061            core::future::poll_fn(|ctx| self.poll_write(ctx, buf)).await
1062        }
1063
1064        pub fn poll_write(
1065            &self,
1066            ctx: &mut Context<'_>,
1067            buf: &[u8],
1068        ) -> Poll<Result<usize, EspError>> {
1069            loop {
1070                let res = self.0.lock().write(buf);
1071
1072                match res {
1073                    Err(e) => ready!(self.poll_wait(ctx, e))?,
1074                    Ok(n) => break Poll::Ready(Ok(n)),
1075                }
1076            }
1077        }
1078
1079        pub async fn write_all(&self, buf: &[u8]) -> Result<(), EspError> {
1080            let mut buf = buf;
1081
1082            while !buf.is_empty() {
1083                match self.write(buf).await {
1084                    Ok(0) => panic!("zero-length write."),
1085                    Ok(n) => buf = &buf[n..],
1086                    Err(e) => return Err(e),
1087                }
1088            }
1089
1090            Ok(())
1091        }
1092
1093        fn poll_wait(&self, ctx: &mut Context<'_>, error: EspError) -> Poll<Result<(), EspError>> {
1094            const EWOULDBLOCK_I32: i32 = EWOULDBLOCK as i32;
1095
1096            match error.code() {
1097                // EWOULDBLOCK models the "0" return code of esp_mbedtls_handshake() which does not allow us
1098                // to figure out whether we need the socket to become readable or writable
1099                // The code below is therefore a hack which just waits with a timeout for the socket to (eventually)
1100                // become readable as we actually don't even know if that's what esp_tls wants
1101                EWOULDBLOCK_I32 => {
1102                    let res = self.0.lock().socket.poll_writable(ctx);
1103                    crate::hal::delay::FreeRtos::delay_ms(0);
1104                    res
1105                }
1106                ESP_TLS_ERR_SSL_WANT_READ => self.0.lock().socket.poll_readable(ctx),
1107                ESP_TLS_ERR_SSL_WANT_WRITE => self.0.lock().socket.poll_writable(ctx),
1108                _ => Poll::Ready(Err(error)),
1109            }
1110        }
1111
1112        async fn wait(&self, error: EspError) -> Result<(), EspError> {
1113            core::future::poll_fn(|ctx| self.poll_wait(ctx, error)).await
1114        }
1115
1116        pub fn context_handle(&self) -> *mut sys::esp_tls {
1117            self.0.lock().context_handle()
1118        }
1119    }
1120
1121    #[cfg(all(
1122        not(esp_idf_version_major = "4"),
1123        any(not(esp_idf_version_major = "5"), not(esp_idf_version_minor = "0"))
1124    ))]
1125    impl<S> io::ErrorType for EspAsyncTls<S>
1126    where
1127        S: PollableSocket,
1128    {
1129        type Error = EspIOError;
1130    }
1131
1132    #[cfg(all(
1133        not(esp_idf_version_major = "4"),
1134        any(not(esp_idf_version_major = "5"), not(esp_idf_version_minor = "0"))
1135    ))]
1136    impl<S> io::asynch::Read for EspAsyncTls<S>
1137    where
1138        S: PollableSocket,
1139    {
1140        async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
1141            EspAsyncTls::read(self, buf).await.map_err(EspIOError)
1142        }
1143    }
1144
1145    #[cfg(all(
1146        not(esp_idf_version_major = "4"),
1147        any(not(esp_idf_version_major = "5"), not(esp_idf_version_minor = "0"))
1148    ))]
1149    impl<S> io::asynch::Write for EspAsyncTls<S>
1150    where
1151        S: PollableSocket,
1152    {
1153        async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
1154            EspAsyncTls::write(self, buf).await.map_err(EspIOError)
1155        }
1156
1157        async fn flush(&mut self) -> Result<(), Self::Error> {
1158            Ok(())
1159        }
1160    }
1161
1162    #[cfg(all(
1163        feature = "std",
1164        not(esp_idf_version_major = "4"),
1165        any(not(esp_idf_version_major = "5"), not(esp_idf_version_minor = "0"))
1166    ))]
1167    impl<S> futures_io::AsyncRead for EspAsyncTls<S>
1168    where
1169        S: PollableSocket,
1170    {
1171        fn poll_read(
1172            self: Pin<&mut Self>,
1173            ctx: &mut Context<'_>,
1174            buf: &mut [u8],
1175        ) -> Poll<std::io::Result<usize>> {
1176            self.as_ref()
1177                .poll_read(ctx, buf)
1178                .map_err(std::io::Error::other)
1179        }
1180    }
1181
1182    #[cfg(all(
1183        feature = "std",
1184        not(esp_idf_version_major = "4"),
1185        any(not(esp_idf_version_major = "5"), not(esp_idf_version_minor = "0"))
1186    ))]
1187    impl<S> futures_io::AsyncRead for &EspAsyncTls<S>
1188    where
1189        S: PollableSocket,
1190    {
1191        fn poll_read(
1192            self: Pin<&mut Self>,
1193            ctx: &mut Context<'_>,
1194            buf: &mut [u8],
1195        ) -> Poll<std::io::Result<usize>> {
1196            self.as_ref()
1197                .poll_read(ctx, buf)
1198                .map_err(std::io::Error::other)
1199        }
1200    }
1201
1202    #[cfg(all(
1203        feature = "std",
1204        not(esp_idf_version_major = "4"),
1205        any(not(esp_idf_version_major = "5"), not(esp_idf_version_minor = "0"))
1206    ))]
1207    impl<S> futures_io::AsyncWrite for EspAsyncTls<S>
1208    where
1209        S: PollableSocket,
1210    {
1211        fn poll_write(
1212            self: Pin<&mut Self>,
1213            ctx: &mut Context<'_>,
1214            buf: &[u8],
1215        ) -> Poll<std::io::Result<usize>> {
1216            self.as_ref()
1217                .poll_write(ctx, buf)
1218                .map_err(std::io::Error::other)
1219        }
1220
1221        fn poll_flush(self: Pin<&mut Self>, _ctx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
1222            Poll::Ready(Ok(()))
1223        }
1224
1225        fn poll_close(self: Pin<&mut Self>, _ctx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
1226            Poll::Ready(Ok(()))
1227        }
1228    }
1229
1230    #[cfg(all(
1231        feature = "std",
1232        not(esp_idf_version_major = "4"),
1233        any(not(esp_idf_version_major = "5"), not(esp_idf_version_minor = "0"))
1234    ))]
1235    impl<S> futures_io::AsyncWrite for &EspAsyncTls<S>
1236    where
1237        S: PollableSocket,
1238    {
1239        fn poll_write(
1240            self: Pin<&mut Self>,
1241            ctx: &mut Context<'_>,
1242            buf: &[u8],
1243        ) -> Poll<std::io::Result<usize>> {
1244            self.as_ref()
1245                .poll_write(ctx, buf)
1246                .map_err(std::io::Error::other)
1247        }
1248
1249        fn poll_flush(self: Pin<&mut Self>, _ctx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
1250            Poll::Ready(Ok(()))
1251        }
1252
1253        fn poll_close(self: Pin<&mut Self>, _ctx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
1254            Poll::Ready(Ok(()))
1255        }
1256    }
1257}