Skip to content

Commit

Permalink
Abstraction for synchronous esp-tls (#288)
Browse files Browse the repository at this point in the history
* Start wrapping esp-tls

* Improve EspTls

* Allow splitting into EspTlsRead and EspTlsWrite
* Don't use std
* Use str and do alloc-free conversion

* Pass Config by reference

* Import from sys reexport

* Implement crt_bundle_attach, add docs

* Fix build, move stuff to private, use str

* Make clippy happy

* Add AsRawFd

* Add example for tls

* Make EspTls work with v5

* Remove unnecessary cast

* Use core for no-std support

* Guard EspTls with appropriate cfg

* Change cstr imports to core
  • Loading branch information
torkleyy authored Aug 24, 2023
1 parent e8b7df7 commit 2605747
Show file tree
Hide file tree
Showing 3 changed files with 546 additions and 2 deletions.
113 changes: 113 additions & 0 deletions examples/tls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//! Example of using blocking TLS/TCP.
//!
//! Add your own ssid and password

use std::ffi::CStr;
use std::io::{BufRead, BufReader};

use embedded_svc::io::adapters::ToStd;
use embedded_svc::io::Write;
use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration};
use esp_idf_hal::prelude::Peripherals;
use esp_idf_svc::log::EspLogger;
use esp_idf_svc::tls::{self, EspTls, X509};
use esp_idf_svc::wifi::{BlockingWifi, EspWifi};
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition};
use esp_idf_sys::{self as _}; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
use log::info;

const SSID: &str = env!("WIFI_SSID");
const PASSWORD: &str = env!("WIFI_PASS");

// cannot use include_str because we need a \0 at the end
const CA_CERT: &str = "-----BEGIN CERTIFICATE-----
MIIEvjCCA6agAwIBAgIQBtjZBNVYQ0b2ii+nVCJ+xDANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaME8xCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBS
U0EgU0hBMjU2IDIwMjAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAwUuzZUdwvN1PWNvsnO3DZuUfMRNUrUpmRh8sCuxkB+Uu3Ny5CiDt3+PE0J6a
qXodgojlEVbbHp9YwlHnLDQNLtKS4VbL8Xlfs7uHyiUDe5pSQWYQYE9XE0nw6Ddn
g9/n00tnTCJRpt8OmRDtV1F0JuJ9x8piLhMbfyOIJVNvwTRYAIuE//i+p1hJInuW
raKImxW8oHzf6VGo1bDtN+I2tIJLYrVJmuzHZ9bjPvXj1hJeRPG/cUJ9WIQDgLGB
Afr5yjK7tI4nhyfFK3TUqNaX3sNk+crOU6JWvHgXjkkDKa77SU+kFbnO8lwZV21r
eacroicgE7XQPUDTITAHk+qZ9QIDAQABo4IBgjCCAX4wEgYDVR0TAQH/BAgwBgEB
/wIBADAdBgNVHQ4EFgQUt2ui6qiqhIx56rTaD5iyxZV2ufQwHwYDVR0jBBgwFoAU
A95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG
CCsGAQUFBwMBBggrBgEFBQcDAjB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGG
GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2Nh
Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBCBgNV
HR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRH
bG9iYWxSb290Q0EuY3JsMD0GA1UdIAQ2MDQwCwYJYIZIAYb9bAIBMAcGBWeBDAEB
MAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EMAQIDMA0GCSqGSIb3DQEBCwUAA4IB
AQCAMs5eC91uWg0Kr+HWhMvAjvqFcO3aXbMM9yt1QP6FCvrzMXi3cEsaiVi6gL3z
ax3pfs8LulicWdSQ0/1s/dCYbbdxglvPbQtaCdB73sRD2Cqk3p5BJl+7j5nL3a7h
qG+fh/50tx8bIKuxT8b1Z11dmzzp/2n3YWzW2fP9NsarA4h20ksudYbj/NhVfSbC
EXffPgK2fPOre3qGNm+499iTcc+G33Mw+nur7SpZyEKEOxEXGlLzyQ4UfaJbcme6
ce1XR2bFuAJKZTRei9AqPCCcUZlM51Ke92sRKw2Sfh3oius2FkOH6ipjv3U/697E
A7sKPPcw7+uvTPyLNhBzPvOk
-----END CERTIFICATE-----\0";

fn main() -> anyhow::Result<()> {
EspLogger::initialize_default();

let peripherals = Peripherals::take().unwrap();
let sys_loop = EspSystemEventLoop::take()?;
let nvs = EspDefaultNvsPartition::take()?;

let mut wifi = BlockingWifi::wrap(
EspWifi::new(peripherals.modem, sys_loop.clone(), Some(nvs))?,
sys_loop,
)?;

connect_wifi(&mut wifi)?;

let ip_info = wifi.wifi().sta_netif().get_ip_info()?;

info!("Wifi DHCP info: {:?}", ip_info);

let mut tls = EspTls::new(
"example.com",
1234,
&tls::Config {
common_name: Some("example.com"),
ca_cert: Some(X509::pem(
CStr::from_bytes_with_nul(CA_CERT.as_bytes()).unwrap(),
)),
..Default::default()
},
)?;
let (reader, writer) = tls.split();
let mut reader = BufReader::with_capacity(512, ToStd::new(reader));
let mut line = String::new();
// receive line by line from server and echo them back
loop {
reader.read_line(&mut line)?;
writer.write_all(line.as_bytes())?;
line.clear();
}
}

