Skip to content

Commit

Permalink
RSDK-4248 - implement server example that pulls credentials from NVS (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
gvaradarajan authored Aug 29, 2023
1 parent a8dd5bc commit 7df5d0a
Show file tree
Hide file tree
Showing 10 changed files with 301 additions and 32 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ jobs:
run: |
bash -c 'git config --global --add safe.directory /opt/esp/esp-idf && export MICRO_RDK_WIFI_PASSWORD=0 && . "$IDF_PATH"/export.sh && . "$ESP_ROOT"/export-esp.sh && make clippy-esp32'
bash -c 'export MICRO_RDK_WIFI_PASSWORD=0 && . "$IDF_PATH"/export.sh && . "$ESP_ROOT"/export-esp.sh && cd examples && cd ../ && make build-esp32-bin'
bash -c 'export MICRO_RDK_USE_NVS=true && . "$IDF_PATH"/export.sh && . "$ESP_ROOT"/export-esp.sh && make build-esp32-with-cred-bin'
- name: Native Build & Clippy
if : needs.changes.outputs.src-native == 'true' || needs.changes.outputs.src-common == 'true' || needs.changes.outputs.src-examples == 'true'
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ size:
build-esp32-bin:
cd examples && cargo espflash save-image --merge --chip esp32 target/esp32-server.bin -T esp32/partitions.csv -s 4M --bin esp32-server --target=xtensa-esp32-espidf -Zbuild-std=std,panic_abort --release

build-esp32-with-cred-bin:
cd examples && cargo espflash save-image --merge --chip esp32 target/esp32-server-with-cred.bin -T esp32/partitions.csv -s 4M --bin esp32-server-with-cred --target=xtensa-esp32-espidf -Zbuild-std=std,panic_abort --release

flash-esp32-bin:
ifneq (,$(wildcard ./examples/target/esp32-server.bin))
espflash write-bin 0x0 ./examples/target/esp32-server.bin -b 460800 && sleep 2 && espflash monitor
Expand Down
9 changes: 5 additions & 4 deletions examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ phf = { version = "0.11", features = ["macros"] }
reqwless = "0.5.0"
serde = "1.0.145"
serde_json = "1.0.97"
thiserror = "1.0.47"


[build-dependencies]
Expand All @@ -77,3 +78,6 @@ path = "native/native-server.rs"
name = "esp32-server"
path = "esp32/esp32-server.rs"

[[bin]]
name = "esp32-server-with-cred"
path = "esp32-with-cred/esp32-server-with-cred.rs"
39 changes: 27 additions & 12 deletions examples/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,27 +143,42 @@ pub struct Cloud {

fn main() -> anyhow::Result<()> {
println!("cargo:rerun-if-changed=viam.json");
let use_nvs = match env::var_os("MICRO_RDK_USE_NVS") {
Some(val) => {
let use_nvs = val.to_ascii_lowercase() == "true";
if !use_nvs {
println!("Found MICRO_RDK_USE_NVS={:?}, will not write credentials as variables to file. If this was not intended provide the value as 'true' or 'True'", val);
}
use_nvs
}
None => false,
};
if env::var("TARGET").unwrap() == "xtensa-esp32-espidf" {
if std::env::var_os("IDF_PATH").is_none() {
return Err(anyhow::anyhow!(
"You need to run IDF's export.sh before building"
));
}
if std::env::var_os("MICRO_RDK_WIFI_SSID").is_none() {
std::env::set_var("MICRO_RDK_WIFI_SSID", "Viam-2G");
println!("cargo:rustc-env=MICRO_RDK_WIFI_SSID=Viam-2G");
}
if std::env::var_os("MICRO_RDK_WIFI_PASSWORD").is_none() {
return Err(anyhow::anyhow!(
"please set the password for WiFi {}",
std::env::var_os("MICRO_RDK_WIFI_PASSWORD")
.unwrap()
.to_str()
.unwrap()
));
if !use_nvs {
if std::env::var_os("MICRO_RDK_WIFI_SSID").is_none() {
std::env::set_var("MICRO_RDK_WIFI_SSID", "Viam-2G");
println!("cargo:rustc-env=MICRO_RDK_WIFI_SSID=Viam-2G");
}
if std::env::var_os("MICRO_RDK_WIFI_PASSWORD").is_none() {
return Err(anyhow::anyhow!(
"please set the password for WiFi {}",
std::env::var_os("MICRO_RDK_WIFI_SSID")
.unwrap()
.to_str()
.unwrap()
));
}
}
embuild::build::LinkArgs::output_propagated("ESP_IDF")?;
}
if use_nvs {
return Ok(());
}

let (cert_der, kp_der, fp) = generate_dtls_certificate()?;

Expand Down
243 changes: 243 additions & 0 deletions examples/esp32-with-cred/esp32-server-with-cred.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
use log::*;
use thiserror::Error;

use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::nvs::{EspDefaultNvs, EspDefaultNvsPartition, EspNvs};
use esp_idf_sys::EspError;
use micro_rdk::{
common::{app_client::AppClientConfig, entry::RobotRepresentation},
esp32::{certificate::WebRtcCertificate, entry::serve_web, tls::Esp32TlsServerConfig},
};

#[cfg(feature = "qemu")]
use {
micro_rdk::{
common::{
board::FakeBoard,
robot::{LocalRobot, ResourceMap, ResourceType},
},
proto::common::v1::ResourceName,
},
std::{
collections::HashMap,
net::Ipv4Addr,
sync::{Arc, Mutex},
},
};

#[cfg(not(feature = "qemu"))]
use {
embedded_svc::wifi::{
AuthMethod, ClientConfiguration as WifiClientConfiguration,
Configuration as WifiConfiguration,
},
esp_idf_hal::{peripheral::Peripheral, prelude::Peripherals},
esp_idf_svc::wifi::{BlockingWifi, EspWifi},
esp_idf_sys as _,
esp_idf_sys::esp_wifi_set_ps,
micro_rdk::common::registry::ComponentRegistry,
};

#[derive(Debug, Error)]
pub enum ServerError {
#[error("Error fetching NVS key: {0}")]
NVSKeyError(String),
#[error("{0}")]
EspError(EspError),
#[error("Error obtaining peripherals")]
PeripheralsError,
}

impl From<EspError> for ServerError {
fn from(value: EspError) -> ServerError {
ServerError::EspError(value)
}
}

const VIAM_NVS_NAMESPACE: &str = "VIAM_NS";

fn get_str_from_nvs(viam_nvs: &EspDefaultNvs, key: &str) -> Result<String, ServerError> {
let mut buffer_ref = [0_u8; 4000];
Ok(viam_nvs
.get_str(key, &mut buffer_ref)?
.ok_or(ServerError::NVSKeyError(key.to_string()))?
.trim_matches(char::from(0))
.to_string())
}

fn get_blob_from_nvs(viam_nvs: &EspDefaultNvs, key: &str) -> Result<Vec<u8>, ServerError> {
let mut buffer_ref = [0_u8; 4000];
Ok(viam_nvs
.get_blob(key, &mut buffer_ref)?
.ok_or(ServerError::NVSKeyError(key.to_string()))?
.to_vec())
}

struct NvsStaticVars {
#[cfg(not(feature = "qemu"))]
wifi_ssid: String,
#[cfg(not(feature = "qemu"))]
wifi_pwd: String,
robot_secret: String,
robot_id: String,
robot_dtls_cert: Vec<u8>,
robot_dtls_key_pair: Vec<u8>,
robot_dtls_cert_fp: String,
robot_srv_pem_chain: Vec<u8>,
robot_srv_pem_ca: Vec<u8>,
robot_srv_der_key: Vec<u8>,
}

impl NvsStaticVars {
fn new() -> Result<NvsStaticVars, ServerError> {
let nvs = EspDefaultNvsPartition::take()?;
info!("get namespace...");
let viam_nvs = EspNvs::new(nvs.clone(), VIAM_NVS_NAMESPACE, true)?;
info!("loading creds...");
Ok(NvsStaticVars {
#[cfg(not(feature = "qemu"))]
wifi_ssid: get_str_from_nvs(&viam_nvs, "WIFI_SSID")?,
#[cfg(not(feature = "qemu"))]
wifi_pwd: get_str_from_nvs(&viam_nvs, "WIFI_PASSWORD")?,
robot_secret: get_str_from_nvs(&viam_nvs, "ROBOT_SECRET")?,
robot_id: get_str_from_nvs(&viam_nvs, "ROBOT_ID")?,
robot_dtls_cert: get_blob_from_nvs(&viam_nvs, "ROBOT_DTLS_CERT")?,
robot_dtls_key_pair: get_blob_from_nvs(&viam_nvs, "DTLS_KEY_PAIR")?,
robot_dtls_cert_fp: get_str_from_nvs(&viam_nvs, "DTLS_CERT_FP")?,
robot_srv_pem_chain: get_blob_from_nvs(&viam_nvs, "SRV_PEM_CHAIN")?,
robot_srv_pem_ca: get_blob_from_nvs(&viam_nvs, "CA_CRT")?,
robot_srv_der_key: get_blob_from_nvs(&viam_nvs, "SRV_DER_KEY")?,
})
}
}

fn main() -> Result<(), ServerError> {
std::env::set_var("RUST_BACKTRACE", "1");
esp_idf_sys::link_patches();

esp_idf_svc::log::EspLogger::initialize_default();
let sys_loop_stack = EspSystemEventLoop::take()?;

#[cfg(not(feature = "qemu"))]
let periph = Peripherals::take().ok_or(ServerError::PeripheralsError)?;

#[cfg(feature = "qemu")]
let repr = {
let board = Arc::new(Mutex::new(FakeBoard::new(vec![])));
let mut res: ResourceMap = HashMap::with_capacity(1);
res.insert(
ResourceName {
namespace: "rdk".to_string(),
r#type: "component".to_string(),
subtype: "board".to_string(),
name: "b".to_string(),
},
ResourceType::Board(board),
);
RobotRepresentation::WithRobot(LocalRobot::new(res))
};
#[cfg(not(feature = "qemu"))]
let repr = RobotRepresentation::WithRegistry(ComponentRegistry::default());

esp_idf_sys::esp!(unsafe {
esp_idf_sys::esp_vfs_eventfd_register(&esp_idf_sys::esp_vfs_eventfd_config_t { max_fds: 5 })
})?;

info!("load vars from NVS...");
let nvs_vars = NvsStaticVars::new()?;

#[cfg(feature = "qemu")]
let (ip, _block_eth) = {
use esp_idf_hal::prelude::Peripherals;
info!("creating eth object");
let mut eth = Box::new(esp_idf_svc::eth::EspEth::wrap(
esp_idf_svc::eth::EthDriver::new_openeth(
Peripherals::take()
.ok_or(ServerError::PeripheralsError)?
.mac,
sys_loop_stack.clone(),
)?,
)?);
let _ = eth_configure(&sys_loop_stack, &mut eth)?;
let ip = Ipv4Addr::new(10, 1, 12, 187);
(ip, eth)
};

info!("starting wifi...");
#[allow(clippy::redundant_clone)]
#[cfg(not(feature = "qemu"))]
let (ip, _wifi) = {
let wifi = start_wifi(
periph.modem,
sys_loop_stack,
&nvs_vars.wifi_ssid,
&nvs_vars.wifi_pwd,
)?;
(wifi.wifi().sta_netif().get_ip_info()?.ip, wifi)
};

let webrtc_certificate = WebRtcCertificate::new(
nvs_vars.robot_dtls_cert,
nvs_vars.robot_dtls_key_pair,
&nvs_vars.robot_dtls_cert_fp,
);

let cert: [Vec<u8>; 2] = [nvs_vars.robot_srv_pem_chain, nvs_vars.robot_srv_pem_ca];
let key = nvs_vars.robot_srv_der_key;
let tls_cfg = Esp32TlsServerConfig::new(cert, key.as_ptr(), key.len() as u32);

let cfg = AppClientConfig::new(nvs_vars.robot_secret, nvs_vars.robot_id, ip, "".to_owned());

serve_web(cfg, tls_cfg, repr, ip, webrtc_certificate);
Ok(())
}

#[cfg(feature = "qemu")]
fn eth_configure<'d, T>(
sl_stack: &EspSystemEventLoop,
eth: &mut esp_idf_svc::eth::EspEth<'d, T>,
) -> Result<Ipv4Addr, ServerError> {
let mut eth = esp_idf_svc::eth::BlockingEth::wrap(eth, sl_stack.clone())?;
eth.start()?;
let ip_info = eth.eth().netif().get_ip_info()?;

info!("ETH IP {:?}", ip_info.ip);
Ok(ip_info.ip)
}

#[cfg(not(feature = "qemu"))]
fn start_wifi(
modem: impl Peripheral<P = esp_idf_hal::modem::Modem> + 'static,
sl_stack: EspSystemEventLoop,
ssid: &str,
password: &str,
) -> Result<Box<BlockingWifi<EspWifi<'static>>>, ServerError> {
let nvs = EspDefaultNvsPartition::take()?;
let mut wifi = BlockingWifi::wrap(
EspWifi::new(modem, sl_stack.clone(), Some(nvs.clone()))?,
sl_stack,
)?;
let ssid_heapless = ssid.into();
let password_heapless = password.into();
let wifi_configuration = WifiConfiguration::Client(WifiClientConfiguration {
ssid: ssid_heapless,
bssid: None,
auth_method: AuthMethod::WPA2Personal,
password: password_heapless,
channel: None,
});
debug!("setting wifi configuration...");
wifi.set_configuration(&wifi_configuration)?;

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

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

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

esp_idf_sys::esp!(unsafe { esp_wifi_set_ps(esp_idf_sys::wifi_ps_type_t_WIFI_PS_NONE) })?;
Ok(Box::new(wifi))
}
9 changes: 6 additions & 3 deletions examples/esp32/esp32-server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,14 @@ fn main() -> anyhow::Result<()> {
ip,
"".to_owned(),
);
let webrtc_certificate =
WebRtcCertificate::new(ROBOT_DTLS_CERT, ROBOT_DTLS_KEY_PAIR, ROBOT_DTLS_CERT_FP);
let webrtc_certificate = WebRtcCertificate::new(
ROBOT_DTLS_CERT.to_vec(),
ROBOT_DTLS_KEY_PAIR.to_vec(),
ROBOT_DTLS_CERT_FP,
);

let tls_cfg = {
let cert = &[ROBOT_SRV_PEM_CHAIN, ROBOT_SRV_PEM_CA];
let cert = [ROBOT_SRV_PEM_CHAIN.to_vec(), ROBOT_SRV_PEM_CA.to_vec()];
let key = ROBOT_SRV_DER_KEY;
Esp32TlsServerConfig::new(cert, key.as_ptr(), key.len() as u32)
};
Expand Down
2 changes: 1 addition & 1 deletion micro-rdk-installer/src/nvs/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl ViamFlashStorageData {
namespace_idx,
},
NVSKeyValuePair {
key: "PASSWORD".to_string(),
key: "WIFI_PASSWORD".to_string(),
value: NVSValue::String(wifi_cred.password.expose_secret().to_string()),
namespace_idx,
},
Expand Down
Loading

0 comments on commit 7df5d0a

Please sign in to comment.