mirror of
https://github.com/STCisGOOD/lunarcore.git
synced 2026-03-28 17:32:37 +01:00
Add files via upload
This commit is contained in:
1747
Cargo.lock
generated
Normal file
1747
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
Cargo.toml
Normal file
37
Cargo.toml
Normal file
@@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "lunarcore"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
esp-idf-hal = { version = "0.45", features = ["critical-section"] }
|
||||
esp-idf-svc = "0.51"
|
||||
esp-idf-sys = { version = "0.36", features = ["binstart"] }
|
||||
embedded-hal = "1.0"
|
||||
embedded-io = "0.6"
|
||||
heapless = "0.8"
|
||||
nb = "1.1"
|
||||
critical-section = "1.2"
|
||||
log = "0.4"
|
||||
|
||||
[build-dependencies]
|
||||
embuild = "0.33"
|
||||
|
||||
[features]
|
||||
default = ["ble"]
|
||||
ble = []
|
||||
meshtastic = []
|
||||
rnode = []
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
lto = false
|
||||
codegen-units = 1
|
||||
|
||||
[profile.dev]
|
||||
opt-level = "z"
|
||||
|
||||
[[bin]]
|
||||
name = "lunarcore"
|
||||
path = "src/main.rs"
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
25
README.md
Normal file
25
README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# LunarCore
|
||||
|
||||
Multi-protocol mesh firmware for ESP32-S3 LoRa devices.
|
||||
|
||||
## Protocols
|
||||
|
||||
- MeshCore
|
||||
- Meshtastic
|
||||
- RNode/KISS (Reticulum)
|
||||
|
||||
## Hardware
|
||||
|
||||
Heltec WiFi LoRa 32 V3 (ESP32-S3 + SX1262)
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
espup install
|
||||
cargo build --release
|
||||
espflash flash target/xtensa-esp32s3-espidf/release/lunarcore --monitor
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "esp"
|
||||
17
sdkconfig.defaults
Normal file
17
sdkconfig.defaults
Normal file
@@ -0,0 +1,17 @@
|
||||
CONFIG_IDF_TARGET="esp32s3"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
||||
CONFIG_SPI_MASTER_IN_IRAM=y
|
||||
CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=3
|
||||
CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y
|
||||
CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y
|
||||
CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=32
|
||||
CONFIG_ESP_TASK_WDT_EN=y
|
||||
CONFIG_ESP_TASK_WDT_TIMEOUT_S=30
|
||||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=131072
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
CONFIG_ADC_DISABLE_DAC=y
|
||||
1267
src/ble.rs
Normal file
1267
src/ble.rs
Normal file
File diff suppressed because it is too large
Load Diff
598
src/contact.rs
Normal file
598
src/contact.rs
Normal file
@@ -0,0 +1,598 @@
|
||||
use crate::crypto::{
|
||||
sha256::Sha256,
|
||||
ed25519::{Ed25519, Signature},
|
||||
};
|
||||
use heapless::Vec as HeaplessVec;
|
||||
|
||||
pub const CONTACT_HELLO_VERSION: u8 = 0x03;
|
||||
|
||||
pub const ED25519_PK_SIZE: usize = 32;
|
||||
|
||||
pub const X25519_PK_SIZE: usize = 32;
|
||||
|
||||
pub const ED25519_SIG_SIZE: usize = 64;
|
||||
|
||||
pub const HASH_SIZE: usize = 32;
|
||||
|
||||
pub const MAX_DID_LENGTH: usize = 128;
|
||||
|
||||
pub const MAX_NAME_LENGTH: usize = 64;
|
||||
|
||||
pub const DILITHIUM_PK_SIZE: usize = 1952;
|
||||
|
||||
pub const DILITHIUM_SIG_SIZE: usize = 2420;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContactHello {
|
||||
|
||||
pub version: u8,
|
||||
|
||||
pub timestamp: u64,
|
||||
|
||||
pub did: HeaplessVec<u8, MAX_DID_LENGTH>,
|
||||
|
||||
pub ed25519_public: [u8; ED25519_PK_SIZE],
|
||||
|
||||
pub x25519_public: [u8; X25519_PK_SIZE],
|
||||
|
||||
pub name: HeaplessVec<u8, MAX_NAME_LENGTH>,
|
||||
|
||||
pub avatar_hash: [u8; HASH_SIZE],
|
||||
|
||||
pub signature: [u8; ED25519_SIG_SIZE],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ContactHelloError {
|
||||
|
||||
InvalidVersion,
|
||||
|
||||
InvalidFormat,
|
||||
|
||||
SignatureInvalid,
|
||||
|
||||
BufferTooSmall,
|
||||
|
||||
DidTooLong,
|
||||
|
||||
NameTooLong,
|
||||
}
|
||||
|
||||
impl ContactHello {
|
||||
|
||||
pub fn new(
|
||||
timestamp: u64,
|
||||
did: &[u8],
|
||||
ed25519_public: [u8; 32],
|
||||
x25519_public: [u8; 32],
|
||||
name: &[u8],
|
||||
avatar_hash: Option<[u8; 32]>,
|
||||
) -> Result<Self, ContactHelloError> {
|
||||
if did.len() > MAX_DID_LENGTH {
|
||||
return Err(ContactHelloError::DidTooLong);
|
||||
}
|
||||
if name.len() > MAX_NAME_LENGTH {
|
||||
return Err(ContactHelloError::NameTooLong);
|
||||
}
|
||||
|
||||
let mut did_vec = HeaplessVec::new();
|
||||
did_vec.extend_from_slice(did).map_err(|_| ContactHelloError::DidTooLong)?;
|
||||
|
||||
let mut name_vec = HeaplessVec::new();
|
||||
name_vec.extend_from_slice(name).map_err(|_| ContactHelloError::NameTooLong)?;
|
||||
|
||||
Ok(Self {
|
||||
version: CONTACT_HELLO_VERSION,
|
||||
timestamp,
|
||||
did: did_vec,
|
||||
ed25519_public,
|
||||
x25519_public,
|
||||
name: name_vec,
|
||||
avatar_hash: avatar_hash.unwrap_or([0u8; 32]),
|
||||
signature: [0u8; 64],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sign(&mut self, private_key: &[u8; 32]) {
|
||||
let data_to_sign = self.encode_for_signing();
|
||||
let sig = Ed25519::sign(private_key, &data_to_sign);
|
||||
self.signature = sig.0;
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<bool, ContactHelloError> {
|
||||
let data = self.encode_for_signing();
|
||||
let sig = Signature(self.signature);
|
||||
Ok(Ed25519::verify(&self.ed25519_public, &data, &sig))
|
||||
}
|
||||
|
||||
fn encode_for_signing(&self) -> HeaplessVec<u8, 256> {
|
||||
let mut buf = HeaplessVec::new();
|
||||
|
||||
let _ = buf.push(self.version);
|
||||
|
||||
let _ = buf.extend_from_slice(&self.timestamp.to_le_bytes());
|
||||
|
||||
let did_len = self.did.len() as u16;
|
||||
let _ = buf.extend_from_slice(&did_len.to_le_bytes());
|
||||
let _ = buf.extend_from_slice(&self.did);
|
||||
|
||||
let _ = buf.extend_from_slice(&self.ed25519_public);
|
||||
|
||||
let _ = buf.extend_from_slice(&self.x25519_public);
|
||||
|
||||
let _ = buf.push(self.name.len() as u8);
|
||||
let _ = buf.extend_from_slice(&self.name);
|
||||
|
||||
let _ = buf.extend_from_slice(&self.avatar_hash);
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> HeaplessVec<u8, 512> {
|
||||
let signed_data = self.encode_for_signing();
|
||||
let mut buf = HeaplessVec::new();
|
||||
let _ = buf.extend_from_slice(&signed_data);
|
||||
let _ = buf.extend_from_slice(&self.signature);
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn decode(data: &[u8]) -> Result<Self, ContactHelloError> {
|
||||
if data.len() < 1 + 8 + 2 + 32 + 32 + 1 + 32 + 64 {
|
||||
return Err(ContactHelloError::InvalidFormat);
|
||||
}
|
||||
|
||||
let mut pos = 0;
|
||||
|
||||
let version = data[pos];
|
||||
if version != CONTACT_HELLO_VERSION {
|
||||
return Err(ContactHelloError::InvalidVersion);
|
||||
}
|
||||
pos += 1;
|
||||
|
||||
let mut ts_bytes = [0u8; 8];
|
||||
ts_bytes.copy_from_slice(&data[pos..pos + 8]);
|
||||
let timestamp = u64::from_le_bytes(ts_bytes);
|
||||
pos += 8;
|
||||
|
||||
let mut did_len_bytes = [0u8; 2];
|
||||
did_len_bytes.copy_from_slice(&data[pos..pos + 2]);
|
||||
let did_len = u16::from_le_bytes(did_len_bytes) as usize;
|
||||
pos += 2;
|
||||
|
||||
if did_len > MAX_DID_LENGTH || pos + did_len > data.len() {
|
||||
return Err(ContactHelloError::DidTooLong);
|
||||
}
|
||||
|
||||
let mut did = HeaplessVec::new();
|
||||
did.extend_from_slice(&data[pos..pos + did_len])
|
||||
.map_err(|_| ContactHelloError::DidTooLong)?;
|
||||
pos += did_len;
|
||||
|
||||
if pos + 32 > data.len() {
|
||||
return Err(ContactHelloError::InvalidFormat);
|
||||
}
|
||||
let mut ed25519_public = [0u8; 32];
|
||||
ed25519_public.copy_from_slice(&data[pos..pos + 32]);
|
||||
pos += 32;
|
||||
|
||||
if pos + 32 > data.len() {
|
||||
return Err(ContactHelloError::InvalidFormat);
|
||||
}
|
||||
let mut x25519_public = [0u8; 32];
|
||||
x25519_public.copy_from_slice(&data[pos..pos + 32]);
|
||||
pos += 32;
|
||||
|
||||
if pos >= data.len() {
|
||||
return Err(ContactHelloError::InvalidFormat);
|
||||
}
|
||||
let name_len = data[pos] as usize;
|
||||
pos += 1;
|
||||
|
||||
if name_len > MAX_NAME_LENGTH || pos + name_len > data.len() {
|
||||
return Err(ContactHelloError::NameTooLong);
|
||||
}
|
||||
|
||||
let mut name = HeaplessVec::new();
|
||||
name.extend_from_slice(&data[pos..pos + name_len])
|
||||
.map_err(|_| ContactHelloError::NameTooLong)?;
|
||||
pos += name_len;
|
||||
|
||||
if pos + 32 > data.len() {
|
||||
return Err(ContactHelloError::InvalidFormat);
|
||||
}
|
||||
let mut avatar_hash = [0u8; 32];
|
||||
avatar_hash.copy_from_slice(&data[pos..pos + 32]);
|
||||
pos += 32;
|
||||
|
||||
if pos + 64 > data.len() {
|
||||
return Err(ContactHelloError::InvalidFormat);
|
||||
}
|
||||
let mut signature = [0u8; 64];
|
||||
signature.copy_from_slice(&data[pos..pos + 64]);
|
||||
|
||||
Ok(Self {
|
||||
version,
|
||||
timestamp,
|
||||
did,
|
||||
ed25519_public,
|
||||
x25519_public,
|
||||
name,
|
||||
avatar_hash,
|
||||
signature,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_qr_data(&self) -> HeaplessVec<u8, 512> {
|
||||
|
||||
let mut buf = HeaplessVec::new();
|
||||
let _ = buf.extend_from_slice(b"YCH:");
|
||||
let _ = buf.extend_from_slice(&self.encode());
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn from_qr_data(data: &[u8]) -> Result<Self, ContactHelloError> {
|
||||
if data.len() < 4 || &data[..4] != b"YCH:" {
|
||||
return Err(ContactHelloError::InvalidFormat);
|
||||
}
|
||||
Self::decode(&data[4..])
|
||||
}
|
||||
|
||||
pub fn fingerprint(&self) -> [u8; 8] {
|
||||
let hash = Sha256::hash(&self.ed25519_public);
|
||||
let mut fp = [0u8; 8];
|
||||
fp.copy_from_slice(&hash[..8]);
|
||||
fp
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum TrustLevel {
|
||||
|
||||
Unknown = 0,
|
||||
|
||||
Seen = 1,
|
||||
|
||||
Verified = 2,
|
||||
|
||||
Trusted = 3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Contact {
|
||||
|
||||
pub hello: ContactHello,
|
||||
|
||||
pub trust: TrustLevel,
|
||||
|
||||
pub petname: HeaplessVec<u8, MAX_NAME_LENGTH>,
|
||||
|
||||
pub last_seen: u64,
|
||||
|
||||
pub message_count: u32,
|
||||
}
|
||||
|
||||
impl Contact {
|
||||
pub fn from_hello(hello: ContactHello) -> Self {
|
||||
Self {
|
||||
hello,
|
||||
trust: TrustLevel::Unknown,
|
||||
petname: HeaplessVec::new(),
|
||||
last_seen: 0,
|
||||
message_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_petname(&mut self, name: &[u8]) {
|
||||
self.petname.clear();
|
||||
let _ = self.petname.extend_from_slice(name);
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> &[u8] {
|
||||
if !self.petname.is_empty() {
|
||||
&self.petname
|
||||
} else {
|
||||
&self.hello.name
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> HeaplessVec<u8, 512> {
|
||||
let mut buf = HeaplessVec::new();
|
||||
|
||||
let hello_encoded = self.hello.encode();
|
||||
let hello_len = hello_encoded.len() as u16;
|
||||
let _ = buf.extend_from_slice(&hello_len.to_le_bytes());
|
||||
let _ = buf.extend_from_slice(&hello_encoded);
|
||||
|
||||
let _ = buf.push(self.trust as u8);
|
||||
|
||||
let _ = buf.push(self.petname.len() as u8);
|
||||
let _ = buf.extend_from_slice(&self.petname);
|
||||
|
||||
let _ = buf.extend_from_slice(&self.last_seen.to_le_bytes());
|
||||
|
||||
let _ = buf.extend_from_slice(&self.message_count.to_le_bytes());
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn deserialize(data: &[u8]) -> Option<Self> {
|
||||
if data.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut pos = 0;
|
||||
|
||||
let mut hello_len_bytes = [0u8; 2];
|
||||
hello_len_bytes.copy_from_slice(&data[pos..pos + 2]);
|
||||
let hello_len = u16::from_le_bytes(hello_len_bytes) as usize;
|
||||
pos += 2;
|
||||
|
||||
if pos + hello_len > data.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let hello = ContactHello::decode(&data[pos..pos + hello_len]).ok()?;
|
||||
pos += hello_len;
|
||||
|
||||
if pos >= data.len() {
|
||||
return None;
|
||||
}
|
||||
let trust = match data[pos] {
|
||||
0 => TrustLevel::Unknown,
|
||||
1 => TrustLevel::Seen,
|
||||
2 => TrustLevel::Verified,
|
||||
3 => TrustLevel::Trusted,
|
||||
_ => TrustLevel::Unknown,
|
||||
};
|
||||
pos += 1;
|
||||
|
||||
if pos >= data.len() {
|
||||
return None;
|
||||
}
|
||||
let petname_len = data[pos] as usize;
|
||||
pos += 1;
|
||||
|
||||
if pos + petname_len > data.len() {
|
||||
return None;
|
||||
}
|
||||
let mut petname = HeaplessVec::new();
|
||||
let _ = petname.extend_from_slice(&data[pos..pos + petname_len]);
|
||||
pos += petname_len;
|
||||
|
||||
if pos + 8 > data.len() {
|
||||
return None;
|
||||
}
|
||||
let mut last_seen_bytes = [0u8; 8];
|
||||
last_seen_bytes.copy_from_slice(&data[pos..pos + 8]);
|
||||
let last_seen = u64::from_le_bytes(last_seen_bytes);
|
||||
pos += 8;
|
||||
|
||||
if pos + 4 > data.len() {
|
||||
return None;
|
||||
}
|
||||
let mut count_bytes = [0u8; 4];
|
||||
count_bytes.copy_from_slice(&data[pos..pos + 4]);
|
||||
let message_count = u32::from_le_bytes(count_bytes);
|
||||
|
||||
Some(Self {
|
||||
hello,
|
||||
trust,
|
||||
petname,
|
||||
last_seen,
|
||||
message_count,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn key(&self) -> [u8; 8] {
|
||||
self.hello.fingerprint()
|
||||
}
|
||||
}
|
||||
|
||||
const NVS_CONTACT_NAMESPACE: &[u8] = b"contacts\0";
|
||||
|
||||
const MAX_CONTACTS: usize = 64;
|
||||
|
||||
pub struct ContactStore {
|
||||
|
||||
contacts: heapless::FnvIndexMap<[u8; 8], Contact, MAX_CONTACTS>,
|
||||
}
|
||||
|
||||
impl ContactStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
contacts: heapless::FnvIndexMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, contact: Contact) -> Result<(), ContactHelloError> {
|
||||
let key = contact.key();
|
||||
let _ = self.contacts.insert(key, contact);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get(&self, fingerprint: &[u8; 8]) -> Option<&Contact> {
|
||||
self.contacts.get(fingerprint)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, fingerprint: &[u8; 8]) -> Option<&mut Contact> {
|
||||
self.contacts.get_mut(fingerprint)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, fingerprint: &[u8; 8]) -> Option<Contact> {
|
||||
self.contacts.remove(fingerprint)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.contacts.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.contacts.is_empty()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&[u8; 8], &Contact)> {
|
||||
self.contacts.iter()
|
||||
}
|
||||
|
||||
pub fn find_by_pubkey(&self, ed25519_public: &[u8; 32]) -> Option<&Contact> {
|
||||
self.contacts.values().find(|c| &c.hello.ed25519_public == ed25519_public)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "xtensa")]
|
||||
pub fn save_to_nvs(&self) -> Result<(), ContactHelloError> {
|
||||
use esp_idf_sys::*;
|
||||
|
||||
unsafe {
|
||||
|
||||
let mut handle: nvs_handle_t = 0;
|
||||
let namespace = core::ffi::CStr::from_ptr(
|
||||
NVS_CONTACT_NAMESPACE.as_ptr() as *const core::ffi::c_char
|
||||
);
|
||||
|
||||
let mut err = nvs_open(
|
||||
namespace.as_ptr(),
|
||||
nvs_open_mode_t_NVS_READWRITE,
|
||||
&mut handle,
|
||||
);
|
||||
|
||||
if err != ESP_OK {
|
||||
nvs_flash_init();
|
||||
err = nvs_open(
|
||||
namespace.as_ptr(),
|
||||
nvs_open_mode_t_NVS_READWRITE,
|
||||
&mut handle,
|
||||
);
|
||||
if err != ESP_OK {
|
||||
return Err(ContactHelloError::BufferTooSmall);
|
||||
}
|
||||
}
|
||||
|
||||
let count_key = core::ffi::CStr::from_ptr(
|
||||
b"cnt_count\0".as_ptr() as *const core::ffi::c_char
|
||||
);
|
||||
nvs_set_u32(handle, count_key.as_ptr(), self.contacts.len() as u32);
|
||||
|
||||
let mut idx = 0u32;
|
||||
for (_key, contact) in self.contacts.iter() {
|
||||
|
||||
let mut key_name = [0u8; 16];
|
||||
let prefix = b"cnt_";
|
||||
key_name[..4].copy_from_slice(prefix);
|
||||
if idx < 10 {
|
||||
key_name[4] = b'0' + idx as u8;
|
||||
key_name[5] = 0;
|
||||
} else {
|
||||
key_name[4] = b'0' + (idx / 10) as u8;
|
||||
key_name[5] = b'0' + (idx % 10) as u8;
|
||||
key_name[6] = 0;
|
||||
}
|
||||
let key_cstr = core::ffi::CStr::from_ptr(
|
||||
key_name.as_ptr() as *const core::ffi::c_char
|
||||
);
|
||||
|
||||
let blob = contact.serialize();
|
||||
|
||||
nvs_set_blob(
|
||||
handle,
|
||||
key_cstr.as_ptr(),
|
||||
blob.as_ptr() as *const _,
|
||||
blob.len(),
|
||||
);
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
|
||||
::log::info!("Saved {} contacts to NVS", self.contacts.len());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "xtensa")]
|
||||
pub fn load_from_nvs(&mut self) -> Result<usize, ContactHelloError> {
|
||||
use esp_idf_sys::*;
|
||||
|
||||
unsafe {
|
||||
|
||||
let mut handle: nvs_handle_t = 0;
|
||||
let namespace = core::ffi::CStr::from_ptr(
|
||||
NVS_CONTACT_NAMESPACE.as_ptr() as *const core::ffi::c_char
|
||||
);
|
||||
|
||||
let err = nvs_open(
|
||||
namespace.as_ptr(),
|
||||
nvs_open_mode_t_NVS_READONLY,
|
||||
&mut handle,
|
||||
);
|
||||
|
||||
if err != ESP_OK {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let count_key = core::ffi::CStr::from_ptr(
|
||||
b"cnt_count\0".as_ptr() as *const core::ffi::c_char
|
||||
);
|
||||
let mut count: u32 = 0;
|
||||
if nvs_get_u32(handle, count_key.as_ptr(), &mut count) != ESP_OK {
|
||||
nvs_close(handle);
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let count = core::cmp::min(count as usize, MAX_CONTACTS);
|
||||
|
||||
let mut loaded = 0;
|
||||
for idx in 0..count {
|
||||
|
||||
let mut key_name = [0u8; 16];
|
||||
let prefix = b"cnt_";
|
||||
key_name[..4].copy_from_slice(prefix);
|
||||
if idx < 10 {
|
||||
key_name[4] = b'0' + idx as u8;
|
||||
key_name[5] = 0;
|
||||
} else {
|
||||
key_name[4] = b'0' + (idx / 10) as u8;
|
||||
key_name[5] = b'0' + (idx % 10) as u8;
|
||||
key_name[6] = 0;
|
||||
}
|
||||
let key_cstr = core::ffi::CStr::from_ptr(
|
||||
key_name.as_ptr() as *const core::ffi::c_char
|
||||
);
|
||||
|
||||
let mut blob = [0u8; 512];
|
||||
let mut blob_len = blob.len();
|
||||
|
||||
if nvs_get_blob(
|
||||
handle,
|
||||
key_cstr.as_ptr(),
|
||||
blob.as_mut_ptr() as *mut _,
|
||||
&mut blob_len,
|
||||
) == ESP_OK && blob_len > 0
|
||||
{
|
||||
|
||||
if let Some(contact) = Contact::deserialize(&blob[..blob_len]) {
|
||||
let key = contact.key();
|
||||
let _ = self.contacts.insert(key, contact);
|
||||
loaded += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nvs_close(handle);
|
||||
::log::info!("Loaded {} contacts from NVS", loaded);
|
||||
Ok(loaded)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "xtensa"))]
|
||||
pub fn save_to_nvs(&self) -> Result<(), ContactHelloError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "xtensa"))]
|
||||
pub fn load_from_nvs(&mut self) -> Result<usize, ContactHelloError> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
913
src/crypto/aes.rs
Normal file
913
src/crypto/aes.rs
Normal file
@@ -0,0 +1,913 @@
|
||||
#[inline(never)]
|
||||
fn sbox_ct(input: u8) -> u8 {
|
||||
|
||||
let x = input;
|
||||
let x2 = gf_square(x);
|
||||
let x4 = gf_square(x2);
|
||||
let x8 = gf_square(x4);
|
||||
let x16 = gf_square(x8);
|
||||
let x32 = gf_square(x16);
|
||||
let x64 = gf_square(x32);
|
||||
let x128 = gf_square(x64);
|
||||
|
||||
let inv = gf_mul_ct(x2, x4);
|
||||
let inv = gf_mul_ct(inv, x8);
|
||||
let inv = gf_mul_ct(inv, x16);
|
||||
let inv = gf_mul_ct(inv, x32);
|
||||
let inv = gf_mul_ct(inv, x64);
|
||||
let inv = gf_mul_ct(inv, x128);
|
||||
|
||||
affine_transform(inv)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn inv_sbox_ct(input: u8) -> u8 {
|
||||
|
||||
let x = inv_affine_transform(input);
|
||||
|
||||
let x2 = gf_square(x);
|
||||
let x4 = gf_square(x2);
|
||||
let x8 = gf_square(x4);
|
||||
let x16 = gf_square(x8);
|
||||
let x32 = gf_square(x16);
|
||||
let x64 = gf_square(x32);
|
||||
let x128 = gf_square(x64);
|
||||
|
||||
let inv = gf_mul_ct(x2, x4);
|
||||
let inv = gf_mul_ct(inv, x8);
|
||||
let inv = gf_mul_ct(inv, x16);
|
||||
let inv = gf_mul_ct(inv, x32);
|
||||
let inv = gf_mul_ct(inv, x64);
|
||||
gf_mul_ct(inv, x128)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn gf_square(x: u8) -> u8 {
|
||||
gf_mul_ct(x, x)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn gf_mul_ct(a: u8, b: u8) -> u8 {
|
||||
let mut result: u8 = 0;
|
||||
let mut aa = a;
|
||||
|
||||
result ^= aa & (((b & 0x01) as i8).wrapping_neg() as u8);
|
||||
let mask = ((aa >> 7) as i8).wrapping_neg() as u8;
|
||||
aa = (aa << 1) ^ (0x1b & mask);
|
||||
|
||||
result ^= aa & ((((b >> 1) & 0x01) as i8).wrapping_neg() as u8);
|
||||
let mask = ((aa >> 7) as i8).wrapping_neg() as u8;
|
||||
aa = (aa << 1) ^ (0x1b & mask);
|
||||
|
||||
result ^= aa & ((((b >> 2) & 0x01) as i8).wrapping_neg() as u8);
|
||||
let mask = ((aa >> 7) as i8).wrapping_neg() as u8;
|
||||
aa = (aa << 1) ^ (0x1b & mask);
|
||||
|
||||
result ^= aa & ((((b >> 3) & 0x01) as i8).wrapping_neg() as u8);
|
||||
let mask = ((aa >> 7) as i8).wrapping_neg() as u8;
|
||||
aa = (aa << 1) ^ (0x1b & mask);
|
||||
|
||||
result ^= aa & ((((b >> 4) & 0x01) as i8).wrapping_neg() as u8);
|
||||
let mask = ((aa >> 7) as i8).wrapping_neg() as u8;
|
||||
aa = (aa << 1) ^ (0x1b & mask);
|
||||
|
||||
result ^= aa & ((((b >> 5) & 0x01) as i8).wrapping_neg() as u8);
|
||||
let mask = ((aa >> 7) as i8).wrapping_neg() as u8;
|
||||
aa = (aa << 1) ^ (0x1b & mask);
|
||||
|
||||
result ^= aa & ((((b >> 6) & 0x01) as i8).wrapping_neg() as u8);
|
||||
let mask = ((aa >> 7) as i8).wrapping_neg() as u8;
|
||||
aa = (aa << 1) ^ (0x1b & mask);
|
||||
|
||||
result ^= aa & ((((b >> 7) & 0x01) as i8).wrapping_neg() as u8);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn affine_transform(x: u8) -> u8 {
|
||||
|
||||
let mut result = 0u8;
|
||||
|
||||
result |= (parity(x & 0b11110001) ^ 1) << 0;
|
||||
result |= (parity(x & 0b11100011) ^ 1) << 1;
|
||||
result |= (parity(x & 0b11000111) ^ 0) << 2;
|
||||
result |= (parity(x & 0b10001111) ^ 0) << 3;
|
||||
result |= (parity(x & 0b00011111) ^ 0) << 4;
|
||||
result |= (parity(x & 0b00111110) ^ 1) << 5;
|
||||
result |= (parity(x & 0b01111100) ^ 1) << 6;
|
||||
result |= (parity(x & 0b11111000) ^ 0) << 7;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inv_affine_transform(x: u8) -> u8 {
|
||||
|
||||
let y = x ^ 0x63;
|
||||
|
||||
let mut result = 0u8;
|
||||
|
||||
result |= parity(y & 0b10100100) << 0;
|
||||
result |= parity(y & 0b01001001) << 1;
|
||||
result |= parity(y & 0b10010010) << 2;
|
||||
result |= parity(y & 0b00100101) << 3;
|
||||
result |= parity(y & 0b01001010) << 4;
|
||||
result |= parity(y & 0b10010100) << 5;
|
||||
result |= parity(y & 0b00101001) << 6;
|
||||
result |= parity(y & 0b01010010) << 7;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parity(mut x: u8) -> u8 {
|
||||
x ^= x >> 4;
|
||||
x ^= x >> 2;
|
||||
x ^= x >> 1;
|
||||
x & 1
|
||||
}
|
||||
|
||||
const SBOX: [u8; 256] = [
|
||||
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
||||
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
||||
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
|
||||
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
|
||||
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
|
||||
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
|
||||
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
|
||||
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
|
||||
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
|
||||
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
||||
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
|
||||
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
|
||||
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
|
||||
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
||||
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
|
||||
];
|
||||
|
||||
const INV_SBOX: [u8; 256] = [
|
||||
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
|
||||
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
|
||||
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
|
||||
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
|
||||
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
|
||||
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
|
||||
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
|
||||
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
|
||||
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
|
||||
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
|
||||
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
|
||||
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
|
||||
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
|
||||
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
|
||||
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
|
||||
];
|
||||
|
||||
const RCON: [u8; 11] = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
|
||||
|
||||
pub const BLOCK_SIZE: usize = 16;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AesMode {
|
||||
|
||||
Ecb,
|
||||
|
||||
Ctr,
|
||||
|
||||
Cbc,
|
||||
}
|
||||
|
||||
pub struct Aes128 {
|
||||
|
||||
round_keys: [u8; 176],
|
||||
}
|
||||
|
||||
pub struct Aes256 {
|
||||
|
||||
round_keys: [u8; 240],
|
||||
}
|
||||
|
||||
impl Drop for Aes128 {
|
||||
fn drop(&mut self) {
|
||||
|
||||
crate::crypto::secure_zero(&mut self.round_keys);
|
||||
}
|
||||
}
|
||||
|
||||
impl Aes128 {
|
||||
|
||||
pub fn new(key: &[u8; 16]) -> Self {
|
||||
let mut cipher = Self {
|
||||
round_keys: [0u8; 176],
|
||||
};
|
||||
cipher.key_expansion(key);
|
||||
cipher
|
||||
}
|
||||
|
||||
fn key_expansion(&mut self, key: &[u8; 16]) {
|
||||
|
||||
self.round_keys[..16].copy_from_slice(key);
|
||||
|
||||
let mut i = 16;
|
||||
let mut rcon_idx = 1;
|
||||
|
||||
while i < 176 {
|
||||
|
||||
let mut temp = [
|
||||
self.round_keys[i - 4],
|
||||
self.round_keys[i - 3],
|
||||
self.round_keys[i - 2],
|
||||
self.round_keys[i - 1],
|
||||
];
|
||||
|
||||
if i % 16 == 0 {
|
||||
|
||||
temp.rotate_left(1);
|
||||
|
||||
for byte in &mut temp {
|
||||
*byte = SBOX[*byte as usize];
|
||||
}
|
||||
|
||||
temp[0] ^= RCON[rcon_idx];
|
||||
rcon_idx += 1;
|
||||
}
|
||||
|
||||
for j in 0..4 {
|
||||
self.round_keys[i + j] = self.round_keys[i - 16 + j] ^ temp[j];
|
||||
}
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt_block(&self, block: &mut [u8; 16]) {
|
||||
let mut state = *block;
|
||||
|
||||
Self::add_round_key(&mut state, &self.round_keys[0..16]);
|
||||
|
||||
for round in 1..10 {
|
||||
Self::sub_bytes(&mut state);
|
||||
Self::shift_rows(&mut state);
|
||||
Self::mix_columns(&mut state);
|
||||
Self::add_round_key(&mut state, &self.round_keys[round * 16..(round + 1) * 16]);
|
||||
}
|
||||
|
||||
Self::sub_bytes(&mut state);
|
||||
Self::shift_rows(&mut state);
|
||||
Self::add_round_key(&mut state, &self.round_keys[160..176]);
|
||||
|
||||
*block = state;
|
||||
}
|
||||
|
||||
pub fn decrypt_block(&self, block: &mut [u8; 16]) {
|
||||
let mut state = *block;
|
||||
|
||||
Self::add_round_key(&mut state, &self.round_keys[160..176]);
|
||||
|
||||
for round in (1..10).rev() {
|
||||
Self::inv_shift_rows(&mut state);
|
||||
Self::inv_sub_bytes(&mut state);
|
||||
Self::add_round_key(&mut state, &self.round_keys[round * 16..(round + 1) * 16]);
|
||||
Self::inv_mix_columns(&mut state);
|
||||
}
|
||||
|
||||
Self::inv_shift_rows(&mut state);
|
||||
Self::inv_sub_bytes(&mut state);
|
||||
Self::add_round_key(&mut state, &self.round_keys[0..16]);
|
||||
|
||||
*block = state;
|
||||
}
|
||||
|
||||
pub fn encrypt_ctr(&self, nonce: &[u8; 16], data: &mut [u8]) {
|
||||
let mut counter = *nonce;
|
||||
let mut keystream = [0u8; 16];
|
||||
|
||||
for (block_idx, chunk) in data.chunks_mut(16).enumerate() {
|
||||
|
||||
keystream = counter;
|
||||
self.encrypt_block(&mut keystream);
|
||||
|
||||
for (i, byte) in chunk.iter_mut().enumerate() {
|
||||
*byte ^= keystream[i];
|
||||
}
|
||||
|
||||
Self::increment_counter(&mut counter);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_ctr(&self, nonce: &[u8; 16], data: &mut [u8]) {
|
||||
self.encrypt_ctr(nonce, data);
|
||||
}
|
||||
|
||||
pub fn encrypt_cbc(&self, iv: &[u8; 16], data: &mut [u8]) {
|
||||
assert!(data.len() % 16 == 0, "Data must be multiple of block size");
|
||||
|
||||
let mut prev = *iv;
|
||||
|
||||
for chunk in data.chunks_mut(16) {
|
||||
|
||||
for (i, byte) in chunk.iter_mut().enumerate() {
|
||||
*byte ^= prev[i];
|
||||
}
|
||||
|
||||
let mut block = [0u8; 16];
|
||||
block.copy_from_slice(chunk);
|
||||
self.encrypt_block(&mut block);
|
||||
chunk.copy_from_slice(&block);
|
||||
|
||||
prev.copy_from_slice(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_cbc(&self, iv: &[u8; 16], data: &mut [u8]) {
|
||||
assert!(data.len() % 16 == 0, "Data must be multiple of block size");
|
||||
|
||||
let mut prev = *iv;
|
||||
|
||||
for chunk in data.chunks_mut(16) {
|
||||
|
||||
let mut saved = [0u8; 16];
|
||||
saved.copy_from_slice(chunk);
|
||||
|
||||
let mut block = [0u8; 16];
|
||||
block.copy_from_slice(chunk);
|
||||
self.decrypt_block(&mut block);
|
||||
|
||||
for (i, byte) in block.iter_mut().enumerate() {
|
||||
*byte ^= prev[i];
|
||||
}
|
||||
chunk.copy_from_slice(&block);
|
||||
|
||||
prev = saved;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sub_bytes(state: &mut [u8; 16]) {
|
||||
for byte in state.iter_mut() {
|
||||
*byte = SBOX[*byte as usize];
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inv_sub_bytes(state: &mut [u8; 16]) {
|
||||
for byte in state.iter_mut() {
|
||||
*byte = INV_SBOX[*byte as usize];
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn shift_rows(state: &mut [u8; 16]) {
|
||||
|
||||
let temp = state[1];
|
||||
state[1] = state[5];
|
||||
state[5] = state[9];
|
||||
state[9] = state[13];
|
||||
state[13] = temp;
|
||||
|
||||
let temp0 = state[2];
|
||||
let temp1 = state[6];
|
||||
state[2] = state[10];
|
||||
state[6] = state[14];
|
||||
state[10] = temp0;
|
||||
state[14] = temp1;
|
||||
|
||||
let temp = state[15];
|
||||
state[15] = state[11];
|
||||
state[11] = state[7];
|
||||
state[7] = state[3];
|
||||
state[3] = temp;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inv_shift_rows(state: &mut [u8; 16]) {
|
||||
|
||||
let temp = state[13];
|
||||
state[13] = state[9];
|
||||
state[9] = state[5];
|
||||
state[5] = state[1];
|
||||
state[1] = temp;
|
||||
|
||||
let temp0 = state[2];
|
||||
let temp1 = state[6];
|
||||
state[2] = state[10];
|
||||
state[6] = state[14];
|
||||
state[10] = temp0;
|
||||
state[14] = temp1;
|
||||
|
||||
let temp = state[3];
|
||||
state[3] = state[7];
|
||||
state[7] = state[11];
|
||||
state[11] = state[15];
|
||||
state[15] = temp;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mix_columns(state: &mut [u8; 16]) {
|
||||
for col in 0..4 {
|
||||
let i = col * 4;
|
||||
let s0 = state[i];
|
||||
let s1 = state[i + 1];
|
||||
let s2 = state[i + 2];
|
||||
let s3 = state[i + 3];
|
||||
|
||||
state[i] = gf_mul(s0, 2) ^ gf_mul(s1, 3) ^ s2 ^ s3;
|
||||
state[i + 1] = s0 ^ gf_mul(s1, 2) ^ gf_mul(s2, 3) ^ s3;
|
||||
state[i + 2] = s0 ^ s1 ^ gf_mul(s2, 2) ^ gf_mul(s3, 3);
|
||||
state[i + 3] = gf_mul(s0, 3) ^ s1 ^ s2 ^ gf_mul(s3, 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inv_mix_columns(state: &mut [u8; 16]) {
|
||||
for col in 0..4 {
|
||||
let i = col * 4;
|
||||
let s0 = state[i];
|
||||
let s1 = state[i + 1];
|
||||
let s2 = state[i + 2];
|
||||
let s3 = state[i + 3];
|
||||
|
||||
state[i] = gf_mul(s0, 0x0e) ^ gf_mul(s1, 0x0b) ^ gf_mul(s2, 0x0d) ^ gf_mul(s3, 0x09);
|
||||
state[i + 1] = gf_mul(s0, 0x09) ^ gf_mul(s1, 0x0e) ^ gf_mul(s2, 0x0b) ^ gf_mul(s3, 0x0d);
|
||||
state[i + 2] = gf_mul(s0, 0x0d) ^ gf_mul(s1, 0x09) ^ gf_mul(s2, 0x0e) ^ gf_mul(s3, 0x0b);
|
||||
state[i + 3] = gf_mul(s0, 0x0b) ^ gf_mul(s1, 0x0d) ^ gf_mul(s2, 0x09) ^ gf_mul(s3, 0x0e);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_round_key(state: &mut [u8; 16], round_key: &[u8]) {
|
||||
for (i, byte) in state.iter_mut().enumerate() {
|
||||
*byte ^= round_key[i];
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment_counter(counter: &mut [u8; 16]) {
|
||||
for i in (0..16).rev() {
|
||||
counter[i] = counter[i].wrapping_add(1);
|
||||
if counter[i] != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Aes256 {
|
||||
fn drop(&mut self) {
|
||||
|
||||
crate::crypto::secure_zero(&mut self.round_keys);
|
||||
}
|
||||
}
|
||||
|
||||
impl Aes256 {
|
||||
|
||||
pub fn new(key: &[u8; 32]) -> Self {
|
||||
let mut cipher = Self {
|
||||
round_keys: [0u8; 240],
|
||||
};
|
||||
cipher.key_expansion(key);
|
||||
cipher
|
||||
}
|
||||
|
||||
fn key_expansion(&mut self, key: &[u8; 32]) {
|
||||
|
||||
self.round_keys[..32].copy_from_slice(key);
|
||||
|
||||
let mut i = 32;
|
||||
let mut rcon_idx = 1;
|
||||
|
||||
while i < 240 {
|
||||
let mut temp = [
|
||||
self.round_keys[i - 4],
|
||||
self.round_keys[i - 3],
|
||||
self.round_keys[i - 2],
|
||||
self.round_keys[i - 1],
|
||||
];
|
||||
|
||||
if i % 32 == 0 {
|
||||
|
||||
temp.rotate_left(1);
|
||||
|
||||
for byte in &mut temp {
|
||||
*byte = SBOX[*byte as usize];
|
||||
}
|
||||
|
||||
temp[0] ^= RCON[rcon_idx];
|
||||
rcon_idx += 1;
|
||||
} else if i % 32 == 16 {
|
||||
|
||||
for byte in &mut temp {
|
||||
*byte = SBOX[*byte as usize];
|
||||
}
|
||||
}
|
||||
|
||||
for j in 0..4 {
|
||||
self.round_keys[i + j] = self.round_keys[i - 32 + j] ^ temp[j];
|
||||
}
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt_block(&self, block: &mut [u8; 16]) {
|
||||
let mut state = *block;
|
||||
|
||||
add_round_key_256(&mut state, &self.round_keys[0..16]);
|
||||
|
||||
for round in 1..14 {
|
||||
sub_bytes_256(&mut state);
|
||||
shift_rows_256(&mut state);
|
||||
mix_columns_256(&mut state);
|
||||
add_round_key_256(&mut state, &self.round_keys[round * 16..(round + 1) * 16]);
|
||||
}
|
||||
|
||||
sub_bytes_256(&mut state);
|
||||
shift_rows_256(&mut state);
|
||||
add_round_key_256(&mut state, &self.round_keys[224..240]);
|
||||
|
||||
*block = state;
|
||||
}
|
||||
|
||||
pub fn decrypt_block(&self, block: &mut [u8; 16]) {
|
||||
let mut state = *block;
|
||||
|
||||
add_round_key_256(&mut state, &self.round_keys[224..240]);
|
||||
|
||||
for round in (1..14).rev() {
|
||||
inv_shift_rows_256(&mut state);
|
||||
inv_sub_bytes_256(&mut state);
|
||||
add_round_key_256(&mut state, &self.round_keys[round * 16..(round + 1) * 16]);
|
||||
inv_mix_columns_256(&mut state);
|
||||
}
|
||||
|
||||
inv_shift_rows_256(&mut state);
|
||||
inv_sub_bytes_256(&mut state);
|
||||
add_round_key_256(&mut state, &self.round_keys[0..16]);
|
||||
|
||||
*block = state;
|
||||
}
|
||||
|
||||
pub fn encrypt_ctr(&self, nonce: &[u8; 16], data: &mut [u8]) {
|
||||
let mut counter = *nonce;
|
||||
let mut keystream = [0u8; 16];
|
||||
|
||||
for chunk in data.chunks_mut(16) {
|
||||
keystream = counter;
|
||||
self.encrypt_block(&mut keystream);
|
||||
|
||||
for (i, byte) in chunk.iter_mut().enumerate() {
|
||||
*byte ^= keystream[i];
|
||||
}
|
||||
|
||||
increment_counter_256(&mut counter);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_ctr(&self, nonce: &[u8; 16], data: &mut [u8]) {
|
||||
self.encrypt_ctr(nonce, data);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sub_bytes_256(state: &mut [u8; 16]) {
|
||||
for byte in state.iter_mut() {
|
||||
*byte = SBOX[*byte as usize];
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inv_sub_bytes_256(state: &mut [u8; 16]) {
|
||||
for byte in state.iter_mut() {
|
||||
*byte = INV_SBOX[*byte as usize];
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn shift_rows_256(state: &mut [u8; 16]) {
|
||||
let temp = state[1];
|
||||
state[1] = state[5];
|
||||
state[5] = state[9];
|
||||
state[9] = state[13];
|
||||
state[13] = temp;
|
||||
|
||||
let temp0 = state[2];
|
||||
let temp1 = state[6];
|
||||
state[2] = state[10];
|
||||
state[6] = state[14];
|
||||
state[10] = temp0;
|
||||
state[14] = temp1;
|
||||
|
||||
let temp = state[15];
|
||||
state[15] = state[11];
|
||||
state[11] = state[7];
|
||||
state[7] = state[3];
|
||||
state[3] = temp;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inv_shift_rows_256(state: &mut [u8; 16]) {
|
||||
let temp = state[13];
|
||||
state[13] = state[9];
|
||||
state[9] = state[5];
|
||||
state[5] = state[1];
|
||||
state[1] = temp;
|
||||
|
||||
let temp0 = state[2];
|
||||
let temp1 = state[6];
|
||||
state[2] = state[10];
|
||||
state[6] = state[14];
|
||||
state[10] = temp0;
|
||||
state[14] = temp1;
|
||||
|
||||
let temp = state[3];
|
||||
state[3] = state[7];
|
||||
state[7] = state[11];
|
||||
state[11] = state[15];
|
||||
state[15] = temp;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mix_columns_256(state: &mut [u8; 16]) {
|
||||
for col in 0..4 {
|
||||
let i = col * 4;
|
||||
let s0 = state[i];
|
||||
let s1 = state[i + 1];
|
||||
let s2 = state[i + 2];
|
||||
let s3 = state[i + 3];
|
||||
|
||||
state[i] = gf_mul(s0, 2) ^ gf_mul(s1, 3) ^ s2 ^ s3;
|
||||
state[i + 1] = s0 ^ gf_mul(s1, 2) ^ gf_mul(s2, 3) ^ s3;
|
||||
state[i + 2] = s0 ^ s1 ^ gf_mul(s2, 2) ^ gf_mul(s3, 3);
|
||||
state[i + 3] = gf_mul(s0, 3) ^ s1 ^ s2 ^ gf_mul(s3, 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inv_mix_columns_256(state: &mut [u8; 16]) {
|
||||
for col in 0..4 {
|
||||
let i = col * 4;
|
||||
let s0 = state[i];
|
||||
let s1 = state[i + 1];
|
||||
let s2 = state[i + 2];
|
||||
let s3 = state[i + 3];
|
||||
|
||||
state[i] = gf_mul(s0, 0x0e) ^ gf_mul(s1, 0x0b) ^ gf_mul(s2, 0x0d) ^ gf_mul(s3, 0x09);
|
||||
state[i + 1] = gf_mul(s0, 0x09) ^ gf_mul(s1, 0x0e) ^ gf_mul(s2, 0x0b) ^ gf_mul(s3, 0x0d);
|
||||
state[i + 2] = gf_mul(s0, 0x0d) ^ gf_mul(s1, 0x09) ^ gf_mul(s2, 0x0e) ^ gf_mul(s3, 0x0b);
|
||||
state[i + 3] = gf_mul(s0, 0x0b) ^ gf_mul(s1, 0x0d) ^ gf_mul(s2, 0x09) ^ gf_mul(s3, 0x0e);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_round_key_256(state: &mut [u8; 16], round_key: &[u8]) {
|
||||
for (i, byte) in state.iter_mut().enumerate() {
|
||||
*byte ^= round_key[i];
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment_counter_256(counter: &mut [u8; 16]) {
|
||||
for i in (0..16).rev() {
|
||||
counter[i] = counter[i].wrapping_add(1);
|
||||
if counter[i] != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn gf_mul(mut a: u8, mut b: u8) -> u8 {
|
||||
let mut result: u8 = 0;
|
||||
while b != 0 {
|
||||
if b & 1 != 0 {
|
||||
result ^= a;
|
||||
}
|
||||
let high_bit = a & 0x80;
|
||||
a <<= 1;
|
||||
if high_bit != 0 {
|
||||
a ^= 0x1b;
|
||||
}
|
||||
b >>= 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub struct Aes128Ct {
|
||||
|
||||
round_keys: [u8; 176],
|
||||
}
|
||||
|
||||
impl Drop for Aes128Ct {
|
||||
fn drop(&mut self) {
|
||||
|
||||
crate::crypto::secure_zero(&mut self.round_keys);
|
||||
}
|
||||
}
|
||||
|
||||
impl Aes128Ct {
|
||||
|
||||
pub fn new(key: &[u8; 16]) -> Self {
|
||||
let mut cipher = Self {
|
||||
round_keys: [0u8; 176],
|
||||
};
|
||||
cipher.key_expansion(key);
|
||||
cipher
|
||||
}
|
||||
|
||||
fn key_expansion(&mut self, key: &[u8; 16]) {
|
||||
self.round_keys[..16].copy_from_slice(key);
|
||||
|
||||
let mut i = 16;
|
||||
let mut rcon_idx = 1;
|
||||
|
||||
while i < 176 {
|
||||
let mut temp = [
|
||||
self.round_keys[i - 4],
|
||||
self.round_keys[i - 3],
|
||||
self.round_keys[i - 2],
|
||||
self.round_keys[i - 1],
|
||||
];
|
||||
|
||||
if i % 16 == 0 {
|
||||
temp.rotate_left(1);
|
||||
|
||||
for byte in &mut temp {
|
||||
*byte = sbox_ct(*byte);
|
||||
}
|
||||
temp[0] ^= RCON[rcon_idx];
|
||||
rcon_idx += 1;
|
||||
}
|
||||
|
||||
for j in 0..4 {
|
||||
self.round_keys[i + j] = self.round_keys[i - 16 + j] ^ temp[j];
|
||||
}
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt_block(&self, block: &mut [u8; 16]) {
|
||||
let mut state = *block;
|
||||
|
||||
Self::add_round_key(&mut state, &self.round_keys[0..16]);
|
||||
|
||||
for round in 1..10 {
|
||||
Self::sub_bytes_ct(&mut state);
|
||||
Self::shift_rows(&mut state);
|
||||
Self::mix_columns(&mut state);
|
||||
Self::add_round_key(&mut state, &self.round_keys[round * 16..(round + 1) * 16]);
|
||||
}
|
||||
|
||||
Self::sub_bytes_ct(&mut state);
|
||||
Self::shift_rows(&mut state);
|
||||
Self::add_round_key(&mut state, &self.round_keys[160..176]);
|
||||
|
||||
*block = state;
|
||||
}
|
||||
|
||||
pub fn decrypt_block(&self, block: &mut [u8; 16]) {
|
||||
let mut state = *block;
|
||||
|
||||
Self::add_round_key(&mut state, &self.round_keys[160..176]);
|
||||
|
||||
for round in (1..10).rev() {
|
||||
Self::inv_shift_rows(&mut state);
|
||||
Self::inv_sub_bytes_ct(&mut state);
|
||||
Self::add_round_key(&mut state, &self.round_keys[round * 16..(round + 1) * 16]);
|
||||
Self::inv_mix_columns(&mut state);
|
||||
}
|
||||
|
||||
Self::inv_shift_rows(&mut state);
|
||||
Self::inv_sub_bytes_ct(&mut state);
|
||||
Self::add_round_key(&mut state, &self.round_keys[0..16]);
|
||||
|
||||
*block = state;
|
||||
}
|
||||
|
||||
pub fn encrypt_ctr(&self, nonce: &[u8; 16], data: &mut [u8]) {
|
||||
let mut counter = *nonce;
|
||||
let mut keystream = [0u8; 16];
|
||||
|
||||
for chunk in data.chunks_mut(16) {
|
||||
keystream = counter;
|
||||
self.encrypt_block(&mut keystream);
|
||||
|
||||
for (i, byte) in chunk.iter_mut().enumerate() {
|
||||
*byte ^= keystream[i];
|
||||
}
|
||||
|
||||
Self::increment_counter(&mut counter);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_ctr(&self, nonce: &[u8; 16], data: &mut [u8]) {
|
||||
self.encrypt_ctr(nonce, data);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sub_bytes_ct(state: &mut [u8; 16]) {
|
||||
for byte in state.iter_mut() {
|
||||
*byte = sbox_ct(*byte);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inv_sub_bytes_ct(state: &mut [u8; 16]) {
|
||||
for byte in state.iter_mut() {
|
||||
*byte = inv_sbox_ct(*byte);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn shift_rows(state: &mut [u8; 16]) {
|
||||
let temp = state[1];
|
||||
state[1] = state[5];
|
||||
state[5] = state[9];
|
||||
state[9] = state[13];
|
||||
state[13] = temp;
|
||||
|
||||
let temp0 = state[2];
|
||||
let temp1 = state[6];
|
||||
state[2] = state[10];
|
||||
state[6] = state[14];
|
||||
state[10] = temp0;
|
||||
state[14] = temp1;
|
||||
|
||||
let temp = state[15];
|
||||
state[15] = state[11];
|
||||
state[11] = state[7];
|
||||
state[7] = state[3];
|
||||
state[3] = temp;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inv_shift_rows(state: &mut [u8; 16]) {
|
||||
let temp = state[13];
|
||||
state[13] = state[9];
|
||||
state[9] = state[5];
|
||||
state[5] = state[1];
|
||||
state[1] = temp;
|
||||
|
||||
let temp0 = state[2];
|
||||
let temp1 = state[6];
|
||||
state[2] = state[10];
|
||||
state[6] = state[14];
|
||||
state[10] = temp0;
|
||||
state[14] = temp1;
|
||||
|
||||
let temp = state[3];
|
||||
state[3] = state[7];
|
||||
state[7] = state[11];
|
||||
state[11] = state[15];
|
||||
state[15] = temp;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mix_columns(state: &mut [u8; 16]) {
|
||||
for col in 0..4 {
|
||||
let i = col * 4;
|
||||
let s0 = state[i];
|
||||
let s1 = state[i + 1];
|
||||
let s2 = state[i + 2];
|
||||
let s3 = state[i + 3];
|
||||
|
||||
state[i] = gf_mul_ct(s0, 2) ^ gf_mul_ct(s1, 3) ^ s2 ^ s3;
|
||||
state[i + 1] = s0 ^ gf_mul_ct(s1, 2) ^ gf_mul_ct(s2, 3) ^ s3;
|
||||
state[i + 2] = s0 ^ s1 ^ gf_mul_ct(s2, 2) ^ gf_mul_ct(s3, 3);
|
||||
state[i + 3] = gf_mul_ct(s0, 3) ^ s1 ^ s2 ^ gf_mul_ct(s3, 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inv_mix_columns(state: &mut [u8; 16]) {
|
||||
for col in 0..4 {
|
||||
let i = col * 4;
|
||||
let s0 = state[i];
|
||||
let s1 = state[i + 1];
|
||||
let s2 = state[i + 2];
|
||||
let s3 = state[i + 3];
|
||||
|
||||
state[i] = gf_mul_ct(s0, 0x0e) ^ gf_mul_ct(s1, 0x0b) ^ gf_mul_ct(s2, 0x0d) ^ gf_mul_ct(s3, 0x09);
|
||||
state[i + 1] = gf_mul_ct(s0, 0x09) ^ gf_mul_ct(s1, 0x0e) ^ gf_mul_ct(s2, 0x0b) ^ gf_mul_ct(s3, 0x0d);
|
||||
state[i + 2] = gf_mul_ct(s0, 0x0d) ^ gf_mul_ct(s1, 0x09) ^ gf_mul_ct(s2, 0x0e) ^ gf_mul_ct(s3, 0x0b);
|
||||
state[i + 3] = gf_mul_ct(s0, 0x0b) ^ gf_mul_ct(s1, 0x0d) ^ gf_mul_ct(s2, 0x09) ^ gf_mul_ct(s3, 0x0e);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_round_key(state: &mut [u8; 16], round_key: &[u8]) {
|
||||
for (i, byte) in state.iter_mut().enumerate() {
|
||||
*byte ^= round_key[i];
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment_counter(counter: &mut [u8; 16]) {
|
||||
for i in (0..16).rev() {
|
||||
counter[i] = counter[i].wrapping_add(1);
|
||||
if counter[i] != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
229
src/crypto/chacha20.rs
Normal file
229
src/crypto/chacha20.rs
Normal file
@@ -0,0 +1,229 @@
|
||||
const STATE_SIZE: usize = 16;
|
||||
|
||||
pub const BLOCK_SIZE: usize = 64;
|
||||
|
||||
pub const KEY_SIZE: usize = 32;
|
||||
|
||||
pub const NONCE_SIZE: usize = 12;
|
||||
|
||||
pub struct ChaCha20 {
|
||||
|
||||
state: [u32; STATE_SIZE],
|
||||
}
|
||||
|
||||
impl Drop for ChaCha20 {
|
||||
fn drop(&mut self) {
|
||||
|
||||
crate::crypto::secure_zero_u32(&mut self.state);
|
||||
}
|
||||
}
|
||||
|
||||
impl ChaCha20 {
|
||||
|
||||
const CONSTANTS: [u32; 4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];
|
||||
|
||||
pub fn new(key: &[u8; KEY_SIZE], nonce: &[u8; NONCE_SIZE]) -> Self {
|
||||
let mut state = [0u32; STATE_SIZE];
|
||||
|
||||
state[0] = Self::CONSTANTS[0];
|
||||
state[1] = Self::CONSTANTS[1];
|
||||
state[2] = Self::CONSTANTS[2];
|
||||
state[3] = Self::CONSTANTS[3];
|
||||
|
||||
state[4] = u32::from_le_bytes([key[0], key[1], key[2], key[3]]);
|
||||
state[5] = u32::from_le_bytes([key[4], key[5], key[6], key[7]]);
|
||||
state[6] = u32::from_le_bytes([key[8], key[9], key[10], key[11]]);
|
||||
state[7] = u32::from_le_bytes([key[12], key[13], key[14], key[15]]);
|
||||
state[8] = u32::from_le_bytes([key[16], key[17], key[18], key[19]]);
|
||||
state[9] = u32::from_le_bytes([key[20], key[21], key[22], key[23]]);
|
||||
state[10] = u32::from_le_bytes([key[24], key[25], key[26], key[27]]);
|
||||
state[11] = u32::from_le_bytes([key[28], key[29], key[30], key[31]]);
|
||||
|
||||
state[12] = 0;
|
||||
|
||||
state[13] = u32::from_le_bytes([nonce[0], nonce[1], nonce[2], nonce[3]]);
|
||||
state[14] = u32::from_le_bytes([nonce[4], nonce[5], nonce[6], nonce[7]]);
|
||||
state[15] = u32::from_le_bytes([nonce[8], nonce[9], nonce[10], nonce[11]]);
|
||||
|
||||
Self { state }
|
||||
}
|
||||
|
||||
pub fn new_with_counter(key: &[u8; KEY_SIZE], nonce: &[u8; NONCE_SIZE], counter: u32) -> Self {
|
||||
let mut cipher = Self::new(key, nonce);
|
||||
cipher.state[12] = counter;
|
||||
cipher
|
||||
}
|
||||
|
||||
fn block(&self, counter: u32) -> [u8; BLOCK_SIZE] {
|
||||
let mut state = self.state;
|
||||
state[12] = counter;
|
||||
|
||||
let mut working = state;
|
||||
|
||||
for _ in 0..10 {
|
||||
|
||||
quarter_round(&mut working, 0, 4, 8, 12);
|
||||
quarter_round(&mut working, 1, 5, 9, 13);
|
||||
quarter_round(&mut working, 2, 6, 10, 14);
|
||||
quarter_round(&mut working, 3, 7, 11, 15);
|
||||
|
||||
quarter_round(&mut working, 0, 5, 10, 15);
|
||||
quarter_round(&mut working, 1, 6, 11, 12);
|
||||
quarter_round(&mut working, 2, 7, 8, 13);
|
||||
quarter_round(&mut working, 3, 4, 9, 14);
|
||||
}
|
||||
|
||||
for i in 0..STATE_SIZE {
|
||||
working[i] = working[i].wrapping_add(state[i]);
|
||||
}
|
||||
|
||||
let mut output = [0u8; BLOCK_SIZE];
|
||||
for (i, word) in working.iter().enumerate() {
|
||||
let bytes = word.to_le_bytes();
|
||||
output[i * 4] = bytes[0];
|
||||
output[i * 4 + 1] = bytes[1];
|
||||
output[i * 4 + 2] = bytes[2];
|
||||
output[i * 4 + 3] = bytes[3];
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
pub fn apply_keystream(&self, data: &mut [u8]) {
|
||||
let mut counter = self.state[12];
|
||||
|
||||
for chunk in data.chunks_mut(BLOCK_SIZE) {
|
||||
let keystream = self.block(counter);
|
||||
for (i, byte) in chunk.iter_mut().enumerate() {
|
||||
*byte ^= keystream[i];
|
||||
}
|
||||
counter = counter.wrapping_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt(&self, data: &mut [u8]) {
|
||||
self.apply_keystream(data);
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, data: &mut [u8]) {
|
||||
self.apply_keystream(data);
|
||||
}
|
||||
|
||||
pub fn keystream(&self, len: usize) -> heapless::Vec<u8, 1024> {
|
||||
let mut output = heapless::Vec::new();
|
||||
let mut counter = self.state[12];
|
||||
|
||||
let mut remaining = len;
|
||||
while remaining > 0 {
|
||||
let block = self.block(counter);
|
||||
let to_copy = core::cmp::min(remaining, BLOCK_SIZE);
|
||||
for i in 0..to_copy {
|
||||
let _ = output.push(block[i]);
|
||||
}
|
||||
remaining -= to_copy;
|
||||
counter = counter.wrapping_add(1);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn quarter_round(state: &mut [u32; STATE_SIZE], a: usize, b: usize, c: usize, d: usize) {
|
||||
state[a] = state[a].wrapping_add(state[b]);
|
||||
state[d] ^= state[a];
|
||||
state[d] = state[d].rotate_left(16);
|
||||
|
||||
state[c] = state[c].wrapping_add(state[d]);
|
||||
state[b] ^= state[c];
|
||||
state[b] = state[b].rotate_left(12);
|
||||
|
||||
state[a] = state[a].wrapping_add(state[b]);
|
||||
state[d] ^= state[a];
|
||||
state[d] = state[d].rotate_left(8);
|
||||
|
||||
state[c] = state[c].wrapping_add(state[d]);
|
||||
state[b] ^= state[c];
|
||||
state[b] = state[b].rotate_left(7);
|
||||
}
|
||||
|
||||
pub fn hchacha20(key: &[u8; 32], nonce: &[u8; 16]) -> [u8; 32] {
|
||||
let mut state = [0u32; 16];
|
||||
|
||||
state[0] = 0x61707865;
|
||||
state[1] = 0x3320646e;
|
||||
state[2] = 0x79622d32;
|
||||
state[3] = 0x6b206574;
|
||||
|
||||
for i in 0..8 {
|
||||
state[4 + i] = u32::from_le_bytes([
|
||||
key[i * 4],
|
||||
key[i * 4 + 1],
|
||||
key[i * 4 + 2],
|
||||
key[i * 4 + 3],
|
||||
]);
|
||||
}
|
||||
|
||||
for i in 0..4 {
|
||||
state[12 + i] = u32::from_le_bytes([
|
||||
nonce[i * 4],
|
||||
nonce[i * 4 + 1],
|
||||
nonce[i * 4 + 2],
|
||||
nonce[i * 4 + 3],
|
||||
]);
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
quarter_round(&mut state, 0, 4, 8, 12);
|
||||
quarter_round(&mut state, 1, 5, 9, 13);
|
||||
quarter_round(&mut state, 2, 6, 10, 14);
|
||||
quarter_round(&mut state, 3, 7, 11, 15);
|
||||
quarter_round(&mut state, 0, 5, 10, 15);
|
||||
quarter_round(&mut state, 1, 6, 11, 12);
|
||||
quarter_round(&mut state, 2, 7, 8, 13);
|
||||
quarter_round(&mut state, 3, 4, 9, 14);
|
||||
}
|
||||
|
||||
let mut output = [0u8; 32];
|
||||
for i in 0..4 {
|
||||
let bytes = state[i].to_le_bytes();
|
||||
output[i * 4] = bytes[0];
|
||||
output[i * 4 + 1] = bytes[1];
|
||||
output[i * 4 + 2] = bytes[2];
|
||||
output[i * 4 + 3] = bytes[3];
|
||||
}
|
||||
for i in 0..4 {
|
||||
let bytes = state[12 + i].to_le_bytes();
|
||||
output[16 + i * 4] = bytes[0];
|
||||
output[16 + i * 4 + 1] = bytes[1];
|
||||
output[16 + i * 4 + 2] = bytes[2];
|
||||
output[16 + i * 4 + 3] = bytes[3];
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
pub struct XChaCha20 {
|
||||
inner: ChaCha20,
|
||||
}
|
||||
|
||||
impl XChaCha20 {
|
||||
|
||||
pub fn new(key: &[u8; 32], nonce: &[u8; 24]) -> Self {
|
||||
|
||||
let mut hnonce = [0u8; 16];
|
||||
hnonce.copy_from_slice(&nonce[..16]);
|
||||
let subkey = hchacha20(key, &hnonce);
|
||||
|
||||
let mut chacha_nonce = [0u8; 12];
|
||||
chacha_nonce[4..].copy_from_slice(&nonce[16..]);
|
||||
|
||||
Self {
|
||||
inner: ChaCha20::new(&subkey, &chacha_nonce),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_keystream(&self, data: &mut [u8]) {
|
||||
self.inner.apply_keystream(data);
|
||||
}
|
||||
}
|
||||
1001
src/crypto/ed25519.rs
Normal file
1001
src/crypto/ed25519.rs
Normal file
File diff suppressed because it is too large
Load Diff
149
src/crypto/hkdf.rs
Normal file
149
src/crypto/hkdf.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use super::hmac::HmacSha256;
|
||||
use super::sha256::DIGEST_SIZE;
|
||||
|
||||
pub struct Hkdf;
|
||||
|
||||
impl Hkdf {
|
||||
|
||||
pub fn extract(salt: &[u8], ikm: &[u8]) -> [u8; DIGEST_SIZE] {
|
||||
let salt = if salt.is_empty() {
|
||||
&[0u8; DIGEST_SIZE][..]
|
||||
} else {
|
||||
salt
|
||||
};
|
||||
HmacSha256::mac(salt, ikm)
|
||||
}
|
||||
|
||||
pub fn expand(prk: &[u8; DIGEST_SIZE], info: &[u8], okm: &mut [u8]) {
|
||||
let n = (okm.len() + DIGEST_SIZE - 1) / DIGEST_SIZE;
|
||||
assert!(n <= 255, "Output too long");
|
||||
|
||||
let mut t = [0u8; DIGEST_SIZE];
|
||||
let mut offset = 0;
|
||||
|
||||
for i in 1..=n {
|
||||
let mut hmac = HmacSha256::new(prk);
|
||||
|
||||
if i > 1 {
|
||||
hmac.update(&t);
|
||||
}
|
||||
hmac.update(info);
|
||||
hmac.update(&[i as u8]);
|
||||
|
||||
t = hmac.finalize();
|
||||
|
||||
let to_copy = core::cmp::min(DIGEST_SIZE, okm.len() - offset);
|
||||
okm[offset..offset + to_copy].copy_from_slice(&t[..to_copy]);
|
||||
offset += to_copy;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derive(salt: &[u8], ikm: &[u8], info: &[u8], okm: &mut [u8]) {
|
||||
let prk = Self::extract(salt, ikm);
|
||||
Self::expand(&prk, info, okm);
|
||||
}
|
||||
|
||||
pub fn derive_key<const N: usize>(salt: &[u8], ikm: &[u8], info: &[u8]) -> [u8; N] {
|
||||
let mut key = [0u8; N];
|
||||
Self::derive(salt, ikm, info, &mut key);
|
||||
key
|
||||
}
|
||||
}
|
||||
|
||||
pub mod reticulum {
|
||||
use super::*;
|
||||
use crate::crypto::sha256::Sha256;
|
||||
|
||||
pub const IDENTITY_HASH_SIZE: usize = 16;
|
||||
|
||||
pub const FULL_HASH_SIZE: usize = 32;
|
||||
|
||||
pub fn identity_hash(signing_key: &[u8; 32], encryption_key: &[u8; 32]) -> [u8; IDENTITY_HASH_SIZE] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(signing_key);
|
||||
hasher.update(encryption_key);
|
||||
let full = hasher.finalize();
|
||||
|
||||
let mut hash = [0u8; IDENTITY_HASH_SIZE];
|
||||
hash.copy_from_slice(&full[..IDENTITY_HASH_SIZE]);
|
||||
hash
|
||||
}
|
||||
|
||||
pub fn full_identity_hash(signing_key: &[u8; 32], encryption_key: &[u8; 32]) -> [u8; FULL_HASH_SIZE] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(signing_key);
|
||||
hasher.update(encryption_key);
|
||||
hasher.finalize()
|
||||
}
|
||||
|
||||
pub fn derive_link_keys(
|
||||
shared_secret: &[u8; 32],
|
||||
initiator_pub: &[u8; 32],
|
||||
responder_pub: &[u8; 32],
|
||||
) -> LinkKeys {
|
||||
|
||||
let mut context = [0u8; 64];
|
||||
context[..32].copy_from_slice(initiator_pub);
|
||||
context[32..].copy_from_slice(responder_pub);
|
||||
|
||||
let mut master = [0u8; 64];
|
||||
Hkdf::derive(b"reticulum", shared_secret, &context, &mut master);
|
||||
|
||||
LinkKeys {
|
||||
tx_key: {
|
||||
let mut k = [0u8; 32];
|
||||
k.copy_from_slice(&master[..32]);
|
||||
k
|
||||
},
|
||||
rx_key: {
|
||||
let mut k = [0u8; 32];
|
||||
k.copy_from_slice(&master[32..]);
|
||||
k
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LinkKeys {
|
||||
pub tx_key: [u8; 32],
|
||||
pub rx_key: [u8; 32],
|
||||
}
|
||||
}
|
||||
|
||||
pub mod meshcore {
|
||||
use super::*;
|
||||
|
||||
pub fn derive_channel_key(psk: &[u8], channel_id: u8) -> [u8; 32] {
|
||||
let info = [b'C', b'H', channel_id];
|
||||
Hkdf::derive_key(b"meshcore", psk, &info)
|
||||
}
|
||||
|
||||
pub fn derive_node_key(identity: &[u8; 32], purpose: &[u8]) -> [u8; 32] {
|
||||
Hkdf::derive_key(b"meshcore-node", identity, purpose)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod meshtastic {
|
||||
use super::*;
|
||||
use crate::crypto::sha256::Sha256;
|
||||
|
||||
pub const DEFAULT_KEY: [u8; 16] = [
|
||||
0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
|
||||
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01
|
||||
];
|
||||
|
||||
pub fn derive_channel_key(channel_name: &str) -> [u8; 32] {
|
||||
|
||||
let hash = Sha256::hash(channel_name.as_bytes());
|
||||
hash
|
||||
}
|
||||
|
||||
pub fn derive_nonce(packet_id: u32, sender: u32) -> [u8; 16] {
|
||||
let mut nonce = [0u8; 16];
|
||||
|
||||
nonce[..4].copy_from_slice(&packet_id.to_le_bytes());
|
||||
|
||||
nonce[8..12].copy_from_slice(&sender.to_le_bytes());
|
||||
|
||||
nonce
|
||||
}
|
||||
}
|
||||
69
src/crypto/hmac.rs
Normal file
69
src/crypto/hmac.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use super::sha256::{Sha256, BLOCK_SIZE, DIGEST_SIZE};
|
||||
|
||||
pub struct HmacSha256 {
|
||||
|
||||
inner: Sha256,
|
||||
|
||||
outer_key: [u8; BLOCK_SIZE],
|
||||
}
|
||||
|
||||
impl HmacSha256 {
|
||||
|
||||
pub fn new(key: &[u8]) -> Self {
|
||||
let mut key_block = [0u8; BLOCK_SIZE];
|
||||
|
||||
if key.len() > BLOCK_SIZE {
|
||||
let hashed = Sha256::hash(key);
|
||||
key_block[..DIGEST_SIZE].copy_from_slice(&hashed);
|
||||
} else {
|
||||
key_block[..key.len()].copy_from_slice(key);
|
||||
}
|
||||
|
||||
let mut inner_key = [0u8; BLOCK_SIZE];
|
||||
let mut outer_key = [0u8; BLOCK_SIZE];
|
||||
|
||||
for i in 0..BLOCK_SIZE {
|
||||
inner_key[i] = key_block[i] ^ 0x36;
|
||||
outer_key[i] = key_block[i] ^ 0x5c;
|
||||
}
|
||||
|
||||
let mut inner = Sha256::new();
|
||||
inner.update(&inner_key);
|
||||
|
||||
Self { inner, outer_key }
|
||||
}
|
||||
|
||||
pub fn update(&mut self, data: &[u8]) {
|
||||
self.inner.update(data);
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> [u8; DIGEST_SIZE] {
|
||||
|
||||
let inner_hash = self.inner.finalize();
|
||||
|
||||
let mut outer = Sha256::new();
|
||||
outer.update(&self.outer_key);
|
||||
outer.update(&inner_hash);
|
||||
outer.finalize()
|
||||
}
|
||||
|
||||
pub fn mac(key: &[u8], data: &[u8]) -> [u8; DIGEST_SIZE] {
|
||||
let mut hmac = Self::new(key);
|
||||
hmac.update(data);
|
||||
hmac.finalize()
|
||||
}
|
||||
|
||||
pub fn verify(key: &[u8], data: &[u8], expected: &[u8; DIGEST_SIZE]) -> bool {
|
||||
let computed = Self::mac(key, data);
|
||||
super::constant_time_eq(&computed, expected)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for HmacSha256 {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
outer_key: self.outer_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/crypto/mod.rs
Normal file
61
src/crypto/mod.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
pub mod aes;
|
||||
pub mod sha256;
|
||||
pub mod hmac;
|
||||
pub mod x25519;
|
||||
pub mod ed25519;
|
||||
pub mod chacha20;
|
||||
pub mod poly1305;
|
||||
pub mod hkdf;
|
||||
|
||||
pub use aes::{Aes128, Aes256, AesMode};
|
||||
pub use sha256::Sha256;
|
||||
pub use hmac::HmacSha256;
|
||||
pub use x25519::{x25519, x25519_base};
|
||||
pub use ed25519::{Ed25519, Signature};
|
||||
pub use chacha20::ChaCha20;
|
||||
pub use poly1305::Poly1305;
|
||||
pub use hkdf::Hkdf;
|
||||
|
||||
#[inline]
|
||||
pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
|
||||
if a.len() != b.len() {
|
||||
return false;
|
||||
}
|
||||
let mut result: u8 = 0;
|
||||
for (x, y) in a.iter().zip(b.iter()) {
|
||||
result |= x ^ y;
|
||||
}
|
||||
result == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn secure_zero(data: &mut [u8]) {
|
||||
for byte in data.iter_mut() {
|
||||
unsafe {
|
||||
core::ptr::write_volatile(byte, 0);
|
||||
}
|
||||
}
|
||||
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn secure_zero_u32(data: &mut [u32]) {
|
||||
for word in data.iter_mut() {
|
||||
unsafe {
|
||||
core::ptr::write_volatile(word, 0);
|
||||
}
|
||||
}
|
||||
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn random_bytes(dest: &mut [u8]) {
|
||||
crate::rng::fill_random(dest);
|
||||
}
|
||||
|
||||
pub fn random_bytes_checked(dest: &mut [u8]) -> bool {
|
||||
crate::rng::fill_random_checked(dest)
|
||||
}
|
||||
|
||||
pub fn rng_is_healthy() -> bool {
|
||||
crate::rng::is_healthy()
|
||||
}
|
||||
318
src/crypto/poly1305.rs
Normal file
318
src/crypto/poly1305.rs
Normal file
@@ -0,0 +1,318 @@
|
||||
pub const TAG_SIZE: usize = 16;
|
||||
|
||||
pub const KEY_SIZE: usize = 32;
|
||||
|
||||
pub struct Poly1305 {
|
||||
|
||||
r: [u32; 5],
|
||||
|
||||
s: [u32; 4],
|
||||
|
||||
h: [u32; 5],
|
||||
|
||||
buffer: [u8; 16],
|
||||
|
||||
buffer_len: usize,
|
||||
}
|
||||
|
||||
impl Poly1305 {
|
||||
|
||||
pub fn new(key: &[u8; KEY_SIZE]) -> Self {
|
||||
|
||||
let r0 = u32::from_le_bytes([key[0], key[1], key[2], key[3]]) & 0x0fff_fffc;
|
||||
let r1 = u32::from_le_bytes([key[4], key[5], key[6], key[7]]) & 0x0fff_fffc;
|
||||
let r2 = u32::from_le_bytes([key[8], key[9], key[10], key[11]]) & 0x0fff_fffc;
|
||||
let r3 = u32::from_le_bytes([key[12], key[13], key[14], key[15]]) & 0x0fff_fffc;
|
||||
|
||||
let r = [
|
||||
r0 & 0x03ff_ffff,
|
||||
((r0 >> 26) | (r1 << 6)) & 0x03ff_ffff,
|
||||
((r1 >> 20) | (r2 << 12)) & 0x03ff_ffff,
|
||||
((r2 >> 14) | (r3 << 18)) & 0x03ff_ffff,
|
||||
r3 >> 8,
|
||||
];
|
||||
|
||||
let s = [
|
||||
u32::from_le_bytes([key[16], key[17], key[18], key[19]]),
|
||||
u32::from_le_bytes([key[20], key[21], key[22], key[23]]),
|
||||
u32::from_le_bytes([key[24], key[25], key[26], key[27]]),
|
||||
u32::from_le_bytes([key[28], key[29], key[30], key[31]]),
|
||||
];
|
||||
|
||||
Self {
|
||||
r,
|
||||
s,
|
||||
h: [0; 5],
|
||||
buffer: [0; 16],
|
||||
buffer_len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn process_block(&mut self, block: &[u8], final_block: bool) {
|
||||
|
||||
let t0 = u32::from_le_bytes([block[0], block[1], block[2], block[3]]);
|
||||
let t1 = u32::from_le_bytes([block[4], block[5], block[6], block[7]]);
|
||||
let t2 = u32::from_le_bytes([block[8], block[9], block[10], block[11]]);
|
||||
let t3 = u32::from_le_bytes([block[12], block[13], block[14], block[15]]);
|
||||
|
||||
let hibit = if final_block { 0 } else { 1 << 24 };
|
||||
|
||||
self.h[0] += t0 & 0x03ff_ffff;
|
||||
self.h[1] += ((t0 >> 26) | (t1 << 6)) & 0x03ff_ffff;
|
||||
self.h[2] += ((t1 >> 20) | (t2 << 12)) & 0x03ff_ffff;
|
||||
self.h[3] += ((t2 >> 14) | (t3 << 18)) & 0x03ff_ffff;
|
||||
self.h[4] += (t3 >> 8) | hibit;
|
||||
|
||||
let r0 = self.r[0] as u64;
|
||||
let r1 = self.r[1] as u64;
|
||||
let r2 = self.r[2] as u64;
|
||||
let r3 = self.r[3] as u64;
|
||||
let r4 = self.r[4] as u64;
|
||||
|
||||
let s1 = r1 * 5;
|
||||
let s2 = r2 * 5;
|
||||
let s3 = r3 * 5;
|
||||
let s4 = r4 * 5;
|
||||
|
||||
let h0 = self.h[0] as u64;
|
||||
let h1 = self.h[1] as u64;
|
||||
let h2 = self.h[2] as u64;
|
||||
let h3 = self.h[3] as u64;
|
||||
let h4 = self.h[4] as u64;
|
||||
|
||||
let d0 = h0 * r0 + h1 * s4 + h2 * s3 + h3 * s2 + h4 * s1;
|
||||
let d1 = h0 * r1 + h1 * r0 + h2 * s4 + h3 * s3 + h4 * s2;
|
||||
let d2 = h0 * r2 + h1 * r1 + h2 * r0 + h3 * s4 + h4 * s3;
|
||||
let d3 = h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * s4;
|
||||
let d4 = h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
|
||||
|
||||
let mut c: u64;
|
||||
c = d0 >> 26;
|
||||
self.h[0] = (d0 & 0x03ff_ffff) as u32;
|
||||
let d1 = d1 + c;
|
||||
c = d1 >> 26;
|
||||
self.h[1] = (d1 & 0x03ff_ffff) as u32;
|
||||
let d2 = d2 + c;
|
||||
c = d2 >> 26;
|
||||
self.h[2] = (d2 & 0x03ff_ffff) as u32;
|
||||
let d3 = d3 + c;
|
||||
c = d3 >> 26;
|
||||
self.h[3] = (d3 & 0x03ff_ffff) as u32;
|
||||
let d4 = d4 + c;
|
||||
c = d4 >> 26;
|
||||
self.h[4] = (d4 & 0x03ff_ffff) as u32;
|
||||
self.h[0] += (c * 5) as u32;
|
||||
c = (self.h[0] >> 26) as u64;
|
||||
self.h[0] &= 0x03ff_ffff;
|
||||
self.h[1] += c as u32;
|
||||
}
|
||||
|
||||
pub fn update(&mut self, data: &[u8]) {
|
||||
let mut offset = 0;
|
||||
|
||||
if self.buffer_len > 0 {
|
||||
let needed = 16 - self.buffer_len;
|
||||
if data.len() >= needed {
|
||||
self.buffer[self.buffer_len..].copy_from_slice(&data[..needed]);
|
||||
let block = self.buffer;
|
||||
self.process_block(&block, false);
|
||||
self.buffer_len = 0;
|
||||
offset = needed;
|
||||
} else {
|
||||
self.buffer[self.buffer_len..self.buffer_len + data.len()].copy_from_slice(data);
|
||||
self.buffer_len += data.len();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while offset + 16 <= data.len() {
|
||||
self.process_block(&data[offset..offset + 16], false);
|
||||
offset += 16;
|
||||
}
|
||||
|
||||
if offset < data.len() {
|
||||
let remaining = data.len() - offset;
|
||||
self.buffer[..remaining].copy_from_slice(&data[offset..]);
|
||||
self.buffer_len = remaining;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(mut self) -> [u8; TAG_SIZE] {
|
||||
|
||||
if self.buffer_len > 0 {
|
||||
|
||||
self.buffer[self.buffer_len] = 1;
|
||||
for i in self.buffer_len + 1..16 {
|
||||
self.buffer[i] = 0;
|
||||
}
|
||||
let block = self.buffer;
|
||||
self.process_block(&block, true);
|
||||
}
|
||||
|
||||
let mut c: u32;
|
||||
c = self.h[1] >> 26;
|
||||
self.h[1] &= 0x03ff_ffff;
|
||||
self.h[2] += c;
|
||||
c = self.h[2] >> 26;
|
||||
self.h[2] &= 0x03ff_ffff;
|
||||
self.h[3] += c;
|
||||
c = self.h[3] >> 26;
|
||||
self.h[3] &= 0x03ff_ffff;
|
||||
self.h[4] += c;
|
||||
c = self.h[4] >> 26;
|
||||
self.h[4] &= 0x03ff_ffff;
|
||||
self.h[0] += c * 5;
|
||||
c = self.h[0] >> 26;
|
||||
self.h[0] &= 0x03ff_ffff;
|
||||
self.h[1] += c;
|
||||
|
||||
let mut g0 = self.h[0].wrapping_add(5);
|
||||
c = g0 >> 26;
|
||||
g0 &= 0x03ff_ffff;
|
||||
let mut g1 = self.h[1].wrapping_add(c);
|
||||
c = g1 >> 26;
|
||||
g1 &= 0x03ff_ffff;
|
||||
let mut g2 = self.h[2].wrapping_add(c);
|
||||
c = g2 >> 26;
|
||||
g2 &= 0x03ff_ffff;
|
||||
let mut g3 = self.h[3].wrapping_add(c);
|
||||
c = g3 >> 26;
|
||||
g3 &= 0x03ff_ffff;
|
||||
let g4 = self.h[4].wrapping_add(c).wrapping_sub(1 << 26);
|
||||
|
||||
let mask = (g4 >> 31).wrapping_sub(1);
|
||||
g0 &= mask;
|
||||
g1 &= mask;
|
||||
g2 &= mask;
|
||||
g3 &= mask;
|
||||
let mask = !mask;
|
||||
self.h[0] = (self.h[0] & mask) | g0;
|
||||
self.h[1] = (self.h[1] & mask) | g1;
|
||||
self.h[2] = (self.h[2] & mask) | g2;
|
||||
self.h[3] = (self.h[3] & mask) | g3;
|
||||
|
||||
let h0 = self.h[0] | (self.h[1] << 26);
|
||||
let h1 = (self.h[1] >> 6) | (self.h[2] << 20);
|
||||
let h2 = (self.h[2] >> 12) | (self.h[3] << 14);
|
||||
let h3 = (self.h[3] >> 18) | (self.h[4] << 8);
|
||||
|
||||
let mut f: u64;
|
||||
f = h0 as u64 + self.s[0] as u64;
|
||||
let t0 = f as u32;
|
||||
f = h1 as u64 + self.s[1] as u64 + (f >> 32);
|
||||
let t1 = f as u32;
|
||||
f = h2 as u64 + self.s[2] as u64 + (f >> 32);
|
||||
let t2 = f as u32;
|
||||
f = h3 as u64 + self.s[3] as u64 + (f >> 32);
|
||||
let t3 = f as u32;
|
||||
|
||||
let mut tag = [0u8; TAG_SIZE];
|
||||
tag[0..4].copy_from_slice(&t0.to_le_bytes());
|
||||
tag[4..8].copy_from_slice(&t1.to_le_bytes());
|
||||
tag[8..12].copy_from_slice(&t2.to_le_bytes());
|
||||
tag[12..16].copy_from_slice(&t3.to_le_bytes());
|
||||
|
||||
tag
|
||||
}
|
||||
|
||||
pub fn mac(key: &[u8; KEY_SIZE], data: &[u8]) -> [u8; TAG_SIZE] {
|
||||
let mut poly = Self::new(key);
|
||||
poly.update(data);
|
||||
poly.finalize()
|
||||
}
|
||||
|
||||
pub fn verify(key: &[u8; KEY_SIZE], data: &[u8], expected: &[u8; TAG_SIZE]) -> bool {
|
||||
let computed = Self::mac(key, data);
|
||||
super::constant_time_eq(&computed, expected)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChaCha20Poly1305;
|
||||
|
||||
impl ChaCha20Poly1305 {
|
||||
|
||||
pub fn seal(
|
||||
key: &[u8; 32],
|
||||
nonce: &[u8; 12],
|
||||
aad: &[u8],
|
||||
plaintext: &[u8],
|
||||
ciphertext: &mut [u8],
|
||||
tag: &mut [u8; 16],
|
||||
) {
|
||||
assert!(ciphertext.len() >= plaintext.len());
|
||||
|
||||
let mut poly_key = [0u8; 32];
|
||||
let chacha = super::chacha20::ChaCha20::new(key, nonce);
|
||||
let keystream = chacha.keystream(32);
|
||||
poly_key.copy_from_slice(&keystream[..32]);
|
||||
|
||||
ciphertext[..plaintext.len()].copy_from_slice(plaintext);
|
||||
let chacha = super::chacha20::ChaCha20::new_with_counter(key, nonce, 1);
|
||||
chacha.encrypt(&mut ciphertext[..plaintext.len()]);
|
||||
|
||||
let mut poly = Poly1305::new(&poly_key);
|
||||
|
||||
poly.update(aad);
|
||||
|
||||
let aad_pad = (16 - (aad.len() % 16)) % 16;
|
||||
if aad_pad > 0 {
|
||||
poly.update(&[0u8; 16][..aad_pad]);
|
||||
}
|
||||
|
||||
poly.update(&ciphertext[..plaintext.len()]);
|
||||
|
||||
let ct_pad = (16 - (plaintext.len() % 16)) % 16;
|
||||
if ct_pad > 0 {
|
||||
poly.update(&[0u8; 16][..ct_pad]);
|
||||
}
|
||||
|
||||
poly.update(&(aad.len() as u64).to_le_bytes());
|
||||
poly.update(&(plaintext.len() as u64).to_le_bytes());
|
||||
|
||||
*tag = poly.finalize();
|
||||
}
|
||||
|
||||
pub fn open(
|
||||
key: &[u8; 32],
|
||||
nonce: &[u8; 12],
|
||||
aad: &[u8],
|
||||
ciphertext: &[u8],
|
||||
tag: &[u8; 16],
|
||||
plaintext: &mut [u8],
|
||||
) -> bool {
|
||||
assert!(plaintext.len() >= ciphertext.len());
|
||||
|
||||
let mut poly_key = [0u8; 32];
|
||||
let chacha = super::chacha20::ChaCha20::new(key, nonce);
|
||||
let keystream = chacha.keystream(32);
|
||||
poly_key.copy_from_slice(&keystream[..32]);
|
||||
|
||||
let mut poly = Poly1305::new(&poly_key);
|
||||
|
||||
poly.update(aad);
|
||||
let aad_pad = (16 - (aad.len() % 16)) % 16;
|
||||
if aad_pad > 0 {
|
||||
poly.update(&[0u8; 16][..aad_pad]);
|
||||
}
|
||||
|
||||
poly.update(ciphertext);
|
||||
let ct_pad = (16 - (ciphertext.len() % 16)) % 16;
|
||||
if ct_pad > 0 {
|
||||
poly.update(&[0u8; 16][..ct_pad]);
|
||||
}
|
||||
|
||||
poly.update(&(aad.len() as u64).to_le_bytes());
|
||||
poly.update(&(ciphertext.len() as u64).to_le_bytes());
|
||||
|
||||
let computed_tag = poly.finalize();
|
||||
if !super::constant_time_eq(&computed_tag, tag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
plaintext[..ciphertext.len()].copy_from_slice(ciphertext);
|
||||
let chacha = super::chacha20::ChaCha20::new_with_counter(key, nonce, 1);
|
||||
chacha.decrypt(&mut plaintext[..ciphertext.len()]);
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
198
src/crypto/sha256.rs
Normal file
198
src/crypto/sha256.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
const K: [u32; 64] = [
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||
];
|
||||
|
||||
const H_INIT: [u32; 8] = [
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
||||
];
|
||||
|
||||
pub const DIGEST_SIZE: usize = 32;
|
||||
|
||||
pub const BLOCK_SIZE: usize = 64;
|
||||
|
||||
pub struct Sha256 {
|
||||
|
||||
state: [u32; 8],
|
||||
|
||||
total_len: u64,
|
||||
|
||||
buffer: [u8; BLOCK_SIZE],
|
||||
|
||||
buffer_len: usize,
|
||||
}
|
||||
|
||||
impl Sha256 {
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: H_INIT,
|
||||
total_len: 0,
|
||||
buffer: [0u8; BLOCK_SIZE],
|
||||
buffer_len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, data: &[u8]) {
|
||||
let mut offset = 0;
|
||||
|
||||
if self.buffer_len > 0 {
|
||||
let needed = BLOCK_SIZE - self.buffer_len;
|
||||
if data.len() >= needed {
|
||||
self.buffer[self.buffer_len..].copy_from_slice(&data[..needed]);
|
||||
self.process_block(&self.buffer.clone());
|
||||
self.buffer_len = 0;
|
||||
offset = needed;
|
||||
} else {
|
||||
self.buffer[self.buffer_len..self.buffer_len + data.len()].copy_from_slice(data);
|
||||
self.buffer_len += data.len();
|
||||
self.total_len += data.len() as u64;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while offset + BLOCK_SIZE <= data.len() {
|
||||
let mut block = [0u8; BLOCK_SIZE];
|
||||
block.copy_from_slice(&data[offset..offset + BLOCK_SIZE]);
|
||||
self.process_block(&block);
|
||||
offset += BLOCK_SIZE;
|
||||
}
|
||||
|
||||
if offset < data.len() {
|
||||
let remaining = data.len() - offset;
|
||||
self.buffer[..remaining].copy_from_slice(&data[offset..]);
|
||||
self.buffer_len = remaining;
|
||||
}
|
||||
|
||||
self.total_len += data.len() as u64;
|
||||
}
|
||||
|
||||
pub fn finalize(mut self) -> [u8; DIGEST_SIZE] {
|
||||
|
||||
let total_bits = self.total_len * 8;
|
||||
|
||||
self.buffer[self.buffer_len] = 0x80;
|
||||
self.buffer_len += 1;
|
||||
|
||||
if self.buffer_len > 56 {
|
||||
|
||||
for i in self.buffer_len..BLOCK_SIZE {
|
||||
self.buffer[i] = 0;
|
||||
}
|
||||
self.process_block(&self.buffer.clone());
|
||||
self.buffer_len = 0;
|
||||
}
|
||||
|
||||
for i in self.buffer_len..56 {
|
||||
self.buffer[i] = 0;
|
||||
}
|
||||
|
||||
self.buffer[56..64].copy_from_slice(&total_bits.to_be_bytes());
|
||||
self.process_block(&self.buffer.clone());
|
||||
|
||||
let mut digest = [0u8; DIGEST_SIZE];
|
||||
for (i, word) in self.state.iter().enumerate() {
|
||||
digest[i * 4..(i + 1) * 4].copy_from_slice(&word.to_be_bytes());
|
||||
}
|
||||
|
||||
digest
|
||||
}
|
||||
|
||||
fn process_block(&mut self, block: &[u8; BLOCK_SIZE]) {
|
||||
|
||||
let mut w = [0u32; 64];
|
||||
|
||||
for i in 0..16 {
|
||||
w[i] = u32::from_be_bytes([
|
||||
block[i * 4],
|
||||
block[i * 4 + 1],
|
||||
block[i * 4 + 2],
|
||||
block[i * 4 + 3],
|
||||
]);
|
||||
}
|
||||
|
||||
for i in 16..64 {
|
||||
let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
|
||||
let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
|
||||
w[i] = w[i - 16]
|
||||
.wrapping_add(s0)
|
||||
.wrapping_add(w[i - 7])
|
||||
.wrapping_add(s1);
|
||||
}
|
||||
|
||||
let mut a = self.state[0];
|
||||
let mut b = self.state[1];
|
||||
let mut c = self.state[2];
|
||||
let mut d = self.state[3];
|
||||
let mut e = self.state[4];
|
||||
let mut f = self.state[5];
|
||||
let mut g = self.state[6];
|
||||
let mut h = self.state[7];
|
||||
|
||||
for i in 0..64 {
|
||||
let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
|
||||
let ch = (e & f) ^ ((!e) & g);
|
||||
let temp1 = h
|
||||
.wrapping_add(s1)
|
||||
.wrapping_add(ch)
|
||||
.wrapping_add(K[i])
|
||||
.wrapping_add(w[i]);
|
||||
let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
|
||||
let maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
let temp2 = s0.wrapping_add(maj);
|
||||
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d.wrapping_add(temp1);
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = temp1.wrapping_add(temp2);
|
||||
}
|
||||
|
||||
self.state[0] = self.state[0].wrapping_add(a);
|
||||
self.state[1] = self.state[1].wrapping_add(b);
|
||||
self.state[2] = self.state[2].wrapping_add(c);
|
||||
self.state[3] = self.state[3].wrapping_add(d);
|
||||
self.state[4] = self.state[4].wrapping_add(e);
|
||||
self.state[5] = self.state[5].wrapping_add(f);
|
||||
self.state[6] = self.state[6].wrapping_add(g);
|
||||
self.state[7] = self.state[7].wrapping_add(h);
|
||||
}
|
||||
|
||||
pub fn hash(data: &[u8]) -> [u8; DIGEST_SIZE] {
|
||||
let mut hasher = Self::new();
|
||||
hasher.update(data);
|
||||
hasher.finalize()
|
||||
}
|
||||
|
||||
pub fn hash256(data: &[u8]) -> [u8; DIGEST_SIZE] {
|
||||
let first = Self::hash(data);
|
||||
Self::hash(&first)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Sha256 {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Sha256 {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
state: self.state,
|
||||
total_len: self.total_len,
|
||||
buffer: self.buffer,
|
||||
buffer_len: self.buffer_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
395
src/crypto/x25519.rs
Normal file
395
src/crypto/x25519.rs
Normal file
@@ -0,0 +1,395 @@
|
||||
#[derive(Clone, Copy)]
|
||||
struct Fe([i64; 10]);
|
||||
|
||||
const P: [i64; 10] = [
|
||||
0x3ffffed, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff,
|
||||
0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff,
|
||||
];
|
||||
|
||||
impl Fe {
|
||||
|
||||
const fn zero() -> Self {
|
||||
Fe([0; 10])
|
||||
}
|
||||
|
||||
const fn one() -> Self {
|
||||
Fe([1, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8; 32]) -> Self {
|
||||
let mut h = [0i64; 10];
|
||||
|
||||
h[0] = load_4(&bytes[0..4]) & 0x3ffffff;
|
||||
h[1] = (load_4(&bytes[3..7]) >> 2) & 0x1ffffff;
|
||||
h[2] = (load_4(&bytes[6..10]) >> 3) & 0x3ffffff;
|
||||
h[3] = (load_4(&bytes[9..13]) >> 5) & 0x1ffffff;
|
||||
h[4] = (load_4(&bytes[12..16]) >> 6) & 0x3ffffff;
|
||||
h[5] = (load_4(&bytes[16..20])) & 0x1ffffff;
|
||||
h[6] = (load_4(&bytes[19..23]) >> 1) & 0x3ffffff;
|
||||
h[7] = (load_4(&bytes[22..26]) >> 3) & 0x1ffffff;
|
||||
h[8] = (load_4(&bytes[25..29]) >> 4) & 0x3ffffff;
|
||||
h[9] = (load_3(&bytes[28..31]) >> 6) & 0x1ffffff;
|
||||
|
||||
Fe(h)
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> [u8; 32] {
|
||||
let mut h = self.0;
|
||||
|
||||
let mut q = (19 * h[9] + (1 << 24)) >> 25;
|
||||
q = (h[0] + q) >> 26;
|
||||
q = (h[1] + q) >> 25;
|
||||
q = (h[2] + q) >> 26;
|
||||
q = (h[3] + q) >> 25;
|
||||
q = (h[4] + q) >> 26;
|
||||
q = (h[5] + q) >> 25;
|
||||
q = (h[6] + q) >> 26;
|
||||
q = (h[7] + q) >> 25;
|
||||
q = (h[8] + q) >> 26;
|
||||
q = (h[9] + q) >> 25;
|
||||
|
||||
h[0] += 19 * q;
|
||||
|
||||
let carry0 = h[0] >> 26;
|
||||
h[1] += carry0;
|
||||
h[0] -= carry0 << 26;
|
||||
let carry1 = h[1] >> 25;
|
||||
h[2] += carry1;
|
||||
h[1] -= carry1 << 25;
|
||||
let carry2 = h[2] >> 26;
|
||||
h[3] += carry2;
|
||||
h[2] -= carry2 << 26;
|
||||
let carry3 = h[3] >> 25;
|
||||
h[4] += carry3;
|
||||
h[3] -= carry3 << 25;
|
||||
let carry4 = h[4] >> 26;
|
||||
h[5] += carry4;
|
||||
h[4] -= carry4 << 26;
|
||||
let carry5 = h[5] >> 25;
|
||||
h[6] += carry5;
|
||||
h[5] -= carry5 << 25;
|
||||
let carry6 = h[6] >> 26;
|
||||
h[7] += carry6;
|
||||
h[6] -= carry6 << 26;
|
||||
let carry7 = h[7] >> 25;
|
||||
h[8] += carry7;
|
||||
h[7] -= carry7 << 25;
|
||||
let carry8 = h[8] >> 26;
|
||||
h[9] += carry8;
|
||||
h[8] -= carry8 << 26;
|
||||
let carry9 = h[9] >> 25;
|
||||
h[9] -= carry9 << 25;
|
||||
|
||||
let mut s = [0u8; 32];
|
||||
s[0] = h[0] as u8;
|
||||
s[1] = (h[0] >> 8) as u8;
|
||||
s[2] = (h[0] >> 16) as u8;
|
||||
s[3] = ((h[0] >> 24) | (h[1] << 2)) as u8;
|
||||
s[4] = (h[1] >> 6) as u8;
|
||||
s[5] = (h[1] >> 14) as u8;
|
||||
s[6] = ((h[1] >> 22) | (h[2] << 3)) as u8;
|
||||
s[7] = (h[2] >> 5) as u8;
|
||||
s[8] = (h[2] >> 13) as u8;
|
||||
s[9] = ((h[2] >> 21) | (h[3] << 5)) as u8;
|
||||
s[10] = (h[3] >> 3) as u8;
|
||||
s[11] = (h[3] >> 11) as u8;
|
||||
s[12] = ((h[3] >> 19) | (h[4] << 6)) as u8;
|
||||
s[13] = (h[4] >> 2) as u8;
|
||||
s[14] = (h[4] >> 10) as u8;
|
||||
s[15] = (h[4] >> 18) as u8;
|
||||
s[16] = h[5] as u8;
|
||||
s[17] = (h[5] >> 8) as u8;
|
||||
s[18] = (h[5] >> 16) as u8;
|
||||
s[19] = ((h[5] >> 24) | (h[6] << 1)) as u8;
|
||||
s[20] = (h[6] >> 7) as u8;
|
||||
s[21] = (h[6] >> 15) as u8;
|
||||
s[22] = ((h[6] >> 23) | (h[7] << 3)) as u8;
|
||||
s[23] = (h[7] >> 5) as u8;
|
||||
s[24] = (h[7] >> 13) as u8;
|
||||
s[25] = ((h[7] >> 21) | (h[8] << 4)) as u8;
|
||||
s[26] = (h[8] >> 4) as u8;
|
||||
s[27] = (h[8] >> 12) as u8;
|
||||
s[28] = ((h[8] >> 20) | (h[9] << 6)) as u8;
|
||||
s[29] = (h[9] >> 2) as u8;
|
||||
s[30] = (h[9] >> 10) as u8;
|
||||
s[31] = (h[9] >> 18) as u8;
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
fn add(&self, rhs: &Fe) -> Fe {
|
||||
Fe([
|
||||
self.0[0] + rhs.0[0],
|
||||
self.0[1] + rhs.0[1],
|
||||
self.0[2] + rhs.0[2],
|
||||
self.0[3] + rhs.0[3],
|
||||
self.0[4] + rhs.0[4],
|
||||
self.0[5] + rhs.0[5],
|
||||
self.0[6] + rhs.0[6],
|
||||
self.0[7] + rhs.0[7],
|
||||
self.0[8] + rhs.0[8],
|
||||
self.0[9] + rhs.0[9],
|
||||
])
|
||||
}
|
||||
|
||||
fn sub(&self, rhs: &Fe) -> Fe {
|
||||
Fe([
|
||||
self.0[0] - rhs.0[0],
|
||||
self.0[1] - rhs.0[1],
|
||||
self.0[2] - rhs.0[2],
|
||||
self.0[3] - rhs.0[3],
|
||||
self.0[4] - rhs.0[4],
|
||||
self.0[5] - rhs.0[5],
|
||||
self.0[6] - rhs.0[6],
|
||||
self.0[7] - rhs.0[7],
|
||||
self.0[8] - rhs.0[8],
|
||||
self.0[9] - rhs.0[9],
|
||||
])
|
||||
}
|
||||
|
||||
fn mul(&self, rhs: &Fe) -> Fe {
|
||||
let f = &self.0;
|
||||
let g = &rhs.0;
|
||||
|
||||
let f0 = f[0] as i128;
|
||||
let f1 = f[1] as i128;
|
||||
let f2 = f[2] as i128;
|
||||
let f3 = f[3] as i128;
|
||||
let f4 = f[4] as i128;
|
||||
let f5 = f[5] as i128;
|
||||
let f6 = f[6] as i128;
|
||||
let f7 = f[7] as i128;
|
||||
let f8 = f[8] as i128;
|
||||
let f9 = f[9] as i128;
|
||||
|
||||
let g0 = g[0] as i128;
|
||||
let g1 = g[1] as i128;
|
||||
let g2 = g[2] as i128;
|
||||
let g3 = g[3] as i128;
|
||||
let g4 = g[4] as i128;
|
||||
let g5 = g[5] as i128;
|
||||
let g6 = g[6] as i128;
|
||||
let g7 = g[7] as i128;
|
||||
let g8 = g[8] as i128;
|
||||
let g9 = g[9] as i128;
|
||||
|
||||
let g1_19 = 19 * g1;
|
||||
let g2_19 = 19 * g2;
|
||||
let g3_19 = 19 * g3;
|
||||
let g4_19 = 19 * g4;
|
||||
let g5_19 = 19 * g5;
|
||||
let g6_19 = 19 * g6;
|
||||
let g7_19 = 19 * g7;
|
||||
let g8_19 = 19 * g8;
|
||||
let g9_19 = 19 * g9;
|
||||
|
||||
let f1_2 = 2 * f1;
|
||||
let f3_2 = 2 * f3;
|
||||
let f5_2 = 2 * f5;
|
||||
let f7_2 = 2 * f7;
|
||||
let f9_2 = 2 * f9;
|
||||
|
||||
let h0 = f0 * g0 + f1_2 * g9_19 + f2 * g8_19 + f3_2 * g7_19 + f4 * g6_19 + f5_2 * g5_19 + f6 * g4_19 + f7_2 * g3_19 + f8 * g2_19 + f9_2 * g1_19;
|
||||
let h1 = f0 * g1 + f1 * g0 + f2 * g9_19 + f3 * g8_19 + f4 * g7_19 + f5 * g6_19 + f6 * g5_19 + f7 * g4_19 + f8 * g3_19 + f9 * g2_19;
|
||||
let h2 = f0 * g2 + f1_2 * g1 + f2 * g0 + f3_2 * g9_19 + f4 * g8_19 + f5_2 * g7_19 + f6 * g6_19 + f7_2 * g5_19 + f8 * g4_19 + f9_2 * g3_19;
|
||||
let h3 = f0 * g3 + f1 * g2 + f2 * g1 + f3 * g0 + f4 * g9_19 + f5 * g8_19 + f6 * g7_19 + f7 * g6_19 + f8 * g5_19 + f9 * g4_19;
|
||||
let h4 = f0 * g4 + f1_2 * g3 + f2 * g2 + f3_2 * g1 + f4 * g0 + f5_2 * g9_19 + f6 * g8_19 + f7_2 * g7_19 + f8 * g6_19 + f9_2 * g5_19;
|
||||
let h5 = f0 * g5 + f1 * g4 + f2 * g3 + f3 * g2 + f4 * g1 + f5 * g0 + f6 * g9_19 + f7 * g8_19 + f8 * g7_19 + f9 * g6_19;
|
||||
let h6 = f0 * g6 + f1_2 * g5 + f2 * g4 + f3_2 * g3 + f4 * g2 + f5_2 * g1 + f6 * g0 + f7_2 * g9_19 + f8 * g8_19 + f9_2 * g7_19;
|
||||
let h7 = f0 * g7 + f1 * g6 + f2 * g5 + f3 * g4 + f4 * g3 + f5 * g2 + f6 * g1 + f7 * g0 + f8 * g9_19 + f9 * g8_19;
|
||||
let h8 = f0 * g8 + f1_2 * g7 + f2 * g6 + f3_2 * g5 + f4 * g4 + f5_2 * g3 + f6 * g2 + f7_2 * g1 + f8 * g0 + f9_2 * g9_19;
|
||||
let h9 = f0 * g9 + f1 * g8 + f2 * g7 + f3 * g6 + f4 * g5 + f5 * g4 + f6 * g3 + f7 * g2 + f8 * g1 + f9 * g0;
|
||||
|
||||
carry_mul([h0, h1, h2, h3, h4, h5, h6, h7, h8, h9])
|
||||
}
|
||||
|
||||
fn square(&self) -> Fe {
|
||||
self.mul(self)
|
||||
}
|
||||
|
||||
fn mul121666(&self) -> Fe {
|
||||
let mut h = [0i128; 10];
|
||||
for i in 0..10 {
|
||||
h[i] = (self.0[i] as i128) * 121666;
|
||||
}
|
||||
carry_mul(h)
|
||||
}
|
||||
|
||||
fn invert(&self) -> Fe {
|
||||
let mut t0 = self.square();
|
||||
let mut t1 = t0.square();
|
||||
t1 = t1.square();
|
||||
t1 = self.mul(&t1);
|
||||
t0 = t0.mul(&t1);
|
||||
let mut t2 = t0.square();
|
||||
t1 = t1.mul(&t2);
|
||||
t2 = t1.square();
|
||||
for _ in 1..5 {
|
||||
t2 = t2.square();
|
||||
}
|
||||
t1 = t2.mul(&t1);
|
||||
t2 = t1.square();
|
||||
for _ in 1..10 {
|
||||
t2 = t2.square();
|
||||
}
|
||||
t2 = t2.mul(&t1);
|
||||
let mut t3 = t2.square();
|
||||
for _ in 1..20 {
|
||||
t3 = t3.square();
|
||||
}
|
||||
t2 = t3.mul(&t2);
|
||||
t2 = t2.square();
|
||||
for _ in 1..10 {
|
||||
t2 = t2.square();
|
||||
}
|
||||
t1 = t2.mul(&t1);
|
||||
t2 = t1.square();
|
||||
for _ in 1..50 {
|
||||
t2 = t2.square();
|
||||
}
|
||||
t2 = t2.mul(&t1);
|
||||
t3 = t2.square();
|
||||
for _ in 1..100 {
|
||||
t3 = t3.square();
|
||||
}
|
||||
t2 = t3.mul(&t2);
|
||||
t2 = t2.square();
|
||||
for _ in 1..50 {
|
||||
t2 = t2.square();
|
||||
}
|
||||
t1 = t2.mul(&t1);
|
||||
t1 = t1.square();
|
||||
t1 = t1.square();
|
||||
t1 = t1.square();
|
||||
t1 = t1.square();
|
||||
t1 = t1.square();
|
||||
t0.mul(&t1)
|
||||
}
|
||||
|
||||
fn cswap(a: &mut Fe, b: &mut Fe, swap: i64) {
|
||||
let swap = -swap;
|
||||
for i in 0..10 {
|
||||
let x = swap & (a.0[i] ^ b.0[i]);
|
||||
a.0[i] ^= x;
|
||||
b.0[i] ^= x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_4(s: &[u8]) -> i64 {
|
||||
(s[0] as i64)
|
||||
| ((s[1] as i64) << 8)
|
||||
| ((s[2] as i64) << 16)
|
||||
| ((s[3] as i64) << 24)
|
||||
}
|
||||
|
||||
fn load_3(s: &[u8]) -> i64 {
|
||||
(s[0] as i64) | ((s[1] as i64) << 8) | ((s[2] as i64) << 16)
|
||||
}
|
||||
|
||||
fn carry_mul(h: [i128; 10]) -> Fe {
|
||||
let mut out = [0i64; 10];
|
||||
|
||||
let mut carry = (h[0] + (1 << 25)) >> 26;
|
||||
out[0] = (h[0] - (carry << 26)) as i64;
|
||||
let h1 = h[1] + carry;
|
||||
|
||||
carry = (h1 + (1 << 24)) >> 25;
|
||||
out[1] = (h1 - (carry << 25)) as i64;
|
||||
let h2 = h[2] + carry;
|
||||
|
||||
carry = (h2 + (1 << 25)) >> 26;
|
||||
out[2] = (h2 - (carry << 26)) as i64;
|
||||
let h3 = h[3] + carry;
|
||||
|
||||
carry = (h3 + (1 << 24)) >> 25;
|
||||
out[3] = (h3 - (carry << 25)) as i64;
|
||||
let h4 = h[4] + carry;
|
||||
|
||||
carry = (h4 + (1 << 25)) >> 26;
|
||||
out[4] = (h4 - (carry << 26)) as i64;
|
||||
let h5 = h[5] + carry;
|
||||
|
||||
carry = (h5 + (1 << 24)) >> 25;
|
||||
out[5] = (h5 - (carry << 25)) as i64;
|
||||
let h6 = h[6] + carry;
|
||||
|
||||
carry = (h6 + (1 << 25)) >> 26;
|
||||
out[6] = (h6 - (carry << 26)) as i64;
|
||||
let h7 = h[7] + carry;
|
||||
|
||||
carry = (h7 + (1 << 24)) >> 25;
|
||||
out[7] = (h7 - (carry << 25)) as i64;
|
||||
let h8 = h[8] + carry;
|
||||
|
||||
carry = (h8 + (1 << 25)) >> 26;
|
||||
out[8] = (h8 - (carry << 26)) as i64;
|
||||
let h9 = h[9] + carry;
|
||||
|
||||
carry = (h9 + (1 << 24)) >> 25;
|
||||
out[9] = (h9 - (carry << 25)) as i64;
|
||||
out[0] += (carry * 19) as i64;
|
||||
|
||||
carry = (out[0] as i128 + (1 << 25)) >> 26;
|
||||
out[0] -= (carry << 26) as i64;
|
||||
out[1] += carry as i64;
|
||||
|
||||
Fe(out)
|
||||
}
|
||||
|
||||
pub fn x25519(scalar: &[u8; 32], point: &[u8; 32]) -> [u8; 32] {
|
||||
|
||||
let mut k = *scalar;
|
||||
k[0] &= 248;
|
||||
k[31] &= 127;
|
||||
k[31] |= 64;
|
||||
|
||||
let u = Fe::from_bytes(point);
|
||||
|
||||
let mut x_1 = u;
|
||||
let mut x_2 = Fe::one();
|
||||
let mut z_2 = Fe::zero();
|
||||
let mut x_3 = u;
|
||||
let mut z_3 = Fe::one();
|
||||
|
||||
let mut swap: i64 = 0;
|
||||
|
||||
for pos in (0..255).rev() {
|
||||
let bit = ((k[pos / 8] >> (pos & 7)) & 1) as i64;
|
||||
swap ^= bit;
|
||||
Fe::cswap(&mut x_2, &mut x_3, swap);
|
||||
Fe::cswap(&mut z_2, &mut z_3, swap);
|
||||
swap = bit;
|
||||
|
||||
let a = x_2.add(&z_2);
|
||||
let aa = a.square();
|
||||
let b = x_2.sub(&z_2);
|
||||
let bb = b.square();
|
||||
let e = aa.sub(&bb);
|
||||
let c = x_3.add(&z_3);
|
||||
let d = x_3.sub(&z_3);
|
||||
let da = d.mul(&a);
|
||||
let cb = c.mul(&b);
|
||||
let sum = da.add(&cb);
|
||||
let diff = da.sub(&cb);
|
||||
x_3 = sum.square();
|
||||
z_3 = x_1.mul(&diff.square());
|
||||
x_2 = aa.mul(&bb);
|
||||
z_2 = e.mul(&aa.add(&e.mul121666()));
|
||||
}
|
||||
|
||||
Fe::cswap(&mut x_2, &mut x_3, swap);
|
||||
Fe::cswap(&mut z_2, &mut z_3, swap);
|
||||
|
||||
let result = x_2.mul(&z_2.invert());
|
||||
result.to_bytes()
|
||||
}
|
||||
|
||||
pub fn x25519_base(scalar: &[u8; 32]) -> [u8; 32] {
|
||||
|
||||
let basepoint: [u8; 32] = [
|
||||
9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
];
|
||||
x25519(scalar, &basepoint)
|
||||
}
|
||||
788
src/display.rs
Normal file
788
src/display.rs
Normal file
@@ -0,0 +1,788 @@
|
||||
use heapless::String;
|
||||
|
||||
pub const SSD1306_ADDR: u8 = 0x3C;
|
||||
|
||||
pub const DISPLAY_WIDTH: usize = 128;
|
||||
pub const DISPLAY_HEIGHT: usize = 64;
|
||||
|
||||
pub const DISPLAY_PAGES: usize = DISPLAY_HEIGHT / 8;
|
||||
|
||||
pub const FRAMEBUFFER_SIZE: usize = DISPLAY_WIDTH * DISPLAY_PAGES;
|
||||
|
||||
const CMD_SET_CONTRAST: u8 = 0x81;
|
||||
const CMD_DISPLAY_ALL_ON_RESUME: u8 = 0xA4;
|
||||
const CMD_DISPLAY_ALL_ON: u8 = 0xA5;
|
||||
const CMD_NORMAL_DISPLAY: u8 = 0xA6;
|
||||
const CMD_INVERT_DISPLAY: u8 = 0xA7;
|
||||
const CMD_DISPLAY_OFF: u8 = 0xAE;
|
||||
const CMD_DISPLAY_ON: u8 = 0xAF;
|
||||
const CMD_SET_DISPLAY_OFFSET: u8 = 0xD3;
|
||||
const CMD_SET_COM_PINS: u8 = 0xDA;
|
||||
const CMD_SET_VCOM_DETECT: u8 = 0xDB;
|
||||
const CMD_SET_DISPLAY_CLOCK_DIV: u8 = 0xD5;
|
||||
const CMD_SET_PRECHARGE: u8 = 0xD9;
|
||||
const CMD_SET_MULTIPLEX: u8 = 0xA8;
|
||||
const CMD_SET_LOW_COLUMN: u8 = 0x00;
|
||||
const CMD_SET_HIGH_COLUMN: u8 = 0x10;
|
||||
const CMD_SET_START_LINE: u8 = 0x40;
|
||||
const CMD_MEMORY_MODE: u8 = 0x20;
|
||||
const CMD_COLUMN_ADDR: u8 = 0x21;
|
||||
const CMD_PAGE_ADDR: u8 = 0x22;
|
||||
const CMD_COM_SCAN_INC: u8 = 0xC0;
|
||||
const CMD_COM_SCAN_DEC: u8 = 0xC8;
|
||||
const CMD_SEG_REMAP: u8 = 0xA0;
|
||||
const CMD_CHARGE_PUMP: u8 = 0x8D;
|
||||
const CMD_DEACTIVATE_SCROLL: u8 = 0x2E;
|
||||
|
||||
const CONTROL_CMD_SINGLE: u8 = 0x80;
|
||||
const CONTROL_CMD_STREAM: u8 = 0x00;
|
||||
const CONTROL_DATA_STREAM: u8 = 0x40;
|
||||
|
||||
static FONT_5X7: [u8; 320] = [
|
||||
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
0x00, 0x00, 0x5F, 0x00, 0x00,
|
||||
|
||||
0x00, 0x07, 0x00, 0x07, 0x00,
|
||||
|
||||
0x14, 0x7F, 0x14, 0x7F, 0x14,
|
||||
|
||||
0x24, 0x2A, 0x7F, 0x2A, 0x12,
|
||||
|
||||
0x23, 0x13, 0x08, 0x64, 0x62,
|
||||
|
||||
0x36, 0x49, 0x55, 0x22, 0x50,
|
||||
|
||||
0x00, 0x05, 0x03, 0x00, 0x00,
|
||||
|
||||
0x00, 0x1C, 0x22, 0x41, 0x00,
|
||||
|
||||
0x00, 0x41, 0x22, 0x1C, 0x00,
|
||||
|
||||
0x08, 0x2A, 0x1C, 0x2A, 0x08,
|
||||
|
||||
0x08, 0x08, 0x3E, 0x08, 0x08,
|
||||
|
||||
0x00, 0x50, 0x30, 0x00, 0x00,
|
||||
|
||||
0x08, 0x08, 0x08, 0x08, 0x08,
|
||||
|
||||
0x00, 0x60, 0x60, 0x00, 0x00,
|
||||
|
||||
0x20, 0x10, 0x08, 0x04, 0x02,
|
||||
|
||||
0x3E, 0x51, 0x49, 0x45, 0x3E,
|
||||
|
||||
0x00, 0x42, 0x7F, 0x40, 0x00,
|
||||
|
||||
0x42, 0x61, 0x51, 0x49, 0x46,
|
||||
|
||||
0x21, 0x41, 0x45, 0x4B, 0x31,
|
||||
|
||||
0x18, 0x14, 0x12, 0x7F, 0x10,
|
||||
|
||||
0x27, 0x45, 0x45, 0x45, 0x39,
|
||||
|
||||
0x3C, 0x4A, 0x49, 0x49, 0x30,
|
||||
|
||||
0x01, 0x71, 0x09, 0x05, 0x03,
|
||||
|
||||
0x36, 0x49, 0x49, 0x49, 0x36,
|
||||
|
||||
0x06, 0x49, 0x49, 0x29, 0x1E,
|
||||
|
||||
0x00, 0x36, 0x36, 0x00, 0x00,
|
||||
|
||||
0x00, 0x56, 0x36, 0x00, 0x00,
|
||||
|
||||
0x00, 0x08, 0x14, 0x22, 0x41,
|
||||
|
||||
0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
|
||||
0x41, 0x22, 0x14, 0x08, 0x00,
|
||||
|
||||
0x02, 0x01, 0x51, 0x09, 0x06,
|
||||
|
||||
0x32, 0x49, 0x79, 0x41, 0x3E,
|
||||
|
||||
0x7E, 0x11, 0x11, 0x11, 0x7E,
|
||||
|
||||
0x7F, 0x49, 0x49, 0x49, 0x36,
|
||||
|
||||
0x3E, 0x41, 0x41, 0x41, 0x22,
|
||||
|
||||
0x7F, 0x41, 0x41, 0x22, 0x1C,
|
||||
|
||||
0x7F, 0x49, 0x49, 0x49, 0x41,
|
||||
|
||||
0x7F, 0x09, 0x09, 0x01, 0x01,
|
||||
|
||||
0x3E, 0x41, 0x41, 0x51, 0x32,
|
||||
|
||||
0x7F, 0x08, 0x08, 0x08, 0x7F,
|
||||
|
||||
0x00, 0x41, 0x7F, 0x41, 0x00,
|
||||
|
||||
0x20, 0x40, 0x41, 0x3F, 0x01,
|
||||
|
||||
0x7F, 0x08, 0x14, 0x22, 0x41,
|
||||
|
||||
0x7F, 0x40, 0x40, 0x40, 0x40,
|
||||
|
||||
0x7F, 0x02, 0x04, 0x02, 0x7F,
|
||||
|
||||
0x7F, 0x04, 0x08, 0x10, 0x7F,
|
||||
|
||||
0x3E, 0x41, 0x41, 0x41, 0x3E,
|
||||
|
||||
0x7F, 0x09, 0x09, 0x09, 0x06,
|
||||
|
||||
0x3E, 0x41, 0x51, 0x21, 0x5E,
|
||||
|
||||
0x7F, 0x09, 0x19, 0x29, 0x46,
|
||||
|
||||
0x46, 0x49, 0x49, 0x49, 0x31,
|
||||
|
||||
0x01, 0x01, 0x7F, 0x01, 0x01,
|
||||
|
||||
0x3F, 0x40, 0x40, 0x40, 0x3F,
|
||||
|
||||
0x1F, 0x20, 0x40, 0x20, 0x1F,
|
||||
|
||||
0x7F, 0x20, 0x18, 0x20, 0x7F,
|
||||
|
||||
0x63, 0x14, 0x08, 0x14, 0x63,
|
||||
|
||||
0x03, 0x04, 0x78, 0x04, 0x03,
|
||||
|
||||
0x61, 0x51, 0x49, 0x45, 0x43,
|
||||
|
||||
0x00, 0x00, 0x7F, 0x41, 0x41,
|
||||
|
||||
0x02, 0x04, 0x08, 0x10, 0x20,
|
||||
|
||||
0x41, 0x41, 0x7F, 0x00, 0x00,
|
||||
|
||||
0x04, 0x02, 0x01, 0x02, 0x04,
|
||||
|
||||
0x40, 0x40, 0x40, 0x40, 0x40,
|
||||
];
|
||||
|
||||
pub struct Display<I2C> {
|
||||
|
||||
i2c: I2C,
|
||||
|
||||
framebuffer: [u8; FRAMEBUFFER_SIZE],
|
||||
|
||||
power_on: bool,
|
||||
|
||||
inverted: bool,
|
||||
|
||||
contrast: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DisplayError {
|
||||
|
||||
I2cError,
|
||||
|
||||
InvalidCoordinates,
|
||||
|
||||
BufferOverflow,
|
||||
}
|
||||
|
||||
impl<I2C, E> Display<I2C>
|
||||
where
|
||||
I2C: embedded_hal::i2c::I2c<Error = E>,
|
||||
{
|
||||
|
||||
pub fn new(i2c: I2C) -> Self {
|
||||
Self {
|
||||
i2c,
|
||||
framebuffer: [0u8; FRAMEBUFFER_SIZE],
|
||||
power_on: false,
|
||||
inverted: false,
|
||||
contrast: 0x7F,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> Result<(), DisplayError> {
|
||||
|
||||
let init_cmds: [u8; 26] = [
|
||||
CMD_DISPLAY_OFF,
|
||||
CMD_SET_DISPLAY_CLOCK_DIV, 0x80,
|
||||
CMD_SET_MULTIPLEX, 0x3F,
|
||||
CMD_SET_DISPLAY_OFFSET, 0x00,
|
||||
CMD_SET_START_LINE | 0x00,
|
||||
CMD_CHARGE_PUMP, 0x14,
|
||||
CMD_MEMORY_MODE, 0x00,
|
||||
CMD_SEG_REMAP | 0x01,
|
||||
CMD_COM_SCAN_DEC,
|
||||
CMD_SET_COM_PINS, 0x12,
|
||||
CMD_SET_CONTRAST, self.contrast,
|
||||
CMD_SET_PRECHARGE, 0xF1,
|
||||
CMD_SET_VCOM_DETECT, 0x40,
|
||||
CMD_DISPLAY_ALL_ON_RESUME,
|
||||
CMD_NORMAL_DISPLAY,
|
||||
CMD_DEACTIVATE_SCROLL,
|
||||
CMD_DISPLAY_ON,
|
||||
];
|
||||
|
||||
for cmd in init_cmds {
|
||||
self.write_command(cmd)?;
|
||||
}
|
||||
|
||||
self.power_on = true;
|
||||
self.clear();
|
||||
self.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_command(&mut self, cmd: u8) -> Result<(), DisplayError> {
|
||||
let buf = [CONTROL_CMD_SINGLE, cmd];
|
||||
self.i2c.write(SSD1306_ADDR, &buf).map_err(|_| DisplayError::I2cError)
|
||||
}
|
||||
|
||||
fn write_commands(&mut self, cmds: &[u8]) -> Result<(), DisplayError> {
|
||||
for &cmd in cmds {
|
||||
self.write_command(cmd)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> Result<(), DisplayError> {
|
||||
|
||||
self.write_commands(&[CMD_COLUMN_ADDR, 0, (DISPLAY_WIDTH - 1) as u8])?;
|
||||
|
||||
self.write_commands(&[CMD_PAGE_ADDR, 0, (DISPLAY_PAGES - 1) as u8])?;
|
||||
|
||||
const CHUNK_SIZE: usize = 128;
|
||||
for chunk in self.framebuffer.chunks(CHUNK_SIZE) {
|
||||
let mut buf = [0u8; CHUNK_SIZE + 1];
|
||||
buf[0] = CONTROL_DATA_STREAM;
|
||||
buf[1..1 + chunk.len()].copy_from_slice(chunk);
|
||||
self.i2c.write(SSD1306_ADDR, &buf[..1 + chunk.len()])
|
||||
.map_err(|_| DisplayError::I2cError)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.framebuffer.fill(0);
|
||||
}
|
||||
|
||||
pub fn fill(&mut self) {
|
||||
self.framebuffer.fill(0xFF);
|
||||
}
|
||||
|
||||
pub fn set_pixel(&mut self, x: usize, y: usize, on: bool) {
|
||||
if x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT {
|
||||
return;
|
||||
}
|
||||
|
||||
let page = y / 8;
|
||||
let bit = y % 8;
|
||||
let idx = page * DISPLAY_WIDTH + x;
|
||||
|
||||
if on {
|
||||
self.framebuffer[idx] |= 1 << bit;
|
||||
} else {
|
||||
self.framebuffer[idx] &= !(1 << bit);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_pixel(&self, x: usize, y: usize) -> bool {
|
||||
if x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT {
|
||||
return false;
|
||||
}
|
||||
|
||||
let page = y / 8;
|
||||
let bit = y % 8;
|
||||
let idx = page * DISPLAY_WIDTH + x;
|
||||
|
||||
(self.framebuffer[idx] >> bit) & 1 != 0
|
||||
}
|
||||
|
||||
pub fn draw_hline(&mut self, x: usize, y: usize, width: usize, on: bool) {
|
||||
for dx in 0..width {
|
||||
self.set_pixel(x + dx, y, on);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_vline(&mut self, x: usize, y: usize, height: usize, on: bool) {
|
||||
for dy in 0..height {
|
||||
self.set_pixel(x, y + dy, on);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_rect(&mut self, x: usize, y: usize, width: usize, height: usize, on: bool) {
|
||||
self.draw_hline(x, y, width, on);
|
||||
self.draw_hline(x, y + height - 1, width, on);
|
||||
self.draw_vline(x, y, height, on);
|
||||
self.draw_vline(x + width - 1, y, height, on);
|
||||
}
|
||||
|
||||
pub fn fill_rect(&mut self, x: usize, y: usize, width: usize, height: usize, on: bool) {
|
||||
for dy in 0..height {
|
||||
self.draw_hline(x, y + dy, width, on);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_char(&mut self, x: usize, y: usize, c: char) -> usize {
|
||||
let c = c as u8;
|
||||
if c < 32 || c > 95 + 32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let idx = ((c - 32) as usize) * 5;
|
||||
if idx + 5 > FONT_5X7.len() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for col in 0..5 {
|
||||
let bits = FONT_5X7[idx + col];
|
||||
for row in 0..7 {
|
||||
let on = (bits >> row) & 1 != 0;
|
||||
self.set_pixel(x + col, y + row, on);
|
||||
}
|
||||
}
|
||||
|
||||
6
|
||||
}
|
||||
|
||||
pub fn draw_text(&mut self, x: usize, y: usize, text: &str) {
|
||||
let mut cx = x;
|
||||
for c in text.chars() {
|
||||
if cx >= DISPLAY_WIDTH {
|
||||
break;
|
||||
}
|
||||
cx += self.draw_char(cx, y, c);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_text_centered(&mut self, y: usize, text: &str) {
|
||||
let width = text.len() * 6;
|
||||
let x = if width < DISPLAY_WIDTH {
|
||||
(DISPLAY_WIDTH - width) / 2
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.draw_text(x, y, text);
|
||||
}
|
||||
|
||||
pub fn set_contrast(&mut self, contrast: u8) -> Result<(), DisplayError> {
|
||||
self.contrast = contrast;
|
||||
self.write_commands(&[CMD_SET_CONTRAST, contrast])
|
||||
}
|
||||
|
||||
pub fn power_on(&mut self) -> Result<(), DisplayError> {
|
||||
self.write_command(CMD_DISPLAY_ON)?;
|
||||
self.power_on = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn power_off(&mut self) -> Result<(), DisplayError> {
|
||||
self.write_command(CMD_DISPLAY_OFF)?;
|
||||
self.power_on = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn invert(&mut self, invert: bool) -> Result<(), DisplayError> {
|
||||
self.inverted = invert;
|
||||
if invert {
|
||||
self.write_command(CMD_INVERT_DISPLAY)
|
||||
} else {
|
||||
self.write_command(CMD_NORMAL_DISPLAY)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_on(&self) -> bool {
|
||||
self.power_on
|
||||
}
|
||||
|
||||
pub fn framebuffer(&self) -> &[u8; FRAMEBUFFER_SIZE] {
|
||||
&self.framebuffer
|
||||
}
|
||||
|
||||
pub fn framebuffer_mut(&mut self) -> &mut [u8; FRAMEBUFFER_SIZE] {
|
||||
&mut self.framebuffer
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StatusDisplay<I2C> {
|
||||
display: Display<I2C>,
|
||||
}
|
||||
|
||||
pub struct StatusContent {
|
||||
|
||||
pub protocol: &'static str,
|
||||
|
||||
pub node_id: u32,
|
||||
|
||||
pub battery_pct: u8,
|
||||
|
||||
pub rx_count: u32,
|
||||
|
||||
pub tx_count: u32,
|
||||
|
||||
pub rssi: i16,
|
||||
|
||||
pub connected: bool,
|
||||
}
|
||||
|
||||
impl<I2C, E> StatusDisplay<I2C>
|
||||
where
|
||||
I2C: embedded_hal::i2c::I2c<Error = E>,
|
||||
{
|
||||
|
||||
pub fn new(i2c: I2C) -> Self {
|
||||
Self {
|
||||
display: Display::new(i2c),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> Result<(), DisplayError> {
|
||||
self.display.init()
|
||||
}
|
||||
|
||||
pub fn show_splash(&mut self) -> Result<(), DisplayError> {
|
||||
self.display.clear();
|
||||
|
||||
self.display.draw_rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, true);
|
||||
|
||||
self.display.draw_text_centered(8, "LunarCore");
|
||||
self.display.draw_text_centered(18, "v1.0.0");
|
||||
|
||||
self.display.draw_text_centered(32, "Unified Mesh");
|
||||
self.display.draw_text_centered(42, "Bridge Firmware");
|
||||
|
||||
self.display.draw_text_centered(54, "MC | MT | RN");
|
||||
|
||||
self.display.flush()
|
||||
}
|
||||
|
||||
pub fn show_status(&mut self, status: &StatusContent) -> Result<(), DisplayError> {
|
||||
self.display.clear();
|
||||
|
||||
self.display.draw_text(0, 0, status.protocol);
|
||||
|
||||
let batt_str = format_battery(status.battery_pct);
|
||||
self.display.draw_text(DISPLAY_WIDTH - 24, 0, &batt_str);
|
||||
|
||||
self.display.draw_hline(0, 9, DISPLAY_WIDTH, true);
|
||||
|
||||
self.display.draw_text(0, 12, "ID:");
|
||||
let id_str = format_hex32(status.node_id);
|
||||
self.display.draw_text(24, 12, &id_str);
|
||||
|
||||
self.display.draw_text(0, 22, "RX:");
|
||||
let rx_str = format_u32(status.rx_count);
|
||||
self.display.draw_text(24, 22, &rx_str);
|
||||
|
||||
self.display.draw_text(64, 22, "TX:");
|
||||
let tx_str = format_u32(status.tx_count);
|
||||
self.display.draw_text(88, 22, &tx_str);
|
||||
|
||||
self.display.draw_text(0, 32, "RSSI:");
|
||||
let rssi_str = format_i16(status.rssi);
|
||||
self.display.draw_text(36, 32, &rssi_str);
|
||||
self.display.draw_text(72, 32, "dBm");
|
||||
|
||||
self.display.draw_text(0, 44, "Status:");
|
||||
if status.connected {
|
||||
self.display.draw_text(48, 44, "CONNECTED");
|
||||
} else {
|
||||
self.display.draw_text(48, 44, "WAITING");
|
||||
}
|
||||
|
||||
self.display.draw_hline(0, 54, DISPLAY_WIDTH, true);
|
||||
|
||||
self.display.draw_text_centered(56, "github.com/yours");
|
||||
|
||||
self.display.flush()
|
||||
}
|
||||
|
||||
pub fn show_error(&mut self, msg: &str) -> Result<(), DisplayError> {
|
||||
self.display.clear();
|
||||
|
||||
self.display.draw_text_centered(20, "ERROR");
|
||||
self.display.draw_hline(20, 30, DISPLAY_WIDTH - 40, true);
|
||||
self.display.draw_text_centered(36, msg);
|
||||
|
||||
self.display.flush()
|
||||
}
|
||||
|
||||
pub fn show_message(&mut self, line1: &str, line2: &str) -> Result<(), DisplayError> {
|
||||
self.display.clear();
|
||||
|
||||
self.display.draw_text_centered(24, line1);
|
||||
self.display.draw_text_centered(36, line2);
|
||||
|
||||
self.display.flush()
|
||||
}
|
||||
|
||||
pub fn power_off(&mut self) -> Result<(), DisplayError> {
|
||||
self.display.power_off()
|
||||
}
|
||||
|
||||
pub fn power_on(&mut self) -> Result<(), DisplayError> {
|
||||
self.display.power_on()
|
||||
}
|
||||
|
||||
pub fn display(&mut self) -> &mut Display<I2C> {
|
||||
&mut self.display
|
||||
}
|
||||
}
|
||||
|
||||
fn format_battery(pct: u8) -> String<4> {
|
||||
let mut s = String::new();
|
||||
if pct >= 100 {
|
||||
let _ = s.push_str("100");
|
||||
} else if pct >= 10 {
|
||||
let _ = s.push((b'0' + pct / 10) as char);
|
||||
let _ = s.push((b'0' + pct % 10) as char);
|
||||
} else {
|
||||
let _ = s.push((b'0' + pct) as char);
|
||||
}
|
||||
let _ = s.push('%');
|
||||
s
|
||||
}
|
||||
|
||||
fn format_hex32(val: u32) -> String<8> {
|
||||
const HEX: &[u8] = b"0123456789ABCDEF";
|
||||
let mut s = String::new();
|
||||
for i in (0..8).rev() {
|
||||
let nibble = ((val >> (i * 4)) & 0xF) as usize;
|
||||
let _ = s.push(HEX[nibble] as char);
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
fn format_u32(val: u32) -> String<10> {
|
||||
let mut s = String::new();
|
||||
if val == 0 {
|
||||
let _ = s.push('0');
|
||||
return s;
|
||||
}
|
||||
|
||||
let mut v = val;
|
||||
let mut digits = [0u8; 10];
|
||||
let mut count = 0;
|
||||
|
||||
while v > 0 {
|
||||
digits[count] = (v % 10) as u8;
|
||||
v /= 10;
|
||||
count += 1;
|
||||
}
|
||||
|
||||
for i in (0..count).rev() {
|
||||
let _ = s.push((b'0' + digits[i]) as char);
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
fn format_i16(val: i16) -> String<6> {
|
||||
let mut s = String::new();
|
||||
if val < 0 {
|
||||
let _ = s.push('-');
|
||||
let abs = (-(val as i32)) as u32;
|
||||
let formatted = format_u32(abs);
|
||||
let _ = s.push_str(&formatted);
|
||||
} else {
|
||||
let formatted = format_u32(val as u32);
|
||||
let _ = s.push_str(&formatted);
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
static MOON_PHASES: [[u8; 72]; 8] = [
|
||||
|
||||
[
|
||||
0x00,0x00,0x00,0x00,0x00,0xE0,0xF0,0x38,0x1C,0x0C,0x06,0x06,
|
||||
0x06,0x06,0x0C,0x1C,0x38,0xF0,0xE0,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x1F,0x7F,0xE0,0x80,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x80,0xE0,0x7F,0x1F,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x07,0x0F,0x1C,0x38,0x30,0x60,0x60,
|
||||
0x60,0x60,0x30,0x38,0x1C,0x0F,0x07,0x00,0x00,0x00,0x00,0x00,
|
||||
],
|
||||
|
||||
[
|
||||
0x00,0x00,0x00,0x00,0x00,0xE0,0xF0,0x38,0x1C,0x0C,0x06,0x06,
|
||||
0x06,0x06,0x0C,0x1C,0x38,0xF0,0xE0,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x1F,0x7F,0xFF,0xFF,0xFE,0xFC,0xF8,0xF0,0xE0,
|
||||
0xC0,0x80,0x00,0x00,0x00,0x80,0xE0,0x7F,0x1F,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x07,0x0F,0x1C,0x38,0x31,0x63,0x63,
|
||||
0x63,0x63,0x31,0x38,0x1C,0x0F,0x07,0x00,0x00,0x00,0x00,0x00,
|
||||
],
|
||||
|
||||
[
|
||||
0x00,0x00,0x00,0x00,0x00,0xE0,0xF0,0x38,0x1C,0x0C,0x06,0x06,
|
||||
0xFE,0xFE,0xFC,0xFC,0xF8,0xF0,0xE0,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x1F,0x7F,0xFF,0xFF,0xFE,0xFC,0xF8,0xF0,0xE0,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0x1F,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x07,0x0F,0x1C,0x38,0x30,0x60,0x60,
|
||||
0x7F,0x7F,0x3F,0x3F,0x1F,0x0F,0x07,0x00,0x00,0x00,0x00,0x00,
|
||||
],
|
||||
|
||||
[
|
||||
0x00,0x00,0x00,0x00,0x00,0xE0,0xF0,0xF8,0xFC,0xFC,0xFE,0xFE,
|
||||
0xFE,0xFE,0xFC,0xFC,0xF8,0xF0,0xE0,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x1F,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0x1F,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x07,0x0F,0x1F,0x3F,0x3F,0x7F,0x7F,
|
||||
0x7F,0x7F,0x3F,0x3F,0x1F,0x0F,0x07,0x00,0x00,0x00,0x00,0x00,
|
||||
],
|
||||
|
||||
[
|
||||
0x00,0x00,0x00,0x00,0x00,0xE0,0xF0,0xF8,0xFC,0xFC,0xFE,0xFE,
|
||||
0xFE,0xFE,0xFC,0xFC,0xF8,0xF0,0xE0,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x1F,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0x1F,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x07,0x0F,0x1F,0x3F,0x3F,0x7F,0x7F,
|
||||
0x7F,0x7F,0x3F,0x3F,0x1F,0x0F,0x07,0x00,0x00,0x00,0x00,0x00,
|
||||
],
|
||||
|
||||
[
|
||||
0x00,0x00,0x00,0x00,0x00,0xE0,0xF0,0xF8,0xFC,0xFC,0xFE,0xFE,
|
||||
0xFE,0xFE,0xFC,0xFC,0xF8,0xF0,0xE0,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x1F,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0x1F,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x07,0x0F,0x1F,0x3F,0x3F,0x7F,0x7F,
|
||||
0x7F,0x7F,0x3F,0x3F,0x1F,0x0F,0x07,0x00,0x00,0x00,0x00,0x00,
|
||||
],
|
||||
|
||||
[
|
||||
0x00,0x00,0x00,0x00,0x00,0xE0,0xF0,0xF8,0xFC,0xFC,0xFE,0xFE,
|
||||
0x06,0x06,0x0C,0x1C,0x38,0xF0,0xE0,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x1F,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x80,0xE0,0x7F,0x1F,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x07,0x0F,0x1F,0x3F,0x3F,0x7F,0x7F,
|
||||
0x60,0x60,0x30,0x38,0x1C,0x0F,0x07,0x00,0x00,0x00,0x00,0x00,
|
||||
],
|
||||
|
||||
[
|
||||
0x00,0x00,0x00,0x00,0x00,0xE0,0xF0,0xF8,0xFC,0xFC,0xFE,0xFE,
|
||||
0x06,0x06,0x0C,0x1C,0x38,0xF0,0xE0,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x1F,0x7F,0xFF,0xFF,0x07,0x03,0x01,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x80,0xE0,0x7F,0x1F,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x07,0x0F,0x1F,0x3F,0x3F,0x7F,0x7F,
|
||||
0x60,0x60,0x30,0x38,0x1C,0x0F,0x07,0x00,0x00,0x00,0x00,0x00,
|
||||
],
|
||||
];
|
||||
|
||||
static YOURS_FACE: [u8; 128] = [
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xE0,0xF0,0xF8,0xF8,0xF8,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xF8,0xF8,0xF0,0xF0,0xE0,0xE0,0xE0,0xC0,0x80,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xFF,0xFF,0xFF,0xFF,0xFF,0xEF,0xE7,0xE7,0xE7,0xC7,0xC7,0xC7,0xC7,0xC7,0xFF,0xFF,0xFF,0xFF,0x0F,0x07,0x03,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x0F,0x0F,0x7F,0xFF,0xFD,0xF9,0xC1,0xC1,0xDE,0xFE,0xF0,0xFF,0xFF,0xFF,0xFF,0x7F,0x1F,0x07,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x07,0x0F,0x1F,0x1F,0x1D,0x1D,0x0F,0x0F,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
];
|
||||
|
||||
impl<I2C, E> Display<I2C>
|
||||
where
|
||||
I2C: embedded_hal::i2c::I2c<Error = E>,
|
||||
{
|
||||
|
||||
pub fn draw_bitmap_24x24(&mut self, x: usize, y: usize, bitmap: &[u8; 72]) {
|
||||
for col in 0..24 {
|
||||
for page in 0..3 {
|
||||
let byte = bitmap[page * 24 + col];
|
||||
for bit in 0..8 {
|
||||
let px_y = y + page * 8 + bit;
|
||||
let px_x = x + col;
|
||||
if px_x < DISPLAY_WIDTH && px_y < DISPLAY_HEIGHT {
|
||||
self.set_pixel(px_x, px_y, (byte >> bit) & 1 != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_bitmap_32x32(&mut self, x: usize, y: usize, bitmap: &[u8; 128]) {
|
||||
for col in 0..32 {
|
||||
for page in 0..4 {
|
||||
let byte = bitmap[page * 32 + col];
|
||||
for bit in 0..8 {
|
||||
let px_y = y + page * 8 + bit;
|
||||
let px_x = x + col;
|
||||
if px_x < DISPLAY_WIDTH && px_y < DISPLAY_HEIGHT {
|
||||
self.set_pixel(px_x, px_y, (byte >> bit) & 1 != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, E> StatusDisplay<I2C>
|
||||
where
|
||||
I2C: embedded_hal::i2c::I2c<Error = E>,
|
||||
{
|
||||
|
||||
pub fn boot_animation(&mut self, delay_fn: &mut dyn FnMut(u32)) -> Result<(), DisplayError> {
|
||||
|
||||
for cycle in 0..2 {
|
||||
for phase in 0..8 {
|
||||
self.display.clear();
|
||||
|
||||
let moon_x = (DISPLAY_WIDTH - 24) / 2;
|
||||
let moon_y = 8;
|
||||
self.display.draw_bitmap_24x24(moon_x, moon_y, &MOON_PHASES[phase]);
|
||||
|
||||
self.display.draw_text_centered(40, "LUNARCORE");
|
||||
|
||||
let dots_x = (DISPLAY_WIDTH - 8 * 4) / 2;
|
||||
for i in 0..8 {
|
||||
let on = i <= phase;
|
||||
self.display.fill_rect(dots_x + i * 4, 54, 2, 2, on);
|
||||
}
|
||||
|
||||
self.display.flush()?;
|
||||
|
||||
let delay = if cycle == 1 { 120 } else { 80 };
|
||||
delay_fn(delay);
|
||||
}
|
||||
}
|
||||
|
||||
self.show_branding()?;
|
||||
delay_fn(1500);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn show_branding(&mut self) -> Result<(), DisplayError> {
|
||||
self.display.clear();
|
||||
|
||||
let logo_x = (DISPLAY_WIDTH - 32) / 2;
|
||||
let logo_y = 4;
|
||||
self.display.draw_bitmap_32x32(logo_x, logo_y, &YOURS_FACE);
|
||||
|
||||
self.display.draw_text_centered(42, "[ YOURS ]");
|
||||
self.display.draw_text_centered(52, "x [ LUNARCORE ]");
|
||||
|
||||
self.display.flush()
|
||||
}
|
||||
|
||||
pub fn show_init_progress(&mut self, step: &str, progress: u8) -> Result<(), DisplayError> {
|
||||
self.display.clear();
|
||||
|
||||
self.display.draw_text_centered(8, "INITIALIZING");
|
||||
|
||||
self.display.draw_text_centered(24, step);
|
||||
|
||||
let bar_width = 100;
|
||||
let bar_x = (DISPLAY_WIDTH - bar_width) / 2;
|
||||
let bar_y = 40;
|
||||
let filled = (bar_width as u32 * progress as u32 / 100) as usize;
|
||||
|
||||
self.display.draw_rect(bar_x, bar_y, bar_width, 8, true);
|
||||
self.display.fill_rect(bar_x + 1, bar_y + 1, filled.saturating_sub(2), 6, true);
|
||||
|
||||
let pct_str = format_battery(progress);
|
||||
self.display.draw_text_centered(52, &pct_str);
|
||||
|
||||
self.display.flush()
|
||||
}
|
||||
}
|
||||
451
src/identity.rs
Normal file
451
src/identity.rs
Normal file
@@ -0,0 +1,451 @@
|
||||
use crate::crypto::ed25519::{Ed25519, PublicKey, PrivateKey, Signature};
|
||||
use crate::crypto::x25519::{x25519, x25519_base};
|
||||
use crate::crypto::sha256::Sha256;
|
||||
use crate::crypto::chacha20::{ChaCha20, KEY_SIZE, NONCE_SIZE};
|
||||
|
||||
pub const SEED_SIZE: usize = 32;
|
||||
|
||||
pub const NODE_ID_SIZE: usize = 4;
|
||||
|
||||
pub const SHORT_ID_SIZE: usize = 8;
|
||||
|
||||
const IDENTITY_FLASH_KEY: &str = "lunar_id";
|
||||
|
||||
const KDF_CONTEXT_IDENTITY: &[u8] = b"LunarCore Identity v1";
|
||||
const KDF_CONTEXT_SIGNING: &[u8] = b"LunarCore Signing v1";
|
||||
const KDF_CONTEXT_ENCRYPTION: &[u8] = b"LunarCore Encryption v1";
|
||||
|
||||
pub struct Identity {
|
||||
|
||||
signing_key: PrivateKey,
|
||||
|
||||
public_key: PublicKey,
|
||||
|
||||
encryption_key: [u8; 32],
|
||||
|
||||
encryption_public: [u8; 32],
|
||||
|
||||
created_at: u32,
|
||||
}
|
||||
|
||||
impl Drop for Identity {
|
||||
fn drop(&mut self) {
|
||||
crate::crypto::secure_zero(&mut self.signing_key);
|
||||
crate::crypto::secure_zero(&mut self.encryption_key);
|
||||
}
|
||||
}
|
||||
|
||||
impl Identity {
|
||||
|
||||
pub fn generate() -> Self {
|
||||
|
||||
let mut seed = [0u8; SEED_SIZE];
|
||||
if !crate::rng::fill_random_checked(&mut seed) {
|
||||
panic!("RNG health check failed - cannot generate identity with weak entropy");
|
||||
}
|
||||
|
||||
Self::from_seed(&seed)
|
||||
}
|
||||
|
||||
pub fn from_seed(seed: &[u8; SEED_SIZE]) -> Self {
|
||||
|
||||
let signing_key = Self::derive_key(seed, KDF_CONTEXT_SIGNING);
|
||||
let public_key = Ed25519::public_key(&signing_key);
|
||||
|
||||
let encryption_key = Self::derive_key(seed, KDF_CONTEXT_ENCRYPTION);
|
||||
let encryption_public = x25519_base(&encryption_key);
|
||||
|
||||
Self {
|
||||
signing_key,
|
||||
public_key,
|
||||
encryption_key,
|
||||
encryption_public,
|
||||
created_at: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_key(seed: &[u8; SEED_SIZE], context: &[u8]) -> [u8; 32] {
|
||||
let mut input = [0u8; 64];
|
||||
input[..32].copy_from_slice(seed);
|
||||
let context_len = context.len().min(32);
|
||||
input[32..32 + context_len].copy_from_slice(&context[..context_len]);
|
||||
Sha256::hash(&input)
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> &PublicKey {
|
||||
&self.public_key
|
||||
}
|
||||
|
||||
pub fn encryption_public_key(&self) -> &[u8; 32] {
|
||||
&self.encryption_public
|
||||
}
|
||||
|
||||
pub fn node_id(&self) -> u32 {
|
||||
let hash = Sha256::hash(&self.public_key);
|
||||
u32::from_le_bytes([hash[0], hash[1], hash[2], hash[3]])
|
||||
}
|
||||
|
||||
pub fn short_id(&self) -> [u8; SHORT_ID_SIZE] {
|
||||
let hash = Sha256::hash(&self.public_key);
|
||||
let mut short = [0u8; SHORT_ID_SIZE];
|
||||
for i in 0..4 {
|
||||
let byte = hash[i];
|
||||
short[i * 2] = hex_char(byte >> 4);
|
||||
short[i * 2 + 1] = hex_char(byte & 0x0F);
|
||||
}
|
||||
short
|
||||
}
|
||||
|
||||
pub fn sign(&self, message: &[u8]) -> Signature {
|
||||
Ed25519::sign(&self.signing_key, message)
|
||||
}
|
||||
|
||||
pub fn verify(public_key: &PublicKey, message: &[u8], signature: &Signature) -> bool {
|
||||
Ed25519::verify(public_key, message, signature)
|
||||
}
|
||||
|
||||
pub fn key_agree(&self, their_public: &[u8; 32]) -> [u8; 32] {
|
||||
x25519(&self.encryption_key, their_public)
|
||||
}
|
||||
|
||||
pub fn encrypt_for_storage(&self, storage_key: &[u8; 32]) -> EncryptedIdentity {
|
||||
|
||||
let mut plaintext = [0u8; 128];
|
||||
plaintext[0..32].copy_from_slice(&self.signing_key);
|
||||
plaintext[32..64].copy_from_slice(&self.encryption_key);
|
||||
plaintext[64..68].copy_from_slice(&self.created_at.to_le_bytes());
|
||||
|
||||
let mut nonce = [0u8; NONCE_SIZE];
|
||||
if !crate::rng::fill_random_checked(&mut nonce) {
|
||||
panic!("RNG health check failed - cannot encrypt identity with weak nonce");
|
||||
}
|
||||
|
||||
let cipher = ChaCha20::new(storage_key, &nonce);
|
||||
let mut ciphertext = plaintext;
|
||||
cipher.encrypt(&mut ciphertext);
|
||||
|
||||
let mut tag_input = [0u8; 128 + NONCE_SIZE];
|
||||
tag_input[..128].copy_from_slice(&ciphertext);
|
||||
tag_input[128..].copy_from_slice(&nonce);
|
||||
let tag = Sha256::hash(&tag_input);
|
||||
|
||||
EncryptedIdentity {
|
||||
ciphertext,
|
||||
nonce,
|
||||
tag,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_from_storage(
|
||||
encrypted: &EncryptedIdentity,
|
||||
storage_key: &[u8; 32],
|
||||
) -> Option<Self> {
|
||||
|
||||
let mut tag_input = [0u8; 128 + NONCE_SIZE];
|
||||
tag_input[..128].copy_from_slice(&encrypted.ciphertext);
|
||||
tag_input[128..].copy_from_slice(&encrypted.nonce);
|
||||
let expected_tag = Sha256::hash(&tag_input);
|
||||
|
||||
if !crate::crypto::constant_time_eq(&encrypted.tag, &expected_tag) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cipher = ChaCha20::new(storage_key, &encrypted.nonce);
|
||||
let mut plaintext = encrypted.ciphertext;
|
||||
cipher.decrypt(&mut plaintext);
|
||||
|
||||
let mut signing_key = [0u8; 32];
|
||||
let mut encryption_key = [0u8; 32];
|
||||
signing_key.copy_from_slice(&plaintext[0..32]);
|
||||
encryption_key.copy_from_slice(&plaintext[32..64]);
|
||||
let created_at = u32::from_le_bytes([
|
||||
plaintext[64], plaintext[65], plaintext[66], plaintext[67]
|
||||
]);
|
||||
|
||||
let public_key = Ed25519::public_key(&signing_key);
|
||||
let encryption_public = x25519_base(&encryption_key);
|
||||
|
||||
crate::crypto::secure_zero(&mut plaintext);
|
||||
|
||||
Some(Self {
|
||||
signing_key,
|
||||
public_key,
|
||||
encryption_key,
|
||||
encryption_public,
|
||||
created_at,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn export_seed(&self) -> [u8; SEED_SIZE] {
|
||||
|
||||
self.signing_key
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EncryptedIdentity {
|
||||
|
||||
pub ciphertext: [u8; 128],
|
||||
|
||||
pub nonce: [u8; NONCE_SIZE],
|
||||
|
||||
pub tag: [u8; 32],
|
||||
}
|
||||
|
||||
impl EncryptedIdentity {
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 128 + NONCE_SIZE + 32] {
|
||||
let mut bytes = [0u8; 128 + NONCE_SIZE + 32];
|
||||
bytes[0..128].copy_from_slice(&self.ciphertext);
|
||||
bytes[128..128 + NONCE_SIZE].copy_from_slice(&self.nonce);
|
||||
bytes[128 + NONCE_SIZE..].copy_from_slice(&self.tag);
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8; 128 + NONCE_SIZE + 32]) -> Self {
|
||||
let mut ciphertext = [0u8; 128];
|
||||
let mut nonce = [0u8; NONCE_SIZE];
|
||||
let mut tag = [0u8; 32];
|
||||
|
||||
ciphertext.copy_from_slice(&bytes[0..128]);
|
||||
nonce.copy_from_slice(&bytes[128..128 + NONCE_SIZE]);
|
||||
tag.copy_from_slice(&bytes[128 + NONCE_SIZE..]);
|
||||
|
||||
Self { ciphertext, nonce, tag }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IdentityManager {
|
||||
|
||||
current: Option<Identity>,
|
||||
|
||||
storage_key: [u8; 32],
|
||||
}
|
||||
|
||||
impl Drop for IdentityManager {
|
||||
fn drop(&mut self) {
|
||||
crate::crypto::secure_zero(&mut self.storage_key);
|
||||
}
|
||||
}
|
||||
|
||||
impl IdentityManager {
|
||||
|
||||
pub fn new() -> Self {
|
||||
let storage_key = Self::derive_storage_key();
|
||||
Self {
|
||||
current: None,
|
||||
storage_key,
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_storage_key() -> [u8; 32] {
|
||||
|
||||
let mut efuse_data = [0u8; 32];
|
||||
|
||||
#[cfg(target_arch = "xtensa")]
|
||||
unsafe {
|
||||
|
||||
let efuse_base = 0x6001_A000 as *const u32;
|
||||
for i in 0..8 {
|
||||
let val = core::ptr::read_volatile(efuse_base.add(i));
|
||||
efuse_data[i * 4..(i + 1) * 4].copy_from_slice(&val.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "xtensa"))]
|
||||
{
|
||||
|
||||
efuse_data = [0u8; 32];
|
||||
}
|
||||
|
||||
let mut kdf_input = [0u8; 64];
|
||||
kdf_input[..32].copy_from_slice(&efuse_data);
|
||||
kdf_input[32..].copy_from_slice(b"LunarCore Storage Key v1\0\0\0\0\0\0\0\0");
|
||||
|
||||
Sha256::hash(&kdf_input)
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> &Identity {
|
||||
|
||||
if let Some(identity) = self.load_from_flash() {
|
||||
self.current = Some(identity);
|
||||
} else {
|
||||
|
||||
let identity = Identity::generate();
|
||||
self.save_to_flash(&identity);
|
||||
self.current = Some(identity);
|
||||
}
|
||||
|
||||
self.current.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn current(&self) -> Option<&Identity> {
|
||||
self.current.as_ref()
|
||||
}
|
||||
|
||||
pub fn rotate(&mut self) -> &Identity {
|
||||
let identity = Identity::generate();
|
||||
self.save_to_flash(&identity);
|
||||
self.current = Some(identity);
|
||||
self.current.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn import_seed(&mut self, seed: &[u8; SEED_SIZE]) -> &Identity {
|
||||
let identity = Identity::from_seed(seed);
|
||||
self.save_to_flash(&identity);
|
||||
self.current = Some(identity);
|
||||
self.current.as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn load_from_flash(&self) -> Option<Identity> {
|
||||
|
||||
#[cfg(target_arch = "xtensa")]
|
||||
{
|
||||
use esp_idf_sys::*;
|
||||
|
||||
unsafe {
|
||||
let mut handle: nvs_handle_t = 0;
|
||||
let namespace = b"lunar\0".as_ptr();
|
||||
|
||||
let ret = nvs_open(namespace, nvs_open_mode_t_NVS_READONLY, &mut handle);
|
||||
if ret != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let key = b"identity\0".as_ptr();
|
||||
let mut size: usize = 128 + NONCE_SIZE + 32;
|
||||
let mut bytes = [0u8; 128 + NONCE_SIZE + 32];
|
||||
|
||||
let ret = nvs_get_blob(handle, key, bytes.as_mut_ptr() as *mut _, &mut size);
|
||||
nvs_close(handle);
|
||||
|
||||
if ret != 0 || size != bytes.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let encrypted = EncryptedIdentity::from_bytes(&bytes);
|
||||
Identity::decrypt_from_storage(&encrypted, &self.storage_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "xtensa"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn save_to_flash(&self, identity: &Identity) {
|
||||
let encrypted = identity.encrypt_for_storage(&self.storage_key);
|
||||
let bytes = encrypted.to_bytes();
|
||||
|
||||
#[cfg(target_arch = "xtensa")]
|
||||
unsafe {
|
||||
use esp_idf_sys::*;
|
||||
|
||||
let mut handle: nvs_handle_t = 0;
|
||||
let namespace = b"lunar\0".as_ptr();
|
||||
|
||||
let ret = nvs_open(namespace, nvs_open_mode_t_NVS_READWRITE, &mut handle);
|
||||
if ret != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = b"identity\0".as_ptr();
|
||||
nvs_set_blob(handle, key, bytes.as_ptr() as *const _, bytes.len());
|
||||
nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "xtensa"))]
|
||||
{
|
||||
let _ = bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for IdentityManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn hex_char(nibble: u8) -> u8 {
|
||||
match nibble {
|
||||
0..=9 => b'0' + nibble,
|
||||
10..=15 => b'a' + (nibble - 10),
|
||||
_ => b'?',
|
||||
}
|
||||
}
|
||||
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
static mut IDENTITY_MANAGER: Option<IdentityManager> = None;
|
||||
static IDENTITY_INIT: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn init() {
|
||||
if !IDENTITY_INIT.swap(true, Ordering::SeqCst) {
|
||||
unsafe {
|
||||
IDENTITY_MANAGER = Some(IdentityManager::new());
|
||||
IDENTITY_MANAGER.as_mut().unwrap().init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_id() -> u32 {
|
||||
init();
|
||||
unsafe {
|
||||
IDENTITY_MANAGER
|
||||
.as_ref()
|
||||
.and_then(|m| m.current())
|
||||
.map(|i| i.node_id())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn public_key() -> Option<PublicKey> {
|
||||
init();
|
||||
unsafe {
|
||||
IDENTITY_MANAGER
|
||||
.as_ref()
|
||||
.and_then(|m| m.current())
|
||||
.map(|i| *i.public_key())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encryption_public_key() -> Option<[u8; 32]> {
|
||||
init();
|
||||
unsafe {
|
||||
IDENTITY_MANAGER
|
||||
.as_ref()
|
||||
.and_then(|m| m.current())
|
||||
.map(|i| *i.encryption_public_key())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign(message: &[u8]) -> Option<Signature> {
|
||||
init();
|
||||
unsafe {
|
||||
IDENTITY_MANAGER
|
||||
.as_ref()
|
||||
.and_then(|m| m.current())
|
||||
.map(|i| i.sign(message))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key_agree(their_public: &[u8; 32]) -> Option<[u8; 32]> {
|
||||
init();
|
||||
unsafe {
|
||||
IDENTITY_MANAGER
|
||||
.as_ref()
|
||||
.and_then(|m| m.current())
|
||||
.map(|i| i.key_agree(their_public))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotate() -> Option<u32> {
|
||||
init();
|
||||
unsafe {
|
||||
IDENTITY_MANAGER
|
||||
.as_mut()
|
||||
.map(|m| m.rotate().node_id())
|
||||
}
|
||||
}
|
||||
1662
src/main.rs
Normal file
1662
src/main.rs
Normal file
File diff suppressed because it is too large
Load Diff
553
src/meshtastic/channel.rs
Normal file
553
src/meshtastic/channel.rs
Normal file
@@ -0,0 +1,553 @@
|
||||
use heapless::Vec;
|
||||
use crate::crypto::aes::{Aes128, Aes256};
|
||||
use crate::crypto::sha256::Sha256;
|
||||
use crate::crypto::hkdf::meshtastic as mesh_kdf;
|
||||
|
||||
pub const MAX_CHANNEL_NAME: usize = 12;
|
||||
|
||||
pub const KEY_SIZE_128: usize = 16;
|
||||
|
||||
pub const KEY_SIZE_256: usize = 32;
|
||||
|
||||
pub const NONCE_SIZE: usize = 16;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum ModemPreset {
|
||||
|
||||
LongSlow = 0,
|
||||
|
||||
LongFast = 1,
|
||||
|
||||
LongModerate = 2,
|
||||
|
||||
VeryLongSlow = 3,
|
||||
|
||||
MediumSlow = 4,
|
||||
|
||||
MediumFast = 5,
|
||||
|
||||
ShortSlow = 6,
|
||||
|
||||
ShortFast = 7,
|
||||
|
||||
ShortTurbo = 8,
|
||||
}
|
||||
|
||||
impl Default for ModemPreset {
|
||||
fn default() -> Self {
|
||||
ModemPreset::LongFast
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LoraParams {
|
||||
|
||||
pub spreading_factor: u8,
|
||||
|
||||
pub bandwidth: u32,
|
||||
|
||||
pub coding_rate: u8,
|
||||
}
|
||||
|
||||
impl ModemPreset {
|
||||
|
||||
pub const fn lora_params(&self) -> LoraParams {
|
||||
match self {
|
||||
ModemPreset::LongSlow => LoraParams {
|
||||
spreading_factor: 12,
|
||||
bandwidth: 125_000,
|
||||
coding_rate: 8,
|
||||
},
|
||||
ModemPreset::LongFast => LoraParams {
|
||||
spreading_factor: 11,
|
||||
bandwidth: 125_000,
|
||||
coding_rate: 8,
|
||||
},
|
||||
ModemPreset::LongModerate => LoraParams {
|
||||
spreading_factor: 11,
|
||||
bandwidth: 125_000,
|
||||
coding_rate: 5,
|
||||
},
|
||||
ModemPreset::VeryLongSlow => LoraParams {
|
||||
spreading_factor: 12,
|
||||
bandwidth: 125_000,
|
||||
coding_rate: 8,
|
||||
},
|
||||
ModemPreset::MediumSlow => LoraParams {
|
||||
spreading_factor: 10,
|
||||
bandwidth: 250_000,
|
||||
coding_rate: 5,
|
||||
},
|
||||
ModemPreset::MediumFast => LoraParams {
|
||||
spreading_factor: 9,
|
||||
bandwidth: 250_000,
|
||||
coding_rate: 5,
|
||||
},
|
||||
ModemPreset::ShortSlow => LoraParams {
|
||||
spreading_factor: 8,
|
||||
bandwidth: 250_000,
|
||||
coding_rate: 5,
|
||||
},
|
||||
ModemPreset::ShortFast => LoraParams {
|
||||
spreading_factor: 7,
|
||||
bandwidth: 250_000,
|
||||
coding_rate: 5,
|
||||
},
|
||||
ModemPreset::ShortTurbo => LoraParams {
|
||||
spreading_factor: 7,
|
||||
bandwidth: 500_000,
|
||||
coding_rate: 5,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn airtime_ms(&self, payload_bytes: usize) -> u32 {
|
||||
let params = self.lora_params();
|
||||
let sf = params.spreading_factor as f32;
|
||||
let bw = params.bandwidth as f32;
|
||||
let cr = params.coding_rate as f32;
|
||||
|
||||
let t_sym = (2.0_f32.powf(sf)) / bw * 1000.0;
|
||||
let t_preamble = (8.0 + 4.25) * t_sym;
|
||||
|
||||
let pl = payload_bytes as f32;
|
||||
let de = if sf >= 11.0 { 1.0 } else { 0.0 };
|
||||
let h = 0.0;
|
||||
let crc = 1.0;
|
||||
|
||||
let numerator = 8.0 * pl - 4.0 * sf + 28.0 + 16.0 * crc - 20.0 * h;
|
||||
let denominator = 4.0 * (sf - 2.0 * de);
|
||||
let n_payload = 8.0 + (numerator / denominator).ceil().max(0.0) * (cr + 4.0);
|
||||
|
||||
let t_payload = n_payload * t_sym;
|
||||
|
||||
(t_preamble + t_payload) as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ChannelKey {
|
||||
|
||||
None,
|
||||
|
||||
Aes128([u8; KEY_SIZE_128]),
|
||||
|
||||
Aes256([u8; KEY_SIZE_256]),
|
||||
}
|
||||
|
||||
impl Drop for ChannelKey {
|
||||
fn drop(&mut self) {
|
||||
|
||||
match self {
|
||||
ChannelKey::None => {}
|
||||
ChannelKey::Aes128(key) => {
|
||||
crate::crypto::secure_zero(key);
|
||||
}
|
||||
ChannelKey::Aes256(key) => {
|
||||
crate::crypto::secure_zero(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChannelKey {
|
||||
|
||||
pub fn from_bytes(key: &[u8]) -> Self {
|
||||
match key.len() {
|
||||
0 => ChannelKey::None,
|
||||
1..=16 => {
|
||||
let mut k = [0u8; KEY_SIZE_128];
|
||||
k[..key.len()].copy_from_slice(key);
|
||||
ChannelKey::Aes128(k)
|
||||
}
|
||||
_ => {
|
||||
let mut k = [0u8; KEY_SIZE_256];
|
||||
let len = core::cmp::min(key.len(), KEY_SIZE_256);
|
||||
k[..len].copy_from_slice(&key[..len]);
|
||||
ChannelKey::Aes256(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_key() -> Self {
|
||||
ChannelKey::Aes128(mesh_kdf::DEFAULT_KEY)
|
||||
}
|
||||
|
||||
pub fn from_channel_name(name: &str) -> Self {
|
||||
if name.is_empty() {
|
||||
return Self::default_key();
|
||||
}
|
||||
let hash = mesh_kdf::derive_channel_key(name);
|
||||
ChannelKey::Aes256(hash)
|
||||
}
|
||||
|
||||
pub fn is_encrypted(&self) -> bool {
|
||||
!matches!(self, ChannelKey::None)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
match self {
|
||||
ChannelKey::None => &[],
|
||||
ChannelKey::Aes128(k) => k,
|
||||
ChannelKey::Aes256(k) => k,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChannelKey {
|
||||
fn default() -> Self {
|
||||
ChannelKey::default_key()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for ChannelKey {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
ChannelKey::None => write!(f, "ChannelKey::None"),
|
||||
ChannelKey::Aes128(_) => write!(f, "ChannelKey::Aes128([REDACTED])"),
|
||||
ChannelKey::Aes256(_) => write!(f, "ChannelKey::Aes256([REDACTED])"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Channel {
|
||||
|
||||
pub index: u8,
|
||||
|
||||
pub name: Vec<u8, MAX_CHANNEL_NAME>,
|
||||
|
||||
pub key: ChannelKey,
|
||||
|
||||
pub modem_preset: ModemPreset,
|
||||
|
||||
pub uplink_enabled: bool,
|
||||
|
||||
pub downlink_enabled: bool,
|
||||
|
||||
pub position_precision: u8,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
|
||||
pub fn new(index: u8) -> Self {
|
||||
Self {
|
||||
index,
|
||||
name: Vec::new(),
|
||||
key: ChannelKey::default_key(),
|
||||
modem_preset: ModemPreset::default(),
|
||||
uplink_enabled: false,
|
||||
downlink_enabled: false,
|
||||
position_precision: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary() -> Self {
|
||||
let mut ch = Self::new(0);
|
||||
ch.name.extend_from_slice(b"Primary").ok();
|
||||
ch
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, name: &str) {
|
||||
self.name.clear();
|
||||
let len = core::cmp::min(name.len(), MAX_CHANNEL_NAME);
|
||||
self.name.extend_from_slice(&name.as_bytes()[..len]).ok();
|
||||
|
||||
self.key = ChannelKey::from_channel_name(name);
|
||||
}
|
||||
|
||||
pub fn set_key(&mut self, key: &[u8]) {
|
||||
self.key = ChannelKey::from_bytes(key);
|
||||
}
|
||||
|
||||
pub fn encrypt(&self, packet_id: u32, sender: u32, plaintext: &[u8]) -> Option<Vec<u8, 256>> {
|
||||
if !self.key.is_encrypted() {
|
||||
|
||||
return Vec::from_slice(plaintext).ok();
|
||||
}
|
||||
|
||||
let nonce = mesh_kdf::derive_nonce(packet_id, sender);
|
||||
|
||||
let mut ciphertext = Vec::new();
|
||||
ciphertext.extend_from_slice(plaintext).ok()?;
|
||||
|
||||
match &self.key {
|
||||
ChannelKey::Aes128(key) => {
|
||||
let cipher = Aes128::new(key);
|
||||
|
||||
let mut nonce_block = [0u8; 16];
|
||||
nonce_block.copy_from_slice(&nonce);
|
||||
cipher.encrypt_ctr(&nonce_block, &mut ciphertext);
|
||||
}
|
||||
ChannelKey::Aes256(key) => {
|
||||
let cipher = Aes256::new(key);
|
||||
let mut nonce_block = [0u8; 16];
|
||||
nonce_block.copy_from_slice(&nonce);
|
||||
cipher.encrypt_ctr(&nonce_block, &mut ciphertext);
|
||||
}
|
||||
ChannelKey::None => {}
|
||||
}
|
||||
|
||||
Some(ciphertext)
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, packet_id: u32, sender: u32, ciphertext: &[u8]) -> Option<Vec<u8, 256>> {
|
||||
if !self.key.is_encrypted() {
|
||||
return Vec::from_slice(ciphertext).ok();
|
||||
}
|
||||
|
||||
let nonce = mesh_kdf::derive_nonce(packet_id, sender);
|
||||
|
||||
let mut plaintext = Vec::new();
|
||||
plaintext.extend_from_slice(ciphertext).ok()?;
|
||||
|
||||
match &self.key {
|
||||
ChannelKey::Aes128(key) => {
|
||||
let cipher = Aes128::new(key);
|
||||
let mut nonce_block = [0u8; 16];
|
||||
nonce_block.copy_from_slice(&nonce);
|
||||
cipher.decrypt_ctr(&nonce_block, &mut plaintext);
|
||||
}
|
||||
ChannelKey::Aes256(key) => {
|
||||
let cipher = Aes256::new(key);
|
||||
let mut nonce_block = [0u8; 16];
|
||||
nonce_block.copy_from_slice(&nonce);
|
||||
cipher.decrypt_ctr(&nonce_block, &mut plaintext);
|
||||
}
|
||||
ChannelKey::None => {}
|
||||
}
|
||||
|
||||
Some(plaintext)
|
||||
}
|
||||
|
||||
pub fn hash(&self) -> u8 {
|
||||
|
||||
let key_bytes = self.key.as_bytes();
|
||||
if key_bytes.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut h: u8 = 0;
|
||||
for &b in key_bytes {
|
||||
h ^= b;
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
pub fn name_str(&self) -> &str {
|
||||
core::str::from_utf8(&self.name).unwrap_or("")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Channel {
|
||||
fn default() -> Self {
|
||||
Self::primary()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Channel {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Channel")
|
||||
.field("index", &self.index)
|
||||
.field("name", &self.name_str())
|
||||
.field("key", &self.key)
|
||||
.field("modem_preset", &self.modem_preset)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub const MAX_CHANNELS: usize = 8;
|
||||
|
||||
pub struct ChannelSet {
|
||||
|
||||
channels: [Option<Channel>; MAX_CHANNELS],
|
||||
}
|
||||
|
||||
impl ChannelSet {
|
||||
|
||||
pub fn new() -> Self {
|
||||
let mut channels = [None, None, None, None, None, None, None, None];
|
||||
channels[0] = Some(Channel::primary());
|
||||
Self { channels }
|
||||
}
|
||||
|
||||
pub fn get(&self, index: u8) -> Option<&Channel> {
|
||||
self.channels.get(index as usize)?.as_ref()
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, index: u8) -> Option<&mut Channel> {
|
||||
self.channels.get_mut(index as usize)?.as_mut()
|
||||
}
|
||||
|
||||
pub fn set(&mut self, index: u8, channel: Channel) {
|
||||
if (index as usize) < MAX_CHANNELS {
|
||||
self.channels[index as usize] = Some(channel);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary(&self) -> Option<&Channel> {
|
||||
self.channels[0].as_ref()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_mut(&mut self) -> Option<&mut Channel> {
|
||||
self.channels[0].as_mut()
|
||||
}
|
||||
|
||||
pub fn primary_or_init(&mut self) -> &mut Channel {
|
||||
if self.channels[0].is_none() {
|
||||
self.channels[0] = Some(Channel::primary());
|
||||
}
|
||||
|
||||
self.channels[0].as_mut().unwrap()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (u8, &Channel)> {
|
||||
self.channels
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, ch)| ch.as_ref().map(|c| (i as u8, c)))
|
||||
}
|
||||
|
||||
pub fn find_by_hash(&self, hash: u8) -> Option<&Channel> {
|
||||
for ch in self.channels.iter().flatten() {
|
||||
if ch.hash() == hash {
|
||||
return Some(ch);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn count(&self) -> usize {
|
||||
self.channels.iter().filter(|c| c.is_some()).count()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChannelSet {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
const BASE64_CHARS: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
||||
const BASE64_DECODE: [i8; 128] = {
|
||||
let mut table = [-1i8; 128];
|
||||
let chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
let mut i = 0;
|
||||
while i < 64 {
|
||||
table[chars[i] as usize] = i as i8;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
table[b'+' as usize] = 62;
|
||||
table[b'/' as usize] = 63;
|
||||
table
|
||||
};
|
||||
|
||||
pub fn base64_encode(data: &[u8], output: &mut [u8]) -> usize {
|
||||
let mut o = 0;
|
||||
let mut i = 0;
|
||||
|
||||
while i + 2 < data.len() {
|
||||
if o + 4 > output.len() {
|
||||
break;
|
||||
}
|
||||
let n = ((data[i] as u32) << 16) | ((data[i + 1] as u32) << 8) | (data[i + 2] as u32);
|
||||
output[o] = BASE64_CHARS[((n >> 18) & 0x3F) as usize];
|
||||
output[o + 1] = BASE64_CHARS[((n >> 12) & 0x3F) as usize];
|
||||
output[o + 2] = BASE64_CHARS[((n >> 6) & 0x3F) as usize];
|
||||
output[o + 3] = BASE64_CHARS[(n & 0x3F) as usize];
|
||||
i += 3;
|
||||
o += 4;
|
||||
}
|
||||
|
||||
if i < data.len() && o + 4 <= output.len() {
|
||||
let remaining = data.len() - i;
|
||||
if remaining == 1 {
|
||||
let n = (data[i] as u32) << 16;
|
||||
output[o] = BASE64_CHARS[((n >> 18) & 0x3F) as usize];
|
||||
output[o + 1] = BASE64_CHARS[((n >> 12) & 0x3F) as usize];
|
||||
o += 2;
|
||||
} else if remaining == 2 {
|
||||
let n = ((data[i] as u32) << 16) | ((data[i + 1] as u32) << 8);
|
||||
output[o] = BASE64_CHARS[((n >> 18) & 0x3F) as usize];
|
||||
output[o + 1] = BASE64_CHARS[((n >> 12) & 0x3F) as usize];
|
||||
output[o + 2] = BASE64_CHARS[((n >> 6) & 0x3F) as usize];
|
||||
o += 3;
|
||||
}
|
||||
}
|
||||
|
||||
o
|
||||
}
|
||||
|
||||
pub fn base64_decode(data: &[u8], output: &mut [u8]) -> usize {
|
||||
let mut o = 0;
|
||||
let mut i = 0;
|
||||
|
||||
let data = if data.starts_with(b"https://meshtastic.org/e/#") {
|
||||
&data[26..]
|
||||
} else {
|
||||
data
|
||||
};
|
||||
|
||||
while i + 3 < data.len() {
|
||||
if o + 3 > output.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
let b0 = BASE64_DECODE.get(data[i] as usize).copied().unwrap_or(-1);
|
||||
let b1 = BASE64_DECODE.get(data[i + 1] as usize).copied().unwrap_or(-1);
|
||||
let b2 = BASE64_DECODE.get(data[i + 2] as usize).copied().unwrap_or(-1);
|
||||
let b3 = BASE64_DECODE.get(data[i + 3] as usize).copied().unwrap_or(-1);
|
||||
|
||||
if b0 < 0 || b1 < 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let n = ((b0 as u32) << 18)
|
||||
| ((b1 as u32) << 12)
|
||||
| (if b2 >= 0 { (b2 as u32) << 6 } else { 0 })
|
||||
| (if b3 >= 0 { b3 as u32 } else { 0 });
|
||||
|
||||
output[o] = (n >> 16) as u8;
|
||||
o += 1;
|
||||
|
||||
if b2 >= 0 {
|
||||
output[o] = (n >> 8) as u8;
|
||||
o += 1;
|
||||
}
|
||||
|
||||
if b3 >= 0 {
|
||||
output[o] = n as u8;
|
||||
o += 1;
|
||||
}
|
||||
|
||||
i += 4;
|
||||
}
|
||||
|
||||
if i + 1 < data.len() && o < output.len() {
|
||||
let b0 = BASE64_DECODE.get(data[i] as usize).copied().unwrap_or(-1);
|
||||
let b1 = BASE64_DECODE.get(data[i + 1] as usize).copied().unwrap_or(-1);
|
||||
|
||||
if b0 >= 0 && b1 >= 0 {
|
||||
let n = ((b0 as u32) << 18) | ((b1 as u32) << 12);
|
||||
output[o] = (n >> 16) as u8;
|
||||
o += 1;
|
||||
|
||||
if i + 2 < data.len() && o < output.len() {
|
||||
let b2 = BASE64_DECODE.get(data[i + 2] as usize).copied().unwrap_or(-1);
|
||||
if b2 >= 0 {
|
||||
let n = ((b0 as u32) << 18) | ((b1 as u32) << 12) | ((b2 as u32) << 6);
|
||||
output[o - 1] = (n >> 16) as u8;
|
||||
output[o] = (n >> 8) as u8;
|
||||
o += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
o
|
||||
}
|
||||
352
src/meshtastic/encryption.rs
Normal file
352
src/meshtastic/encryption.rs
Normal file
@@ -0,0 +1,352 @@
|
||||
use heapless::Vec;
|
||||
use crate::crypto::aes::{Aes128, Aes256};
|
||||
use crate::crypto::sha256::Sha256;
|
||||
use crate::crypto::hmac::HmacSha256;
|
||||
use crate::crypto::hkdf::meshtastic as mesh_kdf;
|
||||
use super::channel::ChannelKey;
|
||||
|
||||
pub const MAX_PAYLOAD_SIZE: usize = 237;
|
||||
|
||||
pub const NONCE_SIZE: usize = 16;
|
||||
|
||||
pub const MIC_SIZE: usize = 4;
|
||||
|
||||
pub const MIC_SIZE_ENHANCED: usize = 8;
|
||||
|
||||
pub struct EncryptionContext {
|
||||
|
||||
key: ChannelKey,
|
||||
}
|
||||
|
||||
impl EncryptionContext {
|
||||
|
||||
pub fn new(key: ChannelKey) -> Self {
|
||||
Self { key }
|
||||
}
|
||||
|
||||
pub fn with_default_key() -> Self {
|
||||
Self::new(ChannelKey::default_key())
|
||||
}
|
||||
|
||||
pub fn from_channel_name(name: &str) -> Self {
|
||||
Self::new(ChannelKey::from_channel_name(name))
|
||||
}
|
||||
|
||||
pub fn from_key_bytes(key: &[u8]) -> Self {
|
||||
Self::new(ChannelKey::from_bytes(key))
|
||||
}
|
||||
|
||||
pub fn is_encrypted(&self) -> bool {
|
||||
self.key.is_encrypted()
|
||||
}
|
||||
|
||||
pub fn encrypt(&self, packet_id: u32, sender: u32, plaintext: &[u8]) -> Option<Vec<u8, MAX_PAYLOAD_SIZE>> {
|
||||
if !self.key.is_encrypted() {
|
||||
|
||||
return Vec::from_slice(plaintext).ok();
|
||||
}
|
||||
|
||||
if plaintext.len() > MAX_PAYLOAD_SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let nonce = mesh_kdf::derive_nonce(packet_id, sender);
|
||||
|
||||
let mut ciphertext = Vec::new();
|
||||
ciphertext.extend_from_slice(plaintext).ok()?;
|
||||
|
||||
match &self.key {
|
||||
ChannelKey::Aes128(key) => {
|
||||
let cipher = Aes128::new(key);
|
||||
cipher.encrypt_ctr(&nonce, &mut ciphertext);
|
||||
}
|
||||
ChannelKey::Aes256(key) => {
|
||||
let cipher = Aes256::new(key);
|
||||
cipher.encrypt_ctr(&nonce, &mut ciphertext);
|
||||
}
|
||||
ChannelKey::None => {}
|
||||
}
|
||||
|
||||
Some(ciphertext)
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, packet_id: u32, sender: u32, ciphertext: &[u8]) -> Option<Vec<u8, MAX_PAYLOAD_SIZE>> {
|
||||
if !self.key.is_encrypted() {
|
||||
return Vec::from_slice(ciphertext).ok();
|
||||
}
|
||||
|
||||
if ciphertext.is_empty() || ciphertext.len() > MAX_PAYLOAD_SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let nonce = mesh_kdf::derive_nonce(packet_id, sender);
|
||||
|
||||
let mut plaintext = Vec::new();
|
||||
plaintext.extend_from_slice(ciphertext).ok()?;
|
||||
|
||||
match &self.key {
|
||||
ChannelKey::Aes128(key) => {
|
||||
let cipher = Aes128::new(key);
|
||||
cipher.decrypt_ctr(&nonce, &mut plaintext);
|
||||
}
|
||||
ChannelKey::Aes256(key) => {
|
||||
let cipher = Aes256::new(key);
|
||||
cipher.decrypt_ctr(&nonce, &mut plaintext);
|
||||
}
|
||||
ChannelKey::None => {}
|
||||
}
|
||||
|
||||
Some(plaintext)
|
||||
}
|
||||
|
||||
pub fn key_hash(&self) -> u8 {
|
||||
let key_bytes = self.key.as_bytes();
|
||||
if key_bytes.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut h: u8 = 0;
|
||||
for &b in key_bytes {
|
||||
h ^= b;
|
||||
}
|
||||
h
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EncryptionContext {
|
||||
fn default() -> Self {
|
||||
Self::with_default_key()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_mic(data: &[u8]) -> [u8; MIC_SIZE] {
|
||||
let hash = Sha256::hash(data);
|
||||
let mut mic = [0u8; MIC_SIZE];
|
||||
mic.copy_from_slice(&hash[..MIC_SIZE]);
|
||||
mic
|
||||
}
|
||||
|
||||
pub fn verify_mic(data: &[u8], expected_mic: &[u8]) -> bool {
|
||||
if expected_mic.len() != MIC_SIZE {
|
||||
return false;
|
||||
}
|
||||
|
||||
let computed = compute_mic(data);
|
||||
constant_time_eq(&computed, expected_mic)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
|
||||
if a.len() != b.len() {
|
||||
return false;
|
||||
}
|
||||
let mut result: u8 = 0;
|
||||
for (x, y) in a.iter().zip(b.iter()) {
|
||||
result |= x ^ y;
|
||||
}
|
||||
result == 0
|
||||
}
|
||||
|
||||
pub fn compute_mic_enhanced(key: &[u8], data: &[u8]) -> [u8; MIC_SIZE_ENHANCED] {
|
||||
|
||||
let mut hmac_key = [0u8; 32];
|
||||
if key.len() >= 32 {
|
||||
hmac_key.copy_from_slice(&key[..32]);
|
||||
} else {
|
||||
hmac_key[..key.len()].copy_from_slice(key);
|
||||
}
|
||||
|
||||
let mac = HmacSha256::mac(&hmac_key, data);
|
||||
let mut mic = [0u8; MIC_SIZE_ENHANCED];
|
||||
mic.copy_from_slice(&mac[..MIC_SIZE_ENHANCED]);
|
||||
mic
|
||||
}
|
||||
|
||||
pub fn verify_mic_enhanced(key: &[u8], data: &[u8], expected_mic: &[u8]) -> bool {
|
||||
if expected_mic.len() != MIC_SIZE_ENHANCED {
|
||||
return false;
|
||||
}
|
||||
|
||||
let computed = compute_mic_enhanced(key, data);
|
||||
constant_time_eq(&computed, expected_mic)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MicMode {
|
||||
|
||||
Standard,
|
||||
|
||||
Enhanced,
|
||||
}
|
||||
|
||||
impl Default for MicMode {
|
||||
fn default() -> Self {
|
||||
MicMode::Standard
|
||||
}
|
||||
}
|
||||
|
||||
use crate::crypto::x25519;
|
||||
use crate::crypto::chacha20::ChaCha20;
|
||||
use crate::crypto::poly1305::ChaCha20Poly1305;
|
||||
use crate::crypto::hkdf::Hkdf;
|
||||
|
||||
pub const PKI_OVERHEAD: usize = 32 + 16;
|
||||
|
||||
pub fn pki_encrypt(
|
||||
recipient_pubkey: &[u8; 32],
|
||||
sender_privkey: &[u8; 32],
|
||||
plaintext: &[u8],
|
||||
nonce: &[u8; 12],
|
||||
) -> Option<Vec<u8, 256>> {
|
||||
if plaintext.len() + PKI_OVERHEAD > 256 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut ephemeral_seed = [0u8; 32];
|
||||
Hkdf::derive(nonce, sender_privkey, b"ephemeral", &mut ephemeral_seed);
|
||||
|
||||
let ephemeral_pubkey = x25519::x25519_base(&ephemeral_seed);
|
||||
|
||||
let shared_secret = x25519::x25519(&ephemeral_seed, recipient_pubkey);
|
||||
|
||||
let mut key = [0u8; 32];
|
||||
Hkdf::derive(b"meshtastic-pki", &shared_secret, &ephemeral_pubkey, &mut key);
|
||||
|
||||
let mut ciphertext = [0u8; 240];
|
||||
let mut tag = [0u8; 16];
|
||||
|
||||
if plaintext.len() > ciphertext.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
ChaCha20Poly1305::seal(&key, nonce, &[], plaintext, &mut ciphertext[..plaintext.len()], &mut tag);
|
||||
|
||||
let mut output = Vec::new();
|
||||
output.extend_from_slice(&ephemeral_pubkey).ok()?;
|
||||
output.extend_from_slice(&ciphertext[..plaintext.len()]).ok()?;
|
||||
output.extend_from_slice(&tag).ok()?;
|
||||
|
||||
Some(output)
|
||||
}
|
||||
|
||||
pub fn pki_decrypt(
|
||||
recipient_privkey: &[u8; 32],
|
||||
encrypted: &[u8],
|
||||
nonce: &[u8; 12],
|
||||
) -> Option<Vec<u8, 240>> {
|
||||
if encrypted.len() < PKI_OVERHEAD {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut ephemeral_pubkey = [0u8; 32];
|
||||
ephemeral_pubkey.copy_from_slice(&encrypted[..32]);
|
||||
|
||||
let ciphertext = &encrypted[32..encrypted.len() - 16];
|
||||
let mut tag = [0u8; 16];
|
||||
tag.copy_from_slice(&encrypted[encrypted.len() - 16..]);
|
||||
|
||||
let shared_secret = x25519::x25519(recipient_privkey, &ephemeral_pubkey);
|
||||
|
||||
let mut key = [0u8; 32];
|
||||
Hkdf::derive(b"meshtastic-pki", &shared_secret, &ephemeral_pubkey, &mut key);
|
||||
|
||||
let mut plaintext = [0u8; 240];
|
||||
if ciphertext.len() > plaintext.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !ChaCha20Poly1305::open(&key, nonce, &[], ciphertext, &tag, &mut plaintext[..ciphertext.len()]) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut output = Vec::new();
|
||||
output.extend_from_slice(&plaintext[..ciphertext.len()]).ok()?;
|
||||
Some(output)
|
||||
}
|
||||
|
||||
pub struct KeyStore {
|
||||
|
||||
channel_keys: [Option<EncryptionContext>; 8],
|
||||
|
||||
node_privkey: Option<[u8; 32]>,
|
||||
|
||||
node_pubkey: Option<[u8; 32]>,
|
||||
}
|
||||
|
||||
impl Drop for KeyStore {
|
||||
fn drop(&mut self) {
|
||||
|
||||
if let Some(ref mut privkey) = self.node_privkey {
|
||||
crate::crypto::secure_zero(privkey);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyStore {
|
||||
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
channel_keys: [None, None, None, None, None, None, None, None],
|
||||
node_privkey: None,
|
||||
node_pubkey: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_channel_key(&mut self, index: u8, key: &[u8]) {
|
||||
if (index as usize) < self.channel_keys.len() {
|
||||
self.channel_keys[index as usize] = Some(EncryptionContext::from_key_bytes(key));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_channel_name(&mut self, index: u8, name: &str) {
|
||||
if (index as usize) < self.channel_keys.len() {
|
||||
self.channel_keys[index as usize] = Some(EncryptionContext::from_channel_name(name));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_channel(&self, index: u8) -> Option<&EncryptionContext> {
|
||||
self.channel_keys.get(index as usize)?.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_node_keypair(&mut self, privkey: &[u8; 32]) {
|
||||
self.node_privkey = Some(*privkey);
|
||||
self.node_pubkey = Some(x25519::x25519_base(privkey));
|
||||
}
|
||||
|
||||
pub fn generate_node_keypair(&mut self, entropy: &[u8; 32]) {
|
||||
|
||||
let mut privkey = [0u8; 32];
|
||||
Hkdf::derive(b"meshtastic", entropy, b"node-key", &mut privkey);
|
||||
self.set_node_keypair(&privkey);
|
||||
}
|
||||
|
||||
pub fn node_pubkey(&self) -> Option<&[u8; 32]> {
|
||||
self.node_pubkey.as_ref()
|
||||
}
|
||||
|
||||
pub fn encrypt_for_node(
|
||||
&self,
|
||||
recipient_pubkey: &[u8; 32],
|
||||
plaintext: &[u8],
|
||||
nonce: &[u8; 12],
|
||||
) -> Option<Vec<u8, 256>> {
|
||||
let privkey = self.node_privkey.as_ref()?;
|
||||
pki_encrypt(recipient_pubkey, privkey, plaintext, nonce)
|
||||
}
|
||||
|
||||
pub fn decrypt_from_node(
|
||||
&self,
|
||||
encrypted: &[u8],
|
||||
nonce: &[u8; 12],
|
||||
) -> Option<Vec<u8, 240>> {
|
||||
let privkey = self.node_privkey.as_ref()?;
|
||||
pki_decrypt(privkey, encrypted, nonce)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for KeyStore {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
1263
src/meshtastic/mod.rs
Normal file
1263
src/meshtastic/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
388
src/meshtastic/packet.rs
Normal file
388
src/meshtastic/packet.rs
Normal file
@@ -0,0 +1,388 @@
|
||||
use heapless::Vec;
|
||||
use super::{MeshPacket, PacketPayload, Priority, LORA_HEADER_SIZE, MAX_LORA_PAYLOAD, MIC_SIZE, DEFAULT_HOP_LIMIT};
|
||||
use crate::crypto::sha256::Sha256;
|
||||
|
||||
const OFFSET_TO: usize = 0;
|
||||
const OFFSET_FROM: usize = 4;
|
||||
const OFFSET_ID: usize = 8;
|
||||
const OFFSET_FLAGS: usize = 12;
|
||||
const OFFSET_CHANNEL_HASH: usize = 13;
|
||||
|
||||
const FLAG_WANT_ACK: u8 = 0x01;
|
||||
const FLAG_HOP_LIMIT_MASK: u8 = 0x0E;
|
||||
const FLAG_HOP_LIMIT_SHIFT: u8 = 1;
|
||||
const FLAG_CHANNEL_MASK: u8 = 0xF0;
|
||||
const FLAG_CHANNEL_SHIFT: u8 = 4;
|
||||
|
||||
pub fn parse_lora_packet(data: &[u8]) -> Option<MeshPacket> {
|
||||
|
||||
if data.len() < LORA_HEADER_SIZE + MIC_SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let to = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
|
||||
let from = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
|
||||
let id = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
|
||||
let flags = data[OFFSET_FLAGS];
|
||||
let channel_hash = data[OFFSET_CHANNEL_HASH];
|
||||
|
||||
let want_ack = (flags & FLAG_WANT_ACK) != 0;
|
||||
let hop_limit = (flags & FLAG_HOP_LIMIT_MASK) >> FLAG_HOP_LIMIT_SHIFT;
|
||||
let channel = (flags & FLAG_CHANNEL_MASK) >> FLAG_CHANNEL_SHIFT;
|
||||
|
||||
let payload_end = data.len() - MIC_SIZE;
|
||||
let payload_start = LORA_HEADER_SIZE;
|
||||
|
||||
if payload_end <= payload_start {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut payload_data = Vec::new();
|
||||
payload_data.extend_from_slice(&data[payload_start..payload_end]).ok()?;
|
||||
|
||||
let received_mic = &data[payload_end..];
|
||||
let computed_mic = compute_mic(&data[..payload_end]);
|
||||
|
||||
if !constant_time_eq(received_mic, &computed_mic) {
|
||||
|
||||
}
|
||||
|
||||
Some(MeshPacket {
|
||||
from,
|
||||
to,
|
||||
channel,
|
||||
id,
|
||||
hop_limit,
|
||||
want_ack,
|
||||
priority: Priority::Default,
|
||||
rx_time: 0,
|
||||
rx_snr: 0.0,
|
||||
rx_rssi: 0,
|
||||
payload: PacketPayload::Encrypted(payload_data),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_lora_packet(
|
||||
from: u32,
|
||||
to: u32,
|
||||
id: u32,
|
||||
channel: u8,
|
||||
hop_limit: u8,
|
||||
want_ack: bool,
|
||||
payload: &[u8],
|
||||
) -> Option<Vec<u8, 256>> {
|
||||
let total_len = LORA_HEADER_SIZE + payload.len() + MIC_SIZE;
|
||||
if total_len > 256 || payload.len() > MAX_LORA_PAYLOAD {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut packet = Vec::new();
|
||||
|
||||
packet.extend_from_slice(&to.to_le_bytes()).ok()?;
|
||||
packet.extend_from_slice(&from.to_le_bytes()).ok()?;
|
||||
packet.extend_from_slice(&id.to_le_bytes()).ok()?;
|
||||
|
||||
let flags = (if want_ack { FLAG_WANT_ACK } else { 0 })
|
||||
| ((hop_limit & 0x07) << FLAG_HOP_LIMIT_SHIFT)
|
||||
| ((channel & 0x0F) << FLAG_CHANNEL_SHIFT);
|
||||
packet.push(flags).ok()?;
|
||||
|
||||
packet.push(compute_channel_hash(channel)).ok()?;
|
||||
|
||||
packet.push(0).ok()?;
|
||||
packet.push(0).ok()?;
|
||||
|
||||
packet.extend_from_slice(payload).ok()?;
|
||||
|
||||
let mic = compute_mic(&packet);
|
||||
packet.extend_from_slice(&mic).ok()?;
|
||||
|
||||
Some(packet)
|
||||
}
|
||||
|
||||
fn compute_mic(data: &[u8]) -> [u8; MIC_SIZE] {
|
||||
let hash = Sha256::hash(data);
|
||||
let mut mic = [0u8; MIC_SIZE];
|
||||
mic.copy_from_slice(&hash[..MIC_SIZE]);
|
||||
mic
|
||||
}
|
||||
|
||||
fn compute_channel_hash(channel: u8) -> u8 {
|
||||
|
||||
let mut h = channel.wrapping_mul(0x9E).wrapping_add(0x37);
|
||||
h ^= h >> 4;
|
||||
h.wrapping_mul(0xB5)
|
||||
}
|
||||
|
||||
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
|
||||
if a.len() != b.len() {
|
||||
return false;
|
||||
}
|
||||
let mut result: u8 = 0;
|
||||
for (x, y) in a.iter().zip(b.iter()) {
|
||||
result |= x ^ y;
|
||||
}
|
||||
result == 0
|
||||
}
|
||||
|
||||
pub struct PacketCache {
|
||||
|
||||
entries: [(u32, u32, u32); 32],
|
||||
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl PacketCache {
|
||||
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
entries: [(0, 0, 0); 32],
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_duplicate(&self, from: u32, packet_id: u32) -> bool {
|
||||
for &(f, id, _) in &self.entries {
|
||||
if f == from && id == packet_id && f != 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn add(&mut self, from: u32, packet_id: u32, timestamp: u32) {
|
||||
self.entries[self.index] = (from, packet_id, timestamp);
|
||||
self.index = (self.index + 1) % self.entries.len();
|
||||
}
|
||||
|
||||
pub fn check_and_add(&mut self, from: u32, packet_id: u32, timestamp: u32) -> bool {
|
||||
if self.is_duplicate(from, packet_id) {
|
||||
return true;
|
||||
}
|
||||
self.add(from, packet_id, timestamp);
|
||||
false
|
||||
}
|
||||
|
||||
pub fn clear_old(&mut self, current_time: u32, max_age: u32) {
|
||||
for entry in &mut self.entries {
|
||||
if entry.0 != 0 && current_time.saturating_sub(entry.2) > max_age {
|
||||
*entry = (0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PacketCache {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RoutingDecision {
|
||||
|
||||
Local,
|
||||
|
||||
Forward,
|
||||
|
||||
Drop,
|
||||
}
|
||||
|
||||
pub fn route_packet(packet: &MeshPacket, our_node_id: u32, cache: &mut PacketCache, timestamp: u32) -> RoutingDecision {
|
||||
|
||||
if cache.check_and_add(packet.from, packet.id, timestamp) {
|
||||
return RoutingDecision::Drop;
|
||||
}
|
||||
|
||||
let is_broadcast = packet.to == 0xFFFFFFFF;
|
||||
let is_for_us = packet.to == our_node_id;
|
||||
|
||||
if is_for_us || is_broadcast {
|
||||
|
||||
if is_broadcast && packet.hop_limit > 0 {
|
||||
return RoutingDecision::Forward;
|
||||
}
|
||||
return RoutingDecision::Local;
|
||||
}
|
||||
|
||||
if packet.hop_limit > 0 {
|
||||
RoutingDecision::Forward
|
||||
} else {
|
||||
RoutingDecision::Drop
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_forward_packet(original: &MeshPacket, payload: &[u8]) -> Option<Vec<u8, 256>> {
|
||||
if original.hop_limit == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
build_lora_packet(
|
||||
original.from,
|
||||
original.to,
|
||||
original.id,
|
||||
original.channel,
|
||||
original.hop_limit - 1,
|
||||
original.want_ack,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PacketStats {
|
||||
|
||||
pub rx_total: u32,
|
||||
|
||||
pub rx_local: u32,
|
||||
|
||||
pub rx_forwarded: u32,
|
||||
|
||||
pub rx_dropped_dup: u32,
|
||||
|
||||
pub rx_dropped_expired: u32,
|
||||
|
||||
pub rx_bad: u32,
|
||||
|
||||
pub tx_total: u32,
|
||||
|
||||
pub tx_retransmit: u32,
|
||||
|
||||
pub tx_fail: u32,
|
||||
}
|
||||
|
||||
impl PacketStats {
|
||||
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
rx_total: 0,
|
||||
rx_local: 0,
|
||||
rx_forwarded: 0,
|
||||
rx_dropped_dup: 0,
|
||||
rx_dropped_expired: 0,
|
||||
rx_bad: 0,
|
||||
tx_total: 0,
|
||||
tx_retransmit: 0,
|
||||
tx_fail: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_rx(&mut self, decision: RoutingDecision) {
|
||||
self.rx_total += 1;
|
||||
match decision {
|
||||
RoutingDecision::Local => self.rx_local += 1,
|
||||
RoutingDecision::Forward => self.rx_forwarded += 1,
|
||||
RoutingDecision::Drop => self.rx_dropped_dup += 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_rx_bad(&mut self) {
|
||||
self.rx_total += 1;
|
||||
self.rx_bad += 1;
|
||||
}
|
||||
|
||||
pub fn record_tx(&mut self, success: bool) {
|
||||
self.tx_total += 1;
|
||||
if !success {
|
||||
self.tx_fail += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_retransmit(&mut self) {
|
||||
self.tx_retransmit += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PendingAck {
|
||||
|
||||
pub packet_id: u32,
|
||||
|
||||
pub to: u32,
|
||||
|
||||
pub packet_data: Vec<u8, 256>,
|
||||
|
||||
pub tx_count: u8,
|
||||
|
||||
pub max_retransmit: u8,
|
||||
|
||||
pub last_tx_time: u32,
|
||||
|
||||
pub retransmit_timeout: u32,
|
||||
}
|
||||
|
||||
pub struct AckTracker {
|
||||
|
||||
pending: [Option<PendingAck>; 8],
|
||||
}
|
||||
|
||||
impl AckTracker {
|
||||
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
pending: [None, None, None, None, None, None, None, None],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, ack: PendingAck) -> bool {
|
||||
for slot in &mut self.pending {
|
||||
if slot.is_none() {
|
||||
*slot = Some(ack);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_pending(&self, to: u32, packet_id: u32) -> bool {
|
||||
self.pending.iter().any(|p| {
|
||||
p.as_ref().map_or(false, |a| a.to == to && a.packet_id == packet_id)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle_ack(&mut self, from: u32, request_id: u32) -> bool {
|
||||
for slot in &mut self.pending {
|
||||
if let Some(ref ack) = slot {
|
||||
if ack.to == from && ack.packet_id == request_id {
|
||||
*slot = None;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_retransmit(&mut self, current_time: u32) -> Option<Vec<u8, 256>> {
|
||||
for slot in &mut self.pending {
|
||||
if let Some(ref mut ack) = slot {
|
||||
let elapsed = current_time.saturating_sub(ack.last_tx_time);
|
||||
if elapsed >= ack.retransmit_timeout {
|
||||
if ack.tx_count < ack.max_retransmit {
|
||||
ack.tx_count += 1;
|
||||
ack.last_tx_time = current_time;
|
||||
return Some(ack.packet_data.clone());
|
||||
} else {
|
||||
|
||||
*slot = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
for slot in &mut self.pending {
|
||||
*slot = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pending_count(&self) -> usize {
|
||||
self.pending.iter().filter(|p| p.is_some()).count()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AckTracker {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
1281
src/meshtastic/protobuf.rs
Normal file
1281
src/meshtastic/protobuf.rs
Normal file
File diff suppressed because it is too large
Load Diff
368
src/onion.rs
Normal file
368
src/onion.rs
Normal file
@@ -0,0 +1,368 @@
|
||||
use crate::crypto::{
|
||||
x25519::x25519,
|
||||
hkdf::Hkdf,
|
||||
aes::Aes256,
|
||||
sha256::Sha256,
|
||||
};
|
||||
use crate::transport::{NODE_HINT_SIZE, AUTH_TAG_SIZE};
|
||||
use heapless::Vec as HeaplessVec;
|
||||
|
||||
#[inline(never)]
|
||||
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
|
||||
if a.len() != b.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut result: u8 = 0;
|
||||
for (x, y) in a.iter().zip(b.iter()) {
|
||||
result |= x ^ y;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
core::ptr::read_volatile(&result) == 0
|
||||
}
|
||||
}
|
||||
|
||||
pub const MAX_HOPS: usize = 7;
|
||||
|
||||
pub const MIN_HOPS: usize = 3;
|
||||
|
||||
pub const HOP_OVERHEAD: usize = NODE_HINT_SIZE + AUTH_TAG_SIZE;
|
||||
|
||||
const ONION_KEY_INFO: &[u8] = b"lunarpunk-onion-key-v1";
|
||||
|
||||
const ONION_BLIND_INFO: &[u8] = b"lunarpunk-onion-blind-v1";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RouteHop {
|
||||
|
||||
pub hint: u16,
|
||||
|
||||
pub public_key: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OnionRoute {
|
||||
|
||||
pub hops: HeaplessVec<RouteHop, MAX_HOPS>,
|
||||
}
|
||||
|
||||
impl OnionRoute {
|
||||
|
||||
pub fn new(hops: &[RouteHop]) -> Option<Self> {
|
||||
if hops.len() < MIN_HOPS || hops.len() > MAX_HOPS {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut route_hops = HeaplessVec::new();
|
||||
for hop in hops {
|
||||
route_hops.push(hop.clone()).ok()?;
|
||||
}
|
||||
|
||||
Some(Self { hops: route_hops })
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.hops.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.hops.is_empty()
|
||||
}
|
||||
|
||||
pub fn entry_hint(&self) -> u16 {
|
||||
self.hops.first().map(|h| h.hint).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn exit_hint(&self) -> u16 {
|
||||
self.hops.last().map(|h| h.hint).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn overhead(&self) -> usize {
|
||||
self.hops.len() * HOP_OVERHEAD
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OnionPacket {
|
||||
|
||||
pub data: HeaplessVec<u8, 256>,
|
||||
|
||||
pub num_layers: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum OnionError {
|
||||
|
||||
InvalidRoute,
|
||||
|
||||
PacketTooLarge,
|
||||
|
||||
AuthenticationFailed,
|
||||
|
||||
DecryptionFailed,
|
||||
|
||||
NoMoreLayers,
|
||||
}
|
||||
|
||||
pub struct OnionRouter {
|
||||
|
||||
our_private: [u8; 32],
|
||||
|
||||
our_public: [u8; 32],
|
||||
|
||||
our_hint: u16,
|
||||
}
|
||||
|
||||
impl OnionRouter {
|
||||
|
||||
pub fn new(private_key: [u8; 32]) -> Self {
|
||||
use crate::crypto::x25519::x25519_base;
|
||||
|
||||
let our_public = x25519_base(&private_key);
|
||||
|
||||
let hint_hash = Sha256::hash(&our_public);
|
||||
let our_hint = ((hint_hash[0] as u16) << 8) | (hint_hash[1] as u16);
|
||||
|
||||
Self {
|
||||
our_private: private_key,
|
||||
our_public,
|
||||
our_hint,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wrap(&self, payload: &[u8], route: &OnionRoute) -> Result<OnionPacket, OnionError> {
|
||||
if route.len() < MIN_HOPS || route.len() > MAX_HOPS {
|
||||
return Err(OnionError::InvalidRoute);
|
||||
}
|
||||
|
||||
let total_overhead = route.overhead();
|
||||
if payload.len() + total_overhead > 256 {
|
||||
return Err(OnionError::PacketTooLarge);
|
||||
}
|
||||
|
||||
let mut current = HeaplessVec::<u8, 256>::new();
|
||||
current.extend_from_slice(payload).map_err(|_| OnionError::PacketTooLarge)?;
|
||||
|
||||
for (i, hop) in route.hops.iter().enumerate().rev() {
|
||||
|
||||
let shared = x25519(&self.our_private, &hop.public_key);
|
||||
|
||||
let layer_index = (route.len() - 1 - i) as u8;
|
||||
let mut key_input = [0u8; 33];
|
||||
key_input[..32].copy_from_slice(&shared);
|
||||
key_input[32] = layer_index;
|
||||
|
||||
let mut layer_key = [0u8; 32];
|
||||
Hkdf::derive(&key_input, &[layer_index], ONION_KEY_INFO, &mut layer_key);
|
||||
|
||||
let encrypted = self.encrypt_layer(&layer_key, ¤t)?;
|
||||
|
||||
let tag = self.compute_tag(&layer_key, &encrypted);
|
||||
|
||||
let mut new_layer = HeaplessVec::<u8, 256>::new();
|
||||
|
||||
let next_hint = if i == route.len() - 1 {
|
||||
|
||||
0u16
|
||||
} else {
|
||||
route.hops[i + 1].hint
|
||||
};
|
||||
|
||||
new_layer.push((next_hint >> 8) as u8).map_err(|_| OnionError::PacketTooLarge)?;
|
||||
new_layer.push(next_hint as u8).map_err(|_| OnionError::PacketTooLarge)?;
|
||||
new_layer.extend_from_slice(&tag).map_err(|_| OnionError::PacketTooLarge)?;
|
||||
new_layer.extend_from_slice(&encrypted).map_err(|_| OnionError::PacketTooLarge)?;
|
||||
|
||||
current = new_layer;
|
||||
}
|
||||
|
||||
Ok(OnionPacket {
|
||||
data: current,
|
||||
num_layers: route.len() as u8,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unwrap(&self, packet: &OnionPacket, sender_public: &[u8; 32]) -> Result<(u16, OnionPacket), OnionError> {
|
||||
if packet.data.len() < HOP_OVERHEAD {
|
||||
return Err(OnionError::DecryptionFailed);
|
||||
}
|
||||
|
||||
let next_hint = ((packet.data[0] as u16) << 8) | (packet.data[1] as u16);
|
||||
let tag = &packet.data[2..18];
|
||||
let encrypted = &packet.data[18..];
|
||||
|
||||
let shared = x25519(&self.our_private, sender_public);
|
||||
|
||||
let layer_index = packet.num_layers.saturating_sub(1);
|
||||
let mut key_input = [0u8; 33];
|
||||
key_input[..32].copy_from_slice(&shared);
|
||||
key_input[32] = layer_index;
|
||||
|
||||
let mut layer_key = [0u8; 32];
|
||||
Hkdf::derive(&key_input, &[layer_index], ONION_KEY_INFO, &mut layer_key);
|
||||
|
||||
let expected_tag = self.compute_tag(&layer_key, encrypted);
|
||||
if !constant_time_eq(tag, &expected_tag) {
|
||||
return Err(OnionError::AuthenticationFailed);
|
||||
}
|
||||
|
||||
let inner = self.decrypt_layer(&layer_key, encrypted)?;
|
||||
|
||||
if next_hint == 0 {
|
||||
return Err(OnionError::NoMoreLayers);
|
||||
}
|
||||
|
||||
let mut inner_packet = OnionPacket {
|
||||
data: inner,
|
||||
num_layers: packet.num_layers.saturating_sub(1),
|
||||
};
|
||||
|
||||
Ok((next_hint, inner_packet))
|
||||
}
|
||||
|
||||
pub fn unwrap_final(&self, packet: &OnionPacket, sender_public: &[u8; 32]) -> Result<HeaplessVec<u8, 256>, OnionError> {
|
||||
if packet.data.len() < HOP_OVERHEAD {
|
||||
return Err(OnionError::DecryptionFailed);
|
||||
}
|
||||
|
||||
let tag = &packet.data[2..18];
|
||||
let encrypted = &packet.data[18..];
|
||||
|
||||
let shared = x25519(&self.our_private, sender_public);
|
||||
|
||||
let layer_index = packet.num_layers.saturating_sub(1);
|
||||
let mut key_input = [0u8; 33];
|
||||
key_input[..32].copy_from_slice(&shared);
|
||||
key_input[32] = layer_index;
|
||||
|
||||
let mut layer_key = [0u8; 32];
|
||||
Hkdf::derive(&key_input, &[layer_index], ONION_KEY_INFO, &mut layer_key);
|
||||
|
||||
let expected_tag = self.compute_tag(&layer_key, encrypted);
|
||||
if !constant_time_eq(tag, &expected_tag) {
|
||||
return Err(OnionError::AuthenticationFailed);
|
||||
}
|
||||
|
||||
self.decrypt_layer(&layer_key, encrypted)
|
||||
}
|
||||
|
||||
fn encrypt_layer(&self, key: &[u8; 32], data: &[u8]) -> Result<HeaplessVec<u8, 256>, OnionError> {
|
||||
let mut output = HeaplessVec::new();
|
||||
output.extend_from_slice(data).map_err(|_| OnionError::PacketTooLarge)?;
|
||||
|
||||
let aes = Aes256::new(key);
|
||||
let mut counter = [0u8; 16];
|
||||
let mut keystream = [0u8; 16];
|
||||
let mut block_num = 0u64;
|
||||
|
||||
for chunk in output.chunks_mut(16) {
|
||||
keystream.copy_from_slice(&counter);
|
||||
keystream[8..].copy_from_slice(&block_num.to_le_bytes());
|
||||
aes.encrypt_block(&mut keystream);
|
||||
for (c, k) in chunk.iter_mut().zip(keystream.iter()) {
|
||||
*c ^= k;
|
||||
}
|
||||
block_num += 1;
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn decrypt_layer(&self, key: &[u8; 32], data: &[u8]) -> Result<HeaplessVec<u8, 256>, OnionError> {
|
||||
|
||||
self.encrypt_layer(key, data)
|
||||
}
|
||||
|
||||
fn compute_tag(&self, key: &[u8; 32], data: &[u8]) -> [u8; 16] {
|
||||
|
||||
let mut inner = [0x36u8; 64];
|
||||
let mut outer = [0x5cu8; 64];
|
||||
|
||||
for (i, k) in key.iter().enumerate() {
|
||||
inner[i] ^= k;
|
||||
outer[i] ^= k;
|
||||
}
|
||||
|
||||
let mut inner_input = HeaplessVec::<u8, 320>::new();
|
||||
let _ = inner_input.extend_from_slice(&inner);
|
||||
let _ = inner_input.extend_from_slice(data);
|
||||
let inner_hash = Sha256::hash(&inner_input);
|
||||
|
||||
let mut outer_input = [0u8; 96];
|
||||
outer_input[..64].copy_from_slice(&outer);
|
||||
outer_input[64..].copy_from_slice(&inner_hash);
|
||||
let full = Sha256::hash(&outer_input);
|
||||
|
||||
let mut tag = [0u8; 16];
|
||||
tag.copy_from_slice(&full[..16]);
|
||||
tag
|
||||
}
|
||||
|
||||
pub fn our_hint(&self) -> u16 {
|
||||
self.our_hint
|
||||
}
|
||||
|
||||
pub fn our_public(&self) -> &[u8; 32] {
|
||||
&self.our_public
|
||||
}
|
||||
|
||||
pub fn derive_blinded_hint(&self, epoch: u64) -> u16 {
|
||||
let mut input = [0u8; 40];
|
||||
input[..32].copy_from_slice(&self.our_public);
|
||||
input[32..].copy_from_slice(&epoch.to_le_bytes());
|
||||
|
||||
let mut blinded = [0u8; 2];
|
||||
Hkdf::derive(&input, &epoch.to_le_bytes(), ONION_BLIND_INFO, &mut blinded);
|
||||
|
||||
((blinded[0] as u16) << 8) | (blinded[1] as u16)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RouteBuilder {
|
||||
|
||||
relays: HeaplessVec<RouteHop, 32>,
|
||||
}
|
||||
|
||||
impl RouteBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
relays: HeaplessVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_relay(&mut self, hint: u16, public_key: [u8; 32]) -> bool {
|
||||
self.relays.push(RouteHop { hint, public_key }).is_ok()
|
||||
}
|
||||
|
||||
pub fn build_route(&self, destination: RouteHop, num_hops: usize) -> Option<OnionRoute> {
|
||||
if num_hops < MIN_HOPS || num_hops > MAX_HOPS {
|
||||
return None;
|
||||
}
|
||||
|
||||
let relay_count = num_hops - 1;
|
||||
if self.relays.len() < relay_count {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut hops = HeaplessVec::<RouteHop, MAX_HOPS>::new();
|
||||
|
||||
for i in 0..relay_count {
|
||||
hops.push(self.relays[i % self.relays.len()].clone()).ok()?;
|
||||
}
|
||||
|
||||
hops.push(destination).ok()?;
|
||||
|
||||
Some(OnionRoute { hops })
|
||||
}
|
||||
|
||||
pub fn relay_count(&self) -> usize {
|
||||
self.relays.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RouteBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
572
src/ota.rs
Normal file
572
src/ota.rs
Normal file
@@ -0,0 +1,572 @@
|
||||
use crate::crypto::sha256::Sha256;
|
||||
use crate::crypto::ed25519;
|
||||
|
||||
pub const OTA_CHUNK_SIZE: usize = 4096;
|
||||
|
||||
pub const MAX_FIRMWARE_SIZE: usize = 3_584_000;
|
||||
|
||||
pub const OTA_HEADER_MAGIC: [u8; 4] = [0x4C, 0x55, 0x4E, 0x41];
|
||||
|
||||
pub const OTA_HEADER_VERSION: u8 = 1;
|
||||
|
||||
pub const SIGNATURE_SIZE: usize = 64;
|
||||
|
||||
pub const PUBLIC_KEY_SIZE: usize = 32;
|
||||
|
||||
#[inline(never)]
|
||||
fn ct_key_eq(a: &[u8; 32], b: &[u8; 32]) -> bool {
|
||||
let mut diff: u8 = 0;
|
||||
for i in 0..32 {
|
||||
diff |= a[i] ^ b[i];
|
||||
}
|
||||
diff == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pack_version(major: u8, minor: u8, patch: u8) -> u32 {
|
||||
((major as u32) << 16) | ((minor as u32) << 8) | (patch as u32)
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct OtaHeader {
|
||||
|
||||
pub magic: [u8; 4],
|
||||
|
||||
pub version: u8,
|
||||
|
||||
pub header_size: u8,
|
||||
|
||||
pub fw_major: u8,
|
||||
|
||||
pub fw_minor: u8,
|
||||
|
||||
pub fw_patch: u8,
|
||||
|
||||
pub reserved: [u8; 3],
|
||||
|
||||
pub firmware_size: u32,
|
||||
|
||||
pub firmware_hash: [u8; 32],
|
||||
|
||||
pub signature: [u8; SIGNATURE_SIZE],
|
||||
|
||||
pub public_key: [u8; PUBLIC_KEY_SIZE],
|
||||
}
|
||||
|
||||
impl OtaHeader {
|
||||
|
||||
pub const SIZE: usize = 4 + 1 + 1 + 3 + 3 + 4 + 32 + 64 + 32;
|
||||
|
||||
pub fn from_bytes(data: &[u8]) -> Option<Self> {
|
||||
if data.len() < Self::SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut magic = [0u8; 4];
|
||||
magic.copy_from_slice(&data[0..4]);
|
||||
|
||||
if magic != OTA_HEADER_MAGIC {
|
||||
return None;
|
||||
}
|
||||
|
||||
let version = data[4];
|
||||
if version != OTA_HEADER_VERSION {
|
||||
return None;
|
||||
}
|
||||
|
||||
let header_size = data[5];
|
||||
let fw_major = data[6];
|
||||
let fw_minor = data[7];
|
||||
let fw_patch = data[8];
|
||||
|
||||
let mut reserved = [0u8; 3];
|
||||
reserved.copy_from_slice(&data[9..12]);
|
||||
|
||||
let firmware_size = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
|
||||
|
||||
let mut firmware_hash = [0u8; 32];
|
||||
firmware_hash.copy_from_slice(&data[16..48]);
|
||||
|
||||
let mut signature = [0u8; SIGNATURE_SIZE];
|
||||
signature.copy_from_slice(&data[48..112]);
|
||||
|
||||
let mut public_key = [0u8; PUBLIC_KEY_SIZE];
|
||||
public_key.copy_from_slice(&data[112..144]);
|
||||
|
||||
Some(Self {
|
||||
magic,
|
||||
version,
|
||||
header_size,
|
||||
fw_major,
|
||||
fw_minor,
|
||||
fw_patch,
|
||||
reserved,
|
||||
firmware_size,
|
||||
firmware_hash,
|
||||
signature,
|
||||
public_key,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn version_string(&self) -> [u8; 12] {
|
||||
let mut buf = [0u8; 12];
|
||||
let mut idx = 0;
|
||||
|
||||
idx += write_u8_to_buf(self.fw_major, &mut buf[idx..]);
|
||||
buf[idx] = b'.';
|
||||
idx += 1;
|
||||
|
||||
idx += write_u8_to_buf(self.fw_minor, &mut buf[idx..]);
|
||||
buf[idx] = b'.';
|
||||
idx += 1;
|
||||
|
||||
write_u8_to_buf(self.fw_patch, &mut buf[idx..]);
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
fn write_u8_to_buf(val: u8, buf: &mut [u8]) -> usize {
|
||||
if val >= 100 {
|
||||
buf[0] = b'0' + val / 100;
|
||||
buf[1] = b'0' + (val / 10) % 10;
|
||||
buf[2] = b'0' + val % 10;
|
||||
3
|
||||
} else if val >= 10 {
|
||||
buf[0] = b'0' + val / 10;
|
||||
buf[1] = b'0' + val % 10;
|
||||
2
|
||||
} else {
|
||||
buf[0] = b'0' + val;
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum OtaState {
|
||||
|
||||
Idle,
|
||||
|
||||
ReceivingHeader,
|
||||
|
||||
Receiving,
|
||||
|
||||
Verifying,
|
||||
|
||||
Writing,
|
||||
|
||||
Complete,
|
||||
|
||||
Error(OtaError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum OtaError {
|
||||
|
||||
InvalidHeader,
|
||||
|
||||
SignatureInvalid,
|
||||
|
||||
HashMismatch,
|
||||
|
||||
TooLarge,
|
||||
|
||||
FlashError,
|
||||
|
||||
PartitionError,
|
||||
|
||||
Busy,
|
||||
|
||||
Aborted,
|
||||
|
||||
Timeout,
|
||||
|
||||
RolledBack,
|
||||
|
||||
DowngradeRejected,
|
||||
|
||||
NoTrustedKeys,
|
||||
}
|
||||
|
||||
pub struct OtaManager {
|
||||
|
||||
state: OtaState,
|
||||
|
||||
header_buf: [u8; OtaHeader::SIZE],
|
||||
|
||||
header_received: usize,
|
||||
|
||||
header: Option<OtaHeader>,
|
||||
|
||||
bytes_received: u32,
|
||||
|
||||
hasher: Sha256,
|
||||
|
||||
partition_handle: i32,
|
||||
|
||||
ota_handle: u32,
|
||||
|
||||
trusted_keys: [[u8; PUBLIC_KEY_SIZE]; 2],
|
||||
|
||||
trusted_key_count: usize,
|
||||
|
||||
progress_percent: u8,
|
||||
|
||||
min_version: u32,
|
||||
}
|
||||
|
||||
impl OtaManager {
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: OtaState::Idle,
|
||||
header_buf: [0u8; OtaHeader::SIZE],
|
||||
header_received: 0,
|
||||
header: None,
|
||||
bytes_received: 0,
|
||||
hasher: Sha256::new(),
|
||||
partition_handle: -1,
|
||||
ota_handle: 0,
|
||||
trusted_keys: [[0u8; PUBLIC_KEY_SIZE]; 2],
|
||||
trusted_key_count: 0,
|
||||
progress_percent: 0,
|
||||
min_version: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_trusted_key(&mut self, key: &[u8; PUBLIC_KEY_SIZE]) -> bool {
|
||||
if self.trusted_key_count >= 2 {
|
||||
return false;
|
||||
}
|
||||
self.trusted_keys[self.trusted_key_count].copy_from_slice(key);
|
||||
self.trusted_key_count += 1;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_min_version(&mut self, major: u8, minor: u8, patch: u8) {
|
||||
self.min_version = pack_version(major, minor, patch);
|
||||
}
|
||||
|
||||
fn is_version_allowed(&self, header: &OtaHeader) -> bool {
|
||||
let new_version = pack_version(header.fw_major, header.fw_minor, header.fw_patch);
|
||||
new_version >= self.min_version
|
||||
}
|
||||
|
||||
pub fn state(&self) -> OtaState {
|
||||
self.state
|
||||
}
|
||||
|
||||
pub fn progress(&self) -> u8 {
|
||||
self.progress_percent
|
||||
}
|
||||
|
||||
pub fn begin(&mut self) -> Result<(), OtaError> {
|
||||
if self.state != OtaState::Idle {
|
||||
return Err(OtaError::Busy);
|
||||
}
|
||||
|
||||
self.header_buf.fill(0);
|
||||
self.header_received = 0;
|
||||
self.header = None;
|
||||
self.bytes_received = 0;
|
||||
self.hasher = Sha256::new();
|
||||
self.progress_percent = 0;
|
||||
|
||||
unsafe {
|
||||
let next_partition = esp_idf_sys::esp_ota_get_next_update_partition(core::ptr::null());
|
||||
if next_partition.is_null() {
|
||||
return Err(OtaError::PartitionError);
|
||||
}
|
||||
|
||||
let mut handle: esp_idf_sys::esp_ota_handle_t = 0;
|
||||
let ret = esp_idf_sys::esp_ota_begin(next_partition, 0, &mut handle);
|
||||
if ret != 0 {
|
||||
return Err(OtaError::FlashError);
|
||||
}
|
||||
|
||||
self.ota_handle = handle;
|
||||
}
|
||||
|
||||
self.state = OtaState::ReceivingHeader;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write(&mut self, data: &[u8]) -> Result<usize, OtaError> {
|
||||
match self.state {
|
||||
OtaState::Idle => Err(OtaError::Aborted),
|
||||
OtaState::Error(e) => Err(e),
|
||||
OtaState::ReceivingHeader => self.write_header(data),
|
||||
OtaState::Receiving | OtaState::Writing => self.write_firmware(data),
|
||||
_ => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_header(&mut self, data: &[u8]) -> Result<usize, OtaError> {
|
||||
let needed = OtaHeader::SIZE - self.header_received;
|
||||
let to_copy = data.len().min(needed);
|
||||
|
||||
self.header_buf[self.header_received..self.header_received + to_copy]
|
||||
.copy_from_slice(&data[..to_copy]);
|
||||
self.header_received += to_copy;
|
||||
|
||||
if self.header_received >= OtaHeader::SIZE {
|
||||
|
||||
let header = OtaHeader::from_bytes(&self.header_buf)
|
||||
.ok_or(OtaError::InvalidHeader)?;
|
||||
|
||||
if header.firmware_size as usize > MAX_FIRMWARE_SIZE {
|
||||
self.state = OtaState::Error(OtaError::TooLarge);
|
||||
return Err(OtaError::TooLarge);
|
||||
}
|
||||
|
||||
if self.trusted_key_count == 0 {
|
||||
self.state = OtaState::Error(OtaError::NoTrustedKeys);
|
||||
return Err(OtaError::NoTrustedKeys);
|
||||
}
|
||||
|
||||
if !self.is_version_allowed(&header) {
|
||||
self.state = OtaState::Error(OtaError::DowngradeRejected);
|
||||
return Err(OtaError::DowngradeRejected);
|
||||
}
|
||||
|
||||
if !self.verify_signature(&header) {
|
||||
self.state = OtaState::Error(OtaError::SignatureInvalid);
|
||||
return Err(OtaError::SignatureInvalid);
|
||||
}
|
||||
|
||||
self.header = Some(header);
|
||||
self.state = OtaState::Receiving;
|
||||
|
||||
if to_copy < data.len() {
|
||||
let remaining = &data[to_copy..];
|
||||
return self.write_firmware(remaining).map(|n| to_copy + n);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(to_copy)
|
||||
}
|
||||
|
||||
fn write_firmware(&mut self, data: &[u8]) -> Result<usize, OtaError> {
|
||||
let header = self.header.as_ref().ok_or(OtaError::InvalidHeader)?;
|
||||
|
||||
let remaining = header.firmware_size - self.bytes_received;
|
||||
let to_write = (data.len() as u32).min(remaining) as usize;
|
||||
|
||||
if to_write == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
self.hasher.update(&data[..to_write]);
|
||||
|
||||
unsafe {
|
||||
let ret = esp_idf_sys::esp_ota_write(
|
||||
self.ota_handle,
|
||||
data.as_ptr() as *const core::ffi::c_void,
|
||||
to_write,
|
||||
);
|
||||
|
||||
if ret != 0 {
|
||||
self.state = OtaState::Error(OtaError::FlashError);
|
||||
return Err(OtaError::FlashError);
|
||||
}
|
||||
}
|
||||
|
||||
self.bytes_received += to_write as u32;
|
||||
|
||||
self.progress_percent = ((self.bytes_received as u64 * 100) / header.firmware_size as u64) as u8;
|
||||
|
||||
if self.bytes_received >= header.firmware_size {
|
||||
self.state = OtaState::Verifying;
|
||||
self.verify_and_finish()?;
|
||||
}
|
||||
|
||||
Ok(to_write)
|
||||
}
|
||||
|
||||
fn verify_and_finish(&mut self) -> Result<(), OtaError> {
|
||||
let header = self.header.as_ref().ok_or(OtaError::InvalidHeader)?;
|
||||
|
||||
let computed_hash = self.hasher.clone().finalize();
|
||||
|
||||
if computed_hash != header.firmware_hash {
|
||||
self.state = OtaState::Error(OtaError::HashMismatch);
|
||||
self.abort();
|
||||
return Err(OtaError::HashMismatch);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ret = esp_idf_sys::esp_ota_end(self.ota_handle);
|
||||
if ret != 0 {
|
||||
self.state = OtaState::Error(OtaError::FlashError);
|
||||
return Err(OtaError::FlashError);
|
||||
}
|
||||
|
||||
let next_partition = esp_idf_sys::esp_ota_get_next_update_partition(core::ptr::null());
|
||||
let ret = esp_idf_sys::esp_ota_set_boot_partition(next_partition);
|
||||
if ret != 0 {
|
||||
self.state = OtaState::Error(OtaError::PartitionError);
|
||||
return Err(OtaError::PartitionError);
|
||||
}
|
||||
}
|
||||
|
||||
self.state = OtaState::Complete;
|
||||
self.progress_percent = 100;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_signature(&self, header: &OtaHeader) -> bool {
|
||||
|
||||
if self.trusted_key_count == 0 {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for i in 0..self.trusted_key_count {
|
||||
|
||||
if ct_key_eq(&header.public_key, &self.trusted_keys[i]) {
|
||||
|
||||
let signature = ed25519::Signature(header.signature);
|
||||
if ed25519::Ed25519::verify(&header.public_key, &header.firmware_hash, &signature) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.trusted_key_count > 0
|
||||
}
|
||||
|
||||
pub fn trusted_key_count(&self) -> usize {
|
||||
self.trusted_key_count
|
||||
}
|
||||
|
||||
pub fn abort(&mut self) {
|
||||
if self.ota_handle != 0 {
|
||||
unsafe {
|
||||
esp_idf_sys::esp_ota_abort(self.ota_handle);
|
||||
}
|
||||
self.ota_handle = 0;
|
||||
}
|
||||
self.state = OtaState::Idle;
|
||||
}
|
||||
|
||||
pub fn confirm() -> Result<(), OtaError> {
|
||||
unsafe {
|
||||
let ret = esp_idf_sys::esp_ota_mark_app_valid_cancel_rollback();
|
||||
if ret != 0 {
|
||||
return Err(OtaError::PartitionError);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rollback() -> Result<(), OtaError> {
|
||||
unsafe {
|
||||
let ret = esp_idf_sys::esp_ota_mark_app_invalid_rollback_and_reboot();
|
||||
if ret != 0 {
|
||||
return Err(OtaError::RolledBack);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_running_partition() -> Option<PartitionInfo> {
|
||||
unsafe {
|
||||
let partition = esp_idf_sys::esp_ota_get_running_partition();
|
||||
if partition.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let part = &*partition;
|
||||
let mut label = [0u8; 16];
|
||||
label[..part.label.len()].copy_from_slice(
|
||||
core::slice::from_raw_parts(part.label.as_ptr() as *const u8, part.label.len())
|
||||
);
|
||||
|
||||
Some(PartitionInfo {
|
||||
address: part.address,
|
||||
size: part.size,
|
||||
label,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reboot() -> ! {
|
||||
unsafe {
|
||||
esp_idf_sys::esp_restart();
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OtaManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PartitionInfo {
|
||||
|
||||
pub address: u32,
|
||||
|
||||
pub size: u32,
|
||||
|
||||
pub label: [u8; 16],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum OtaCommand {
|
||||
|
||||
Begin = 0x01,
|
||||
|
||||
Data = 0x02,
|
||||
|
||||
End = 0x03,
|
||||
|
||||
Abort = 0x04,
|
||||
|
||||
Status = 0x05,
|
||||
|
||||
Confirm = 0x06,
|
||||
|
||||
Rollback = 0x07,
|
||||
|
||||
Reboot = 0x08,
|
||||
}
|
||||
|
||||
impl From<u8> for OtaCommand {
|
||||
fn from(v: u8) -> Self {
|
||||
match v {
|
||||
0x01 => OtaCommand::Begin,
|
||||
0x02 => OtaCommand::Data,
|
||||
0x03 => OtaCommand::End,
|
||||
0x04 => OtaCommand::Abort,
|
||||
0x05 => OtaCommand::Status,
|
||||
0x06 => OtaCommand::Confirm,
|
||||
0x07 => OtaCommand::Rollback,
|
||||
0x08 => OtaCommand::Reboot,
|
||||
_ => OtaCommand::Status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum OtaResponse {
|
||||
|
||||
Ok = 0x00,
|
||||
|
||||
Error = 0x01,
|
||||
|
||||
Busy = 0x02,
|
||||
|
||||
Progress = 0x03,
|
||||
|
||||
Complete = 0x04,
|
||||
}
|
||||
256
src/packet_id.rs
Normal file
256
src/packet_id.rs
Normal file
@@ -0,0 +1,256 @@
|
||||
const RTC_SLOW_MEM_BASE: u32 = 0x5000_0000;
|
||||
|
||||
const RTC_SLOT_SESSION_HI: u32 = 8;
|
||||
const RTC_SLOT_SESSION_LO: u32 = 9;
|
||||
const RTC_SLOT_SEQUENCE: u32 = 10;
|
||||
const RTC_SLOT_BOOT_COUNT: u32 = 11;
|
||||
const RTC_SLOT_MAGIC: u32 = 12;
|
||||
|
||||
const RTC_MAGIC: u32 = 0x4C554E42;
|
||||
|
||||
const MAX_SEQUENCE: u32 = 0xFFFF_FFFE;
|
||||
|
||||
const WAKE_SEQUENCE_SKIP: u32 = 256;
|
||||
|
||||
const RNG_DATA_REG: u32 = 0x6003_5110;
|
||||
|
||||
struct PacketIdState {
|
||||
|
||||
session_id: u32,
|
||||
|
||||
sequence: u32,
|
||||
|
||||
boot_count: u32,
|
||||
}
|
||||
|
||||
impl PacketIdState {
|
||||
|
||||
fn new_session(&mut self) {
|
||||
|
||||
let hw_random1 = hw_rng_u32();
|
||||
let hw_random2 = hw_rng_u32();
|
||||
let boot_entropy = self.boot_count.wrapping_mul(0x9E3779B9);
|
||||
let timestamp = read_rtc_time();
|
||||
|
||||
let mixed = hw_random1
|
||||
^ boot_entropy
|
||||
^ timestamp
|
||||
^ self.session_id.wrapping_mul(0x85EBCA6B);
|
||||
|
||||
let mixed = mix32(mixed);
|
||||
let mixed = mix32(mixed ^ hw_random2);
|
||||
|
||||
self.session_id = mixed;
|
||||
|
||||
if self.session_id == 0 {
|
||||
self.session_id = mix32(hw_rng_u32()) | 1;
|
||||
}
|
||||
|
||||
self.sequence = 0;
|
||||
|
||||
self.persist();
|
||||
}
|
||||
|
||||
fn persist(&self) {
|
||||
rtc_write(RTC_SLOT_SESSION_HI, self.session_id);
|
||||
rtc_write(RTC_SLOT_SEQUENCE, self.sequence);
|
||||
}
|
||||
|
||||
fn to_u64(&self) -> u64 {
|
||||
((self.session_id as u64) << 32) | (self.sequence as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rtc_read(slot: u32) -> u32 {
|
||||
unsafe {
|
||||
let addr = (RTC_SLOW_MEM_BASE + slot * 4) as *const u32;
|
||||
core::ptr::read_volatile(addr)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rtc_write(slot: u32, value: u32) {
|
||||
unsafe {
|
||||
let addr = (RTC_SLOW_MEM_BASE + slot * 4) as *mut u32;
|
||||
core::ptr::write_volatile(addr, value);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hw_rng_u32() -> u32 {
|
||||
|
||||
crate::rng::random_u32()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
fn hw_rng_raw() -> u32 {
|
||||
unsafe {
|
||||
let rng_reg = RNG_DATA_REG as *const u32;
|
||||
|
||||
let r1 = core::ptr::read_volatile(rng_reg);
|
||||
|
||||
for _ in 0..10 {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
let r2 = core::ptr::read_volatile(rng_reg);
|
||||
r1 ^ r2.rotate_left(13)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_rtc_time() -> u32 {
|
||||
|
||||
unsafe {
|
||||
let rtc_time_low = 0x6000_8048 as *const u32;
|
||||
core::ptr::read_volatile(rtc_time_low)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn mix32(mut x: u32) -> u32 {
|
||||
x ^= x >> 16;
|
||||
x = x.wrapping_mul(0x85EBCA6B);
|
||||
x ^= x >> 13;
|
||||
x = x.wrapping_mul(0xC2B2AE35);
|
||||
x ^= x >> 16;
|
||||
x
|
||||
}
|
||||
|
||||
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
|
||||
static PACKET_ID_SESSION: AtomicU32 = AtomicU32::new(0);
|
||||
static PACKET_ID_SEQUENCE: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
static BOOT_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
static INITIALIZED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn init() {
|
||||
|
||||
if INITIALIZED.load(Ordering::Acquire) {
|
||||
return;
|
||||
}
|
||||
|
||||
if INITIALIZED.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() {
|
||||
|
||||
while !INITIALIZED.load(Ordering::Acquire) {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let magic = rtc_read(RTC_SLOT_MAGIC);
|
||||
|
||||
let (session_id, sequence, boot_count) = if magic == RTC_MAGIC {
|
||||
|
||||
let stored_session = rtc_read(RTC_SLOT_SESSION_HI);
|
||||
let stored_sequence = rtc_read(RTC_SLOT_SEQUENCE);
|
||||
let stored_boot_count = rtc_read(RTC_SLOT_BOOT_COUNT);
|
||||
|
||||
let boot_count = stored_boot_count.wrapping_add(1);
|
||||
|
||||
if stored_sequence >= MAX_SEQUENCE {
|
||||
|
||||
let new_session = generate_session_id(stored_session, boot_count);
|
||||
(new_session, 0u32, boot_count)
|
||||
} else {
|
||||
|
||||
let new_sequence = stored_sequence.saturating_add(WAKE_SEQUENCE_SKIP);
|
||||
(stored_session, new_sequence, boot_count)
|
||||
}
|
||||
} else {
|
||||
|
||||
let new_session = generate_session_id(0, 0);
|
||||
rtc_write(RTC_SLOT_MAGIC, RTC_MAGIC);
|
||||
(new_session, 0u32, 0u32)
|
||||
};
|
||||
|
||||
BOOT_COUNT.store(boot_count, Ordering::SeqCst);
|
||||
rtc_write(RTC_SLOT_BOOT_COUNT, boot_count);
|
||||
|
||||
PACKET_ID_SESSION.store(session_id, Ordering::SeqCst);
|
||||
PACKET_ID_SEQUENCE.store(sequence, Ordering::SeqCst);
|
||||
|
||||
rtc_write(RTC_SLOT_SESSION_HI, session_id);
|
||||
rtc_write(RTC_SLOT_SEQUENCE, sequence);
|
||||
}
|
||||
|
||||
fn generate_session_id(previous: u32, boot_count: u32) -> u32 {
|
||||
let hw_random1 = hw_rng_u32();
|
||||
let hw_random2 = hw_rng_u32();
|
||||
let boot_entropy = boot_count.wrapping_mul(0x9E3779B9);
|
||||
let timestamp = read_rtc_time();
|
||||
|
||||
let mixed = hw_random1
|
||||
^ boot_entropy
|
||||
^ timestamp
|
||||
^ previous.wrapping_mul(0x85EBCA6B);
|
||||
|
||||
let mixed = mix32(mixed);
|
||||
let mixed = mix32(mixed ^ hw_random2);
|
||||
|
||||
if mixed == 0 { mix32(hw_rng_u32()) | 1 } else { mixed }
|
||||
}
|
||||
|
||||
pub fn next_packet_id() -> u32 {
|
||||
init();
|
||||
|
||||
let sequence = PACKET_ID_SEQUENCE.fetch_add(1, Ordering::SeqCst);
|
||||
let session_id = PACKET_ID_SESSION.load(Ordering::SeqCst);
|
||||
|
||||
if sequence >= MAX_SEQUENCE {
|
||||
|
||||
rotate_session_internal();
|
||||
}
|
||||
|
||||
if sequence & 0xFF == 0 {
|
||||
rtc_write(RTC_SLOT_SESSION_HI, session_id);
|
||||
rtc_write(RTC_SLOT_SEQUENCE, sequence);
|
||||
}
|
||||
|
||||
mix32(session_id ^ sequence.wrapping_mul(0x85EBCA6B))
|
||||
}
|
||||
|
||||
fn rotate_session_internal() {
|
||||
|
||||
let boot_count = BOOT_COUNT.load(Ordering::SeqCst);
|
||||
let old_session = PACKET_ID_SESSION.load(Ordering::SeqCst);
|
||||
|
||||
let new_session = generate_session_id(old_session, boot_count);
|
||||
|
||||
PACKET_ID_SESSION.store(new_session, Ordering::SeqCst);
|
||||
PACKET_ID_SEQUENCE.store(0, Ordering::SeqCst);
|
||||
|
||||
rtc_write(RTC_SLOT_SESSION_HI, new_session);
|
||||
rtc_write(RTC_SLOT_SEQUENCE, 0);
|
||||
}
|
||||
|
||||
pub fn rotate_session() {
|
||||
init();
|
||||
rotate_session_internal();
|
||||
}
|
||||
|
||||
pub fn session_info() -> (u32, u32, u32) {
|
||||
init();
|
||||
let session_id = PACKET_ID_SESSION.load(Ordering::SeqCst);
|
||||
let sequence = PACKET_ID_SEQUENCE.load(Ordering::SeqCst);
|
||||
let boot_count = BOOT_COUNT.load(Ordering::SeqCst);
|
||||
(session_id, sequence, boot_count)
|
||||
}
|
||||
|
||||
pub fn next_packet_id_64() -> u64 {
|
||||
init();
|
||||
let sequence = PACKET_ID_SEQUENCE.fetch_add(1, Ordering::SeqCst);
|
||||
let session_id = PACKET_ID_SESSION.load(Ordering::SeqCst);
|
||||
|
||||
if sequence >= MAX_SEQUENCE {
|
||||
rotate_session_internal();
|
||||
}
|
||||
|
||||
((session_id as u64) << 32) | (sequence as u64)
|
||||
}
|
||||
|
||||
pub fn invalidate_rtc() {
|
||||
rtc_write(RTC_SLOT_MAGIC, 0);
|
||||
}
|
||||
402
src/power.rs
Normal file
402
src/power.rs
Normal file
@@ -0,0 +1,402 @@
|
||||
pub const MIN_DEEP_SLEEP_US: u64 = 1_000;
|
||||
|
||||
pub const MAX_DEEP_SLEEP_US: u64 = 86_400_000_000;
|
||||
|
||||
pub const DEFAULT_LIGHT_SLEEP_MS: u32 = 100;
|
||||
|
||||
pub const LOW_BATTERY_THRESHOLD_MV: u32 = 3400;
|
||||
|
||||
pub const CRITICAL_BATTERY_THRESHOLD_MV: u32 = 3200;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PowerMode {
|
||||
|
||||
Performance,
|
||||
|
||||
Balanced,
|
||||
|
||||
LowPower,
|
||||
|
||||
UltraLow,
|
||||
}
|
||||
|
||||
impl Default for PowerMode {
|
||||
fn default() -> Self {
|
||||
PowerMode::Balanced
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct WakeSources(pub u32);
|
||||
|
||||
impl WakeSources {
|
||||
|
||||
pub const NONE: Self = Self(0);
|
||||
|
||||
pub const TIMER: Self = Self(1 << 0);
|
||||
|
||||
pub const GPIO: Self = Self(1 << 1);
|
||||
|
||||
pub const UART: Self = Self(1 << 2);
|
||||
|
||||
pub const TOUCH: Self = Self(1 << 3);
|
||||
|
||||
pub const ULP: Self = Self(1 << 4);
|
||||
|
||||
pub const BLE: Self = Self(1 << 5);
|
||||
|
||||
pub const WIFI: Self = Self(1 << 6);
|
||||
|
||||
pub const EXT0: Self = Self(1 << 7);
|
||||
|
||||
pub const EXT1: Self = Self(1 << 8);
|
||||
|
||||
pub const ALL: Self = Self(0x1FF);
|
||||
|
||||
pub const fn or(self, other: Self) -> Self {
|
||||
Self(self.0 | other.0)
|
||||
}
|
||||
|
||||
pub const fn has(self, source: Self) -> bool {
|
||||
(self.0 & source.0) != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::BitOr for WakeSources {
|
||||
type Output = Self;
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
Self(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WakeCause {
|
||||
|
||||
PowerOn,
|
||||
|
||||
Timer,
|
||||
|
||||
Gpio(u8),
|
||||
|
||||
Uart,
|
||||
|
||||
Touch,
|
||||
|
||||
Ulp,
|
||||
|
||||
Ble,
|
||||
|
||||
Wifi,
|
||||
|
||||
Ext0,
|
||||
|
||||
Ext1,
|
||||
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GpioWakeConfig {
|
||||
|
||||
pub pin: u8,
|
||||
|
||||
pub level_high: bool,
|
||||
}
|
||||
|
||||
pub struct PowerManager {
|
||||
|
||||
mode: PowerMode,
|
||||
|
||||
light_sleep_wake: WakeSources,
|
||||
|
||||
deep_sleep_wake: WakeSources,
|
||||
|
||||
gpio_wake_pins: [Option<GpioWakeConfig>; 8],
|
||||
|
||||
cpu_freq_mhz: u32,
|
||||
|
||||
last_wake_cause: WakeCause,
|
||||
|
||||
total_sleep_us: u64,
|
||||
|
||||
sleep_count: u32,
|
||||
}
|
||||
|
||||
impl PowerManager {
|
||||
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
mode: PowerMode::Balanced,
|
||||
light_sleep_wake: WakeSources::TIMER.or(WakeSources::GPIO).or(WakeSources::UART),
|
||||
deep_sleep_wake: WakeSources::TIMER.or(WakeSources::GPIO),
|
||||
gpio_wake_pins: [None; 8],
|
||||
cpu_freq_mhz: 240,
|
||||
last_wake_cause: WakeCause::PowerOn,
|
||||
total_sleep_us: 0,
|
||||
sleep_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> Result<(), PowerError> {
|
||||
|
||||
self.last_wake_cause = self.read_wake_cause();
|
||||
|
||||
self.apply_mode()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_mode(&mut self, mode: PowerMode) -> Result<(), PowerError> {
|
||||
self.mode = mode;
|
||||
self.apply_mode()
|
||||
}
|
||||
|
||||
fn apply_mode(&mut self) -> Result<(), PowerError> {
|
||||
let (cpu_freq, wifi_ps, bt_power) = match self.mode {
|
||||
PowerMode::Performance => (240, false, true),
|
||||
PowerMode::Balanced => (160, true, true),
|
||||
PowerMode::LowPower => (80, true, false),
|
||||
PowerMode::UltraLow => (40, true, false),
|
||||
};
|
||||
|
||||
self.set_cpu_frequency(cpu_freq)?;
|
||||
|
||||
if wifi_ps {
|
||||
self.enable_wifi_power_save();
|
||||
} else {
|
||||
self.disable_wifi_power_save();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_cpu_frequency(&mut self, mhz: u32) -> Result<(), PowerError> {
|
||||
|
||||
match mhz {
|
||||
240 | 160 | 80 | 40 | 20 | 10 => {},
|
||||
_ => return Err(PowerError::InvalidFrequency),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let pm_config = esp_idf_sys::esp_pm_config_esp32s3_t {
|
||||
max_freq_mhz: mhz as i32,
|
||||
min_freq_mhz: 10,
|
||||
light_sleep_enable: false,
|
||||
};
|
||||
|
||||
let ret = esp_idf_sys::esp_pm_configure(&pm_config as *const _ as *const core::ffi::c_void);
|
||||
if ret != 0 {
|
||||
return Err(PowerError::ConfigFailed);
|
||||
}
|
||||
}
|
||||
|
||||
self.cpu_freq_mhz = mhz;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn configure_gpio_wake(&mut self, config: GpioWakeConfig) -> Result<(), PowerError> {
|
||||
|
||||
for slot in &mut self.gpio_wake_pins {
|
||||
if slot.is_none() || slot.as_ref().map(|c| c.pin) == Some(config.pin) {
|
||||
*slot = Some(config);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(PowerError::TooManyWakePins)
|
||||
}
|
||||
|
||||
pub fn light_sleep(&mut self, duration_ms: u32) -> Result<WakeCause, PowerError> {
|
||||
if duration_ms == 0 {
|
||||
return Err(PowerError::InvalidDuration);
|
||||
}
|
||||
|
||||
let duration_us = duration_ms as u64 * 1000;
|
||||
|
||||
unsafe {
|
||||
|
||||
if self.light_sleep_wake.has(WakeSources::TIMER) {
|
||||
esp_idf_sys::esp_sleep_enable_timer_wakeup(duration_us);
|
||||
}
|
||||
|
||||
if self.light_sleep_wake.has(WakeSources::UART) {
|
||||
|
||||
esp_idf_sys::esp_sleep_enable_uart_wakeup(0);
|
||||
}
|
||||
|
||||
if self.light_sleep_wake.has(WakeSources::GPIO) {
|
||||
self.configure_gpio_wake_internal()?;
|
||||
}
|
||||
|
||||
let ret = esp_idf_sys::esp_light_sleep_start();
|
||||
if ret != 0 {
|
||||
return Err(PowerError::SleepFailed);
|
||||
}
|
||||
}
|
||||
|
||||
self.sleep_count += 1;
|
||||
self.total_sleep_us += duration_us;
|
||||
|
||||
let cause = self.read_wake_cause();
|
||||
self.last_wake_cause = cause;
|
||||
Ok(cause)
|
||||
}
|
||||
|
||||
pub fn deep_sleep(&mut self, duration_us: u64) -> ! {
|
||||
if duration_us < MIN_DEEP_SLEEP_US {
|
||||
|
||||
unsafe {
|
||||
esp_idf_sys::esp_sleep_enable_timer_wakeup(MIN_DEEP_SLEEP_US);
|
||||
}
|
||||
} else if duration_us > MAX_DEEP_SLEEP_US {
|
||||
unsafe {
|
||||
esp_idf_sys::esp_sleep_enable_timer_wakeup(MAX_DEEP_SLEEP_US);
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
esp_idf_sys::esp_sleep_enable_timer_wakeup(duration_us);
|
||||
}
|
||||
}
|
||||
|
||||
if self.deep_sleep_wake.has(WakeSources::GPIO) {
|
||||
let _ = self.configure_gpio_wake_internal();
|
||||
}
|
||||
|
||||
unsafe {
|
||||
esp_idf_sys::esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
loop {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_gpio_wake_internal(&self) -> Result<(), PowerError> {
|
||||
unsafe {
|
||||
let mut mask: u64 = 0;
|
||||
let mut mode = esp_idf_sys::esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_HIGH;
|
||||
|
||||
for config in &self.gpio_wake_pins {
|
||||
if let Some(cfg) = config {
|
||||
if cfg.pin < 64 {
|
||||
mask |= 1u64 << cfg.pin;
|
||||
if !cfg.level_high {
|
||||
mode = esp_idf_sys::esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ALL_LOW;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mask != 0 {
|
||||
esp_idf_sys::esp_sleep_enable_ext1_wakeup(mask, mode);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_wake_cause(&self) -> WakeCause {
|
||||
unsafe {
|
||||
let cause = esp_idf_sys::esp_sleep_get_wakeup_cause();
|
||||
match cause {
|
||||
esp_idf_sys::esp_sleep_source_t_ESP_SLEEP_WAKEUP_UNDEFINED => WakeCause::PowerOn,
|
||||
esp_idf_sys::esp_sleep_source_t_ESP_SLEEP_WAKEUP_TIMER => WakeCause::Timer,
|
||||
esp_idf_sys::esp_sleep_source_t_ESP_SLEEP_WAKEUP_EXT0 => WakeCause::Ext0,
|
||||
esp_idf_sys::esp_sleep_source_t_ESP_SLEEP_WAKEUP_EXT1 => {
|
||||
|
||||
let gpio_mask = esp_idf_sys::esp_sleep_get_ext1_wakeup_status();
|
||||
let gpio = gpio_mask.trailing_zeros() as u8;
|
||||
WakeCause::Gpio(gpio)
|
||||
}
|
||||
esp_idf_sys::esp_sleep_source_t_ESP_SLEEP_WAKEUP_TOUCHPAD => WakeCause::Touch,
|
||||
esp_idf_sys::esp_sleep_source_t_ESP_SLEEP_WAKEUP_ULP => WakeCause::Ulp,
|
||||
esp_idf_sys::esp_sleep_source_t_ESP_SLEEP_WAKEUP_GPIO => {
|
||||
WakeCause::Gpio(0)
|
||||
}
|
||||
esp_idf_sys::esp_sleep_source_t_ESP_SLEEP_WAKEUP_UART => WakeCause::Uart,
|
||||
esp_idf_sys::esp_sleep_source_t_ESP_SLEEP_WAKEUP_WIFI => WakeCause::Wifi,
|
||||
esp_idf_sys::esp_sleep_source_t_ESP_SLEEP_WAKEUP_BT => WakeCause::Ble,
|
||||
_ => WakeCause::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_wifi_power_save(&self) {
|
||||
unsafe {
|
||||
esp_idf_sys::esp_wifi_set_ps(esp_idf_sys::wifi_ps_type_t_WIFI_PS_MIN_MODEM);
|
||||
}
|
||||
}
|
||||
|
||||
fn disable_wifi_power_save(&self) {
|
||||
unsafe {
|
||||
esp_idf_sys::esp_wifi_set_ps(esp_idf_sys::wifi_ps_type_t_WIFI_PS_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mode(&self) -> PowerMode {
|
||||
self.mode
|
||||
}
|
||||
|
||||
pub fn last_wake_cause(&self) -> WakeCause {
|
||||
self.last_wake_cause
|
||||
}
|
||||
|
||||
pub fn total_sleep_us(&self) -> u64 {
|
||||
self.total_sleep_us
|
||||
}
|
||||
|
||||
pub fn sleep_count(&self) -> u32 {
|
||||
self.sleep_count
|
||||
}
|
||||
|
||||
pub fn cpu_freq_mhz(&self) -> u32 {
|
||||
self.cpu_freq_mhz
|
||||
}
|
||||
|
||||
pub fn is_battery_low(voltage_mv: u32) -> bool {
|
||||
voltage_mv < LOW_BATTERY_THRESHOLD_MV
|
||||
}
|
||||
|
||||
pub fn is_battery_critical(voltage_mv: u32) -> bool {
|
||||
voltage_mv < CRITICAL_BATTERY_THRESHOLD_MV
|
||||
}
|
||||
|
||||
pub fn set_rtc_data(&self, slot: u8, value: u32) {
|
||||
if slot >= 8 {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
|
||||
let rtc_mem = (0x50000000 + slot as u32 * 4) as *mut u32;
|
||||
core::ptr::write_volatile(rtc_mem, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rtc_data(&self, slot: u8) -> u32 {
|
||||
if slot >= 8 {
|
||||
return 0;
|
||||
}
|
||||
unsafe {
|
||||
let rtc_mem = (0x50000000 + slot as u32 * 4) as *const u32;
|
||||
core::ptr::read_volatile(rtc_mem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PowerManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PowerError {
|
||||
|
||||
InvalidFrequency,
|
||||
|
||||
ConfigFailed,
|
||||
|
||||
InvalidDuration,
|
||||
|
||||
SleepFailed,
|
||||
|
||||
TooManyWakePins,
|
||||
}
|
||||
403
src/protocol.rs
Normal file
403
src/protocol.rs
Normal file
@@ -0,0 +1,403 @@
|
||||
use heapless::Vec;
|
||||
|
||||
pub const SYNC: [u8; 2] = [0xAA, 0x55];
|
||||
|
||||
pub const END: u8 = 0x0D;
|
||||
|
||||
pub const MAX_DATA_SIZE: usize = 255;
|
||||
|
||||
pub const MAX_FRAME_SIZE: usize = 2 + 2 + 1 + 1 + MAX_DATA_SIZE + 2 + 1;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum Command {
|
||||
|
||||
Ping = 0x01,
|
||||
|
||||
Pong = 0x02,
|
||||
|
||||
Configure = 0x10,
|
||||
|
||||
ConfigAck = 0x11,
|
||||
|
||||
Transmit = 0x20,
|
||||
|
||||
TxDone = 0x21,
|
||||
|
||||
TxError = 0x22,
|
||||
|
||||
Receive = 0x30,
|
||||
|
||||
GetStats = 0x40,
|
||||
|
||||
StatsResponse = 0x41,
|
||||
|
||||
Cad = 0x50,
|
||||
|
||||
CadResult = 0x51,
|
||||
|
||||
Reset = 0xF0,
|
||||
|
||||
Version = 0xF1,
|
||||
|
||||
VersionResponse = 0xF2,
|
||||
|
||||
Error = 0xFF,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn from_byte(b: u8) -> Option<Self> {
|
||||
match b {
|
||||
0x01 => Some(Command::Ping),
|
||||
0x02 => Some(Command::Pong),
|
||||
0x10 => Some(Command::Configure),
|
||||
0x11 => Some(Command::ConfigAck),
|
||||
0x20 => Some(Command::Transmit),
|
||||
0x21 => Some(Command::TxDone),
|
||||
0x22 => Some(Command::TxError),
|
||||
0x30 => Some(Command::Receive),
|
||||
0x40 => Some(Command::GetStats),
|
||||
0x41 => Some(Command::StatsResponse),
|
||||
0x50 => Some(Command::Cad),
|
||||
0x51 => Some(Command::CadResult),
|
||||
0xF0 => Some(Command::Reset),
|
||||
0xF1 => Some(Command::Version),
|
||||
0xF2 => Some(Command::VersionResponse),
|
||||
0xFF => Some(Command::Error),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Frame {
|
||||
pub command: Command,
|
||||
pub sequence: u8,
|
||||
pub data: Vec<u8, MAX_DATA_SIZE>,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
|
||||
pub fn new(command: Command, sequence: u8) -> Self {
|
||||
Self {
|
||||
command,
|
||||
sequence,
|
||||
data: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_data(command: Command, sequence: u8, data: &[u8]) -> Option<Self> {
|
||||
let mut frame = Self::new(command, sequence);
|
||||
if data.len() > MAX_DATA_SIZE {
|
||||
return None;
|
||||
}
|
||||
for &b in data {
|
||||
let _ = frame.data.push(b);
|
||||
}
|
||||
Some(frame)
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> Vec<u8, MAX_FRAME_SIZE> {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let _ = buf.push(SYNC[0]);
|
||||
let _ = buf.push(SYNC[1]);
|
||||
|
||||
let len = self.data.len() as u16;
|
||||
let _ = buf.push(len as u8);
|
||||
let _ = buf.push((len >> 8) as u8);
|
||||
|
||||
let _ = buf.push(self.command as u8);
|
||||
let _ = buf.push(self.sequence);
|
||||
|
||||
for &b in &self.data {
|
||||
let _ = buf.push(b);
|
||||
}
|
||||
|
||||
let crc = crc16(&buf[4..]);
|
||||
let _ = buf.push(crc as u8);
|
||||
let _ = buf.push((crc >> 8) as u8);
|
||||
|
||||
let _ = buf.push(END);
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ParserState {
|
||||
WaitSync1,
|
||||
WaitSync2,
|
||||
WaitLenLow,
|
||||
WaitLenHigh,
|
||||
WaitCommand,
|
||||
WaitSequence,
|
||||
WaitData,
|
||||
WaitCrcLow,
|
||||
WaitCrcHigh,
|
||||
WaitEnd,
|
||||
}
|
||||
|
||||
pub struct FrameParser {
|
||||
state: ParserState,
|
||||
data_len: u16,
|
||||
data_idx: u16,
|
||||
command: u8,
|
||||
sequence: u8,
|
||||
data: Vec<u8, MAX_DATA_SIZE>,
|
||||
crc_low: u8,
|
||||
}
|
||||
|
||||
impl Default for FrameParser {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameParser {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: ParserState::WaitSync1,
|
||||
data_len: 0,
|
||||
data_idx: 0,
|
||||
command: 0,
|
||||
sequence: 0,
|
||||
data: Vec::new(),
|
||||
crc_low: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.state = ParserState::WaitSync1;
|
||||
self.data.clear();
|
||||
self.data_len = 0;
|
||||
self.data_idx = 0;
|
||||
}
|
||||
|
||||
pub fn feed(&mut self, byte: u8) -> Option<Frame> {
|
||||
match self.state {
|
||||
ParserState::WaitSync1 => {
|
||||
if byte == SYNC[0] {
|
||||
self.state = ParserState::WaitSync2;
|
||||
}
|
||||
}
|
||||
ParserState::WaitSync2 => {
|
||||
if byte == SYNC[1] {
|
||||
self.state = ParserState::WaitLenLow;
|
||||
} else if byte == SYNC[0] {
|
||||
|
||||
} else {
|
||||
self.reset();
|
||||
}
|
||||
}
|
||||
ParserState::WaitLenLow => {
|
||||
self.data_len = byte as u16;
|
||||
self.state = ParserState::WaitLenHigh;
|
||||
}
|
||||
ParserState::WaitLenHigh => {
|
||||
self.data_len |= (byte as u16) << 8;
|
||||
if self.data_len > MAX_DATA_SIZE as u16 {
|
||||
self.reset();
|
||||
return None;
|
||||
}
|
||||
self.state = ParserState::WaitCommand;
|
||||
}
|
||||
ParserState::WaitCommand => {
|
||||
self.command = byte;
|
||||
self.state = ParserState::WaitSequence;
|
||||
}
|
||||
ParserState::WaitSequence => {
|
||||
self.sequence = byte;
|
||||
self.data.clear();
|
||||
self.data_idx = 0;
|
||||
if self.data_len == 0 {
|
||||
self.state = ParserState::WaitCrcLow;
|
||||
} else {
|
||||
self.state = ParserState::WaitData;
|
||||
}
|
||||
}
|
||||
ParserState::WaitData => {
|
||||
let _ = self.data.push(byte);
|
||||
self.data_idx += 1;
|
||||
if self.data_idx >= self.data_len {
|
||||
self.state = ParserState::WaitCrcLow;
|
||||
}
|
||||
}
|
||||
ParserState::WaitCrcLow => {
|
||||
self.crc_low = byte;
|
||||
self.state = ParserState::WaitCrcHigh;
|
||||
}
|
||||
ParserState::WaitCrcHigh => {
|
||||
let received_crc = (self.crc_low as u16) | ((byte as u16) << 8);
|
||||
|
||||
let mut crc_data: Vec<u8, 258> = Vec::new();
|
||||
let _ = crc_data.push(self.command);
|
||||
let _ = crc_data.push(self.sequence);
|
||||
for &b in &self.data {
|
||||
let _ = crc_data.push(b);
|
||||
}
|
||||
let calculated_crc = crc16(&crc_data);
|
||||
|
||||
if received_crc == calculated_crc {
|
||||
self.state = ParserState::WaitEnd;
|
||||
} else {
|
||||
self.reset();
|
||||
}
|
||||
}
|
||||
ParserState::WaitEnd => {
|
||||
if byte == END {
|
||||
|
||||
if let Some(cmd) = Command::from_byte(self.command) {
|
||||
let frame = Frame {
|
||||
command: cmd,
|
||||
sequence: self.sequence,
|
||||
data: self.data.clone(),
|
||||
};
|
||||
self.reset();
|
||||
return Some(frame);
|
||||
}
|
||||
}
|
||||
self.reset();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
const CRC16_TABLE: [u16; 256] = [
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
|
||||
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
|
||||
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
|
||||
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
|
||||
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
|
||||
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
|
||||
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
|
||||
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
|
||||
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
|
||||
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
|
||||
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
|
||||
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
|
||||
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
|
||||
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
|
||||
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
|
||||
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
|
||||
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
|
||||
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
|
||||
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
|
||||
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
|
||||
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
|
||||
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
|
||||
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
|
||||
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
|
||||
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
|
||||
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
|
||||
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
|
||||
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
|
||||
];
|
||||
|
||||
#[inline]
|
||||
pub fn crc16(data: &[u8]) -> u16 {
|
||||
let mut crc: u16 = 0xFFFF;
|
||||
for &byte in data {
|
||||
let index = ((crc >> 8) ^ (byte as u16)) as usize;
|
||||
crc = (crc << 8) ^ CRC16_TABLE[index];
|
||||
}
|
||||
crc
|
||||
}
|
||||
|
||||
pub fn build_pong(sequence: u8) -> Frame {
|
||||
Frame::new(Command::Pong, sequence)
|
||||
}
|
||||
|
||||
pub fn build_config_ack(sequence: u8) -> Frame {
|
||||
Frame::new(Command::ConfigAck, sequence)
|
||||
}
|
||||
|
||||
pub fn build_tx_done(sequence: u8) -> Frame {
|
||||
Frame::new(Command::TxDone, sequence)
|
||||
}
|
||||
|
||||
pub fn build_tx_error(sequence: u8, error_code: u8) -> Option<Frame> {
|
||||
Frame::with_data(Command::TxError, sequence, &[error_code])
|
||||
}
|
||||
|
||||
pub fn build_receive(rssi: i16, snr: i8, data: &[u8]) -> Option<Frame> {
|
||||
if data.len() > MAX_DATA_SIZE - 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut frame = Frame::new(Command::Receive, 0);
|
||||
|
||||
let _ = frame.data.push(rssi as u8);
|
||||
let _ = frame.data.push((rssi >> 8) as u8);
|
||||
|
||||
let _ = frame.data.push(snr as u8);
|
||||
|
||||
let _ = frame.data.push(0);
|
||||
|
||||
for &b in data {
|
||||
let _ = frame.data.push(b);
|
||||
}
|
||||
|
||||
Some(frame)
|
||||
}
|
||||
|
||||
pub fn build_cad_result(sequence: u8, detected: bool) -> Option<Frame> {
|
||||
Frame::with_data(Command::CadResult, sequence, &[if detected { 1 } else { 0 }])
|
||||
}
|
||||
|
||||
pub fn build_version_response(sequence: u8, version: &str) -> Option<Frame> {
|
||||
Frame::with_data(Command::VersionResponse, sequence, version.as_bytes())
|
||||
}
|
||||
|
||||
pub fn build_error(sequence: u8, message: &str) -> Option<Frame> {
|
||||
Frame::with_data(Command::Error, sequence, message.as_bytes())
|
||||
}
|
||||
|
||||
pub fn build_stats_response(
|
||||
sequence: u8,
|
||||
tx_packets: u32,
|
||||
rx_packets: u32,
|
||||
tx_errors: u32,
|
||||
rx_errors: u32,
|
||||
) -> Option<Frame> {
|
||||
let mut data = [0u8; 16];
|
||||
data[0..4].copy_from_slice(&tx_packets.to_le_bytes());
|
||||
data[4..8].copy_from_slice(&rx_packets.to_le_bytes());
|
||||
data[8..12].copy_from_slice(&tx_errors.to_le_bytes());
|
||||
data[12..16].copy_from_slice(&rx_errors.to_le_bytes());
|
||||
Frame::with_data(Command::StatsResponse, sequence, &data)
|
||||
}
|
||||
|
||||
pub fn parse_config(data: &[u8]) -> Option<crate::sx1262::RadioConfig> {
|
||||
if data.len() < 14 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let frequency = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
|
||||
let spreading_factor = data[4];
|
||||
let bandwidth = (u16::from_le_bytes([data[5], data[6]]) / 125) as u8;
|
||||
let coding_rate = data[7];
|
||||
let tx_power = data[8] as i8;
|
||||
let sync_word = data[9];
|
||||
let preamble_length = u16::from_le_bytes([data[10], data[11]]);
|
||||
let flags = data[12];
|
||||
|
||||
Some(crate::sx1262::RadioConfig {
|
||||
frequency,
|
||||
spreading_factor,
|
||||
bandwidth: bandwidth.min(2),
|
||||
coding_rate,
|
||||
tx_power,
|
||||
sync_word,
|
||||
preamble_length,
|
||||
crc_enabled: flags & 0x01 != 0,
|
||||
implicit_header: flags & 0x02 != 0,
|
||||
ldro: flags & 0x04 != 0,
|
||||
})
|
||||
}
|
||||
494
src/protocol_router.rs
Normal file
494
src/protocol_router.rs
Normal file
@@ -0,0 +1,494 @@
|
||||
use heapless::Vec;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Protocol {
|
||||
|
||||
Unknown,
|
||||
|
||||
MeshCore,
|
||||
|
||||
Meshtastic,
|
||||
|
||||
RNode,
|
||||
|
||||
AtCommand,
|
||||
}
|
||||
|
||||
impl Protocol {
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Protocol::Unknown => "Unknown",
|
||||
Protocol::MeshCore => "MeshCore",
|
||||
Protocol::Meshtastic => "Meshtastic",
|
||||
Protocol::RNode => "RNode/KISS",
|
||||
Protocol::AtCommand => "AT Command",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod magic {
|
||||
|
||||
pub const MESHCORE_SYNC1: u8 = 0xAA;
|
||||
pub const MESHCORE_SYNC2: u8 = 0x55;
|
||||
|
||||
pub const MESHTASTIC_SYNC1: u8 = 0x94;
|
||||
pub const MESHTASTIC_SYNC2: u8 = 0xC3;
|
||||
|
||||
pub const KISS_FEND: u8 = 0xC0;
|
||||
|
||||
pub const AT_PREFIX: [u8; 2] = [b'A', b'T'];
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum DetectState {
|
||||
|
||||
Idle,
|
||||
|
||||
MeshCore1,
|
||||
|
||||
Meshtastic1,
|
||||
|
||||
At1,
|
||||
}
|
||||
|
||||
const SYNC_TIMEOUT_BYTES: u16 = 256;
|
||||
|
||||
pub struct ProtocolDetector {
|
||||
state: DetectState,
|
||||
detected: Protocol,
|
||||
|
||||
bytes_seen: u16,
|
||||
|
||||
lock_threshold: u8,
|
||||
|
||||
lock_count: u8,
|
||||
|
||||
state_bytes: u16,
|
||||
|
||||
last_detect_ms: u32,
|
||||
}
|
||||
|
||||
impl Default for ProtocolDetector {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDetector {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: DetectState::Idle,
|
||||
detected: Protocol::Unknown,
|
||||
bytes_seen: 0,
|
||||
lock_threshold: 3,
|
||||
lock_count: 0,
|
||||
state_bytes: 0,
|
||||
last_detect_ms: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.state = DetectState::Idle;
|
||||
self.detected = Protocol::Unknown;
|
||||
self.bytes_seen = 0;
|
||||
self.lock_count = 0;
|
||||
self.state_bytes = 0;
|
||||
}
|
||||
|
||||
pub fn soft_reset(&mut self) {
|
||||
self.state = DetectState::Idle;
|
||||
self.state_bytes = 0;
|
||||
}
|
||||
|
||||
pub fn force_protocol(&mut self, protocol: Protocol) {
|
||||
self.detected = protocol;
|
||||
self.state = DetectState::Idle;
|
||||
|
||||
}
|
||||
|
||||
pub fn protocol(&self) -> Protocol {
|
||||
self.detected
|
||||
}
|
||||
|
||||
pub fn is_locked(&self) -> bool {
|
||||
self.lock_count >= self.lock_threshold
|
||||
}
|
||||
|
||||
pub fn confirm_frame(&mut self) {
|
||||
if self.lock_count < 255 {
|
||||
self.lock_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error_frame(&mut self) {
|
||||
if self.lock_count > 0 {
|
||||
self.lock_count = self.lock_count.saturating_sub(2);
|
||||
}
|
||||
|
||||
if self.lock_count == 0 && self.detected != Protocol::Unknown {
|
||||
self.detected = Protocol::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_timeout(&mut self) {
|
||||
if self.state != DetectState::Idle && self.state_bytes > SYNC_TIMEOUT_BYTES {
|
||||
|
||||
self.state = DetectState::Idle;
|
||||
self.state_bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feed(&mut self, byte: u8) -> Option<Protocol> {
|
||||
self.bytes_seen += 1;
|
||||
self.state_bytes += 1;
|
||||
|
||||
self.check_timeout();
|
||||
|
||||
if self.is_locked() {
|
||||
return Some(self.detected);
|
||||
}
|
||||
|
||||
let prev_state = self.state;
|
||||
|
||||
match self.state {
|
||||
DetectState::Idle => {
|
||||
match byte {
|
||||
magic::MESHCORE_SYNC1 => {
|
||||
self.state = DetectState::MeshCore1;
|
||||
}
|
||||
magic::MESHTASTIC_SYNC1 => {
|
||||
self.state = DetectState::Meshtastic1;
|
||||
}
|
||||
magic::KISS_FEND => {
|
||||
|
||||
if self.detected == Protocol::Unknown || self.detected == Protocol::RNode {
|
||||
self.detected = Protocol::RNode;
|
||||
return Some(Protocol::RNode);
|
||||
}
|
||||
}
|
||||
b'A' => {
|
||||
self.state = DetectState::At1;
|
||||
}
|
||||
_ => {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DetectState::MeshCore1 => {
|
||||
if byte == magic::MESHCORE_SYNC2 {
|
||||
self.detected = Protocol::MeshCore;
|
||||
self.state = DetectState::Idle;
|
||||
return Some(Protocol::MeshCore);
|
||||
} else if byte == magic::MESHCORE_SYNC1 {
|
||||
|
||||
} else {
|
||||
self.state = DetectState::Idle;
|
||||
}
|
||||
}
|
||||
|
||||
DetectState::Meshtastic1 => {
|
||||
if byte == magic::MESHTASTIC_SYNC2 {
|
||||
self.detected = Protocol::Meshtastic;
|
||||
self.state = DetectState::Idle;
|
||||
return Some(Protocol::Meshtastic);
|
||||
} else {
|
||||
self.state = DetectState::Idle;
|
||||
}
|
||||
}
|
||||
|
||||
DetectState::At1 => {
|
||||
if byte == b'T' {
|
||||
self.detected = Protocol::AtCommand;
|
||||
self.state = DetectState::Idle;
|
||||
return Some(Protocol::AtCommand);
|
||||
} else {
|
||||
self.state = DetectState::Idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.state != prev_state {
|
||||
self.state_bytes = 0;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub const MAX_TRANSPORTS: usize = 3;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TransportType {
|
||||
UsbSerial,
|
||||
Ble,
|
||||
WiFi,
|
||||
}
|
||||
|
||||
pub struct TransportState {
|
||||
pub transport: TransportType,
|
||||
pub detector: ProtocolDetector,
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
impl TransportState {
|
||||
pub fn new(transport: TransportType) -> Self {
|
||||
Self {
|
||||
transport,
|
||||
detector: ProtocolDetector::new(),
|
||||
active: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProtocolRouter {
|
||||
|
||||
transports: [TransportState; MAX_TRANSPORTS],
|
||||
|
||||
lora_protocol: Protocol,
|
||||
|
||||
lora_shared: bool,
|
||||
|
||||
priority_transport: Option<TransportType>,
|
||||
}
|
||||
|
||||
impl ProtocolRouter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
transports: [
|
||||
TransportState::new(TransportType::UsbSerial),
|
||||
TransportState::new(TransportType::Ble),
|
||||
TransportState::new(TransportType::WiFi),
|
||||
],
|
||||
lora_protocol: Protocol::Unknown,
|
||||
lora_shared: true,
|
||||
priority_transport: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transport(&mut self, t: TransportType) -> &mut TransportState {
|
||||
match t {
|
||||
TransportType::UsbSerial => &mut self.transports[0],
|
||||
TransportType::Ble => &mut self.transports[1],
|
||||
TransportType::WiFi => &mut self.transports[2],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transport_ref(&self, t: TransportType) -> &TransportState {
|
||||
match t {
|
||||
TransportType::UsbSerial => &self.transports[0],
|
||||
TransportType::Ble => &self.transports[1],
|
||||
TransportType::WiFi => &self.transports[2],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lora_protocol(&self) -> Protocol {
|
||||
self.lora_protocol
|
||||
}
|
||||
|
||||
pub fn set_lora_protocol(&mut self, protocol: Protocol) {
|
||||
self.lora_protocol = protocol;
|
||||
}
|
||||
|
||||
pub fn is_lora_shared(&self) -> bool {
|
||||
self.lora_shared
|
||||
}
|
||||
|
||||
pub fn priority_transport(&self) -> Option<TransportType> {
|
||||
self.priority_transport
|
||||
}
|
||||
|
||||
pub fn release_lora_control(&mut self, transport: TransportType) {
|
||||
if self.priority_transport == Some(transport) {
|
||||
self.priority_transport = None;
|
||||
|
||||
}
|
||||
|
||||
let state = self.transport(transport);
|
||||
state.detector.reset();
|
||||
state.active = false;
|
||||
}
|
||||
|
||||
fn can_claim_lora(&self, transport: TransportType, protocol: Protocol) -> bool {
|
||||
|
||||
if self.priority_transport.is_none() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.priority_transport == Some(transport) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.lora_protocol == protocol {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn route_incoming(&mut self, transport: TransportType, byte: u8) -> Protocol {
|
||||
|
||||
let idx = match transport {
|
||||
TransportType::UsbSerial => 0,
|
||||
TransportType::Ble => 1,
|
||||
TransportType::WiFi => 2,
|
||||
};
|
||||
|
||||
self.transports[idx].active = true;
|
||||
|
||||
if let Some(protocol) = self.transports[idx].detector.feed(byte) {
|
||||
|
||||
let can_claim = self.priority_transport.is_none()
|
||||
|| self.priority_transport == Some(transport)
|
||||
|| self.lora_protocol == protocol;
|
||||
|
||||
if can_claim {
|
||||
|
||||
if self.lora_protocol == Protocol::Unknown || self.lora_protocol == protocol {
|
||||
self.lora_protocol = protocol;
|
||||
}
|
||||
|
||||
if self.priority_transport.is_none() && self.transports[idx].detector.is_locked() {
|
||||
self.priority_transport = Some(transport);
|
||||
}
|
||||
}
|
||||
protocol
|
||||
} else {
|
||||
self.transports[idx].detector.protocol()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_conflict(&mut self, transport: TransportType, new_protocol: Protocol) -> bool {
|
||||
|
||||
if new_protocol == Protocol::AtCommand {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.priority_transport.is_none() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.priority_transport == Some(transport) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.lora_protocol != new_protocol && self.lora_protocol != Protocol::Unknown {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn status(&self) -> [(TransportType, Protocol, bool); MAX_TRANSPORTS] {
|
||||
[
|
||||
(TransportType::UsbSerial, self.transports[0].detector.protocol(), self.transports[0].active),
|
||||
(TransportType::Ble, self.transports[1].detector.protocol(), self.transports[1].active),
|
||||
(TransportType::WiFi, self.transports[2].detector.protocol(), self.transports[2].active),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProtocolRouter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LoRaPacket {
|
||||
pub protocol: Protocol,
|
||||
pub data: Vec<u8, 256>,
|
||||
pub rssi: i16,
|
||||
pub snr: i8,
|
||||
}
|
||||
|
||||
impl LoRaPacket {
|
||||
|
||||
pub fn detect_protocol(data: &[u8]) -> Protocol {
|
||||
if data.len() < 4 {
|
||||
return Protocol::Unknown;
|
||||
}
|
||||
|
||||
if data.len() >= 20 {
|
||||
|
||||
let channel_hash = data[3];
|
||||
|
||||
if data.len() >= 12 {
|
||||
let flags = data[11];
|
||||
let hop_limit = flags & 0x07;
|
||||
if hop_limit >= 1 && hop_limit <= 7 && channel_hash != 0 {
|
||||
return Protocol::Meshtastic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if data.len() >= 9 {
|
||||
let flags = data[8];
|
||||
|
||||
let msg_type = flags & 0x0F;
|
||||
|
||||
let hop = (flags >> 4) & 0x0F;
|
||||
if msg_type <= 15 && hop <= 7 {
|
||||
return Protocol::MeshCore;
|
||||
}
|
||||
}
|
||||
|
||||
if data.len() >= 18 {
|
||||
|
||||
let context = data[16];
|
||||
let header_type = (context >> 6) & 0x03;
|
||||
let propagation_type = (context >> 4) & 0x03;
|
||||
|
||||
if header_type <= 3 && propagation_type <= 3 {
|
||||
|
||||
let zeros = data[..16].iter().filter(|&&b| b == 0).count();
|
||||
if zeros < 8 {
|
||||
return Protocol::RNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Protocol::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UnifiedPacket {
|
||||
|
||||
pub source_protocol: Protocol,
|
||||
|
||||
pub dest_protocol: Protocol,
|
||||
|
||||
pub payload: Vec<u8, 237>,
|
||||
|
||||
pub source_addr: [u8; 32],
|
||||
|
||||
pub dest_addr: [u8; 32],
|
||||
|
||||
pub hops: u8,
|
||||
|
||||
pub rssi: i16,
|
||||
|
||||
pub snr: i8,
|
||||
}
|
||||
|
||||
impl UnifiedPacket {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
source_protocol: Protocol::Unknown,
|
||||
dest_protocol: Protocol::Unknown,
|
||||
payload: Vec::new(),
|
||||
source_addr: [0; 32],
|
||||
dest_addr: [0; 32],
|
||||
hops: 0,
|
||||
rssi: 0,
|
||||
snr: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UnifiedPacket {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
292
src/rng.rs
Normal file
292
src/rng.rs
Normal file
@@ -0,0 +1,292 @@
|
||||
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
|
||||
const RNG_DATA_REG: u32 = 0x6003_5110;
|
||||
|
||||
const WIFI_MAC_TIME_REG: u32 = 0x6003_3010;
|
||||
|
||||
const MAX_REPETITION_COUNT: u32 = 8;
|
||||
|
||||
const APT_WINDOW_SIZE: u32 = 512;
|
||||
|
||||
const APT_CUTOFF: u32 = 20;
|
||||
|
||||
const MIN_SAMPLES_FOR_HEALTH: u32 = 64;
|
||||
|
||||
const MIN_ENTROPY_SCALED: u32 = 24;
|
||||
|
||||
static RNG_HEALTHY: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
static SAMPLE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
static REPETITION_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
static LAST_VALUE: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
static FAILURE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
static BIT_COUNTS: [AtomicU32; 32] = [
|
||||
AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0),
|
||||
AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0),
|
||||
AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0),
|
||||
AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0),
|
||||
AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0),
|
||||
AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0),
|
||||
AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0),
|
||||
AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0),
|
||||
];
|
||||
|
||||
static INITIALIZED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn init() {
|
||||
if INITIALIZED.swap(true, Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
|
||||
SAMPLE_COUNT.store(0, Ordering::SeqCst);
|
||||
REPETITION_COUNT.store(0, Ordering::SeqCst);
|
||||
FAILURE_COUNT.store(0, Ordering::SeqCst);
|
||||
|
||||
for _ in 0..MIN_SAMPLES_FOR_HEALTH {
|
||||
let _ = raw_random_u32_with_health();
|
||||
}
|
||||
|
||||
let failures = FAILURE_COUNT.load(Ordering::SeqCst);
|
||||
let healthy = failures == 0 && estimate_entropy() >= MIN_ENTROPY_SCALED;
|
||||
RNG_HEALTHY.store(healthy, Ordering::SeqCst);
|
||||
|
||||
if !healthy {
|
||||
log_rng_warning("RNG health check failed during initialization");
|
||||
}
|
||||
}
|
||||
|
||||
static RNG_WARNING_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
#[inline]
|
||||
fn log_rng_warning(_msg: &str) {
|
||||
RNG_WARNING_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
}
|
||||
|
||||
pub fn warning_count() -> u32 {
|
||||
RNG_WARNING_COUNT.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hw_rng_raw() -> u32 {
|
||||
unsafe {
|
||||
let reg = RNG_DATA_REG as *const u32;
|
||||
core::ptr::read_volatile(reg)
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_random_u32_with_health() -> u32 {
|
||||
let value = hw_rng_raw();
|
||||
update_health_state(value);
|
||||
value
|
||||
}
|
||||
|
||||
fn update_health_state(value: u32) {
|
||||
let count = SAMPLE_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
let last = LAST_VALUE.swap(value, Ordering::SeqCst);
|
||||
if value == last {
|
||||
let rep = REPETITION_COUNT.fetch_add(1, Ordering::SeqCst) + 1;
|
||||
if rep >= MAX_REPETITION_COUNT {
|
||||
FAILURE_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
RNG_HEALTHY.store(false, Ordering::SeqCst);
|
||||
log_rng_warning("RNG repetition count exceeded");
|
||||
}
|
||||
} else {
|
||||
REPETITION_COUNT.store(0, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
for i in 0..32 {
|
||||
if (value >> i) & 1 == 1 {
|
||||
BIT_COUNTS[i].fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 && count % APT_WINDOW_SIZE == 0 {
|
||||
reassess_health();
|
||||
}
|
||||
}
|
||||
|
||||
fn reassess_health() {
|
||||
let failures = FAILURE_COUNT.load(Ordering::SeqCst);
|
||||
let entropy = estimate_entropy();
|
||||
|
||||
if failures == 0 && entropy >= MIN_ENTROPY_SCALED {
|
||||
RNG_HEALTHY.store(true, Ordering::SeqCst);
|
||||
} else if entropy < MIN_ENTROPY_SCALED {
|
||||
RNG_HEALTHY.store(false, Ordering::SeqCst);
|
||||
log_rng_warning("RNG entropy below threshold");
|
||||
}
|
||||
|
||||
if failures > 0 {
|
||||
|
||||
let _ = FAILURE_COUNT.compare_exchange(
|
||||
failures,
|
||||
failures - 1,
|
||||
Ordering::SeqCst,
|
||||
Ordering::Relaxed
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if SAMPLE_COUNT.load(Ordering::SeqCst) % (APT_WINDOW_SIZE * 4) == 0 {
|
||||
for bc in &BIT_COUNTS {
|
||||
bc.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn estimate_entropy() -> u32 {
|
||||
let samples = SAMPLE_COUNT.load(Ordering::SeqCst);
|
||||
if samples < MIN_SAMPLES_FOR_HEALTH {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut total_entropy: u32 = 0;
|
||||
|
||||
for bc in &BIT_COUNTS {
|
||||
let ones = bc.load(Ordering::Relaxed);
|
||||
|
||||
let expected = samples / 2;
|
||||
let diff = if ones > expected { ones - expected } else { expected - ones };
|
||||
|
||||
let scaled_entropy = if diff >= expected {
|
||||
0
|
||||
} else {
|
||||
|
||||
8u32.saturating_sub((8 * diff) / expected.max(1))
|
||||
};
|
||||
|
||||
total_entropy += scaled_entropy;
|
||||
}
|
||||
|
||||
total_entropy / 32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_healthy() -> bool {
|
||||
RNG_HEALTHY.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn health_stats() -> RngHealthStats {
|
||||
RngHealthStats {
|
||||
healthy: RNG_HEALTHY.load(Ordering::SeqCst),
|
||||
sample_count: SAMPLE_COUNT.load(Ordering::SeqCst),
|
||||
failure_count: FAILURE_COUNT.load(Ordering::SeqCst),
|
||||
estimated_entropy: estimate_entropy(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RngHealthStats {
|
||||
|
||||
pub healthy: bool,
|
||||
|
||||
pub sample_count: u32,
|
||||
|
||||
pub failure_count: u32,
|
||||
|
||||
pub estimated_entropy: u32,
|
||||
}
|
||||
|
||||
pub fn random_u32() -> u32 {
|
||||
if !INITIALIZED.load(Ordering::SeqCst) {
|
||||
init();
|
||||
}
|
||||
|
||||
let healthy = RNG_HEALTHY.load(Ordering::SeqCst);
|
||||
|
||||
let r1 = raw_random_u32_with_health();
|
||||
|
||||
for _ in 0..5 {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
|
||||
let r2 = raw_random_u32_with_health();
|
||||
|
||||
let time_entropy = read_timer_entropy();
|
||||
|
||||
let mut result = mix_entropy(r1, r2, time_entropy);
|
||||
|
||||
if !healthy {
|
||||
log_rng_warning("RNG unhealthy - applying compensating entropy mixing");
|
||||
|
||||
for i in 0..4 {
|
||||
for _ in 0..10 {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
let extra = raw_random_u32_with_health();
|
||||
let time = read_timer_entropy();
|
||||
result = mix_entropy(result, extra, time.wrapping_add(i));
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn random_u32_checked() -> Option<u32> {
|
||||
if !is_healthy() {
|
||||
return None;
|
||||
}
|
||||
Some(random_u32())
|
||||
}
|
||||
|
||||
pub fn fill_random(dest: &mut [u8]) {
|
||||
if !INITIALIZED.load(Ordering::SeqCst) {
|
||||
init();
|
||||
}
|
||||
|
||||
let mut offset = 0;
|
||||
while offset < dest.len() {
|
||||
let random = random_u32();
|
||||
let bytes = random.to_le_bytes();
|
||||
|
||||
let remaining = dest.len() - offset;
|
||||
let to_copy = remaining.min(4);
|
||||
|
||||
dest[offset..offset + to_copy].copy_from_slice(&bytes[..to_copy]);
|
||||
offset += to_copy;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_random_checked(dest: &mut [u8]) -> bool {
|
||||
if !is_healthy() {
|
||||
return false;
|
||||
}
|
||||
fill_random(dest);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn recheck_health() {
|
||||
|
||||
FAILURE_COUNT.store(0, Ordering::SeqCst);
|
||||
|
||||
for _ in 0..MIN_SAMPLES_FOR_HEALTH {
|
||||
let _ = raw_random_u32_with_health();
|
||||
}
|
||||
|
||||
reassess_health();
|
||||
}
|
||||
|
||||
fn read_timer_entropy() -> u32 {
|
||||
unsafe {
|
||||
|
||||
let timer = core::ptr::read_volatile(WIFI_MAC_TIME_REG as *const u32);
|
||||
timer
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mix_entropy(a: u32, b: u32, c: u32) -> u32 {
|
||||
let mut h = a;
|
||||
h ^= b.rotate_left(13);
|
||||
h = h.wrapping_mul(0x85EBCA6B);
|
||||
h ^= c.rotate_right(7);
|
||||
h = h.wrapping_mul(0xC2B2AE35);
|
||||
h ^= h >> 16;
|
||||
h
|
||||
}
|
||||
1014
src/rnode.rs
Normal file
1014
src/rnode.rs
Normal file
File diff suppressed because it is too large
Load Diff
781
src/session.rs
Normal file
781
src/session.rs
Normal file
@@ -0,0 +1,781 @@
|
||||
use crate::crypto::{
|
||||
x25519::{x25519, x25519_base},
|
||||
hkdf::Hkdf,
|
||||
aes::{Aes256, AesMode},
|
||||
};
|
||||
use heapless::Vec as HeaplessVec;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(target_arch = "xtensa")]
|
||||
fn fill_random(dest: &mut [u8]) {
|
||||
|
||||
const RNG_DATA_REG: u32 = 0x3FF7_5144;
|
||||
|
||||
for chunk in dest.chunks_mut(4) {
|
||||
|
||||
let random_word: u32 = unsafe {
|
||||
core::ptr::read_volatile(RNG_DATA_REG as *const u32)
|
||||
};
|
||||
let bytes = random_word.to_le_bytes();
|
||||
for (i, byte) in chunk.iter_mut().enumerate() {
|
||||
*byte = bytes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "xtensa"))]
|
||||
fn fill_random(dest: &mut [u8]) {
|
||||
|
||||
use crate::crypto::sha256::Sha256;
|
||||
static mut COUNTER: u64 = 0;
|
||||
|
||||
let mut seed = [0u8; 40];
|
||||
unsafe {
|
||||
seed[..8].copy_from_slice(&COUNTER.to_le_bytes());
|
||||
COUNTER = COUNTER.wrapping_add(1);
|
||||
}
|
||||
|
||||
let stack_addr = &seed as *const _ as usize;
|
||||
seed[8..16].copy_from_slice(&stack_addr.to_le_bytes());
|
||||
|
||||
let hash = Sha256::hash(&seed);
|
||||
let copy_len = core::cmp::min(dest.len(), 32);
|
||||
dest[..copy_len].copy_from_slice(&hash[..copy_len]);
|
||||
|
||||
if dest.len() > 32 {
|
||||
fill_random(&mut dest[32..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
|
||||
if a.len() != b.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut result: u8 = 0;
|
||||
for (x, y) in a.iter().zip(b.iter()) {
|
||||
result |= x ^ y;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
core::ptr::read_volatile(&result) == 0
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_MESSAGES_BEFORE_RATCHET: u64 = 100;
|
||||
|
||||
const MAX_TIME_BEFORE_RATCHET_SECS: u64 = 600;
|
||||
|
||||
const MAX_SKIPPED_KEYS: usize = 100;
|
||||
|
||||
const SESSION_HINT_INFO: &[u8] = b"session-hint-v1";
|
||||
|
||||
const ROOT_KEY_INFO: &[u8] = b"lunarpunk-root-key-v2";
|
||||
|
||||
const CHAIN_KEY_INFO: &[u8] = b"lunarpunk-chain-key-v2";
|
||||
|
||||
const MESSAGE_KEY_INFO: &[u8] = b"lunarpunk-message-key-v2";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Session {
|
||||
|
||||
root_key: [u8; 32],
|
||||
|
||||
send_chain_key: [u8; 32],
|
||||
|
||||
recv_chain_key: [u8; 32],
|
||||
|
||||
send_ratchet_private: [u8; 32],
|
||||
|
||||
send_ratchet_public: [u8; 32],
|
||||
|
||||
recv_ratchet_public: [u8; 32],
|
||||
|
||||
send_count: u64,
|
||||
|
||||
recv_count: u64,
|
||||
|
||||
prev_recv_chain: u64,
|
||||
|
||||
last_ratchet_time: u64,
|
||||
|
||||
skipped_keys: HashMap<([u8; 8], u64), [u8; 32]>,
|
||||
|
||||
established: bool,
|
||||
}
|
||||
|
||||
pub struct SessionParams {
|
||||
|
||||
pub shared_secret: [u8; 32],
|
||||
|
||||
pub our_private: [u8; 32],
|
||||
|
||||
pub their_public: [u8; 32],
|
||||
|
||||
pub is_initiator: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MessageHeader {
|
||||
|
||||
pub dh_public: [u8; 32],
|
||||
|
||||
pub prev_chain_len: u64,
|
||||
|
||||
pub message_num: u64,
|
||||
}
|
||||
|
||||
impl MessageHeader {
|
||||
|
||||
pub fn encode(&self) -> [u8; 48] {
|
||||
let mut buf = [0u8; 48];
|
||||
buf[..32].copy_from_slice(&self.dh_public);
|
||||
buf[32..40].copy_from_slice(&self.prev_chain_len.to_le_bytes());
|
||||
buf[40..48].copy_from_slice(&self.message_num.to_le_bytes());
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn decode(data: &[u8]) -> Option<Self> {
|
||||
if data.len() < 48 {
|
||||
return None;
|
||||
}
|
||||
let mut dh_public = [0u8; 32];
|
||||
dh_public.copy_from_slice(&data[..32]);
|
||||
|
||||
let mut prev_bytes = [0u8; 8];
|
||||
prev_bytes.copy_from_slice(&data[32..40]);
|
||||
let prev_chain_len = u64::from_le_bytes(prev_bytes);
|
||||
|
||||
let mut num_bytes = [0u8; 8];
|
||||
num_bytes.copy_from_slice(&data[40..48]);
|
||||
let message_num = u64::from_le_bytes(num_bytes);
|
||||
|
||||
Some(Self {
|
||||
dh_public,
|
||||
prev_chain_len,
|
||||
message_num,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SessionError {
|
||||
|
||||
NotEstablished,
|
||||
|
||||
InvalidFormat,
|
||||
|
||||
DecryptionFailed,
|
||||
|
||||
OldChain,
|
||||
|
||||
TooManySkipped,
|
||||
|
||||
KeyDerivationFailed,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
|
||||
pub fn new(params: SessionParams) -> Self {
|
||||
|
||||
let mut root_key = [0u8; 32];
|
||||
let mut send_chain_key = [0u8; 32];
|
||||
let mut recv_chain_key = [0u8; 32];
|
||||
|
||||
let salt = if params.is_initiator {
|
||||
b"initiator-salt-v1"
|
||||
} else {
|
||||
b"responder-salt-v1"
|
||||
};
|
||||
|
||||
Hkdf::derive(¶ms.shared_secret, salt, ROOT_KEY_INFO, &mut root_key);
|
||||
|
||||
let dh_output = x25519(¶ms.our_private, ¶ms.their_public);
|
||||
|
||||
let mut kdf_input = [0u8; 64];
|
||||
kdf_input[..32].copy_from_slice(&root_key);
|
||||
kdf_input[32..].copy_from_slice(&dh_output);
|
||||
|
||||
if params.is_initiator {
|
||||
Hkdf::derive(&kdf_input, b"send", CHAIN_KEY_INFO, &mut send_chain_key);
|
||||
Hkdf::derive(&kdf_input, b"recv", CHAIN_KEY_INFO, &mut recv_chain_key);
|
||||
} else {
|
||||
Hkdf::derive(&kdf_input, b"recv", CHAIN_KEY_INFO, &mut send_chain_key);
|
||||
Hkdf::derive(&kdf_input, b"send", CHAIN_KEY_INFO, &mut recv_chain_key);
|
||||
}
|
||||
|
||||
let send_ratchet_public = x25519_base(¶ms.our_private);
|
||||
|
||||
Self {
|
||||
root_key,
|
||||
send_chain_key,
|
||||
recv_chain_key,
|
||||
send_ratchet_private: params.our_private,
|
||||
send_ratchet_public,
|
||||
recv_ratchet_public: params.their_public,
|
||||
send_count: 0,
|
||||
recv_count: 0,
|
||||
prev_recv_chain: 0,
|
||||
last_ratchet_time: 0,
|
||||
skipped_keys: HashMap::new(),
|
||||
established: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uninitialized() -> Self {
|
||||
Self {
|
||||
root_key: [0u8; 32],
|
||||
send_chain_key: [0u8; 32],
|
||||
recv_chain_key: [0u8; 32],
|
||||
send_ratchet_private: [0u8; 32],
|
||||
send_ratchet_public: [0u8; 32],
|
||||
recv_ratchet_public: [0u8; 32],
|
||||
send_count: 0,
|
||||
recv_count: 0,
|
||||
prev_recv_chain: 0,
|
||||
last_ratchet_time: 0,
|
||||
skipped_keys: HashMap::new(),
|
||||
established: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_established(&self) -> bool {
|
||||
self.established
|
||||
}
|
||||
|
||||
pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<(MessageHeader, HeaplessVec<u8, 256>), SessionError> {
|
||||
if !self.established {
|
||||
return Err(SessionError::NotEstablished);
|
||||
}
|
||||
|
||||
let mut message_key = [0u8; 32];
|
||||
Hkdf::derive(&self.send_chain_key, &self.send_count.to_le_bytes(), MESSAGE_KEY_INFO, &mut message_key);
|
||||
|
||||
let mut new_chain_key = [0u8; 32];
|
||||
Hkdf::derive(&self.send_chain_key, b"chain-advance", CHAIN_KEY_INFO, &mut new_chain_key);
|
||||
self.send_chain_key = new_chain_key;
|
||||
|
||||
let header = MessageHeader {
|
||||
dh_public: self.send_ratchet_public,
|
||||
prev_chain_len: self.prev_recv_chain,
|
||||
message_num: self.send_count,
|
||||
};
|
||||
|
||||
let mut nonce = [0u8; 16];
|
||||
nonce[..8].copy_from_slice(&self.send_count.to_le_bytes());
|
||||
|
||||
let mut ciphertext = HeaplessVec::new();
|
||||
let _ = ciphertext.extend_from_slice(plaintext);
|
||||
|
||||
let aes = Aes256::new(&message_key);
|
||||
let mut keystream = [0u8; 16];
|
||||
let mut block_counter = 0u64;
|
||||
|
||||
for chunk in ciphertext.chunks_mut(16) {
|
||||
keystream = nonce;
|
||||
keystream[8..].copy_from_slice(&block_counter.to_le_bytes());
|
||||
aes.encrypt_block(&mut keystream);
|
||||
for (c, k) in chunk.iter_mut().zip(keystream.iter()) {
|
||||
*c ^= k;
|
||||
}
|
||||
block_counter += 1;
|
||||
}
|
||||
|
||||
let tag = Self::compute_tag(&message_key, &header.encode(), &ciphertext);
|
||||
let _ = ciphertext.extend_from_slice(&tag);
|
||||
|
||||
self.send_count += 1;
|
||||
|
||||
if self.should_ratchet() {
|
||||
self.advance_send_ratchet();
|
||||
}
|
||||
|
||||
Ok((header, ciphertext))
|
||||
}
|
||||
|
||||
pub fn decrypt(&mut self, header: &MessageHeader, ciphertext: &[u8]) -> Result<HeaplessVec<u8, 256>, SessionError> {
|
||||
if !self.established {
|
||||
return Err(SessionError::NotEstablished);
|
||||
}
|
||||
|
||||
if ciphertext.len() < 16 {
|
||||
return Err(SessionError::InvalidFormat);
|
||||
}
|
||||
|
||||
if header.dh_public != self.recv_ratchet_public {
|
||||
|
||||
self.skip_message_keys(header.prev_chain_len)?;
|
||||
|
||||
self.advance_recv_ratchet(&header.dh_public)?;
|
||||
}
|
||||
|
||||
let message_key = self.get_message_key(header)?;
|
||||
|
||||
let tag_start = ciphertext.len() - 16;
|
||||
let received_tag = &ciphertext[tag_start..];
|
||||
let ct_without_tag = &ciphertext[..tag_start];
|
||||
|
||||
let expected_tag = Self::compute_tag(&message_key, &header.encode(), ct_without_tag);
|
||||
if !constant_time_eq(received_tag, &expected_tag) {
|
||||
return Err(SessionError::DecryptionFailed);
|
||||
}
|
||||
|
||||
let mut plaintext = HeaplessVec::new();
|
||||
let _ = plaintext.extend_from_slice(ct_without_tag);
|
||||
|
||||
let mut nonce = [0u8; 16];
|
||||
nonce[..8].copy_from_slice(&header.message_num.to_le_bytes());
|
||||
|
||||
let aes = Aes256::new(&message_key);
|
||||
let mut keystream = [0u8; 16];
|
||||
let mut block_counter = 0u64;
|
||||
|
||||
for chunk in plaintext.chunks_mut(16) {
|
||||
keystream = nonce;
|
||||
keystream[8..].copy_from_slice(&block_counter.to_le_bytes());
|
||||
aes.encrypt_block(&mut keystream);
|
||||
for (c, k) in chunk.iter_mut().zip(keystream.iter()) {
|
||||
*c ^= k;
|
||||
}
|
||||
block_counter += 1;
|
||||
}
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
pub fn derive_session_hint(&self, epoch: u64) -> [u8; 4] {
|
||||
let mut input = [0u8; 40];
|
||||
input[..32].copy_from_slice(&self.root_key);
|
||||
input[32..].copy_from_slice(&epoch.to_le_bytes());
|
||||
|
||||
let mut hint = [0u8; 4];
|
||||
Hkdf::derive(&input, b"hint", SESSION_HINT_INFO, &mut hint);
|
||||
hint
|
||||
}
|
||||
|
||||
fn should_ratchet(&self) -> bool {
|
||||
self.send_count >= MAX_MESSAGES_BEFORE_RATCHET
|
||||
|
||||
}
|
||||
|
||||
fn advance_send_ratchet(&mut self) {
|
||||
|
||||
let mut new_private = [0u8; 32];
|
||||
fill_random(&mut new_private);
|
||||
|
||||
new_private[0] &= 248;
|
||||
new_private[31] &= 127;
|
||||
new_private[31] |= 64;
|
||||
|
||||
let new_public = x25519_base(&new_private);
|
||||
|
||||
let dh_output = x25519(&new_private, &self.recv_ratchet_public);
|
||||
|
||||
let mut kdf_input = [0u8; 64];
|
||||
kdf_input[..32].copy_from_slice(&self.root_key);
|
||||
kdf_input[32..].copy_from_slice(&dh_output);
|
||||
|
||||
Hkdf::derive(&kdf_input, b"root", ROOT_KEY_INFO, &mut self.root_key);
|
||||
Hkdf::derive(&kdf_input, b"chain", CHAIN_KEY_INFO, &mut self.send_chain_key);
|
||||
|
||||
self.send_ratchet_private = new_private;
|
||||
self.send_ratchet_public = new_public;
|
||||
self.prev_recv_chain = self.recv_count;
|
||||
self.send_count = 0;
|
||||
}
|
||||
|
||||
fn advance_recv_ratchet(&mut self, their_new_public: &[u8; 32]) -> Result<(), SessionError> {
|
||||
|
||||
let dh_output = x25519(&self.send_ratchet_private, their_new_public);
|
||||
|
||||
let mut kdf_input = [0u8; 64];
|
||||
kdf_input[..32].copy_from_slice(&self.root_key);
|
||||
kdf_input[32..].copy_from_slice(&dh_output);
|
||||
|
||||
Hkdf::derive(&kdf_input, b"root", ROOT_KEY_INFO, &mut self.root_key);
|
||||
Hkdf::derive(&kdf_input, b"chain", CHAIN_KEY_INFO, &mut self.recv_chain_key);
|
||||
|
||||
self.recv_ratchet_public = *their_new_public;
|
||||
self.recv_count = 0;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn skip_message_keys(&mut self, until: u64) -> Result<(), SessionError> {
|
||||
let to_skip = until.saturating_sub(self.recv_count);
|
||||
if to_skip as usize > MAX_SKIPPED_KEYS {
|
||||
return Err(SessionError::TooManySkipped);
|
||||
}
|
||||
|
||||
while self.recv_count < until {
|
||||
let mut message_key = [0u8; 32];
|
||||
Hkdf::derive(&self.recv_chain_key, &self.recv_count.to_le_bytes(), MESSAGE_KEY_INFO, &mut message_key);
|
||||
|
||||
let mut key_prefix = [0u8; 8];
|
||||
key_prefix.copy_from_slice(&self.recv_ratchet_public[..8]);
|
||||
|
||||
let _ = self.skipped_keys.insert((key_prefix, self.recv_count), message_key);
|
||||
|
||||
let mut new_chain = [0u8; 32];
|
||||
Hkdf::derive(&self.recv_chain_key, b"chain-advance", CHAIN_KEY_INFO, &mut new_chain);
|
||||
self.recv_chain_key = new_chain;
|
||||
self.recv_count += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_message_key(&mut self, header: &MessageHeader) -> Result<[u8; 32], SessionError> {
|
||||
|
||||
let mut key_prefix = [0u8; 8];
|
||||
key_prefix.copy_from_slice(&header.dh_public[..8]);
|
||||
|
||||
if let Some(key) = self.skipped_keys.remove(&(key_prefix, header.message_num)) {
|
||||
return Ok(key);
|
||||
}
|
||||
|
||||
if header.message_num > self.recv_count {
|
||||
self.skip_message_keys(header.message_num)?;
|
||||
}
|
||||
|
||||
let mut message_key = [0u8; 32];
|
||||
Hkdf::derive(&self.recv_chain_key, &header.message_num.to_le_bytes(), MESSAGE_KEY_INFO, &mut message_key);
|
||||
|
||||
let mut new_chain = [0u8; 32];
|
||||
Hkdf::derive(&self.recv_chain_key, b"chain-advance", CHAIN_KEY_INFO, &mut new_chain);
|
||||
self.recv_chain_key = new_chain;
|
||||
self.recv_count = header.message_num + 1;
|
||||
|
||||
Ok(message_key)
|
||||
}
|
||||
|
||||
fn compute_tag(key: &[u8; 32], header: &[u8], ciphertext: &[u8]) -> [u8; 16] {
|
||||
use crate::crypto::sha256::Sha256;
|
||||
|
||||
let mut inner = [0x36u8; 64];
|
||||
let mut outer = [0x5cu8; 64];
|
||||
|
||||
for (i, k) in key.iter().enumerate() {
|
||||
inner[i] ^= k;
|
||||
outer[i] ^= k;
|
||||
}
|
||||
|
||||
let mut hasher_data = HeaplessVec::<u8, 512>::new();
|
||||
let _ = hasher_data.extend_from_slice(&inner);
|
||||
let _ = hasher_data.extend_from_slice(header);
|
||||
let _ = hasher_data.extend_from_slice(ciphertext);
|
||||
let inner_hash = Sha256::hash(&hasher_data);
|
||||
|
||||
let mut outer_data = [0u8; 96];
|
||||
outer_data[..64].copy_from_slice(&outer);
|
||||
outer_data[64..].copy_from_slice(&inner_hash);
|
||||
let full_tag = Sha256::hash(&outer_data);
|
||||
|
||||
let mut tag = [0u8; 16];
|
||||
tag.copy_from_slice(&full_tag[..16]);
|
||||
tag
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SessionManager {
|
||||
|
||||
sessions: HashMap<[u8; 8], Session>,
|
||||
}
|
||||
|
||||
impl SessionManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sessions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_session(&mut self, peer_public: &[u8; 32]) -> Option<&mut Session> {
|
||||
let mut key = [0u8; 8];
|
||||
key.copy_from_slice(&peer_public[..8]);
|
||||
self.sessions.get_mut(&key)
|
||||
}
|
||||
|
||||
pub fn create_session(&mut self, params: SessionParams) {
|
||||
let mut key = [0u8; 8];
|
||||
key.copy_from_slice(¶ms.their_public[..8]);
|
||||
|
||||
let session = Session::new(params);
|
||||
self.sessions.insert(key, session);
|
||||
}
|
||||
|
||||
pub fn remove_session(&mut self, peer_public: &[u8; 32]) {
|
||||
let mut key = [0u8; 8];
|
||||
key.copy_from_slice(&peer_public[..8]);
|
||||
self.sessions.remove(&key);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SessionManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
const SESSION_SERIALIZED_SIZE: usize = 225;
|
||||
|
||||
impl Session {
|
||||
|
||||
pub fn serialize(&self) -> [u8; SESSION_SERIALIZED_SIZE] {
|
||||
let mut buf = [0u8; SESSION_SERIALIZED_SIZE];
|
||||
let mut pos = 0;
|
||||
|
||||
buf[pos..pos + 32].copy_from_slice(&self.root_key);
|
||||
pos += 32;
|
||||
buf[pos..pos + 32].copy_from_slice(&self.send_chain_key);
|
||||
pos += 32;
|
||||
buf[pos..pos + 32].copy_from_slice(&self.recv_chain_key);
|
||||
pos += 32;
|
||||
buf[pos..pos + 32].copy_from_slice(&self.send_ratchet_private);
|
||||
pos += 32;
|
||||
buf[pos..pos + 32].copy_from_slice(&self.send_ratchet_public);
|
||||
pos += 32;
|
||||
buf[pos..pos + 32].copy_from_slice(&self.recv_ratchet_public);
|
||||
pos += 32;
|
||||
buf[pos..pos + 8].copy_from_slice(&self.send_count.to_le_bytes());
|
||||
pos += 8;
|
||||
buf[pos..pos + 8].copy_from_slice(&self.recv_count.to_le_bytes());
|
||||
pos += 8;
|
||||
buf[pos..pos + 8].copy_from_slice(&self.prev_recv_chain.to_le_bytes());
|
||||
pos += 8;
|
||||
buf[pos..pos + 8].copy_from_slice(&self.last_ratchet_time.to_le_bytes());
|
||||
pos += 8;
|
||||
buf[pos] = if self.established { 1 } else { 0 };
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn deserialize(data: &[u8]) -> Option<Self> {
|
||||
if data.len() < SESSION_SERIALIZED_SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut pos = 0;
|
||||
|
||||
let mut root_key = [0u8; 32];
|
||||
root_key.copy_from_slice(&data[pos..pos + 32]);
|
||||
pos += 32;
|
||||
|
||||
let mut send_chain_key = [0u8; 32];
|
||||
send_chain_key.copy_from_slice(&data[pos..pos + 32]);
|
||||
pos += 32;
|
||||
|
||||
let mut recv_chain_key = [0u8; 32];
|
||||
recv_chain_key.copy_from_slice(&data[pos..pos + 32]);
|
||||
pos += 32;
|
||||
|
||||
let mut send_ratchet_private = [0u8; 32];
|
||||
send_ratchet_private.copy_from_slice(&data[pos..pos + 32]);
|
||||
pos += 32;
|
||||
|
||||
let mut send_ratchet_public = [0u8; 32];
|
||||
send_ratchet_public.copy_from_slice(&data[pos..pos + 32]);
|
||||
pos += 32;
|
||||
|
||||
let mut recv_ratchet_public = [0u8; 32];
|
||||
recv_ratchet_public.copy_from_slice(&data[pos..pos + 32]);
|
||||
pos += 32;
|
||||
|
||||
let mut u64_bytes = [0u8; 8];
|
||||
|
||||
u64_bytes.copy_from_slice(&data[pos..pos + 8]);
|
||||
let send_count = u64::from_le_bytes(u64_bytes);
|
||||
pos += 8;
|
||||
|
||||
u64_bytes.copy_from_slice(&data[pos..pos + 8]);
|
||||
let recv_count = u64::from_le_bytes(u64_bytes);
|
||||
pos += 8;
|
||||
|
||||
u64_bytes.copy_from_slice(&data[pos..pos + 8]);
|
||||
let prev_recv_chain = u64::from_le_bytes(u64_bytes);
|
||||
pos += 8;
|
||||
|
||||
u64_bytes.copy_from_slice(&data[pos..pos + 8]);
|
||||
let last_ratchet_time = u64::from_le_bytes(u64_bytes);
|
||||
pos += 8;
|
||||
|
||||
let established = data[pos] != 0;
|
||||
|
||||
Some(Self {
|
||||
root_key,
|
||||
send_chain_key,
|
||||
recv_chain_key,
|
||||
send_ratchet_private,
|
||||
send_ratchet_public,
|
||||
recv_ratchet_public,
|
||||
send_count,
|
||||
recv_count,
|
||||
prev_recv_chain,
|
||||
last_ratchet_time,
|
||||
skipped_keys: HashMap::new(),
|
||||
established,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const NVS_SESSION_NAMESPACE: &[u8] = b"sessions\0";
|
||||
|
||||
const MAX_PERSISTED_SESSIONS: usize = 32;
|
||||
|
||||
impl SessionManager {
|
||||
|
||||
#[cfg(target_arch = "xtensa")]
|
||||
pub fn save_to_nvs(&self) -> Result<(), SessionError> {
|
||||
use esp_idf_sys::*;
|
||||
|
||||
unsafe {
|
||||
|
||||
let mut handle: nvs_handle_t = 0;
|
||||
let namespace = core::ffi::CStr::from_ptr(NVS_SESSION_NAMESPACE.as_ptr() as *const core::ffi::c_char);
|
||||
|
||||
let mut err = nvs_open(
|
||||
namespace.as_ptr(),
|
||||
nvs_open_mode_t_NVS_READWRITE,
|
||||
&mut handle,
|
||||
);
|
||||
|
||||
if err != ESP_OK {
|
||||
nvs_flash_init();
|
||||
err = nvs_open(
|
||||
namespace.as_ptr(),
|
||||
nvs_open_mode_t_NVS_READWRITE,
|
||||
&mut handle,
|
||||
);
|
||||
if err != ESP_OK {
|
||||
return Err(SessionError::KeyDerivationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
let count_key = core::ffi::CStr::from_ptr(b"sess_count\0".as_ptr() as *const core::ffi::c_char);
|
||||
nvs_set_u32(handle, count_key.as_ptr(), self.sessions.len() as u32);
|
||||
|
||||
let mut idx = 0u32;
|
||||
for (key, session) in self.sessions.iter() {
|
||||
|
||||
let mut key_name = [0u8; 16];
|
||||
let prefix = b"sess_";
|
||||
key_name[..5].copy_from_slice(prefix);
|
||||
|
||||
if idx < 10 {
|
||||
key_name[5] = b'0' + idx as u8;
|
||||
key_name[6] = 0;
|
||||
} else {
|
||||
key_name[5] = b'0' + (idx / 10) as u8;
|
||||
key_name[6] = b'0' + (idx % 10) as u8;
|
||||
key_name[7] = 0;
|
||||
}
|
||||
let key_cstr = core::ffi::CStr::from_ptr(key_name.as_ptr() as *const core::ffi::c_char);
|
||||
|
||||
let mut blob = [0u8; 8 + SESSION_SERIALIZED_SIZE];
|
||||
blob[..8].copy_from_slice(key);
|
||||
blob[8..].copy_from_slice(&session.serialize());
|
||||
|
||||
nvs_set_blob(
|
||||
handle,
|
||||
key_cstr.as_ptr(),
|
||||
blob.as_ptr() as *const _,
|
||||
blob.len(),
|
||||
);
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
|
||||
::log::info!("Saved {} sessions to NVS", self.sessions.len());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "xtensa")]
|
||||
pub fn load_from_nvs(&mut self) -> Result<usize, SessionError> {
|
||||
use esp_idf_sys::*;
|
||||
|
||||
unsafe {
|
||||
|
||||
let mut handle: nvs_handle_t = 0;
|
||||
let namespace = core::ffi::CStr::from_ptr(NVS_SESSION_NAMESPACE.as_ptr() as *const core::ffi::c_char);
|
||||
|
||||
let err = nvs_open(
|
||||
namespace.as_ptr(),
|
||||
nvs_open_mode_t_NVS_READONLY,
|
||||
&mut handle,
|
||||
);
|
||||
|
||||
if err != ESP_OK {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let count_key = core::ffi::CStr::from_ptr(b"sess_count\0".as_ptr() as *const core::ffi::c_char);
|
||||
let mut count: u32 = 0;
|
||||
if nvs_get_u32(handle, count_key.as_ptr(), &mut count) != ESP_OK {
|
||||
nvs_close(handle);
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let count = core::cmp::min(count as usize, MAX_PERSISTED_SESSIONS);
|
||||
|
||||
let mut loaded = 0;
|
||||
for idx in 0..count {
|
||||
|
||||
let mut key_name = [0u8; 16];
|
||||
let prefix = b"sess_";
|
||||
key_name[..5].copy_from_slice(prefix);
|
||||
if idx < 10 {
|
||||
key_name[5] = b'0' + idx as u8;
|
||||
key_name[6] = 0;
|
||||
} else {
|
||||
key_name[5] = b'0' + (idx / 10) as u8;
|
||||
key_name[6] = b'0' + (idx % 10) as u8;
|
||||
key_name[7] = 0;
|
||||
}
|
||||
let key_cstr = core::ffi::CStr::from_ptr(key_name.as_ptr() as *const core::ffi::c_char);
|
||||
|
||||
let mut blob = [0u8; 8 + SESSION_SERIALIZED_SIZE];
|
||||
let mut blob_len = blob.len();
|
||||
|
||||
if nvs_get_blob(
|
||||
handle,
|
||||
key_cstr.as_ptr(),
|
||||
blob.as_mut_ptr() as *mut _,
|
||||
&mut blob_len,
|
||||
) == ESP_OK && blob_len == blob.len()
|
||||
{
|
||||
|
||||
let mut peer_key = [0u8; 8];
|
||||
peer_key.copy_from_slice(&blob[..8]);
|
||||
|
||||
if let Some(session) = Session::deserialize(&blob[8..]) {
|
||||
let _ = self.sessions.insert(peer_key, session);
|
||||
loaded += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nvs_close(handle);
|
||||
::log::info!("Loaded {} sessions from NVS", loaded);
|
||||
Ok(loaded)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "xtensa"))]
|
||||
pub fn save_to_nvs(&self) -> Result<(), SessionError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "xtensa"))]
|
||||
pub fn load_from_nvs(&mut self) -> Result<usize, SessionError> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
pub fn session_count(&self) -> usize {
|
||||
self.sessions.len()
|
||||
}
|
||||
}
|
||||
610
src/sx1262.rs
Normal file
610
src/sx1262.rs
Normal file
@@ -0,0 +1,610 @@
|
||||
use embedded_hal::spi::SpiDevice;
|
||||
use embedded_hal::digital::{InputPin, OutputPin};
|
||||
use esp_idf_hal::delay::FreeRtos;
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod opcode {
|
||||
|
||||
pub const SET_SLEEP: u8 = 0x84;
|
||||
pub const SET_STANDBY: u8 = 0x80;
|
||||
pub const SET_FS: u8 = 0xC1;
|
||||
pub const SET_TX: u8 = 0x83;
|
||||
pub const SET_RX: u8 = 0x82;
|
||||
pub const STOP_TIMER_ON_PREAMBLE: u8 = 0x9F;
|
||||
pub const SET_RX_DUTY_CYCLE: u8 = 0x94;
|
||||
pub const SET_CAD: u8 = 0xC5;
|
||||
pub const SET_TX_CONTINUOUS_WAVE: u8 = 0xD1;
|
||||
pub const SET_TX_INFINITE_PREAMBLE: u8 = 0xD2;
|
||||
pub const SET_REGULATOR_MODE: u8 = 0x96;
|
||||
pub const CALIBRATE: u8 = 0x89;
|
||||
pub const CALIBRATE_IMAGE: u8 = 0x98;
|
||||
pub const SET_PA_CONFIG: u8 = 0x95;
|
||||
pub const SET_RX_TX_FALLBACK_MODE: u8 = 0x93;
|
||||
|
||||
pub const WRITE_REGISTER: u8 = 0x0D;
|
||||
pub const READ_REGISTER: u8 = 0x1D;
|
||||
pub const WRITE_BUFFER: u8 = 0x0E;
|
||||
pub const READ_BUFFER: u8 = 0x1E;
|
||||
|
||||
pub const SET_DIO_IRQ_PARAMS: u8 = 0x08;
|
||||
pub const GET_IRQ_STATUS: u8 = 0x12;
|
||||
pub const CLEAR_IRQ_STATUS: u8 = 0x02;
|
||||
pub const SET_DIO2_AS_RF_SWITCH_CTRL: u8 = 0x9D;
|
||||
pub const SET_DIO3_AS_TCXO_CTRL: u8 = 0x97;
|
||||
|
||||
pub const SET_RF_FREQUENCY: u8 = 0x86;
|
||||
pub const SET_PACKET_TYPE: u8 = 0x8A;
|
||||
pub const GET_PACKET_TYPE: u8 = 0x11;
|
||||
pub const SET_TX_PARAMS: u8 = 0x8E;
|
||||
pub const SET_MODULATION_PARAMS: u8 = 0x8B;
|
||||
pub const SET_PACKET_PARAMS: u8 = 0x8C;
|
||||
pub const SET_CAD_PARAMS: u8 = 0x88;
|
||||
pub const SET_BUFFER_BASE_ADDRESS: u8 = 0x8F;
|
||||
pub const SET_LORA_SYMB_NUM_TIMEOUT: u8 = 0xA0;
|
||||
|
||||
pub const GET_STATUS: u8 = 0xC0;
|
||||
pub const GET_RX_BUFFER_STATUS: u8 = 0x13;
|
||||
pub const GET_PACKET_STATUS: u8 = 0x14;
|
||||
pub const GET_RSSI_INST: u8 = 0x15;
|
||||
pub const GET_STATS: u8 = 0x10;
|
||||
pub const RESET_STATS: u8 = 0x00;
|
||||
pub const GET_DEVICE_ERRORS: u8 = 0x17;
|
||||
pub const CLEAR_DEVICE_ERRORS: u8 = 0x07;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod register {
|
||||
pub const WHITENING_INITIAL_MSB: u16 = 0x06B8;
|
||||
pub const WHITENING_INITIAL_LSB: u16 = 0x06B9;
|
||||
pub const CRC_INITIAL_MSB: u16 = 0x06BC;
|
||||
pub const CRC_INITIAL_LSB: u16 = 0x06BD;
|
||||
pub const CRC_POLYNOMIAL_MSB: u16 = 0x06BE;
|
||||
pub const CRC_POLYNOMIAL_LSB: u16 = 0x06BF;
|
||||
pub const SYNC_WORD_0: u16 = 0x06C0;
|
||||
pub const SYNC_WORD_1: u16 = 0x06C1;
|
||||
pub const NODE_ADDRESS: u16 = 0x06CD;
|
||||
pub const BROADCAST_ADDRESS: u16 = 0x06CE;
|
||||
pub const LORA_SYNC_WORD_MSB: u16 = 0x0740;
|
||||
pub const LORA_SYNC_WORD_LSB: u16 = 0x0741;
|
||||
pub const RANDOM_NUMBER_0: u16 = 0x0819;
|
||||
pub const RANDOM_NUMBER_1: u16 = 0x081A;
|
||||
pub const RANDOM_NUMBER_2: u16 = 0x081B;
|
||||
pub const RANDOM_NUMBER_3: u16 = 0x081C;
|
||||
pub const RX_GAIN: u16 = 0x08AC;
|
||||
pub const OCP_CONFIGURATION: u16 = 0x08E7;
|
||||
pub const XTA_TRIM: u16 = 0x0911;
|
||||
pub const XTB_TRIM: u16 = 0x0912;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub mod irq {
|
||||
pub const TX_DONE: u16 = 1 << 0;
|
||||
pub const RX_DONE: u16 = 1 << 1;
|
||||
pub const PREAMBLE_DETECTED: u16 = 1 << 2;
|
||||
pub const SYNC_WORD_VALID: u16 = 1 << 3;
|
||||
pub const HEADER_VALID: u16 = 1 << 4;
|
||||
pub const HEADER_ERR: u16 = 1 << 5;
|
||||
pub const CRC_ERR: u16 = 1 << 6;
|
||||
pub const CAD_DONE: u16 = 1 << 7;
|
||||
pub const CAD_DETECTED: u16 = 1 << 8;
|
||||
pub const TIMEOUT: u16 = 1 << 9;
|
||||
pub const ALL: u16 = 0x03FF;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RadioConfig {
|
||||
|
||||
pub frequency: u32,
|
||||
|
||||
pub spreading_factor: u8,
|
||||
|
||||
pub bandwidth: u8,
|
||||
|
||||
pub coding_rate: u8,
|
||||
|
||||
pub tx_power: i8,
|
||||
|
||||
pub sync_word: u8,
|
||||
|
||||
pub preamble_length: u16,
|
||||
|
||||
pub crc_enabled: bool,
|
||||
|
||||
pub implicit_header: bool,
|
||||
|
||||
pub ldro: bool,
|
||||
}
|
||||
|
||||
impl Default for RadioConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
frequency: 868_100_000,
|
||||
spreading_factor: 9,
|
||||
bandwidth: 0,
|
||||
coding_rate: 1,
|
||||
tx_power: 14,
|
||||
sync_word: 0x12,
|
||||
preamble_length: 8,
|
||||
crc_enabled: true,
|
||||
implicit_header: false,
|
||||
ldro: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RadioState {
|
||||
Sleep,
|
||||
Standby,
|
||||
Tx,
|
||||
Rx,
|
||||
Cad,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RadioError {
|
||||
|
||||
Spi,
|
||||
|
||||
BusyTimeout,
|
||||
|
||||
InvalidConfig,
|
||||
|
||||
TxTimeout,
|
||||
|
||||
RxTimeout,
|
||||
|
||||
CrcError,
|
||||
|
||||
BufferOverflow,
|
||||
}
|
||||
|
||||
pub struct Sx1262<SPI, NSS, RESET, BUSY, DIO1> {
|
||||
spi: SPI,
|
||||
nss: NSS,
|
||||
reset: RESET,
|
||||
busy: BUSY,
|
||||
dio1: DIO1,
|
||||
|
||||
pub config: RadioConfig,
|
||||
state: RadioState,
|
||||
}
|
||||
|
||||
impl<SPI, NSS, RESET, BUSY, DIO1, E> Sx1262<SPI, NSS, RESET, BUSY, DIO1>
|
||||
where
|
||||
SPI: SpiDevice<Error = E>,
|
||||
NSS: OutputPin,
|
||||
RESET: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DIO1: InputPin,
|
||||
{
|
||||
|
||||
pub fn new(spi: SPI, nss: NSS, reset: RESET, busy: BUSY, dio1: DIO1) -> Self {
|
||||
Self {
|
||||
spi,
|
||||
nss,
|
||||
reset,
|
||||
busy,
|
||||
dio1,
|
||||
config: RadioConfig::default(),
|
||||
state: RadioState::Sleep,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> Result<(), RadioError> {
|
||||
|
||||
self.reset()?;
|
||||
|
||||
self.wait_busy_extended()?;
|
||||
|
||||
self.write_command(&[
|
||||
opcode::SET_DIO3_AS_TCXO_CTRL,
|
||||
0x02,
|
||||
0x00,
|
||||
0x01,
|
||||
0x40,
|
||||
])?;
|
||||
|
||||
self.delay_ms(10);
|
||||
self.wait_busy_extended()?;
|
||||
|
||||
self.write_command(&[opcode::SET_STANDBY, 0x01])?;
|
||||
self.state = RadioState::Standby;
|
||||
self.wait_busy()?;
|
||||
|
||||
self.write_command(&[opcode::SET_REGULATOR_MODE, 0x01])?;
|
||||
self.wait_busy()?;
|
||||
|
||||
self.write_command(&[opcode::CALIBRATE, 0x7F])?;
|
||||
self.wait_busy_extended()?;
|
||||
|
||||
self.write_command(&[
|
||||
opcode::CALIBRATE_IMAGE,
|
||||
0xE1,
|
||||
0xE9,
|
||||
])?;
|
||||
self.wait_busy()?;
|
||||
|
||||
self.write_command(&[opcode::SET_DIO2_AS_RF_SWITCH_CTRL, 0x01])?;
|
||||
|
||||
self.write_command(&[opcode::SET_PACKET_TYPE, 0x01])?;
|
||||
|
||||
self.configure(&self.config.clone())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> Result<(), RadioError> {
|
||||
|
||||
let _ = self.reset.set_low();
|
||||
|
||||
FreeRtos::delay_ms(1);
|
||||
|
||||
let _ = self.reset.set_high();
|
||||
|
||||
FreeRtos::delay_ms(10);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_busy(&mut self) -> Result<(), RadioError> {
|
||||
|
||||
for _ in 0..100 {
|
||||
|
||||
match self.busy.is_high() {
|
||||
Ok(false) => return Ok(()),
|
||||
Ok(true) => {},
|
||||
Err(_) => {},
|
||||
}
|
||||
|
||||
FreeRtos::delay_ms(1);
|
||||
}
|
||||
Err(RadioError::BusyTimeout)
|
||||
}
|
||||
|
||||
fn wait_busy_extended(&mut self) -> Result<(), RadioError> {
|
||||
|
||||
for _ in 0..500 {
|
||||
match self.busy.is_high() {
|
||||
Ok(false) => return Ok(()),
|
||||
Ok(true) => {},
|
||||
Err(_) => {},
|
||||
}
|
||||
|
||||
FreeRtos::delay_ms(1);
|
||||
}
|
||||
Err(RadioError::BusyTimeout)
|
||||
}
|
||||
|
||||
fn delay_ms(&self, ms: u32) {
|
||||
FreeRtos::delay_ms(ms);
|
||||
}
|
||||
|
||||
fn write_command(&mut self, data: &[u8]) -> Result<(), RadioError> {
|
||||
self.wait_busy()?;
|
||||
let _ = self.nss.set_low();
|
||||
let result = self.spi.write(data);
|
||||
let _ = self.nss.set_high();
|
||||
result.map_err(|_| RadioError::Spi)
|
||||
}
|
||||
|
||||
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), RadioError> {
|
||||
self.wait_busy()?;
|
||||
let _ = self.nss.set_low();
|
||||
let result = self.spi.transfer(rx, tx);
|
||||
let _ = self.nss.set_high();
|
||||
result.map_err(|_| RadioError::Spi)
|
||||
}
|
||||
|
||||
pub fn set_standby(&mut self) -> Result<(), RadioError> {
|
||||
self.write_command(&[opcode::SET_STANDBY, 0x01])?;
|
||||
self.state = RadioState::Standby;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn configure(&mut self, config: &RadioConfig) -> Result<(), RadioError> {
|
||||
|
||||
if config.spreading_factor < 7 || config.spreading_factor > 12 {
|
||||
return Err(RadioError::InvalidConfig);
|
||||
}
|
||||
if config.bandwidth > 2 {
|
||||
return Err(RadioError::InvalidConfig);
|
||||
}
|
||||
|
||||
let freq_reg = ((config.frequency as u64 * (1 << 25)) / 32_000_000) as u32;
|
||||
self.write_command(&[
|
||||
opcode::SET_RF_FREQUENCY,
|
||||
(freq_reg >> 24) as u8,
|
||||
(freq_reg >> 16) as u8,
|
||||
(freq_reg >> 8) as u8,
|
||||
freq_reg as u8,
|
||||
])?;
|
||||
|
||||
self.write_command(&[
|
||||
opcode::SET_PA_CONFIG,
|
||||
0x04,
|
||||
0x07,
|
||||
0x00,
|
||||
0x01,
|
||||
])?;
|
||||
|
||||
let power = config.tx_power.max(-9).min(22) as u8;
|
||||
self.write_command(&[
|
||||
opcode::SET_TX_PARAMS,
|
||||
power.wrapping_add(9),
|
||||
0x04,
|
||||
])?;
|
||||
|
||||
let bw_hz: u32 = match config.bandwidth {
|
||||
0 => 125_000,
|
||||
1 => 250_000,
|
||||
2 => 500_000,
|
||||
_ => 125_000,
|
||||
};
|
||||
let symbol_time_us = ((1u32 << config.spreading_factor) * 1_000_000) / bw_hz;
|
||||
let ldro_required = symbol_time_us > 16380;
|
||||
let ldro = if config.ldro || ldro_required { 0x01 } else { 0x00 };
|
||||
self.write_command(&[
|
||||
opcode::SET_MODULATION_PARAMS,
|
||||
config.spreading_factor,
|
||||
config.bandwidth,
|
||||
config.coding_rate,
|
||||
ldro,
|
||||
])?;
|
||||
|
||||
let header_type = if config.implicit_header { 0x01 } else { 0x00 };
|
||||
let crc_type = if config.crc_enabled { 0x01 } else { 0x00 };
|
||||
self.write_command(&[
|
||||
opcode::SET_PACKET_PARAMS,
|
||||
(config.preamble_length >> 8) as u8,
|
||||
config.preamble_length as u8,
|
||||
header_type,
|
||||
255,
|
||||
crc_type,
|
||||
0x00,
|
||||
])?;
|
||||
|
||||
let sync_msb = (config.sync_word >> 4) | 0x40;
|
||||
let sync_lsb = (config.sync_word << 4) | 0x04;
|
||||
self.write_register(register::LORA_SYNC_WORD_MSB, sync_msb)?;
|
||||
self.write_register(register::LORA_SYNC_WORD_LSB, sync_lsb)?;
|
||||
|
||||
self.write_command(&[opcode::SET_BUFFER_BASE_ADDRESS, 0x00, 0x00])?;
|
||||
|
||||
self.write_command(&[
|
||||
opcode::SET_DIO_IRQ_PARAMS,
|
||||
(irq::ALL >> 8) as u8,
|
||||
irq::ALL as u8,
|
||||
(irq::ALL >> 8) as u8,
|
||||
irq::ALL as u8,
|
||||
0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
])?;
|
||||
|
||||
self.config = config.clone();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_register(&mut self, addr: u16, value: u8) -> Result<(), RadioError> {
|
||||
self.write_command(&[
|
||||
opcode::WRITE_REGISTER,
|
||||
(addr >> 8) as u8,
|
||||
addr as u8,
|
||||
value,
|
||||
])
|
||||
}
|
||||
|
||||
pub fn transmit(&mut self, data: &[u8]) -> Result<(), RadioError> {
|
||||
if data.len() > 255 {
|
||||
return Err(RadioError::BufferOverflow);
|
||||
}
|
||||
|
||||
self.set_standby()?;
|
||||
|
||||
let mut cmd = heapless::Vec::<u8, 258>::new();
|
||||
let _ = cmd.push(opcode::WRITE_BUFFER);
|
||||
let _ = cmd.push(0x00);
|
||||
for &b in data {
|
||||
let _ = cmd.push(b);
|
||||
}
|
||||
self.write_command(&cmd)?;
|
||||
|
||||
let header_type = if self.config.implicit_header { 0x01 } else { 0x00 };
|
||||
let crc_type = if self.config.crc_enabled { 0x01 } else { 0x00 };
|
||||
self.write_command(&[
|
||||
opcode::SET_PACKET_PARAMS,
|
||||
(self.config.preamble_length >> 8) as u8,
|
||||
self.config.preamble_length as u8,
|
||||
header_type,
|
||||
data.len() as u8,
|
||||
crc_type,
|
||||
0x00,
|
||||
])?;
|
||||
|
||||
self.write_command(&[opcode::CLEAR_IRQ_STATUS, 0x03, 0xFF])?;
|
||||
|
||||
self.write_command(&[opcode::SET_TX, 0x00, 0x00, 0x00])?;
|
||||
self.state = RadioState::Tx;
|
||||
|
||||
self.wait_tx_done()?;
|
||||
|
||||
self.state = RadioState::Standby;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_tx_done(&mut self) -> Result<(), RadioError> {
|
||||
|
||||
for _ in 0..10000 {
|
||||
|
||||
if self.dio1.is_high().unwrap_or(false) {
|
||||
let irq = self.get_irq_status()?;
|
||||
if irq & irq::TX_DONE != 0 {
|
||||
self.write_command(&[opcode::CLEAR_IRQ_STATUS, 0x03, 0xFF])?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
FreeRtos::delay_ms(1);
|
||||
}
|
||||
self.set_standby()?;
|
||||
Err(RadioError::TxTimeout)
|
||||
}
|
||||
|
||||
pub fn get_irq_status(&mut self) -> Result<u16, RadioError> {
|
||||
let mut rx = [0u8; 4];
|
||||
self.transfer(&[opcode::GET_IRQ_STATUS, 0, 0, 0], &mut rx)?;
|
||||
Ok(((rx[2] as u16) << 8) | (rx[3] as u16))
|
||||
}
|
||||
|
||||
pub fn clear_irq(&mut self, flags: u16) -> Result<(), RadioError> {
|
||||
self.write_command(&[
|
||||
opcode::CLEAR_IRQ_STATUS,
|
||||
(flags >> 8) as u8,
|
||||
flags as u8,
|
||||
])
|
||||
}
|
||||
|
||||
pub fn start_rx(&mut self, timeout_ms: u32) -> Result<(), RadioError> {
|
||||
self.set_standby()?;
|
||||
|
||||
self.write_command(&[opcode::CLEAR_IRQ_STATUS, 0x03, 0xFF])?;
|
||||
|
||||
let timeout_ticks = if timeout_ms == 0 {
|
||||
0xFFFFFF
|
||||
} else {
|
||||
((timeout_ms as u64 * 64) / 1000).min(0xFFFFFF) as u32
|
||||
};
|
||||
|
||||
self.write_command(&[
|
||||
opcode::SET_RX,
|
||||
(timeout_ticks >> 16) as u8,
|
||||
(timeout_ticks >> 8) as u8,
|
||||
timeout_ticks as u8,
|
||||
])?;
|
||||
|
||||
self.state = RadioState::Rx;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_rx(&mut self) -> Result<Option<(heapless::Vec<u8, 256>, i16, i8)>, RadioError> {
|
||||
|
||||
if !self.dio1.is_high().unwrap_or(false) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let irq = self.get_irq_status()?;
|
||||
|
||||
if irq & irq::CRC_ERR != 0 {
|
||||
self.write_command(&[opcode::CLEAR_IRQ_STATUS, 0x03, 0xFF])?;
|
||||
return Err(RadioError::CrcError);
|
||||
}
|
||||
|
||||
if irq & irq::TIMEOUT != 0 {
|
||||
self.write_command(&[opcode::CLEAR_IRQ_STATUS, 0x03, 0xFF])?;
|
||||
return Err(RadioError::RxTimeout);
|
||||
}
|
||||
|
||||
if irq & irq::RX_DONE != 0 {
|
||||
|
||||
let mut buf_status = [0u8; 4];
|
||||
self.transfer(&[opcode::GET_RX_BUFFER_STATUS, 0, 0, 0], &mut buf_status)?;
|
||||
let payload_len = buf_status[2];
|
||||
let start_offset = buf_status[3];
|
||||
|
||||
let mut pkt_status = [0u8; 5];
|
||||
self.transfer(&[opcode::GET_PACKET_STATUS, 0, 0, 0, 0], &mut pkt_status)?;
|
||||
let rssi = -(pkt_status[2] as i16 / 2);
|
||||
let snr = pkt_status[3] as i8 / 4;
|
||||
|
||||
let mut data = heapless::Vec::<u8, 256>::new();
|
||||
let mut read_cmd = [0u8; 258];
|
||||
read_cmd[0] = opcode::READ_BUFFER;
|
||||
read_cmd[1] = start_offset;
|
||||
read_cmd[2] = 0;
|
||||
|
||||
let len = payload_len as usize + 3;
|
||||
let mut rx_buf = [0u8; 258];
|
||||
self.transfer(&read_cmd[..len], &mut rx_buf[..len])?;
|
||||
|
||||
for i in 0..payload_len as usize {
|
||||
let _ = data.push(rx_buf[3 + i]);
|
||||
}
|
||||
|
||||
self.write_command(&[opcode::CLEAR_IRQ_STATUS, 0x03, 0xFF])?;
|
||||
|
||||
return Ok(Some((data, rssi, snr)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn cad(&mut self) -> Result<bool, RadioError> {
|
||||
self.set_standby()?;
|
||||
|
||||
self.write_command(&[
|
||||
opcode::SET_CAD_PARAMS,
|
||||
0x04,
|
||||
24,
|
||||
10,
|
||||
0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
])?;
|
||||
|
||||
self.write_command(&[opcode::CLEAR_IRQ_STATUS, 0x03, 0xFF])?;
|
||||
|
||||
self.write_command(&[opcode::SET_CAD])?;
|
||||
self.state = RadioState::Cad;
|
||||
|
||||
for _ in 0..1000 {
|
||||
if self.dio1.is_high().unwrap_or(false) {
|
||||
let irq = self.get_irq_status()?;
|
||||
if irq & irq::CAD_DONE != 0 {
|
||||
let detected = irq & irq::CAD_DETECTED != 0;
|
||||
self.write_command(&[opcode::CLEAR_IRQ_STATUS, 0x03, 0xFF])?;
|
||||
self.state = RadioState::Standby;
|
||||
return Ok(detected);
|
||||
}
|
||||
}
|
||||
|
||||
FreeRtos::delay_ms(1);
|
||||
}
|
||||
self.set_standby()?;
|
||||
Err(RadioError::BusyTimeout)
|
||||
}
|
||||
|
||||
pub fn state(&self) -> RadioState {
|
||||
self.state
|
||||
}
|
||||
|
||||
pub fn get_rssi(&mut self) -> Result<i16, RadioError> {
|
||||
let mut rx = [0u8; 3];
|
||||
self.transfer(&[opcode::GET_RSSI_INST, 0, 0], &mut rx)?;
|
||||
Ok(-(rx[2] as i16 / 2))
|
||||
}
|
||||
|
||||
pub fn random(&mut self) -> Result<u32, RadioError> {
|
||||
|
||||
self.start_rx(0)?;
|
||||
|
||||
FreeRtos::delay_ms(10);
|
||||
self.set_standby()?;
|
||||
|
||||
let mut random = 0u32;
|
||||
let mut rx = [0u8; 3];
|
||||
|
||||
for (i, addr) in [
|
||||
register::RANDOM_NUMBER_0,
|
||||
register::RANDOM_NUMBER_1,
|
||||
register::RANDOM_NUMBER_2,
|
||||
register::RANDOM_NUMBER_3,
|
||||
].iter().enumerate() {
|
||||
self.transfer(&[
|
||||
opcode::READ_REGISTER,
|
||||
(*addr >> 8) as u8,
|
||||
*addr as u8,
|
||||
], &mut rx)?;
|
||||
random |= (rx[2] as u32) << (i * 8);
|
||||
}
|
||||
|
||||
Ok(random)
|
||||
}
|
||||
}
|
||||
450
src/transport.rs
Normal file
450
src/transport.rs
Normal file
@@ -0,0 +1,450 @@
|
||||
use heapless::Vec as HeaplessVec;
|
||||
|
||||
pub const MAX_PACKET_SIZE: usize = 237;
|
||||
|
||||
pub const NODE_HINT_SIZE: usize = 2;
|
||||
|
||||
pub const SESSION_HINT_SIZE: usize = 4;
|
||||
|
||||
pub const AUTH_TAG_SIZE: usize = 16;
|
||||
|
||||
pub const FLAGS_SIZE: usize = 1;
|
||||
|
||||
pub const DATA_OVERHEAD: usize = FLAGS_SIZE + NODE_HINT_SIZE + SESSION_HINT_SIZE + AUTH_TAG_SIZE;
|
||||
|
||||
pub const DATA_MAX_PAYLOAD: usize = MAX_PACKET_SIZE - DATA_OVERHEAD;
|
||||
|
||||
pub const PADDED_MESSAGE_SIZE: usize = 200;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UniversalAddress {
|
||||
|
||||
pub did: HeaplessVec<u8, 128>,
|
||||
|
||||
pub public_key: [u8; 32],
|
||||
|
||||
pub meshcore_addr: u16,
|
||||
|
||||
pub meshtastic_id: u32,
|
||||
|
||||
pub reticulum_hash: [u8; 16],
|
||||
}
|
||||
|
||||
pub struct AddressTranslator;
|
||||
|
||||
impl AddressTranslator {
|
||||
|
||||
pub fn from_public_key(public_key: &[u8; 32]) -> UniversalAddress {
|
||||
use crate::crypto::sha256::Sha256;
|
||||
|
||||
let pubkey_hash = Sha256::hash(public_key);
|
||||
|
||||
let meshcore_addr = ((pubkey_hash[0] as u16) << 8) | (pubkey_hash[1] as u16);
|
||||
|
||||
let meshtastic_id = ((pubkey_hash[0] as u32) << 24)
|
||||
| ((pubkey_hash[1] as u32) << 16)
|
||||
| ((pubkey_hash[2] as u32) << 8)
|
||||
| (pubkey_hash[3] as u32);
|
||||
|
||||
let app_hash = Sha256::hash(b"yours.messaging");
|
||||
let mut combined = [0u8; 64];
|
||||
combined[..32].copy_from_slice(&app_hash);
|
||||
combined[32..].copy_from_slice(public_key);
|
||||
let reticulum_full = Sha256::hash(&combined);
|
||||
let mut reticulum_hash = [0u8; 16];
|
||||
reticulum_hash.copy_from_slice(&reticulum_full[..16]);
|
||||
|
||||
let mut did = HeaplessVec::new();
|
||||
let _ = did.extend_from_slice(b"did:offgrid:z");
|
||||
|
||||
UniversalAddress {
|
||||
did,
|
||||
public_key: *public_key,
|
||||
meshcore_addr,
|
||||
meshtastic_id,
|
||||
reticulum_hash,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derive_meshcore_address(public_key: &[u8; 32]) -> u16 {
|
||||
use crate::crypto::sha256::Sha256;
|
||||
let hash = Sha256::hash(public_key);
|
||||
((hash[0] as u16) << 8) | (hash[1] as u16)
|
||||
}
|
||||
|
||||
pub fn derive_meshtastic_id(public_key: &[u8; 32]) -> u32 {
|
||||
use crate::crypto::sha256::Sha256;
|
||||
let hash = Sha256::hash(public_key);
|
||||
((hash[0] as u32) << 24)
|
||||
| ((hash[1] as u32) << 16)
|
||||
| ((hash[2] as u32) << 8)
|
||||
| (hash[3] as u32)
|
||||
}
|
||||
|
||||
pub fn derive_reticulum_hash(public_key: &[u8; 32], app_name: &[u8]) -> [u8; 16] {
|
||||
use crate::crypto::sha256::Sha256;
|
||||
let app_hash = Sha256::hash(app_name);
|
||||
let mut combined = [0u8; 64];
|
||||
combined[..32].copy_from_slice(&app_hash);
|
||||
combined[32..].copy_from_slice(public_key);
|
||||
let full_hash = Sha256::hash(&combined);
|
||||
let mut result = [0u8; 16];
|
||||
result.copy_from_slice(&full_hash[..16]);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum PacketType {
|
||||
|
||||
Data = 0b00,
|
||||
|
||||
Handshake = 0b01,
|
||||
|
||||
Control = 0b10,
|
||||
|
||||
Cover = 0b11,
|
||||
}
|
||||
|
||||
impl PacketType {
|
||||
pub fn from_flags(flags: u8) -> Self {
|
||||
match (flags >> 6) & 0b11 {
|
||||
0b00 => PacketType::Data,
|
||||
0b01 => PacketType::Handshake,
|
||||
0b10 => PacketType::Control,
|
||||
0b11 => PacketType::Cover,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WirePacket {
|
||||
|
||||
pub packet_type: PacketType,
|
||||
|
||||
pub hop_count: u8,
|
||||
|
||||
pub next_hop_hint: u16,
|
||||
|
||||
pub session_hint: u32,
|
||||
|
||||
pub payload: HeaplessVec<u8, 214>,
|
||||
}
|
||||
|
||||
impl WirePacket {
|
||||
|
||||
pub fn new_data(next_hop: u16, session: u32, payload: &[u8]) -> Option<Self> {
|
||||
if payload.len() > DATA_MAX_PAYLOAD {
|
||||
return None;
|
||||
}
|
||||
let mut p = HeaplessVec::new();
|
||||
p.extend_from_slice(payload).ok()?;
|
||||
Some(Self {
|
||||
packet_type: PacketType::Data,
|
||||
hop_count: 0,
|
||||
next_hop_hint: next_hop,
|
||||
session_hint: session,
|
||||
payload: p,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> HeaplessVec<u8, 237> {
|
||||
let mut buf = HeaplessVec::new();
|
||||
|
||||
let flags = ((self.packet_type as u8) << 6) | (self.hop_count & 0x0F);
|
||||
let _ = buf.push(flags);
|
||||
|
||||
let _ = buf.push((self.next_hop_hint >> 8) as u8);
|
||||
let _ = buf.push(self.next_hop_hint as u8);
|
||||
|
||||
let _ = buf.push((self.session_hint >> 24) as u8);
|
||||
let _ = buf.push((self.session_hint >> 16) as u8);
|
||||
let _ = buf.push((self.session_hint >> 8) as u8);
|
||||
let _ = buf.push(self.session_hint as u8);
|
||||
|
||||
let _ = buf.extend_from_slice(&self.payload);
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn decode(data: &[u8]) -> Option<Self> {
|
||||
if data.len() < 7 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let flags = data[0];
|
||||
let packet_type = PacketType::from_flags(flags);
|
||||
let hop_count = flags & 0x0F;
|
||||
|
||||
let next_hop_hint = ((data[1] as u16) << 8) | (data[2] as u16);
|
||||
let session_hint = ((data[3] as u32) << 24)
|
||||
| ((data[4] as u32) << 16)
|
||||
| ((data[5] as u32) << 8)
|
||||
| (data[6] as u32);
|
||||
|
||||
let mut payload = HeaplessVec::new();
|
||||
if data.len() > 7 {
|
||||
payload.extend_from_slice(&data[7..]).ok()?;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
packet_type,
|
||||
hop_count,
|
||||
next_hop_hint,
|
||||
session_hint,
|
||||
payload,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn increment_hop(&mut self) -> bool {
|
||||
if self.hop_count < 15 {
|
||||
self.hop_count += 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum MessagePriority {
|
||||
|
||||
Low = 0,
|
||||
|
||||
Normal = 1,
|
||||
|
||||
High = 2,
|
||||
|
||||
Critical = 3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UniversalMessage {
|
||||
|
||||
pub id: [u8; 8],
|
||||
|
||||
pub recipient: UniversalAddress,
|
||||
|
||||
pub payload: HeaplessVec<u8, 237>,
|
||||
|
||||
pub priority: MessagePriority,
|
||||
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ConnectionState {
|
||||
|
||||
Disconnected,
|
||||
|
||||
Connecting,
|
||||
|
||||
Connected,
|
||||
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SignalQuality {
|
||||
|
||||
pub rssi: i16,
|
||||
|
||||
pub snr: i8,
|
||||
|
||||
pub quality: u8,
|
||||
}
|
||||
|
||||
impl SignalQuality {
|
||||
pub fn new(rssi: i16, snr: i8) -> Self {
|
||||
|
||||
let rssi_norm = ((rssi.max(-120).min(-50) + 120) as u16 * 100 / 70) as u8;
|
||||
|
||||
let snr_norm = ((snr.max(-20).min(10) + 20) as u16 * 100 / 30) as u8;
|
||||
|
||||
let quality = (rssi_norm + snr_norm) / 2;
|
||||
|
||||
Self { rssi, snr, quality }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MeshDeviceInfo {
|
||||
|
||||
pub name: HeaplessVec<u8, 32>,
|
||||
|
||||
pub firmware_version: HeaplessVec<u8, 16>,
|
||||
|
||||
pub hardware_model: HeaplessVec<u8, 32>,
|
||||
|
||||
pub protocols: ProtocolSupport,
|
||||
|
||||
pub battery_level: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct ProtocolSupport {
|
||||
pub meshcore: bool,
|
||||
pub meshtastic: bool,
|
||||
pub reticulum: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TransportError {
|
||||
|
||||
NotConnected,
|
||||
|
||||
ConnectionFailed,
|
||||
|
||||
SendFailed,
|
||||
|
||||
ReceiveTimeout,
|
||||
|
||||
InvalidMessage,
|
||||
|
||||
BufferOverflow,
|
||||
|
||||
DeviceBusy,
|
||||
|
||||
ProtocolError,
|
||||
|
||||
Unknown,
|
||||
}
|
||||
|
||||
pub trait UniversalMeshTransport {
|
||||
|
||||
fn connect(&mut self) -> Result<(), TransportError>;
|
||||
|
||||
fn disconnect(&mut self);
|
||||
|
||||
fn send_message(&mut self, message: &UniversalMessage) -> Result<[u8; 8], TransportError>;
|
||||
|
||||
fn poll_message(&mut self) -> Option<UniversalMessage>;
|
||||
|
||||
fn get_device_info(&self) -> Result<MeshDeviceInfo, TransportError>;
|
||||
|
||||
fn connection_state(&self) -> ConnectionState;
|
||||
|
||||
fn signal_quality(&self) -> SignalQuality;
|
||||
|
||||
fn discover_peers(&mut self, timeout_ms: u32) -> HeaplessVec<UniversalAddress, 16>;
|
||||
|
||||
fn ping_peer(&mut self, address: &UniversalAddress) -> Result<u32, TransportError>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Protocol {
|
||||
MeshCore,
|
||||
Meshtastic,
|
||||
Reticulum,
|
||||
}
|
||||
|
||||
impl Protocol {
|
||||
|
||||
pub fn magic_bytes(&self) -> [u8; 2] {
|
||||
match self {
|
||||
Protocol::MeshCore => [0xAA, 0x55],
|
||||
Protocol::Meshtastic => [0x94, 0xC3],
|
||||
Protocol::Reticulum => [0xC0, 0x00],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect(data: &[u8]) -> Option<Self> {
|
||||
if data.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
match (data[0], data[1]) {
|
||||
(0xAA, 0x55) => Some(Protocol::MeshCore),
|
||||
(0x94, 0xC3) => Some(Protocol::Meshtastic),
|
||||
(0xC0, _) => Some(Protocol::Reticulum),
|
||||
(b'A', b'T') => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_KNOWN_ADDRESSES: usize = 64;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ProtocolAddress {
|
||||
MeshCore(u16),
|
||||
Meshtastic(u32),
|
||||
Reticulum([u8; 16]),
|
||||
}
|
||||
|
||||
pub struct AddressLookupTable {
|
||||
|
||||
meshcore_index: heapless::FnvIndexMap<u16, [u8; 32], MAX_KNOWN_ADDRESSES>,
|
||||
|
||||
meshtastic_index: heapless::FnvIndexMap<u32, [u8; 32], MAX_KNOWN_ADDRESSES>,
|
||||
|
||||
reticulum_index: heapless::FnvIndexMap<[u8; 8], [u8; 32], MAX_KNOWN_ADDRESSES>,
|
||||
}
|
||||
|
||||
impl AddressLookupTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
meshcore_index: heapless::FnvIndexMap::new(),
|
||||
meshtastic_index: heapless::FnvIndexMap::new(),
|
||||
reticulum_index: heapless::FnvIndexMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(&mut self, public_key: &[u8; 32]) {
|
||||
let addr = AddressTranslator::from_public_key(public_key);
|
||||
|
||||
let _ = self.meshcore_index.insert(addr.meshcore_addr, *public_key);
|
||||
|
||||
let _ = self.meshtastic_index.insert(addr.meshtastic_id, *public_key);
|
||||
|
||||
let mut ret_key = [0u8; 8];
|
||||
ret_key.copy_from_slice(&addr.reticulum_hash[..8]);
|
||||
let _ = self.reticulum_index.insert(ret_key, *public_key);
|
||||
}
|
||||
|
||||
pub fn unregister(&mut self, public_key: &[u8; 32]) {
|
||||
let addr = AddressTranslator::from_public_key(public_key);
|
||||
self.meshcore_index.remove(&addr.meshcore_addr);
|
||||
self.meshtastic_index.remove(&addr.meshtastic_id);
|
||||
let mut ret_key = [0u8; 8];
|
||||
ret_key.copy_from_slice(&addr.reticulum_hash[..8]);
|
||||
self.reticulum_index.remove(&ret_key);
|
||||
}
|
||||
|
||||
pub fn lookup_meshcore(&self, addr: u16) -> Option<&[u8; 32]> {
|
||||
self.meshcore_index.get(&addr)
|
||||
}
|
||||
|
||||
pub fn lookup_meshtastic(&self, id: u32) -> Option<&[u8; 32]> {
|
||||
self.meshtastic_index.get(&id)
|
||||
}
|
||||
|
||||
pub fn lookup_reticulum(&self, hash: &[u8; 16]) -> Option<&[u8; 32]> {
|
||||
let mut key = [0u8; 8];
|
||||
key.copy_from_slice(&hash[..8]);
|
||||
self.reticulum_index.get(&key)
|
||||
}
|
||||
|
||||
pub fn lookup(&self, addr: ProtocolAddress) -> Option<&[u8; 32]> {
|
||||
match addr {
|
||||
ProtocolAddress::MeshCore(a) => self.lookup_meshcore(a),
|
||||
ProtocolAddress::Meshtastic(id) => self.lookup_meshtastic(id),
|
||||
ProtocolAddress::Reticulum(hash) => self.lookup_reticulum(&hash),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.meshcore_index.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.meshcore_index.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AddressLookupTable {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
964
src/wifi.rs
Normal file
964
src/wifi.rs
Normal file
@@ -0,0 +1,964 @@
|
||||
use heapless::{String, Vec};
|
||||
use crate::crypto::chacha20::ChaCha20;
|
||||
use crate::crypto::sha256::Sha256;
|
||||
use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
|
||||
pub const DEFAULT_TCP_PORT: u16 = 4000;
|
||||
|
||||
pub const MAX_TCP_CLIENTS: usize = 4;
|
||||
|
||||
pub const TCP_RX_BUFFER_SIZE: usize = 512;
|
||||
|
||||
pub const TCP_TX_BUFFER_SIZE: usize = 512;
|
||||
|
||||
pub const WIFI_CONNECT_TIMEOUT_SEC: u32 = 30;
|
||||
|
||||
pub const DEFAULT_AP_SSID: &str = "LunarCore";
|
||||
|
||||
pub const DEFAULT_AP_PASSWORD: &str = "lunarpunk";
|
||||
|
||||
pub const MAX_SSID_LEN: usize = 32;
|
||||
|
||||
pub const MAX_PASSWORD_LEN: usize = 64;
|
||||
|
||||
pub const AUTH_CHALLENGE_SIZE: usize = 32;
|
||||
|
||||
pub const AUTH_RESPONSE_SIZE: usize = 32;
|
||||
|
||||
pub const SESSION_KEY_SIZE: usize = 32;
|
||||
|
||||
pub const SESSION_NONCE_SIZE: usize = 12;
|
||||
|
||||
pub const AUTH_TIMEOUT_SEC: u32 = 10;
|
||||
|
||||
pub const MAX_AUTH_FAILURES: u8 = 3;
|
||||
|
||||
pub const AUTH_LOCKOUT_SEC: u32 = 300;
|
||||
|
||||
pub const CONN_RATE_LIMIT: u8 = 10;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WifiMode {
|
||||
|
||||
Off,
|
||||
|
||||
Ap,
|
||||
|
||||
Sta,
|
||||
|
||||
ApSta,
|
||||
}
|
||||
|
||||
impl Default for WifiMode {
|
||||
fn default() -> Self {
|
||||
WifiMode::Off
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SecurityConfig {
|
||||
|
||||
pub require_auth: bool,
|
||||
|
||||
pub psk: [u8; 32],
|
||||
|
||||
pub encrypt_traffic: bool,
|
||||
|
||||
pub rate_limiting: bool,
|
||||
|
||||
pub allow_external: bool,
|
||||
}
|
||||
|
||||
impl Drop for SecurityConfig {
|
||||
fn drop(&mut self) {
|
||||
|
||||
crate::crypto::secure_zero(&mut self.psk);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SecurityConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
require_auth: true,
|
||||
psk: [0u8; 32],
|
||||
encrypt_traffic: true,
|
||||
rate_limiting: true,
|
||||
allow_external: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SecurityConfig {
|
||||
|
||||
pub fn with_psk(psk: &[u8; 32]) -> Self {
|
||||
Self {
|
||||
require_auth: true,
|
||||
psk: *psk,
|
||||
encrypt_traffic: true,
|
||||
rate_limiting: true,
|
||||
allow_external: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insecure() -> Self {
|
||||
Self {
|
||||
require_auth: false,
|
||||
psk: [0u8; 32],
|
||||
encrypt_traffic: false,
|
||||
rate_limiting: false,
|
||||
allow_external: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_psk_set(&self) -> bool {
|
||||
self.psk.iter().any(|&b| b != 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WifiConfig {
|
||||
|
||||
pub mode: WifiMode,
|
||||
|
||||
pub ap_ssid: String<MAX_SSID_LEN>,
|
||||
|
||||
pub ap_password: String<MAX_PASSWORD_LEN>,
|
||||
|
||||
pub ap_channel: u8,
|
||||
|
||||
pub sta_ssid: String<MAX_SSID_LEN>,
|
||||
|
||||
pub sta_password: String<MAX_PASSWORD_LEN>,
|
||||
|
||||
pub tcp_port: u16,
|
||||
|
||||
pub mdns_enabled: bool,
|
||||
|
||||
pub mdns_hostname: String<32>,
|
||||
|
||||
pub security: SecurityConfig,
|
||||
}
|
||||
|
||||
impl Default for WifiConfig {
|
||||
fn default() -> Self {
|
||||
let mut ap_ssid = String::new();
|
||||
let _ = ap_ssid.push_str(DEFAULT_AP_SSID);
|
||||
|
||||
let mut ap_password = String::new();
|
||||
let _ = ap_password.push_str(DEFAULT_AP_PASSWORD);
|
||||
|
||||
let mut mdns_hostname = String::new();
|
||||
let _ = mdns_hostname.push_str("lunarcore");
|
||||
|
||||
Self {
|
||||
mode: WifiMode::Off,
|
||||
ap_ssid,
|
||||
ap_password,
|
||||
ap_channel: 1,
|
||||
sta_ssid: String::new(),
|
||||
sta_password: String::new(),
|
||||
tcp_port: DEFAULT_TCP_PORT,
|
||||
mdns_enabled: true,
|
||||
mdns_hostname,
|
||||
security: SecurityConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NetworkStatus {
|
||||
|
||||
pub mode: WifiMode,
|
||||
|
||||
pub sta_connected: bool,
|
||||
|
||||
pub sta_ip: Option<Ipv4Addr>,
|
||||
|
||||
pub ap_active: bool,
|
||||
|
||||
pub ap_ip: Option<Ipv4Addr>,
|
||||
|
||||
pub ap_client_count: u8,
|
||||
|
||||
pub tcp_client_count: u8,
|
||||
|
||||
pub rssi: i8,
|
||||
}
|
||||
|
||||
impl Default for NetworkStatus {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: WifiMode::Off,
|
||||
sta_connected: false,
|
||||
sta_ip: None,
|
||||
ap_active: false,
|
||||
ap_ip: None,
|
||||
ap_client_count: 0,
|
||||
tcp_client_count: 0,
|
||||
rssi: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hmac_sha256(key: &[u8; 32], message: &[u8]) -> [u8; 32] {
|
||||
|
||||
const IPAD: u8 = 0x36;
|
||||
const OPAD: u8 = 0x5c;
|
||||
const BLOCK_SIZE: usize = 64;
|
||||
|
||||
let mut k_pad = [0u8; BLOCK_SIZE];
|
||||
k_pad[..32].copy_from_slice(key);
|
||||
|
||||
let mut inner_hasher = Sha256::new();
|
||||
let mut inner_key = [0u8; BLOCK_SIZE];
|
||||
for i in 0..BLOCK_SIZE {
|
||||
inner_key[i] = k_pad[i] ^ IPAD;
|
||||
}
|
||||
inner_hasher.update(&inner_key);
|
||||
inner_hasher.update(message);
|
||||
let inner_hash = inner_hasher.finalize();
|
||||
|
||||
let mut outer_hasher = Sha256::new();
|
||||
let mut outer_key = [0u8; BLOCK_SIZE];
|
||||
for i in 0..BLOCK_SIZE {
|
||||
outer_key[i] = k_pad[i] ^ OPAD;
|
||||
}
|
||||
outer_hasher.update(&outer_key);
|
||||
outer_hasher.update(&inner_hash);
|
||||
outer_hasher.finalize()
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn ct_eq_32(a: &[u8; 32], b: &[u8; 32]) -> bool {
|
||||
let mut diff: u8 = 0;
|
||||
for i in 0..32 {
|
||||
diff |= a[i] ^ b[i];
|
||||
}
|
||||
diff == 0
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AuthState {
|
||||
|
||||
None,
|
||||
|
||||
ChallengeSent,
|
||||
|
||||
Authenticated,
|
||||
|
||||
Failed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TcpClientState {
|
||||
|
||||
Disconnected,
|
||||
|
||||
Authenticating,
|
||||
|
||||
Detecting,
|
||||
|
||||
Connected,
|
||||
}
|
||||
|
||||
pub struct SessionCrypto {
|
||||
|
||||
pub key: [u8; SESSION_KEY_SIZE],
|
||||
|
||||
pub nonce: [u8; SESSION_NONCE_SIZE],
|
||||
|
||||
pub counter: u32,
|
||||
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl Drop for SessionCrypto {
|
||||
fn drop(&mut self) {
|
||||
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionCrypto {
|
||||
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
key: [0u8; SESSION_KEY_SIZE],
|
||||
nonce: [0u8; SESSION_NONCE_SIZE],
|
||||
counter: 0,
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self, key: &[u8; SESSION_KEY_SIZE], nonce: &[u8; SESSION_NONCE_SIZE]) {
|
||||
self.key.copy_from_slice(key);
|
||||
self.nonce.copy_from_slice(nonce);
|
||||
self.counter = 0;
|
||||
self.enabled = true;
|
||||
}
|
||||
|
||||
pub fn encrypt(&mut self, data: &mut [u8]) {
|
||||
if !self.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let counter_bytes = self.counter.to_le_bytes();
|
||||
self.nonce[0..4].copy_from_slice(&counter_bytes);
|
||||
|
||||
let cipher = ChaCha20::new(&self.key, &self.nonce);
|
||||
cipher.encrypt(data);
|
||||
|
||||
self.counter = self.counter.wrapping_add(1);
|
||||
}
|
||||
|
||||
pub fn decrypt(&mut self, data: &mut [u8]) {
|
||||
|
||||
self.encrypt(data);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
|
||||
for b in &mut self.key {
|
||||
unsafe { core::ptr::write_volatile(b, 0) };
|
||||
}
|
||||
for b in &mut self.nonce {
|
||||
unsafe { core::ptr::write_volatile(b, 0) };
|
||||
}
|
||||
self.counter = 0;
|
||||
self.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TcpClient {
|
||||
|
||||
pub fd: i32,
|
||||
|
||||
pub addr: SocketAddrV4,
|
||||
|
||||
pub state: TcpClientState,
|
||||
|
||||
pub auth_state: AuthState,
|
||||
|
||||
pub auth_challenge: [u8; AUTH_CHALLENGE_SIZE],
|
||||
|
||||
pub auth_failures: u8,
|
||||
|
||||
pub auth_started: u32,
|
||||
|
||||
pub session: SessionCrypto,
|
||||
|
||||
pub protocol: u8,
|
||||
|
||||
pub rx_buffer: Vec<u8, TCP_RX_BUFFER_SIZE>,
|
||||
|
||||
pub tx_buffer: Vec<u8, TCP_TX_BUFFER_SIZE>,
|
||||
|
||||
pub last_activity: u32,
|
||||
}
|
||||
|
||||
impl TcpClient {
|
||||
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
fd: -1,
|
||||
addr: SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 0),
|
||||
state: TcpClientState::Disconnected,
|
||||
auth_state: AuthState::None,
|
||||
auth_challenge: [0u8; AUTH_CHALLENGE_SIZE],
|
||||
auth_failures: 0,
|
||||
auth_started: 0,
|
||||
session: SessionCrypto::new(),
|
||||
protocol: 0,
|
||||
rx_buffer: Vec::new(),
|
||||
tx_buffer: Vec::new(),
|
||||
last_activity: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_available(&self) -> bool {
|
||||
self.state == TcpClientState::Disconnected
|
||||
}
|
||||
|
||||
pub fn is_authenticated(&self) -> bool {
|
||||
self.auth_state == AuthState::Authenticated || self.auth_state == AuthState::None
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.fd = -1;
|
||||
self.state = TcpClientState::Disconnected;
|
||||
self.auth_state = AuthState::None;
|
||||
self.auth_challenge.fill(0);
|
||||
self.auth_failures = 0;
|
||||
self.auth_started = 0;
|
||||
self.session.clear();
|
||||
self.protocol = 0;
|
||||
self.rx_buffer.clear();
|
||||
self.tx_buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WifiManager {
|
||||
|
||||
pub config: WifiConfig,
|
||||
|
||||
pub status: NetworkStatus,
|
||||
|
||||
server_fd: i32,
|
||||
|
||||
clients: [TcpClient; MAX_TCP_CLIENTS],
|
||||
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WifiError {
|
||||
|
||||
NotInitialized,
|
||||
|
||||
AlreadyInitialized,
|
||||
|
||||
ConfigError,
|
||||
|
||||
ConnectionFailed,
|
||||
|
||||
Timeout,
|
||||
|
||||
SocketError,
|
||||
|
||||
NoClientSlots,
|
||||
|
||||
BufferFull,
|
||||
|
||||
AuthenticationFailed,
|
||||
|
||||
NoPskConfigured,
|
||||
|
||||
NotAuthenticated,
|
||||
|
||||
RateLimited,
|
||||
}
|
||||
|
||||
impl WifiManager {
|
||||
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
config: WifiConfig {
|
||||
mode: WifiMode::Off,
|
||||
ap_ssid: String::new(),
|
||||
ap_password: String::new(),
|
||||
ap_channel: 1,
|
||||
sta_ssid: String::new(),
|
||||
sta_password: String::new(),
|
||||
tcp_port: DEFAULT_TCP_PORT,
|
||||
mdns_enabled: true,
|
||||
mdns_hostname: String::new(),
|
||||
security: SecurityConfig {
|
||||
require_auth: true,
|
||||
psk: [0u8; 32],
|
||||
encrypt_traffic: true,
|
||||
rate_limiting: true,
|
||||
allow_external: false,
|
||||
},
|
||||
},
|
||||
status: NetworkStatus {
|
||||
mode: WifiMode::Off,
|
||||
sta_connected: false,
|
||||
sta_ip: None,
|
||||
ap_active: false,
|
||||
ap_ip: None,
|
||||
ap_client_count: 0,
|
||||
tcp_client_count: 0,
|
||||
rssi: 0,
|
||||
},
|
||||
server_fd: -1,
|
||||
clients: [
|
||||
TcpClient::empty(),
|
||||
TcpClient::empty(),
|
||||
TcpClient::empty(),
|
||||
TcpClient::empty(),
|
||||
],
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_psk(&mut self, psk: &[u8; 32]) {
|
||||
self.config.security.psk.copy_from_slice(psk);
|
||||
}
|
||||
|
||||
pub fn is_auth_configured(&self) -> bool {
|
||||
!self.config.security.require_auth || self.config.security.is_psk_set()
|
||||
}
|
||||
|
||||
pub fn init(&mut self, config: WifiConfig) -> Result<(), WifiError> {
|
||||
if self.initialized {
|
||||
return Err(WifiError::AlreadyInitialized);
|
||||
}
|
||||
|
||||
self.config = config;
|
||||
|
||||
unsafe {
|
||||
|
||||
let ret = esp_idf_sys::nvs_flash_init();
|
||||
if ret != 0 && ret != esp_idf_sys::ESP_ERR_NVS_NO_FREE_PAGES as i32 {
|
||||
|
||||
esp_idf_sys::nvs_flash_erase();
|
||||
esp_idf_sys::nvs_flash_init();
|
||||
}
|
||||
|
||||
esp_idf_sys::esp_netif_init();
|
||||
|
||||
esp_idf_sys::esp_event_loop_create_default();
|
||||
|
||||
match self.config.mode {
|
||||
WifiMode::Off => {}
|
||||
WifiMode::Ap => {
|
||||
esp_idf_sys::esp_netif_create_default_wifi_ap();
|
||||
}
|
||||
WifiMode::Sta => {
|
||||
esp_idf_sys::esp_netif_create_default_wifi_sta();
|
||||
}
|
||||
WifiMode::ApSta => {
|
||||
esp_idf_sys::esp_netif_create_default_wifi_ap();
|
||||
esp_idf_sys::esp_netif_create_default_wifi_sta();
|
||||
}
|
||||
}
|
||||
|
||||
let mut wifi_init_config = esp_idf_sys::wifi_init_config_t::default();
|
||||
esp_idf_sys::esp_wifi_init(&wifi_init_config);
|
||||
|
||||
esp_idf_sys::esp_wifi_set_storage(esp_idf_sys::wifi_storage_t_WIFI_STORAGE_RAM);
|
||||
}
|
||||
|
||||
self.initialized = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start(&mut self) -> Result<(), WifiError> {
|
||||
if !self.initialized {
|
||||
return Err(WifiError::NotInitialized);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
match self.config.mode {
|
||||
WifiMode::Off => {
|
||||
esp_idf_sys::esp_wifi_stop();
|
||||
}
|
||||
WifiMode::Ap => {
|
||||
self.configure_ap()?;
|
||||
esp_idf_sys::esp_wifi_set_mode(esp_idf_sys::wifi_mode_t_WIFI_MODE_AP);
|
||||
esp_idf_sys::esp_wifi_start();
|
||||
self.status.ap_active = true;
|
||||
self.status.ap_ip = Some(Ipv4Addr::new(192, 168, 4, 1));
|
||||
}
|
||||
WifiMode::Sta => {
|
||||
self.configure_sta()?;
|
||||
esp_idf_sys::esp_wifi_set_mode(esp_idf_sys::wifi_mode_t_WIFI_MODE_STA);
|
||||
esp_idf_sys::esp_wifi_start();
|
||||
esp_idf_sys::esp_wifi_connect();
|
||||
}
|
||||
WifiMode::ApSta => {
|
||||
self.configure_ap()?;
|
||||
self.configure_sta()?;
|
||||
esp_idf_sys::esp_wifi_set_mode(esp_idf_sys::wifi_mode_t_WIFI_MODE_APSTA);
|
||||
esp_idf_sys::esp_wifi_start();
|
||||
esp_idf_sys::esp_wifi_connect();
|
||||
self.status.ap_active = true;
|
||||
self.status.ap_ip = Some(Ipv4Addr::new(192, 168, 4, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.status.mode = self.config.mode;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_ap(&self) -> Result<(), WifiError> {
|
||||
unsafe {
|
||||
let mut ap_config: esp_idf_sys::wifi_config_t = core::mem::zeroed();
|
||||
|
||||
let ssid_bytes = self.config.ap_ssid.as_bytes();
|
||||
let ssid_len = ssid_bytes.len().min(32);
|
||||
ap_config.ap.ssid[..ssid_len].copy_from_slice(&ssid_bytes[..ssid_len]);
|
||||
ap_config.ap.ssid_len = ssid_len as u8;
|
||||
|
||||
let pass_bytes = self.config.ap_password.as_bytes();
|
||||
let pass_len = pass_bytes.len().min(64);
|
||||
ap_config.ap.password[..pass_len].copy_from_slice(&pass_bytes[..pass_len]);
|
||||
|
||||
ap_config.ap.channel = self.config.ap_channel;
|
||||
ap_config.ap.max_connection = MAX_TCP_CLIENTS as u8;
|
||||
|
||||
if pass_len > 0 {
|
||||
ap_config.ap.authmode = esp_idf_sys::wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK;
|
||||
} else {
|
||||
ap_config.ap.authmode = esp_idf_sys::wifi_auth_mode_t_WIFI_AUTH_OPEN;
|
||||
}
|
||||
|
||||
let ret = esp_idf_sys::esp_wifi_set_config(
|
||||
esp_idf_sys::wifi_interface_t_WIFI_IF_AP,
|
||||
&mut ap_config,
|
||||
);
|
||||
|
||||
if ret != 0 {
|
||||
return Err(WifiError::ConfigError);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_sta(&self) -> Result<(), WifiError> {
|
||||
unsafe {
|
||||
let mut sta_config: esp_idf_sys::wifi_config_t = core::mem::zeroed();
|
||||
|
||||
let ssid_bytes = self.config.sta_ssid.as_bytes();
|
||||
let ssid_len = ssid_bytes.len().min(32);
|
||||
sta_config.sta.ssid[..ssid_len].copy_from_slice(&ssid_bytes[..ssid_len]);
|
||||
|
||||
let pass_bytes = self.config.sta_password.as_bytes();
|
||||
let pass_len = pass_bytes.len().min(64);
|
||||
sta_config.sta.password[..pass_len].copy_from_slice(&pass_bytes[..pass_len]);
|
||||
|
||||
let ret = esp_idf_sys::esp_wifi_set_config(
|
||||
esp_idf_sys::wifi_interface_t_WIFI_IF_STA,
|
||||
&mut sta_config,
|
||||
);
|
||||
|
||||
if ret != 0 {
|
||||
return Err(WifiError::ConfigError);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_tcp_server(&mut self) -> Result<(), WifiError> {
|
||||
if !self.initialized {
|
||||
return Err(WifiError::NotInitialized);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
|
||||
let fd = esp_idf_sys::lwip_socket(
|
||||
esp_idf_sys::AF_INET as i32,
|
||||
esp_idf_sys::SOCK_STREAM as i32,
|
||||
esp_idf_sys::IPPROTO_TCP as i32,
|
||||
);
|
||||
|
||||
if fd < 0 {
|
||||
return Err(WifiError::SocketError);
|
||||
}
|
||||
|
||||
let opt: i32 = 1;
|
||||
esp_idf_sys::lwip_setsockopt(
|
||||
fd,
|
||||
esp_idf_sys::SOL_SOCKET as i32,
|
||||
esp_idf_sys::SO_REUSEADDR as i32,
|
||||
&opt as *const _ as *const core::ffi::c_void,
|
||||
core::mem::size_of::<i32>() as u32,
|
||||
);
|
||||
|
||||
let mut addr: esp_idf_sys::sockaddr_in = core::mem::zeroed();
|
||||
addr.sin_family = esp_idf_sys::AF_INET as u8;
|
||||
addr.sin_port = self.config.tcp_port.to_be();
|
||||
addr.sin_addr.s_addr = 0;
|
||||
|
||||
let ret = esp_idf_sys::lwip_bind(
|
||||
fd,
|
||||
&addr as *const _ as *const esp_idf_sys::sockaddr,
|
||||
core::mem::size_of::<esp_idf_sys::sockaddr_in>() as u32,
|
||||
);
|
||||
|
||||
if ret < 0 {
|
||||
esp_idf_sys::lwip_close(fd);
|
||||
return Err(WifiError::SocketError);
|
||||
}
|
||||
|
||||
let ret = esp_idf_sys::lwip_listen(fd, MAX_TCP_CLIENTS as i32);
|
||||
if ret < 0 {
|
||||
esp_idf_sys::lwip_close(fd);
|
||||
return Err(WifiError::SocketError);
|
||||
}
|
||||
|
||||
let flags = esp_idf_sys::lwip_fcntl(fd, esp_idf_sys::F_GETFL as i32, 0);
|
||||
esp_idf_sys::lwip_fcntl(fd, esp_idf_sys::F_SETFL as i32, flags | esp_idf_sys::O_NONBLOCK as i32);
|
||||
|
||||
self.server_fd = fd;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop_tcp_server(&mut self) {
|
||||
if self.server_fd >= 0 {
|
||||
unsafe {
|
||||
esp_idf_sys::lwip_close(self.server_fd);
|
||||
}
|
||||
self.server_fd = -1;
|
||||
}
|
||||
|
||||
for client in &mut self.clients {
|
||||
if client.fd >= 0 {
|
||||
unsafe {
|
||||
esp_idf_sys::lwip_close(client.fd);
|
||||
}
|
||||
client.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll(&mut self) -> Option<(usize, Vec<u8, TCP_RX_BUFFER_SIZE>)> {
|
||||
if self.server_fd < 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.accept_connections();
|
||||
|
||||
for i in 0..MAX_TCP_CLIENTS {
|
||||
if self.clients[i].fd >= 0 {
|
||||
if let Some(data) = self.read_client(i) {
|
||||
return Some((i, data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn accept_connections(&mut self) {
|
||||
unsafe {
|
||||
let mut client_addr: esp_idf_sys::sockaddr_in = core::mem::zeroed();
|
||||
let mut addr_len: u32 = core::mem::size_of::<esp_idf_sys::sockaddr_in>() as u32;
|
||||
|
||||
let client_fd = esp_idf_sys::lwip_accept(
|
||||
self.server_fd,
|
||||
&mut client_addr as *mut _ as *mut esp_idf_sys::sockaddr,
|
||||
&mut addr_len,
|
||||
);
|
||||
|
||||
if client_fd >= 0 {
|
||||
|
||||
for client in &mut self.clients {
|
||||
if client.is_available() {
|
||||
|
||||
let flags = esp_idf_sys::lwip_fcntl(client_fd, esp_idf_sys::F_GETFL as i32, 0);
|
||||
esp_idf_sys::lwip_fcntl(client_fd, esp_idf_sys::F_SETFL as i32, flags | esp_idf_sys::O_NONBLOCK as i32);
|
||||
|
||||
client.fd = client_fd;
|
||||
client.state = TcpClientState::Detecting;
|
||||
|
||||
let ip_bytes = client_addr.sin_addr.s_addr.to_le_bytes();
|
||||
let ip = Ipv4Addr::new(ip_bytes[0], ip_bytes[1], ip_bytes[2], ip_bytes[3]);
|
||||
let port = u16::from_be(client_addr.sin_port);
|
||||
client.addr = SocketAddrV4::new(ip, port);
|
||||
|
||||
self.status.tcp_client_count += 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
esp_idf_sys::lwip_close(client_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_client(&mut self, idx: usize) -> Option<Vec<u8, TCP_RX_BUFFER_SIZE>> {
|
||||
let client = &mut self.clients[idx];
|
||||
if client.fd < 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut buf = [0u8; TCP_RX_BUFFER_SIZE];
|
||||
|
||||
unsafe {
|
||||
let n = esp_idf_sys::lwip_recv(
|
||||
client.fd,
|
||||
buf.as_mut_ptr() as *mut core::ffi::c_void,
|
||||
buf.len(),
|
||||
0,
|
||||
);
|
||||
|
||||
if n > 0 {
|
||||
let mut data = Vec::new();
|
||||
for &b in &buf[..n as usize] {
|
||||
let _ = data.push(b);
|
||||
}
|
||||
return Some(data);
|
||||
} else if n == 0 {
|
||||
|
||||
self.disconnect_client(idx);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn send_to_client(&mut self, idx: usize, data: &[u8]) -> Result<usize, WifiError> {
|
||||
if idx >= MAX_TCP_CLIENTS {
|
||||
return Err(WifiError::SocketError);
|
||||
}
|
||||
|
||||
let client = &mut self.clients[idx];
|
||||
if client.fd < 0 {
|
||||
return Err(WifiError::SocketError);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let n = esp_idf_sys::lwip_send(
|
||||
client.fd,
|
||||
data.as_ptr() as *const core::ffi::c_void,
|
||||
data.len(),
|
||||
0,
|
||||
);
|
||||
|
||||
if n < 0 {
|
||||
self.disconnect_client(idx);
|
||||
return Err(WifiError::SocketError);
|
||||
}
|
||||
|
||||
Ok(n as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn broadcast(&mut self, data: &[u8]) {
|
||||
for i in 0..MAX_TCP_CLIENTS {
|
||||
if self.clients[i].fd >= 0 {
|
||||
let _ = self.send_to_client(i, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect_client(&mut self, idx: usize) {
|
||||
if idx >= MAX_TCP_CLIENTS {
|
||||
return;
|
||||
}
|
||||
|
||||
let client = &mut self.clients[idx];
|
||||
if client.fd >= 0 {
|
||||
unsafe {
|
||||
esp_idf_sys::lwip_close(client.fd);
|
||||
}
|
||||
client.reset();
|
||||
if self.status.tcp_client_count > 0 {
|
||||
self.status.tcp_client_count -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_client(&self, idx: usize) -> Option<&TcpClient> {
|
||||
if idx < MAX_TCP_CLIENTS && self.clients[idx].fd >= 0 {
|
||||
Some(&self.clients[idx])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_client_mut(&mut self, idx: usize) -> Option<&mut TcpClient> {
|
||||
if idx < MAX_TCP_CLIENTS && self.clients[idx].fd >= 0 {
|
||||
Some(&mut self.clients[idx])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
self.stop_tcp_server();
|
||||
|
||||
unsafe {
|
||||
esp_idf_sys::esp_wifi_stop();
|
||||
}
|
||||
|
||||
self.status.mode = WifiMode::Off;
|
||||
self.status.sta_connected = false;
|
||||
self.status.ap_active = false;
|
||||
}
|
||||
|
||||
pub fn status(&self) -> &NetworkStatus {
|
||||
&self.status
|
||||
}
|
||||
|
||||
pub fn update_status(&mut self) {
|
||||
unsafe {
|
||||
|
||||
let mut ap_info: esp_idf_sys::wifi_ap_record_t = core::mem::zeroed();
|
||||
if esp_idf_sys::esp_wifi_sta_get_ap_info(&mut ap_info) == 0 {
|
||||
self.status.sta_connected = true;
|
||||
self.status.rssi = ap_info.rssi;
|
||||
} else {
|
||||
self.status.sta_connected = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scan(&mut self) -> Result<Vec<NetworkInfo, 16>, WifiError> {
|
||||
if !self.initialized {
|
||||
return Err(WifiError::NotInitialized);
|
||||
}
|
||||
|
||||
let mut networks = Vec::new();
|
||||
|
||||
unsafe {
|
||||
|
||||
let scan_config: esp_idf_sys::wifi_scan_config_t = core::mem::zeroed();
|
||||
let ret = esp_idf_sys::esp_wifi_scan_start(&scan_config, true);
|
||||
if ret != 0 {
|
||||
return Err(WifiError::ConfigError);
|
||||
}
|
||||
|
||||
let mut ap_count: u16 = 0;
|
||||
esp_idf_sys::esp_wifi_scan_get_ap_num(&mut ap_count);
|
||||
|
||||
let count = ap_count.min(16) as usize;
|
||||
let mut ap_records: [esp_idf_sys::wifi_ap_record_t; 16] = core::mem::zeroed();
|
||||
let mut actual_count = count as u16;
|
||||
|
||||
esp_idf_sys::esp_wifi_scan_get_ap_records(&mut actual_count, ap_records.as_mut_ptr());
|
||||
|
||||
for i in 0..actual_count as usize {
|
||||
let ap = &ap_records[i];
|
||||
let mut ssid = String::new();
|
||||
for &b in &ap.ssid {
|
||||
if b == 0 {
|
||||
break;
|
||||
}
|
||||
let _ = ssid.push(b as char);
|
||||
}
|
||||
|
||||
let info = NetworkInfo {
|
||||
ssid,
|
||||
rssi: ap.rssi,
|
||||
channel: ap.primary,
|
||||
auth: ap.authmode as u8,
|
||||
};
|
||||
let _ = networks.push(info);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(networks)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WifiManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NetworkInfo {
|
||||
|
||||
pub ssid: String<MAX_SSID_LEN>,
|
||||
|
||||
pub rssi: i8,
|
||||
|
||||
pub channel: u8,
|
||||
|
||||
pub auth: u8,
|
||||
}
|
||||
|
||||
impl NetworkInfo {
|
||||
|
||||
pub fn is_open(&self) -> bool {
|
||||
self.auth == 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user