Add files via upload

This commit is contained in:
STCisGood
2026-01-10 20:17:45 -05:00
committed by GitHub
commit f12e94092f
38 changed files with 20394 additions and 0 deletions

1747
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

37
Cargo.toml Normal file
View 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
View 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
View 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

3
build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
embuild::espidf::sysenv::output();
}

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "esp"

17
sdkconfig.defaults Normal file
View 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

File diff suppressed because it is too large Load Diff

598
src/contact.rs Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

149
src/crypto/hkdf.rs Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

553
src/meshtastic/channel.rs Normal file
View 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
}

View 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

File diff suppressed because it is too large Load Diff

388
src/meshtastic/packet.rs Normal file
View 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

File diff suppressed because it is too large Load Diff

368
src/onion.rs Normal file
View 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, &current)?;
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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

781
src/session.rs Normal file
View 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(&params.shared_secret, salt, ROOT_KEY_INFO, &mut root_key);
let dh_output = x25519(&params.our_private, &params.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(&params.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(&params.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
View 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
View 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
View 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
}
}