fn connect_wifi(wifi: &mut BlockingWifi<EspWifi<'static>>) -> anyhow::Result<()> {
let wifi_configuration: Configuration = Configuration::Client(ClientConfiguration {
ssid: SSID.into(),
bssid: None,
auth_method: AuthMethod::WPA2Personal,
password: PASSWORD.into(),
channel: None,
});

wifi.set_configuration(&wifi_configuration)?;

wifi.start()?;
info!("Wifi started");

wifi.connect()?;
info!("Wifi connected");

wifi.wait_netif_up()?;
info!("Wifi netif up");

Ok(())
}
106 changes: 106 additions & 0 deletions src/private/cstr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub use alloc::ffi::CString;

pub use core::ffi::{c_char, CStr};

use crate::sys::{EspError, ESP_ERR_INVALID_SIZE};

#[cfg(feature = "alloc")]
pub fn set_str(buf: &mut [u8], s: &str) -> Result<(), crate::sys::EspError> {
assert!(s.len() < buf.len());
Expand Down Expand Up @@ -78,3 +80,107 @@ pub fn nul_to_invalid_arg(_err: alloc::ffi::NulError) -> crate::sys::EspError {
pub fn to_cstring_arg(value: &str) -> Result<CString, crate::sys::EspError> {
CString::new(value).map_err(nul_to_invalid_arg)
}

/// str to cstr, will be truncated if str is larger than buf.len() - 1
///
/// # Panics
///
/// * Panics if buffer is empty.
pub fn cstr_from_str_truncating<'a>(rust_str: &str, buf: &'a mut [u8]) -> &'a CStr {
assert!(!buf.is_empty());

let max_str_size = buf.len() - 1; // account for NUL
let truncated_str = &rust_str[..max_str_size.min(rust_str.len())];
buf[..truncated_str.len()].copy_from_slice(truncated_str.as_bytes());
buf[truncated_str.len()] = b'\0';

CStr::from_bytes_with_nul(&buf[..truncated_str.len() + 1]).unwrap()
}

/// Convert slice of rust strs to NULL-terminated fixed size array of c string pointers
///
/// # Panics
///
/// * Panics if cbuf is empty.
/// * Panics if N is <= 1
pub fn cstr_arr_from_str_slice<const N: usize>(
rust_strs: &[&str],
mut cbuf: &mut [u8],
) -> Result<[*const i8; N], EspError> {
assert!(N > 1);
assert!(!cbuf.is_empty());

// ensure last element stays NULL
if rust_strs.len() > N - 1 {
return Err(EspError::from_infallible::<ESP_ERR_INVALID_SIZE>());
}

let mut cstrs = [core::ptr::null(); N];

for (i, s) in rust_strs.iter().enumerate() {
let max_str_size = cbuf.len() - 1; // account for NUL
if s.len() > max_str_size {
return Err(EspError::from_infallible::<ESP_ERR_INVALID_SIZE>());
}
cbuf[..s.len()].copy_from_slice(s.as_bytes());
cbuf[s.len()] = b'\0';
let cstr = CStr::from_bytes_with_nul(&cbuf[..s.len() + 1]).unwrap();
cstrs[i] = cstr.as_ptr();

cbuf = &mut cbuf[s.len() + 1..];
}

Ok(cstrs)
}

#[cfg(test)]
mod tests {
use super::{cstr_arr_from_str_slice, cstr_from_str_truncating, CStr};

#[test]
fn cstr_from_str_happy() {
let mut same_size = [0u8; 6];
let hello = cstr_from_str_truncating("Hello", &mut same_size);
assert_eq!(hello.to_bytes(), b"Hello");

let mut larger = [0u8; 42];
let hello = cstr_from_str_truncating("Hello", &mut larger);
assert_eq!(hello.to_bytes(), b"Hello");
}

#[test]
fn cstr_from_str_unhappy() {
let mut smaller = [0u8; 6];
let hello = cstr_from_str_truncating("Hello World", &mut smaller);
assert_eq!(hello.to_bytes(), b"Hello");
}

#[test]
fn cstr_arr_happy() {
let mut same_size = [0u8; 13];
let hello = cstr_arr_from_str_slice::<3>(&["Hello", "World"], &mut same_size).unwrap();
assert_eq!(unsafe { CStr::from_ptr(hello[0]) }.to_bytes(), b"Hello");
assert_eq!(unsafe { CStr::from_ptr(hello[1]) }.to_bytes(), b"World");
assert_eq!(hello[2], core::ptr::null());
}

#[test]
#[should_panic]
fn cstr_arr_unhappy_n1() {
let mut cbuf = [0u8; 25];
let _ = cstr_arr_from_str_slice::<1>(&["Hello"], &mut cbuf);
}

#[test]
fn cstr_arr_unhappy_n_too_small() {
let mut cbuf = [0u8; 25];
assert!(cstr_arr_from_str_slice::<2>(&["Hello", "World"], &mut cbuf).is_err());
}

#[test]
#[should_panic]
fn cstr_arr_unhappy_cbuf_too_small() {
let mut cbuf = [0u8; 12];
assert!(cstr_arr_from_str_slice::<3>(&["Hello", "World"], &mut cbuf).is_err());
}
}
Loading

0 comments on commit 2605747

Please sign in to comment.