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//! 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
16use core::fmt;
17
18use crate::bindings;
19use crate::error::{Error, Result};
20use crate::net::TcpStream;
21
22// ---------------------------------------------------------------------------
23// TlsConfig
24// ---------------------------------------------------------------------------
25
26/// TLS session configuration.
27///
28/// `hostname` must be a null-terminated byte slice when provided (used for
29/// SNI and certificate verification).
30pub struct TlsConfig<'a> {
31 /// PEM or DER CA certificate for server verification (`None` to skip).
32 pub ca_cert: Option<&'a [u8]>,
33 /// Expected server hostname for SNI/verify (null-terminated, or `None`).
34 pub hostname: Option<&'a [u8]>,
35}
36
37// ---------------------------------------------------------------------------
38// Session
39// ---------------------------------------------------------------------------
40
41/// TLS session with RAII cleanup.
42///
43/// Wraps `ove_tls_t` and frees resources on drop.
44pub struct Session {
45 handle: bindings::ove_tls_t,
46}
47
48impl Session {
49 /// Create a new TLS session via heap allocation (only in heap mode).
50 #[cfg(not(zero_heap))]
51 pub fn new() -> Result<Self> {
52 let mut handle: bindings::ove_tls_t = core::ptr::null_mut();
53 let rc = unsafe { bindings::ove_tls_create(&mut handle) };
54 Error::from_code(rc)?;
55 Ok(Self { handle })
56 }
57
58 /// Create from caller-provided static storage.
59 ///
60 /// # Safety
61 /// Caller must ensure `storage` outlives the `Session` and is not
62 /// shared with another primitive.
63 #[cfg(zero_heap)]
64 pub unsafe fn from_static(
65 storage: *mut bindings::ove_tls_storage_t,
66 ) -> Result<Self> {
67 let mut handle: bindings::ove_tls_t = core::ptr::null_mut();
68 let rc = unsafe { bindings::ove_tls_init(&mut handle, storage) };
69 Error::from_code(rc)?;
70 Ok(Self { handle })
71 }
72
73 /// Perform the TLS handshake over an established TCP connection.
74 ///
75 /// After a successful handshake, use [`send`](Session::send) and
76 /// [`recv`](Session::recv) for encrypted I/O.
77 ///
78 /// # Errors
79 /// Returns an error if the handshake fails (certificate mismatch,
80 /// protocol error, etc.).
81 pub fn handshake(&self, sock: &TcpStream, cfg: &TlsConfig) -> Result<()> {
82 let mut c: bindings::ove_tls_config_t = unsafe { core::mem::zeroed() };
83
84 if let Some(cert) = cfg.ca_cert {
85 c.ca_cert = cert.as_ptr();
86 c.ca_cert_len = cert.len();
87 }
88 if let Some(host) = cfg.hostname {
89 c.hostname = host.as_ptr() as *const _;
90 }
91
92 let rc = unsafe {
93 bindings::ove_tls_handshake(self.handle, sock.handle(), &c)
94 };
95 Error::from_code(rc)
96 }
97
98 /// Send data over the encrypted session.
99 ///
100 /// Returns the number of bytes actually sent.
101 ///
102 /// # Errors
103 /// Returns an error if the send fails.
104 pub fn send(&self, data: &[u8]) -> Result<usize> {
105 let mut sent: usize = 0;
106 let rc = unsafe {
107 bindings::ove_tls_send(
108 self.handle,
109 data.as_ptr() as *const _,
110 data.len(),
111 &mut sent,
112 )
113 };
114 Error::from_code(rc)?;
115 Ok(sent)
116 }
117
118 /// Receive data from the encrypted session.
119 ///
120 /// Returns the number of bytes received into `buf`.
121 ///
122 /// # Errors
123 /// Returns an error if the receive fails or the peer closed the
124 /// connection.
125 pub fn recv(&self, buf: &mut [u8]) -> Result<usize> {
126 let mut received: usize = 0;
127 let rc = unsafe {
128 bindings::ove_tls_recv(
129 self.handle,
130 buf.as_mut_ptr() as *mut _,
131 buf.len(),
132 &mut received,
133 )
134 };
135 Error::from_code(rc)?;
136 Ok(received)
137 }
138
139 /// Shut down the TLS session (sends `close_notify`).
140 ///
141 /// The underlying socket is NOT closed -- the caller must close it
142 /// separately.
143 pub fn close(&self) {
144 unsafe { bindings::ove_tls_close(self.handle) }
145 }
146}
147
148impl fmt::Debug for Session {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 f.debug_struct("Session")
151 .field("handle", &format_args!("{:p}", self.handle))
152 .finish()
153 }
154}
155
156impl Drop for Session {
157 fn drop(&mut self) {
158 if self.handle.is_null() { return; }
159 #[cfg(not(zero_heap))]
160 unsafe { bindings::ove_tls_destroy(self.handle) }
161 #[cfg(zero_heap)]
162 unsafe { bindings::ove_tls_deinit(self.handle) }
163 }
164}
165
166// SAFETY: Wraps a ove handle. Send/recv are thread-safe RTOS calls.
167// Create/destroy are single-threaded (lifecycle guarantee).
168unsafe impl Send for Session {}