Skip to main content

ove/
net_tls.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Blocking TLS/SSL session wrapper (mbedTLS).
8//!
9//! [`Session`] wraps the oveRTOS TLS handle with automatic cleanup.
10//! After creating a session and completing the handshake over an existing
11//! [`crate::net::TcpStream`], all I/O goes through [`Session::send`] and
12//! [`Session::recv`].
13//!
14//! Works in both heap and zero-heap modes.
15//!
16//! ## Async alternative
17//!
18//! For async TLS on top of [`crate::async_net`] use the
19//! [`embedded-tls`](https://crates.io/crates/embedded-tls) crate from
20//! crates.io. Smaller binary (no mbedTLS in the image) and the same
21//! cipher suites we ship here (AES-128/256-GCM, ChaCha20-Poly1305).
22//! See [`crate::async_net`]'s module docs for the full pairing recipe.
23
24use crate::bindings;
25use crate::error::{Error, Result};
26use crate::net::TcpStream;
27
28// SAFETY (module-wide contract for the `unsafe { bindings::ove_*(...) }` FFI
29// calls below): any handle passed to the C API is non-null and refers to a
30// live RTOS object — wrapper constructors establish validity via
31// `Error::from_code`, and `Drop` (or an explicit `deinit`) is the only place
32// a handle is released. Pointer and slice arguments reference caller-owned
33// memory valid for the duration of the call; the C side copies whatever it
34// retains and does not alias them past return (verified against the
35// signatures in `include/ove/*.h`). Blocks that deviate — `transmute`, raw
36// pointer casts from user data, slice reconstruction via `from_raw_parts`,
37// or storing a callback across the FFI boundary — carry their own
38// `// SAFETY:` comment.
39
40// ---------------------------------------------------------------------------
41// TlsConfig
42// ---------------------------------------------------------------------------
43
44/// TLS session configuration.
45///
46/// `hostname` must be a null-terminated byte slice when provided (used for
47/// SNI and certificate verification).
48pub struct TlsConfig<'a> {
49    /// PEM or DER CA certificate for server verification (`None` to skip).
50    pub ca_cert: Option<&'a [u8]>,
51    /// Expected server hostname for SNI/verify (null-terminated, or `None`).
52    pub hostname: Option<&'a [u8]>,
53}
54
55// ---------------------------------------------------------------------------
56// Session
57// ---------------------------------------------------------------------------
58
59/// TLS session with RAII cleanup.
60///
61/// Wraps `ove_tls_t` and frees resources on drop.
62pub struct Session {
63    handle: bindings::ove_tls_t,
64}
65
66impl Session {
67    /// Create a new TLS session via heap allocation (only in heap mode).
68    #[cfg(not(zero_heap))]
69    pub fn new() -> Result<Self> {
70        let mut handle: bindings::ove_tls_t = core::ptr::null_mut();
71        let rc = unsafe { bindings::ove_tls_create(&mut handle) };
72        Error::from_code(rc)?;
73        Ok(Self { handle })
74    }
75
76    /// Create from caller-provided static storage.
77    ///
78    /// # Safety
79    /// Caller must ensure `storage` outlives the `Session` and is not
80    /// shared with another primitive.
81    #[cfg(zero_heap)]
82    pub unsafe fn from_static(storage: *mut bindings::ove_tls_storage_t) -> Result<Self> {
83        let mut handle: bindings::ove_tls_t = core::ptr::null_mut();
84        let rc = unsafe { bindings::ove_tls_init(&mut handle, storage) };
85        Error::from_code(rc)?;
86        Ok(Self { handle })
87    }
88
89    /// Perform the TLS handshake over an established TCP connection.
90    ///
91    /// After a successful handshake, use [`send`](Session::send) and
92    /// [`recv`](Session::recv) for encrypted I/O.
93    ///
94    /// # Errors
95    /// Returns an error if the handshake fails (certificate mismatch,
96    /// protocol error, etc.).
97    pub fn handshake(&self, sock: &TcpStream, cfg: &TlsConfig) -> Result<()> {
98        let mut c: bindings::ove_tls_config_t = unsafe { core::mem::zeroed() };
99
100        if let Some(cert) = cfg.ca_cert {
101            c.ca_cert = cert.as_ptr();
102            c.ca_cert_len = cert.len();
103        }
104        if let Some(host) = cfg.hostname {
105            c.hostname = host.as_ptr() as *const _;
106        }
107
108        let rc = unsafe { bindings::ove_tls_handshake(self.handle, sock.handle(), &c) };
109        Error::from_code(rc)
110    }
111
112    /// Send data over the encrypted session.
113    ///
114    /// Returns the number of bytes actually sent.
115    ///
116    /// # Errors
117    /// Returns an error if the send fails.
118    pub fn send(&self, data: &[u8]) -> Result<usize> {
119        let mut sent: usize = 0;
120        let rc = unsafe {
121            bindings::ove_tls_send(
122                self.handle,
123                data.as_ptr() as *const _,
124                data.len(),
125                &mut sent,
126            )
127        };
128        Error::from_code(rc)?;
129        Ok(sent)
130    }
131
132    /// Receive data from the encrypted session.
133    ///
134    /// Returns the number of bytes received into `buf`.
135    ///
136    /// # Errors
137    /// Returns an error if the receive fails or the peer closed the
138    /// connection.
139    pub fn recv(&self, buf: &mut [u8]) -> Result<usize> {
140        let mut received: usize = 0;
141        let rc = unsafe {
142            bindings::ove_tls_recv(
143                self.handle,
144                buf.as_mut_ptr() as *mut _,
145                buf.len(),
146                &mut received,
147            )
148        };
149        Error::from_code(rc)?;
150        Ok(received)
151    }
152
153    /// Shut down the TLS session (sends `close_notify`).
154    ///
155    /// The underlying socket is NOT closed -- the caller must close it
156    /// separately.
157    pub fn close(&self) {
158        unsafe { bindings::ove_tls_close(self.handle) }
159    }
160}
161
162crate::ove_handle_impl!(Session, ove_tls_destroy, ove_tls_deinit, send_only);