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);