diff options
| author | Will Dillon <william@housedillon.com> | 2025-12-04 17:07:12 +0000 |
|---|---|---|
| committer | Will Dillon <william@housedillon.com> | 2025-12-04 17:07:12 +0000 |
| commit | bf21ebaaae44b267cfefc87eee9608c09aab96b7 (patch) | |
| tree | 67234092d3c85a9c58d1fa9ca51337ac480ebfb7 /src | |
| parent | Better than 90% everywhere. (diff) | |
| download | meshcore-rs-bf21ebaaae44b267cfefc87eee9608c09aab96b7.tar.gz meshcore-rs-bf21ebaaae44b267cfefc87eee9608c09aab96b7.zip | |
Getting closer to no-std being done
Diffstat (limited to 'src')
| -rw-r--r-- | src/ack.rs | 16 | ||||
| -rw-r--r-- | src/advert.rs | 128 | ||||
| -rw-r--r-- | src/anon_req.rs | 53 | ||||
| -rw-r--r-- | src/bin/packet_analyzer.rs | 2 | ||||
| -rw-r--r-- | src/crypto.rs | 117 | ||||
| -rw-r--r-- | src/lib.rs | 20 | ||||
| -rw-r--r-- | src/multipart.rs | 17 | ||||
| -rw-r--r-- | src/no_std_identity.rs | 108 | ||||
| -rw-r--r-- | src/packet.rs | 166 | ||||
| -rw-r--r-- | src/packet_content.rs | 71 | ||||
| -rw-r--r-- | src/path.rs | 18 | ||||
| -rw-r--r-- | src/request.rs | 125 | ||||
| -rw-r--r-- | src/response.rs | 219 | ||||
| -rw-r--r-- | src/std_identity.rs (renamed from src/identity.rs) | 49 | ||||
| -rw-r--r-- | src/string_helper.rs | 70 | ||||
| -rw-r--r-- | src/text.rs | 144 | ||||
| -rw-r--r-- | src/trace.rs | 37 |
17 files changed, 929 insertions, 431 deletions
@@ -1,10 +1,7 @@ -use std::fmt::Display; use bytes::{Buf, Bytes}; -use structdiff::{Difference, StructDiff}; -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct Ack { checksum: u32 } @@ -16,17 +13,18 @@ impl From<Bytes> for Ack { } } -impl Display for Ack { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for Ack { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_fmt(format_args!("Checksum: {:4x?}", self.checksum)) } } #[cfg(test)] mod tests { - use std::str::FromStr; + use core::str::FromStr; use hex::decode; use bytes::Bytes; + use tinyvec::array_vec; use crate::{ack::Ack, packet::*, packet_content::PacketContent}; #[test] @@ -36,7 +34,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Flood, version: PayloadVersion::VersionOne, - path: vec![0x78, 0xB5, 0x56, 0x1B, 0x03, 0xCD, 0x70, 0xDA, 0x32, 0x60, 0x66, 0xB8, 0xA0, 0xCF], + path: array_vec!([u16; 64] => 0x78, 0xB5, 0x56, 0x1B, 0x03, 0xCD, 0x70, 0xDA, 0x32, 0x60, 0x66, 0xB8, 0xA0, 0xCF), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("24F3214D").unwrap()), content: PacketContent::Ack(Ack { @@ -53,7 +51,7 @@ mod tests { fn display() { let ack = Ack { checksum: 0x24F3214D }; println!("{}", ack); - assert!(format!("{}", ack) == "Checksum: 24f3214d"); + assert_eq!(format!("{}", ack), "Checksum: 24f3214d"); } }
\ No newline at end of file diff --git a/src/advert.rs b/src/advert.rs index 8d29949..24533cf 100644 --- a/src/advert.rs +++ b/src/advert.rs @@ -1,11 +1,8 @@ -use structdiff::{Difference, StructDiff}; -use crate::{crypto::PublicKey, packet_content::NodeType}; +use crate::{crypto::PublicKey, packet_content::NodeType, string_helper::NameString}; use bytes::{Buf, Bytes}; -use std::fmt::{Debug, Display}; use chrono::{DateTime, Local, Utc}; -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Clone, core::fmt::Debug)] pub struct Advert { pub public_key: PublicKey, pub timestamp: DateTime<Utc>, @@ -16,25 +13,70 @@ pub struct Advert { pub longitude: Option<f32>, pub feature1: Option<u16>, pub feature2: Option<u16>, - pub name: String + + pub name: Option<NameString>, } -impl From<Bytes> for Advert { - fn from(value: Bytes) -> Self { - let mut bytes = value; +impl Advert { + #[cfg(feature = "std")] + fn name_from_bytes(bytes: Bytes) -> Option<NameString> { + let mut name_split = bytes.chunk().split(|c| *c == 0); + + if let Some(name) = name_split.next() { + + if let Ok(name) = NameString::from_utf8(name.to_vec()) { + Some(name) + } else { + None + } + } else { + None + } + } + + #[cfg(not(feature = "std"))] + fn name_from_bytes(bytes: Bytes) -> Option<NameString> { + let mut name_split = bytes.chunk().split(|c| *c == 0); - let mut advert = Advert { + if let Some(name) = name_split.next() { + + if let Ok(name) = NameString::from_utf8(name) { + Some(name) + } else { + None + } + } else { + None + } + } +} + +impl Default for Advert { + fn default() -> Self { + Self { public_key: PublicKey::default(), timestamp: Local::now().into(), signature: [0_u8; 64], - node_type: NodeType::Invalid, latitude: None, longitude: None, feature1: None, feature2: None, - name: "".to_string(), - }; + + #[cfg(feature = "std")] + name: None, + + #[cfg(not(feature = "std"))] + name: None, + } + } +} + +impl From<Bytes> for Advert { + fn from(value: Bytes) -> Self { + let mut bytes = value; + + let mut advert = Advert::default(); if bytes.len() < 32 { return advert } if let Ok(key) = PublicKey::try_from(bytes.split_to(32)) { @@ -76,30 +118,30 @@ impl From<Bytes> for Advert { } // Lastly the name... The flag is a little - // irrelevant because the only different is + // irrelevant because the only difference is // whether the name is omitted or just empty. // I'm not going to make the distinction - // The string is assumed to be utf8, but if that - // fails to parse correctly, try to make it lossy - // which will insert U+FFFD REPLACEMENT CHARACTER (�) - if let Ok(string) = String::from_utf8(bytes.to_vec()) { - advert.name = string - } else { - advert.name = String::from_utf8_lossy(&bytes).to_string(); - } - + // Find only the bytes (characters) before the first null. + advert.name = Self::name_from_bytes(bytes); + advert } } -impl Display for Advert { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.node_type.fmt(f)?; +impl core::fmt::Display for Advert { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.node_type, f)?; f.write_str(" \"")?; - f.write_str(&self.name)?; - f.write_fmt(format_args!("\" ({:2x?}) at: ", self.public_key.hash_prefix()))?; - std::fmt::Display::fmt(&self.timestamp, f)?; + + if let Some(name) = &self.name { + f.write_str(&name)?; + } else { + f.write_str("<no name>")?; + } + + f.write_fmt(format_args!("\" ({:2x?}) at: ", self.public_key.hash_prefix() >> 24))?; + core::fmt::Display::fmt(&self.timestamp, f)?; match (self.latitude, self.longitude) { (Some(lat), Some(lon)) => { @@ -112,32 +154,41 @@ impl Display for Advert { } } +// Tests for std operations #[cfg(test)] mod tests { - use std::str::FromStr; + use core::str::FromStr; use chrono::DateTime; use hex::decode; + use tinyvec::ArrayVec; use crate::packet::*; use crate::crypto::*; use crate::packet_content::PacketContent; + use crate::string_helper::name_string_from_slice; use super::*; - #[test] fn advert_with_lat_lon() { // Real sample packet from the air + + use tinyvec::array_vec; + let sample = "110c015f7e9b60661da0f2671512460728508c17ef336412a223144d3a623215162682045c44fef7241af0161923d3af0769d2d976b687a506dd5325ef526bf3eb52ae687277fcbde9969a5b0087e0eb0f7c1760a50c6a88bec13cc30a2a9b681d713166515e3bbc2bc27f20c0e4d7b67e08910c29dc02c468b8f8484f574c"; let mut signature_slice = [0 as u8; 64]; hex::decode_to_slice("d2d976b687a506dd5325ef526bf3eb52ae687277fcbde9969a5b0087e0eb0f7c1760a50c6a88bec13cc30a2a9b681d713166515e3bbc2bc27f20c0e4d7b67e08", &mut signature_slice).unwrap(); + let mut name_slice = [0u8; 32]; + let string_slice = "HOWL".as_bytes(); + name_slice[0..string_slice.len()].copy_from_slice(string_slice); + let lhs_packet = Packet { route_type: RouteType::Flood, version: PayloadVersion::VersionOne, - path: vec![0x01, 0x5f, 0x7e, 0x9b, 0x60, 0x66, 0x1d, 0xa0, 0xf2, 0x67, 0x15, 0x12], + path: array_vec!([u16; 64] => 0x01, 0x5f, 0x7e, 0x9b, 0x60, 0x66, 0x1d, 0xa0, 0xf2, 0x67, 0x15, 0x12), transport: [0x00, 0x00], raw_content: Bytes::copy_from_slice(&decode("460728508c17ef336412a223144d3a623215162682045c44fef7241af0161923d3af0769d2d976b687a506dd5325ef526bf3eb52ae687277fcbde9969a5b0087e0eb0f7c1760a50c6a88bec13cc30a2a9b681d713166515e3bbc2bc27f20c0e4d7b67e08910c29dc02c468b8f8484f574c").unwrap()), content: PacketContent::Advert(Advert { @@ -149,7 +200,7 @@ mod tests { longitude: Some(-122.132286), feature1: None, feature2: None, - name: "HOWL".to_owned(), + name: Some(name_string_from_slice(b"HOWL")), }), incomplete: false, }; @@ -169,7 +220,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0x00, 0x00], raw_content: Bytes::copy_from_slice(&decode("7890b8573a6ba4a05b173d6ccfdfa73ac8ec4a12bf3c745ace636e1d191e132a4eb407695601f50907999735f3699a3c73ace2d8acc385ed209606abef6b914a346fbb88648c540ae794586b4d54eb08b473c8571a00c6b8d3dbcc77699afa169811fa098168707578373335").unwrap()), content: PacketContent::Advert(Advert { @@ -181,7 +232,7 @@ mod tests { longitude: None, feature1: None, feature2: None, - name: "hpux735".to_owned(), + name: Some(name_string_from_slice(b"hpux735")), }), incomplete: false, }; @@ -205,10 +256,11 @@ mod tests { longitude: Some(-122.132286), feature1: None, feature2: None, - name: "HOWL".to_owned(), + name: Some(name_string_from_slice(b"HOWL")), }; - println!("{}", advert); - assert!(format!("{}", advert) == "Chat \"HOWL\" (46) at: 2025-11-02 19:24:03 UTC location: 47.98286, -122.132286"); + + #[cfg(feature = "std")] + assert_eq!(format!("{}", advert), "Chat \"HOWL\" (46) at: 2025-11-02 19:24:03 UTC location: 47.98286, -122.132286"); } }
\ No newline at end of file diff --git a/src/anon_req.rs b/src/anon_req.rs index b468e9e..60d58e4 100644 --- a/src/anon_req.rs +++ b/src/anon_req.rs @@ -1,11 +1,8 @@ -use std::fmt::{Debug, Display}; use chrono::{DateTime, Utc}; use bytes::{Buf, Bytes}; -use structdiff::{Difference, StructDiff}; -use crate::crypto::PublicKey; +use crate::{MeshcoreStringError, crypto::PublicKey, string_helper::{PasswordString, password_string_from_slice}}; -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Clone, core::fmt::Debug)] pub struct AnonReq { pub dest: u8, pub public_key: PublicKey, @@ -15,6 +12,17 @@ pub struct AnonReq { incomplete: bool } +impl AnonReq { + #[allow(dead_code)] + fn password(&self) -> Result<PasswordString, MeshcoreStringError> { + if let Some(clear_request) = &self.request { + Ok(clear_request.password.clone()) + } else { + Err(MeshcoreStringError::StringEncrypted) + } + } +} + impl From<Bytes> for AnonReq { fn from(value: Bytes) -> Self { let mut bytes = value; @@ -45,9 +53,9 @@ impl From<Bytes> for AnonReq { } } -impl Display for AnonReq { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("({:2x?}) -> ({:2x?}) MAC: {:4x?} ", self.public_key.hash_prefix(), self.dest, self.mac))?; +impl core::fmt::Display for AnonReq { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_fmt(format_args!("({:2x?}) -> ({:2x?}) MAC: {:4x?} ", self.public_key.hash_prefix() >> 24, self.dest, self.mac))?; if let Some(cleartext) = &self.request { f.write_fmt(format_args!("at: {} password: \"{}\"", cleartext.timestamp, cleartext.password)) @@ -57,12 +65,11 @@ impl Display for AnonReq { } } -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct ClearAnonRequest { timestamp: DateTime<Utc>, sync_timestamp: Option<DateTime<Utc>>, - password: String + password: PasswordString, } impl From<Bytes> for ClearAnonRequest { @@ -70,9 +77,11 @@ impl From<Bytes> for ClearAnonRequest { let mut bytes = value; let mut anon_req = ClearAnonRequest { + // Safety: this is ok to unwrap because it's value isn't user controlled + // and never changes, and it exercised extensively in tests. timestamp: DateTime::from_timestamp(0, 0).unwrap(), sync_timestamp: None, - password: "".to_string(), + password: PasswordString::new() }; // Just check for the whole fixed-size part at once @@ -81,23 +90,23 @@ impl From<Bytes> for ClearAnonRequest { anon_req.timestamp = timestamp; } - // Strip-off any null characters after the password - let raw_password = String::from_utf8_lossy(&bytes).to_string(); - let trimmed_password: Vec<&str> = raw_password.splitn(2, "\0").collect(); - if let Some(password) = trimmed_password.first() { - anon_req.password = password.to_string(); - + // Strip-off any null characters after the password + if let Some(pass) = bytes.split(|b| *b == 0).next() { + anon_req.password = password_string_from_slice(pass); } + anon_req } } +// Tests for std operations #[cfg(test)] mod tests { use std::collections::HashMap; use std::str::FromStr; use hex::decode; - use crate::identity::KeystoreInput; + use tinyvec::ArrayVec; + use crate::std_identity::KeystoreInput; use crate::packet::*; use crate::crypto::*; use crate::packet_content::PacketContent; @@ -110,7 +119,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Flood, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("3412349BDC1F76A0C12149BB15F791DBE42FDE02C209B04A85C6F512990C8CEDEC4E7B8DBF1C3C928D64D87AA8293B9603EEE0").unwrap()), content: PacketContent::AnonReq(AnonReq { @@ -146,13 +155,13 @@ mod tests { assert_eq!(lhs_packet, rhs_packet); println!("\"{}\"", rhs_packet); - assert!(format!("{}", rhs_packet) == " Flood | v1 | | [] | | ANON REQ. | (12) -> (34) MAC: 4e7b ENCRYPTED"); + assert_eq!(format!("{}", rhs_packet), " Flood | v1 | | [] | | ANON REQ. | (12) -> (34) MAC: 4e7b ENCRYPTED"); rhs_packet.try_decrypt(&keystore); let lhs_string = format!("{}", rhs_packet); let rhs_string = " Flood | v1 | | [] | | ANON REQ. | (12) -> (34) MAC: 4e7b at: 2025-11-13 17:21:13 UTC password: \"12345\""; - assert!(lhs_string == rhs_string); + assert_eq!(lhs_string, rhs_string); } }
\ No newline at end of file diff --git a/src/bin/packet_analyzer.rs b/src/bin/packet_analyzer.rs index 98f4862..54d2f5b 100644 --- a/src/bin/packet_analyzer.rs +++ b/src/bin/packet_analyzer.rs @@ -6,7 +6,7 @@ use bytes::Bytes; use clap::Parser; use color_eyre::eyre::Result; -use meshcore::{identity::{Keystore, KeystoreInput}, packet::Packet}; +use meshcore::{std_identity::{Keystore, KeystoreInput}, packet::Packet}; use pretty_env_logger; use pcap_file_tokio::pcapng::PcapNgReader; diff --git a/src/crypto.rs b/src/crypto.rs index b62fb5d..cd184dc 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,7 +1,3 @@ -use std::{fmt::{Debug, Display}, str::FromStr}; -use ed25519_dalek::{VerifyingKey, hazmat::ExpandedSecretKey}; -use bytes::{Buf, BufMut, Bytes, BytesMut}; -use hex::{decode, decode_to_slice, encode}; // This seems to be an absolute nightmare. GenericArray sucks // but I can't seem to figure out how to pull it out of this @@ -12,8 +8,21 @@ use curve25519_dalek::MontgomeryPoint; use aes::Aes128; use sha2::{Sha256}; use hmac::{Hmac, Mac}; +use ed25519_dalek::{VerifyingKey, hazmat::ExpandedSecretKey}; +use bytes::{Buf, BufMut, Bytes, BytesMut}; + +use crate::string_helper::NameString; + + type HmacSha256 = Hmac<Sha256>; +pub trait Keystore { + fn decrypt_and_id_p2p(&self, source: u8, _dest: u8, mac: u16, data: &Bytes) -> Option<(Bytes, u32, u32)>; + + fn decrypt_and_id_group(&self, group_hash_prefix: u8, mac: u16, data: &Bytes) -> Option<(Bytes, Option<NameString>)>; + + fn decrypt_anon(&self, dest: u8, pub_key: &PublicKey, mac: u16, data: &Bytes) -> Option<(Bytes, u32)>; +} #[derive(Debug, PartialEq)] pub enum MeshcoreCryptoError { @@ -23,10 +32,11 @@ pub enum MeshcoreCryptoError { KeyCreationError, } -impl std::error::Error for MeshcoreCryptoError {} -impl Display for MeshcoreCryptoError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::error::Error for MeshcoreCryptoError {} + +impl core::fmt::Display for MeshcoreCryptoError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { MeshcoreCryptoError::KeyLengthError => f.write_str("Key Length Error"), MeshcoreCryptoError::TryFromSliceError => f.write_str("Try From Slice Error"), @@ -39,9 +49,9 @@ impl Display for MeshcoreCryptoError { #[derive(PartialEq)] pub struct PrivateKey(ExpandedSecretKey); -impl std::fmt::Debug for PrivateKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("PrivateKey").field(&encode(self.0.scalar.as_bytes())).finish() +impl core::fmt::Debug for PrivateKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("PrivateKey").field(&hex::encode(self.0.scalar.as_bytes())).finish() } } @@ -74,18 +84,28 @@ impl Default for PrivateKey { } } +impl PrivateKey { + pub fn hash_prefix(&self) -> u32 { + // The has prefix is the beginning of the public key of the secret + let public_key = PublicKey::from(self); + public_key.hash_prefix() + } +} + #[derive(PartialEq, Clone)] pub struct PublicKey(ed25519_dalek::VerifyingKey); -impl std::fmt::Debug for PublicKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("PublicKey").field(&encode(self.0.as_bytes())).finish() +impl core::fmt::Debug for PublicKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("PublicKey").field(&hex::encode(self.0.as_bytes())).finish() } } impl PublicKey { - pub fn hash_prefix(&self) -> u8 { - self.0.as_bytes()[0] + pub fn hash_prefix(&self) -> u32 { + let mut bytes = Bytes::copy_from_slice(self.0.as_bytes()); + + bytes.get_u32() } } @@ -102,13 +122,13 @@ impl PartialEq for SharedSecret { } } -impl std::fmt::Debug for SharedSecret { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("SharedSecret").field(&encode(self.0.as_bytes())).finish() +impl core::fmt::Debug for SharedSecret { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("SharedSecret").field(&hex::encode(self.0.as_bytes())).finish() } } -impl FromStr for SharedSecret { +impl core::str::FromStr for SharedSecret { type Err = MeshcoreCryptoError; fn from_str(s: &str) -> Result<Self, Self::Err> { @@ -117,7 +137,7 @@ impl FromStr for SharedSecret { // The provided group secrets are only 16 bytes, // but they're zero-paded to be 32. So, we're // going to get the hex from the string and copy it in. - if decode_to_slice(s, &mut array[0..16]).is_err() { + if hex::decode_to_slice(s, &mut array[0..16]).is_err() { return Err(MeshcoreCryptoError::TryFromSliceError) } else { Ok(SharedSecret(MontgomeryPoint(array))) @@ -139,6 +159,15 @@ impl TryFrom<Bytes> for SharedSecret { } } +// This is kinda meaningless because a shared secret only works +// when it's connected to a key pair, but it needs to exist for +// Arrayvec. +impl Default for SharedSecret { + fn default() -> Self { + Self(curve25519_dalek::MontgomeryPoint([0u8; 32])) + } +} + impl SharedSecret { fn get_key(&self) -> &[u8; 16] { // Safety: The size of the slice ensures that this will never be wrong. @@ -289,11 +318,11 @@ impl TryFrom<Bytes> for PublicKey { } -impl FromStr for PublicKey { +impl core::str::FromStr for PublicKey { type Err = MeshcoreCryptoError; fn from_str(hex_str: &str) -> Result<Self, Self::Err> { - if let Ok(hex) = decode(hex_str) { + if let Ok(hex) = hex::decode(hex_str) { if let Ok(slice) = TryInto::<[u8; 32]>::try_into(hex) { if let Ok(key) = VerifyingKey::from_bytes(&slice) { Ok(PublicKey(key)) @@ -309,11 +338,11 @@ impl FromStr for PublicKey { } } -impl FromStr for PrivateKey { +impl core::str::FromStr for PrivateKey { type Err = MeshcoreCryptoError; fn from_str(hex_str: &str) -> Result<Self, Self::Err> { - if let Ok(hex) = decode(hex_str) { + if let Ok(hex) = hex::decode(hex_str) { if let Ok(bytes) = TryInto::<[u8; 64]>::try_into(hex) { Ok(PrivateKey(ExpandedSecretKey::from_bytes(&bytes))) } else { @@ -355,9 +384,11 @@ impl SharedSecret { } } +// Tests for std operations #[cfg(test)] mod tests { - use hex::{decode_to_slice, encode}; + use core::str::FromStr; + use hex::{decode_to_slice, encode, decode}; use super::*; #[test] @@ -365,7 +396,7 @@ mod tests { let mut slice = [0_u8; 32]; decode_to_slice("12349bdc1f76a0c12149bb15f791dbe42fde02c209b04a85c6f512990c8cedec", &mut slice).unwrap(); let public_key = PublicKey::from_str("12349bdc1f76a0c12149bb15f791dbe42fde02c209b04a85c6f512990c8cedec"); - assert!(Ok(PublicKey(VerifyingKey::from_bytes(&slice).unwrap())) == public_key); + assert_eq!(Ok(PublicKey(VerifyingKey::from_bytes(&slice).unwrap())), public_key); } #[test] @@ -373,7 +404,7 @@ mod tests { let private_key = PrivateKey::from_str("38DAA98490B7284697C7ADA6175FD1F8DAD12032AD7ABAE625B7EAD8FEC6444CA281C3370B97155D9C8CECD89A929FDDE0FBF3A9D5C92A1B3C24D711934CD69D").unwrap(); let public_key = PublicKey::from(&private_key); println!("Public key: {:#?}", public_key); - assert!(PublicKey::from_str("12349bdc1f76a0c12149bb15f791dbe42fde02c209b04a85c6f512990c8cedec").unwrap() == public_key); + assert_eq!(PublicKey::from_str("12349bdc1f76a0c12149bb15f791dbe42fde02c209b04a85c6f512990c8cedec").unwrap(), public_key); } #[test] @@ -385,8 +416,8 @@ mod tests { let alice_public = PublicKey::from(&alice_private); let bob_public = PublicKey::from(&bob_private); - assert!(alice_public.0.as_bytes().to_vec() == decode("34569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8f").unwrap()); - assert!( bob_public.0.as_bytes().to_vec() == decode("12349bdc1f76a0c12149bb15f791dbe42fde02c209b04a85c6f512990c8cedec").unwrap()); + assert_eq!(alice_public.0.as_bytes().to_vec(), decode("34569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8f").unwrap()); + assert_eq!( bob_public.0.as_bytes().to_vec(), decode("12349bdc1f76a0c12149bb15f791dbe42fde02c209b04a85c6f512990c8cedec").unwrap()); println!("Alice's public key: {}", encode(&alice_public.0.to_bytes())); println!("Bob's public key: {}", encode(&bob_public.0.to_bytes())); @@ -394,12 +425,12 @@ mod tests { let left_secret = alice_private.create_secret(&bob_public); let right_secret = bob_private.create_secret(&alice_public); - assert!(left_secret.0.as_bytes().to_vec() == decode("eb7a365363bd8548ee2b54b9234247be5e42e96be9625adcdf3a55b6c1d04850").unwrap()); + assert_eq!(left_secret.0.as_bytes().to_vec(), decode("eb7a365363bd8548ee2b54b9234247be5e42e96be9625adcdf3a55b6c1d04850").unwrap()); println!("Left shared secret: {}", encode(&left_secret.0.as_bytes())); println!("Right shared secret: {}", encode(&right_secret.0.as_bytes())); - assert!(left_secret == right_secret); + assert_eq!(left_secret, right_secret); } #[test] @@ -407,12 +438,12 @@ mod tests { // Test using a group secret let group_secret = SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("8b3387e9c5cdea6ac9e5edbaa115cd72").unwrap())); let test_group_secret =SharedSecret::from_str("8b3387e9c5cdea6ac9e5edbaa115cd72").unwrap(); - assert!(group_secret == test_group_secret); + assert_eq!(group_secret, test_group_secret); // Test using the secret and ciphertext to make a MAC and ensure it matches an example for a group secret let sample_data = Bytes::copy_from_slice(&decode("354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap()); let mac = group_secret.get_hmac(&sample_data); - assert!(0xC3C1 == mac); + assert_eq!(0xC3C1, mac); } #[test] @@ -430,7 +461,7 @@ mod tests { let cleartext = secret.decrypt(&ciphertext); let vec = cleartext.to_vec(); let string = String::from_utf8_lossy(&vec); - assert!("Hello my world!!" == string); + assert_eq!("Hello my world!!", string); } #[test] @@ -438,7 +469,7 @@ mod tests { let plaintext = Bytes::copy_from_slice("Meshcore!".as_bytes()); let secret = SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("44A6F78DAD2E54D73A32CDE3ECAA9E75").unwrap())); let ciphertext = secret.encrypt(plaintext); - assert!(ciphertext == decode("62374852B6A11405A081F87356C88861").unwrap()); + assert_eq!(ciphertext, decode("62374852B6A11405A081F87356C88861").unwrap()); } #[test] @@ -468,7 +499,7 @@ mod tests { println!("Result: {:#?}", encode(&text)); - assert!(text == decode("102030405060708090A0B0C0D0E0F0").unwrap()); + assert_eq!(text, decode("102030405060708090A0B0C0D0E0F0").unwrap()); } @@ -525,15 +556,15 @@ mod tests { let mac = 0xC3C1; let cleartext = group_secret.mac_then_decrypt(mac, &sample_data).unwrap(); - assert!(cleartext == decode("3757d06800f09f8cb220547265653a20e29881efb88f00000000000000000000").unwrap()); + assert_eq!(cleartext, decode("3757d06800f09f8cb220547265653a20e29881efb88f00000000000000000000").unwrap()); } #[test] fn test_error_display() { - assert!(format!("{}", MeshcoreCryptoError::KeyLengthError) == "Key Length Error"); - assert!(format!("{}", MeshcoreCryptoError::TryFromSliceError) == "Try From Slice Error"); - assert!(format!("{}", MeshcoreCryptoError::HexDecodeError) == "Hex Decode Error"); - assert!(format!("{}", MeshcoreCryptoError::KeyCreationError) == "Key Creation Error"); + assert_eq!(format!("{}", MeshcoreCryptoError::KeyLengthError), "Key Length Error"); + assert_eq!(format!("{}", MeshcoreCryptoError::TryFromSliceError), "Try From Slice Error"); + assert_eq!(format!("{}", MeshcoreCryptoError::HexDecodeError), "Hex Decode Error"); + assert_eq!(format!("{}", MeshcoreCryptoError::KeyCreationError), "Key Creation Error"); } #[test] @@ -565,9 +596,7 @@ mod tests { .first() .unwrap(); - assert!(lhs_string == cleartext, "{}", - format!("\"{}\" != \"{}\"", lhs_string, cleartext) - ); + assert_eq!(lhs_string, cleartext); } else { assert!(false, "Unable to decrypt"); } @@ -1,5 +1,23 @@ +#![cfg_attr(not(feature = "std"), no_std)] + pub mod crypto; -pub mod identity; +pub mod string_helper; + +#[derive(Debug, PartialEq)] +pub enum MeshcoreStringError { + StringLengthError, + StringUnicodeError, + StringEncrypted, +} + +// This version of identity is pretty specific +// to the std version of this library. +#[cfg(feature = "std")] +pub mod std_identity; + +// This version of identity is no-std +#[cfg(not(feature = "std"))] +pub(crate) mod no_std_identity; pub mod packet; pub mod packet_content; diff --git a/src/multipart.rs b/src/multipart.rs index c68b442..ced4aaa 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -1,9 +1,7 @@ -use std::fmt::{Debug, Display}; + use bytes::Bytes; -use structdiff::{Difference, StructDiff}; -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct MultiPart { payload: Bytes } @@ -14,16 +12,18 @@ impl From<Bytes> for MultiPart { } } -impl Display for MultiPart { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for MultiPart { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_fmt(format_args!("Multipart isn't defined.")) } } +// Tests for std operations #[cfg(test)] mod tests { - use std::str::FromStr; + use core::str::FromStr; use hex::decode; + use tinyvec::ArrayVec; use crate::{packet::*, packet_content::PacketContent}; use super::*; @@ -35,7 +35,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("13E8C59624").unwrap()), content: PacketContent::Multipart(MultiPart { @@ -47,6 +47,7 @@ mod tests { let rhs_packet = Packet::from_str(sample).unwrap(); assert_eq!(lhs_packet, rhs_packet); + #[cfg(feature = "std")] assert_eq!(format!("{}", lhs_packet), " Direct | v1 | | [] | | MULTIPART | Multipart isn't defined."); } }
\ No newline at end of file diff --git a/src/no_std_identity.rs b/src/no_std_identity.rs new file mode 100644 index 0000000..2a0018e --- /dev/null +++ b/src/no_std_identity.rs @@ -0,0 +1,108 @@ +use bytes::Bytes; +use crate::{NameString, crypto::{Keystore, PrivateKey, PublicKey, SharedSecret}}; +use tinyvec::ArrayVec; + +#[derive(PartialEq, Clone, Debug)] +pub struct Identity { + pub private_key: PrivateKey, + pub hash_prefix: u32, + pub secrets: ArrayVec<[(u32, SharedSecret); 10]>, +} + +impl Default for Identity { + fn default() -> Self { + let private_key = PrivateKey::default(); + let hash_prefix = private_key.hash_prefix(); + + Self { + private_key, + hash_prefix, + secrets: Default::default() + } + } +} + +#[derive(PartialEq, Clone, Debug)] +pub struct StaticKeystore { + pub identities: ArrayVec<[Identity; 10]>, + pub groups: ArrayVec<[SharedSecret; 10]>, +} + +impl Keystore for StaticKeystore { + fn decrypt_and_id_p2p(&self, _source: u8, _dest: u8, mac: u16, data: &Bytes) -> Option<(Bytes, u32, u32)> { + // Just brute-force all the secrets until one matches... + for identity in self.identities.iter() { + for (peer_prefix, secret) in identity.secrets.iter() { + let result = secret.mac_then_decrypt(mac, data); + if let Some(result) = result { + return Some((result, identity.hash_prefix, *peer_prefix)); + } + } + } + None + } + + fn decrypt_and_id_group(&self, _group_hash_prefix: u8, mac: u16, data: &Bytes) -> Option<(Bytes, Option<NameString>)> { + for group in self.groups.iter() { + let result = group.mac_then_decrypt(mac, data); + if let Some(result) = result { + return Some((result, None)); + } + } + + None + } + + fn decrypt_anon(&self, _dest: u8, pub_key: &PublicKey, mac: u16, data: &Bytes) -> Option<(Bytes, u32)> { + // For each identity, create a shared secret against the provided public key and check it. + for identity in self.identities.iter() { + let secret = identity.private_key.create_secret(pub_key); + let result = secret.mac_then_decrypt(mac, data); + if let Some(result) = result { + return Some((result, identity.hash_prefix)); + } + } + None + } +} + +#[derive(PartialEq, Debug, Clone)] +pub struct StaticKeystoreInput { + pub identities: ArrayVec<[PrivateKey; 10]>, + pub contacts: ArrayVec<[PublicKey; 10]>, + pub groups: ArrayVec<[SharedSecret; 10]>, +} + +impl StaticKeystoreInput { + pub fn compile(self) -> StaticKeystore { + let mut retval = StaticKeystore { + identities: ArrayVec::new(), + groups: ArrayVec::new(), + }; + + for identity_in in self.identities { + let mut identity = Identity::default(); + identity.private_key = identity_in; + identity.hash_prefix = PublicKey::from(&identity.private_key).hash_prefix(); + for contact_in in self.contacts.iter() { + identity.secrets.push((contact_in.hash_prefix(), identity.private_key.create_secret(&contact_in))); + } + + retval.identities.push(identity); + } + + for group in self.groups.iter() { + retval.groups.push(group.clone()); + } + + retval + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_compile() { + + } +}
\ No newline at end of file diff --git a/src/packet.rs b/src/packet.rs index 3fa26cb..50374a7 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,27 +1,29 @@ -use std::{fmt::{Debug, Display}, str::FromStr}; +#[cfg(feature = "std")] +use crate::std_identity::Keystore; + +#[cfg(not(feature = "std"))] +use crate::no_std_identity::Keystore; -use hex::decode; use bytes::{Buf, Bytes}; -use structdiff::{Difference, StructDiff}; -use crate::{anon_req::ClearAnonRequest, identity::Keystore, packet_content::PacketContent, request::ClearRequest, response::ClearResponse, text::ClearText}; +use tinyvec::ArrayVec; +use crate::{anon_req::ClearAnonRequest, packet_content::PacketContent, request::ClearRequest, response::ClearResponse, text::ClearText}; -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Clone, core::fmt::Debug)] pub struct Packet { pub route_type: RouteType, pub version: PayloadVersion, - pub path: Vec<u16>, + pub path: ArrayVec<[u16; 64]>, pub transport: [u16; 2], pub raw_content: Bytes, pub content: PacketContent, pub incomplete: bool } -impl FromStr for Packet { +impl core::str::FromStr for Packet { type Err = hex::FromHexError; fn from_str(hex_str: &str) -> Result<Self, Self::Err> { - let hex = decode(hex_str)?; + let hex = hex::decode(hex_str)?; Ok(Packet::from(Bytes::copy_from_slice(&hex))) } } @@ -67,22 +69,24 @@ impl From<Bytes> for Packet { // The packet isn't long enough for the indicated route if bytes.len() < path_length { return packet; } - let route: Vec<u16> = bytes - .split_to(path_length) - .into_iter() - .map(|x| x as u16) - .collect(); + let mut route = ArrayVec::new(); + for _ in 0..path_length { + let path_element = bytes.get_u8() as u16; + route.push(path_element); + } + route }, + PayloadVersion::VersionTwo => { // The packet isn't long enough for the indicated route if bytes.len() < path_length * 2 { return packet; } - let route: Vec<u16> = bytes - .split_to(path_length * 2) - .chunks(2) - .map(|c| Bytes::copy_from_slice(c).get_u16_le()) - .collect(); + let mut route = ArrayVec::new(); + for i in 0..path_length { + let path_element = bytes.get_u16(); + route[i] = path_element; + } route }, @@ -101,12 +105,18 @@ impl From<Bytes> for Packet { if let PacketContent::Trace(trace) = packet.content { let mut trace = trace; let path_snr = packet.path; - packet.path = trace.temp_path.iter().map(|i| *i as u16).collect(); - trace.path_snr = path_snr.iter().map( |u| { + + for (index, element) in trace.temp_path.iter().enumerate() { + packet.path[index] = *element as u16; + } + + for u in path_snr.iter() { let i = *u as i8; let f = i as f32; - f / 4.0 - }).collect(); + + trace.path_snr.push(f / 4.0); + } + // DON'T delete the temp path, so Trace has access to the path // when it prints the SNR results in its Display function. packet.content = PacketContent::Trace(trace); @@ -124,7 +134,7 @@ impl Default for Packet { Packet { route_type: RouteType::Invalid, version: PayloadVersion::Invalid, - path: vec![], + path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::new(), content: PacketContent::Invalid, @@ -133,11 +143,11 @@ impl Default for Packet { } } -impl Display for Packet { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.route_type, f)?; +impl core::fmt::Display for Packet { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(&self.route_type, f)?; f.write_str(" | ")?; - std::fmt::Display::fmt(&self.version, f)?; + core::fmt::Display::fmt(&self.version, f)?; if self.route_type == RouteType::TransportDirect || self.route_type == RouteType::TransportFlood { f.write_fmt(format_args!(" | {:4x?}, {:4x?} | ", self.transport[0], self.transport[1]))? @@ -160,11 +170,12 @@ impl Display for Packet { f.write_str(" | ") }?; - std::fmt::Display::fmt(&self.content, f) + core::fmt::Display::fmt(&self.content, f) } } impl Packet { + #[cfg(feature = "std")] pub fn try_decrypt(&mut self, keystore: &Keystore) -> bool { match self.content { // Encrypted packet types @@ -240,8 +251,8 @@ pub enum RouteType { Invalid, } -impl Display for RouteType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for RouteType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { RouteType::TransportFlood => f.write_str("T-Flood "), RouteType::Flood => f.write_str(" Flood "), @@ -288,8 +299,8 @@ impl From<u8> for PayloadVersion { } } -impl Display for PayloadVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for PayloadVersion { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { PayloadVersion::VersionOne => f.write_str("v1"), PayloadVersion::VersionTwo => f.write_str("v2"), @@ -302,54 +313,57 @@ impl Display for PayloadVersion { #[cfg(test)] mod tests { + use core::str::FromStr; + use tinyvec::array_vec; + use crate::packet_content::{PacketContent, Raw}; use super::*; #[test] fn header_route_type() { // The route type is the lowest-order two bits - assert!(RouteType::TransportFlood == RouteType::from(0x00)); - assert!(RouteType::TransportFlood == RouteType::from(0xFC)); - assert!(RouteType::Flood == RouteType::from(0x01)); - assert!(RouteType::Flood == RouteType::from(0xFD)); - assert!(RouteType::Direct == RouteType::from(0x02)); - assert!(RouteType::Direct == RouteType::from(0xFE)); - assert!(RouteType::TransportDirect == RouteType::from(0x03)); - assert!(RouteType::TransportDirect == RouteType::from(0xFF)); - - assert!(format!("{}", RouteType::TransportFlood) == "T-Flood "); - assert!(format!("{}", RouteType::Flood) == " Flood "); - assert!(format!("{}", RouteType::Direct) == " Direct"); - assert!(format!("{}", RouteType::TransportDirect) == "T-Direct"); - assert!(format!("{}", RouteType::Invalid) == "INVALID "); + assert_eq!(RouteType::TransportFlood, RouteType::from(0x00)); + assert_eq!(RouteType::TransportFlood, RouteType::from(0xFC)); + assert_eq!(RouteType::Flood, RouteType::from(0x01)); + assert_eq!(RouteType::Flood, RouteType::from(0xFD)); + assert_eq!(RouteType::Direct, RouteType::from(0x02)); + assert_eq!(RouteType::Direct, RouteType::from(0xFE)); + assert_eq!(RouteType::TransportDirect, RouteType::from(0x03)); + assert_eq!(RouteType::TransportDirect, RouteType::from(0xFF)); + + assert_eq!(format!("{}", RouteType::TransportFlood), "T-Flood "); + assert_eq!(format!("{}", RouteType::Flood), " Flood "); + assert_eq!(format!("{}", RouteType::Direct), " Direct"); + assert_eq!(format!("{}", RouteType::TransportDirect), "T-Direct"); + assert_eq!(format!("{}", RouteType::Invalid), "INVALID "); } #[test] fn header_version() { - assert!(PayloadVersion::VersionOne == PayloadVersion::from(0x00)); - assert!(PayloadVersion::VersionOne == PayloadVersion::from(0x3F)); - assert!(PayloadVersion::VersionTwo == PayloadVersion::from(0x40)); - assert!(PayloadVersion::VersionTwo == PayloadVersion::from(0x7F)); - assert!(PayloadVersion::VersionThree == PayloadVersion::from(0x80)); - assert!(PayloadVersion::VersionThree == PayloadVersion::from(0xBF)); - assert!(PayloadVersion::VersionFour == PayloadVersion::from(0xC0)); - assert!(PayloadVersion::VersionFour == PayloadVersion::from(0xFF)); - - assert!(format!("{}", PayloadVersion::VersionOne) == "v1"); - assert!(format!("{}", PayloadVersion::VersionTwo) == "v2"); - assert!(format!("{}", PayloadVersion::VersionThree) == "v3"); - assert!(format!("{}", PayloadVersion::VersionFour) == "v4"); - assert!(format!("{}", PayloadVersion::Invalid) == "xx"); + assert_eq!(PayloadVersion::VersionOne, PayloadVersion::from(0x00)); + assert_eq!(PayloadVersion::VersionOne, PayloadVersion::from(0x3F)); + assert_eq!(PayloadVersion::VersionTwo, PayloadVersion::from(0x40)); + assert_eq!(PayloadVersion::VersionTwo, PayloadVersion::from(0x7F)); + assert_eq!(PayloadVersion::VersionThree, PayloadVersion::from(0x80)); + assert_eq!(PayloadVersion::VersionThree, PayloadVersion::from(0xBF)); + assert_eq!(PayloadVersion::VersionFour, PayloadVersion::from(0xC0)); + assert_eq!(PayloadVersion::VersionFour, PayloadVersion::from(0xFF)); + + assert_eq!(format!("{}", PayloadVersion::VersionOne), "v1"); + assert_eq!(format!("{}", PayloadVersion::VersionTwo), "v2"); + assert_eq!(format!("{}", PayloadVersion::VersionThree), "v3"); + assert_eq!(format!("{}", PayloadVersion::VersionFour), "v4"); + assert_eq!(format!("{}", PayloadVersion::Invalid), "xx"); } #[test] fn packet() { // Check the hex decode errors - assert!(Err(hex::FromHexError::InvalidHexCharacter { c: 's', index: 0 }) == Packet::from_str("s0")); - assert!(Err(hex::FromHexError::OddLength) == Packet::from_str("0")); + assert_eq!(Err(hex::FromHexError::InvalidHexCharacter { c: 's', index: 0 }), Packet::from_str("s0")); + assert_eq!(Err(hex::FromHexError::OddLength), Packet::from_str("0")); // Check errors related to packet length issues - assert!(Packet::default() == Packet::from_str("").unwrap()); + assert_eq!(Packet::default(), Packet::from_str("").unwrap()); let rhs_packet = Packet::from_str("01").unwrap(); assert_eq!(Packet::default(), rhs_packet); @@ -379,17 +393,17 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::TransportDirect, version: PayloadVersion::VersionOne, - path: [0x06, 0x07, 0x08, 0x09, 0x0A].to_vec(), + path: array_vec!([u16; 64] => 0x06, 0x07, 0x08, 0x09, 0x0A), transport: [0x0102, 0x0304], raw_content: Bytes::copy_from_slice(&[0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19]), content: PacketContent::Raw(Raw { bytes: Bytes::copy_from_slice(&[0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19])}), incomplete: false, }; - let rhs_packet = Packet::from_str("3F0201040305060708090A10111213141516171819").unwrap(); + let rhs_packet: Packet = Packet::from_str("3F0201040305060708090A10111213141516171819").unwrap(); let compare_string = "T-Direct | v1 | 102, 304 | [06, 07, ... 09, 0a] | | RAW | Raw { bytes: b\"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\" }"; - assert!(format!("{}", rhs_packet) == compare_string); + assert_eq!(format!("{}", rhs_packet), compare_string); assert_eq!(lhs_packet, rhs_packet); } @@ -399,25 +413,25 @@ mod tests { let mut packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0, 1], raw_content: Bytes::new(), content: PacketContent::Invalid, incomplete: false }; - assert!(format!("{}", packet) == " Direct | v1 | | [] | | INVALID | INVALID"); + assert_eq!(format!("{}", packet), " Direct | v1 | | [] | | INVALID | INVALID"); - packet.path = vec![0x01]; - assert!(format!("{}", packet) == " Direct | v1 | | [01] | | INVALID | INVALID"); + packet.path.push(0x01); + assert_eq!(format!("{}", packet), " Direct | v1 | | [01] | | INVALID | INVALID"); - packet.path = vec![0x01, 0x02]; - assert!(format!("{}", packet) == " Direct | v1 | | [01, 02] | | INVALID | INVALID"); + packet.path.push(0x02); + assert_eq!(format!("{}", packet), " Direct | v1 | | [01, 02] | | INVALID | INVALID"); - packet.path = vec![0x01, 0x02, 0xff]; - assert!(format!("{}", packet) == " Direct | v1 | | [01, 02, ff] | | INVALID | INVALID"); + packet.path.push(0xff); + assert_eq!(format!("{}", packet), " Direct | v1 | | [01, 02, ff] | | INVALID | INVALID"); packet.incomplete = true; - assert!(format!("{}", packet) == " Direct | v1 | | [01, 02, ff] | x | INVALID | INVALID"); + assert_eq!(format!("{}", packet), " Direct | v1 | | [01, 02, ff] | x | INVALID | INVALID"); } }
\ No newline at end of file diff --git a/src/packet_content.rs b/src/packet_content.rs index d13c663..26230e1 100644 --- a/src/packet_content.rs +++ b/src/packet_content.rs @@ -1,10 +1,10 @@ -use std::fmt::{Debug, Display}; use bytes::{Buf, Bytes}; -use structdiff::{Difference, StructDiff}; -use crate::{ack::Ack, advert::Advert, anon_req::AnonReq, identity::Keystore, multipart::MultiPart, path::Path, request::Request, response::Response, text::{GroupData, GroupText, Text}, trace::Trace}; +use crate::{ack::Ack, advert::Advert, anon_req::AnonReq, multipart::MultiPart, path::Path, request::Request, response::Response, text::{GroupData, GroupText, Text}, trace::Trace}; -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[cfg(feature = "std")] +use crate::std_identity::Keystore; + +#[derive(PartialEq, Clone, core::fmt::Debug)] pub enum PacketContent { Request(Request), Response(Response), @@ -62,23 +62,23 @@ impl PacketContent { } } -impl Display for PacketContent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for PacketContent { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str(self.justified_name())?; match self { - PacketContent::Request(c) => std::fmt::Display::fmt(&c, f), - PacketContent::Response(c) => std::fmt::Display::fmt(&c, f), - PacketContent::Text(c) => std::fmt::Display::fmt(&c, f), - PacketContent::Ack(c) => std::fmt::Display::fmt(&c, f), - PacketContent::Advert(c) => std::fmt::Display::fmt(&c, f), - PacketContent::GroupText(c) => std::fmt::Display::fmt(&c, f), - PacketContent::GroupData(c) => std::fmt::Display::fmt(&c, f), - PacketContent::AnonReq(c) => std::fmt::Display::fmt(&c, f), - PacketContent::Path(c) => std::fmt::Display::fmt(&c, f), - PacketContent::Trace(c) => std::fmt::Display::fmt(&c, f), - PacketContent::Multipart(c) => std::fmt::Display::fmt(&c, f), - PacketContent::Raw(c) => std::fmt::Display::fmt(&c, f), + PacketContent::Request(c) => core::fmt::Display::fmt(&c, f), + PacketContent::Response(c) => core::fmt::Display::fmt(&c, f), + PacketContent::Text(c) => core::fmt::Display::fmt(&c, f), + PacketContent::Ack(c) => core::fmt::Display::fmt(&c, f), + PacketContent::Advert(c) => core::fmt::Display::fmt(&c, f), + PacketContent::GroupText(c) => core::fmt::Display::fmt(&c, f), + PacketContent::GroupData(c) => core::fmt::Display::fmt(&c, f), + PacketContent::AnonReq(c) => core::fmt::Display::fmt(&c, f), + PacketContent::Path(c) => core::fmt::Display::fmt(&c, f), + PacketContent::Trace(c) => core::fmt::Display::fmt(&c, f), + PacketContent::Multipart(c) => core::fmt::Display::fmt(&c, f), + PacketContent::Raw(c) => core::fmt::Display::fmt(&c, f), PacketContent::Invalid => f.write_str("INVALID") } } @@ -106,8 +106,7 @@ impl From<u8> for NodeType { } } -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct PeerToPeerCipher { pub destination: u8, pub source: u8, @@ -141,6 +140,7 @@ impl From<Bytes> for PeerToPeerCipher { } impl PeerToPeerCipher { + #[cfg(feature = "std")] pub fn try_decrypt(&mut self, keystore: &Keystore) -> bool { let decrypt = keystore.decrypt_and_id_p2p( self.source, @@ -163,28 +163,29 @@ pub struct Raw { pub(crate) bytes: Bytes } -impl Display for Raw { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(&self, f) +impl core::fmt::Display for Raw { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(&self, f) } } +// Tests for std operations #[cfg(test)] mod tests { use super::*; #[test] fn node_type() { - assert!(NodeType::Invalid == NodeType::from(0x00)); - assert!(NodeType::Invalid == NodeType::from(0xF8)); - assert!(NodeType::Chat == NodeType::from(0x01)); - assert!(NodeType::Chat == NodeType::from(0xF9)); - assert!(NodeType::Repeater == NodeType::from(0x02)); - assert!(NodeType::Repeater == NodeType::from(0xFA)); - assert!(NodeType::Room == NodeType::from(0x03)); - assert!(NodeType::Room == NodeType::from(0xFB)); - assert!(NodeType::Sensor == NodeType::from(0x04)); - assert!(NodeType::Sensor == NodeType::from(0xFC)); - assert!(NodeType::Invalid == NodeType::from(0x05)); + assert_eq!(NodeType::Invalid, NodeType::from(0x00)); + assert_eq!(NodeType::Invalid, NodeType::from(0xF8)); + assert_eq!(NodeType::Chat, NodeType::from(0x01)); + assert_eq!(NodeType::Chat, NodeType::from(0xF9)); + assert_eq!(NodeType::Repeater, NodeType::from(0x02)); + assert_eq!(NodeType::Repeater, NodeType::from(0xFA)); + assert_eq!(NodeType::Room, NodeType::from(0x03)); + assert_eq!(NodeType::Room, NodeType::from(0xFB)); + assert_eq!(NodeType::Sensor, NodeType::from(0x04)); + assert_eq!(NodeType::Sensor, NodeType::from(0xFC)); + assert_eq!(NodeType::Invalid, NodeType::from(0x05)); } }
\ No newline at end of file diff --git a/src/path.rs b/src/path.rs index 9cb499a..a2eb1c9 100644 --- a/src/path.rs +++ b/src/path.rs @@ -1,11 +1,7 @@ -use std::fmt::{Debug, Display}; use bytes::Bytes; -use structdiff::{Difference, StructDiff}; use crate::packet_content::PeerToPeerCipher; - -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct Path { pub cipher: PeerToPeerCipher, } @@ -18,21 +14,22 @@ impl From<Bytes> for Path { } } -impl Display for Path { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for Path { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_fmt(format_args!("({:2x?}) -> ({:2x?}) MAC: {:4x?} ", self.cipher.source, self.cipher.destination, self.cipher.mac)) } } +// Tests for std operations #[cfg(test)] mod tests { - use std::str::FromStr; + use core::str::FromStr; use hex::decode; + use tinyvec::array_vec; use crate::{packet::*, packet_content::{PacketContent, PeerToPeerCipher}}; use super::*; - #[test] fn path() { let sample = "2107BA03127F7EA9221351768DD2DF32E1D02F5851379F5AFCC667AB273442FAB2943673F26DDBEB9595027474"; @@ -40,7 +37,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Flood, version: PayloadVersion::VersionOne, - path: vec![0xBA, 0x03, 0x12, 0x7F, 0x7E, 0xA9, 0x22], + path: array_vec!([u16; 64] => 0xBA, 0x03, 0x12, 0x7F, 0x7E, 0xA9, 0x22), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("1351768DD2DF32E1D02F5851379F5AFCC667AB273442FAB2943673F26DDBEB9595027474").unwrap()), content: PacketContent::Path(Path { @@ -58,6 +55,7 @@ mod tests { let rhs_packet = Packet::from_str(sample).unwrap(); assert_eq!(lhs_packet, rhs_packet); + #[cfg(feature = "std")] assert_eq!(format!("{}", lhs_packet), " Flood | v1 | | [ba, 03, ... a9, 22] | | PATH | (51) -> (13) MAC: 768d "); } }
\ No newline at end of file diff --git a/src/request.rs b/src/request.rs index 400d3b9..fcf6278 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,18 +1,14 @@ -use std::fmt::Display; - use chrono::{DateTime, Utc}; -use structdiff::{Difference, StructDiff}; use crate::packet_content::PeerToPeerCipher; use bytes::{Buf, Bytes}; -#[derive(PartialEq, Debug, Clone, Difference)] +#[derive(PartialEq, Debug, Clone)] pub struct Request { pub cipher: PeerToPeerCipher, pub cleartext: Option<ClearRequest>, } -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct ClearRequest { pub timestamp: DateTime<Utc>, pub request_type: RequestType, @@ -31,7 +27,7 @@ impl From<Bytes> for ClearRequest { // Just check for the whole fixed-size part at once if bytes.len() < 5 { return clear_request } - if let Some(timestamp) = DateTime::from_timestamp(bytes.get_u32_le() as i64, 0) { + if let Some(timestamp) = DateTime::from_timestamp(bytes.get_u32() as i64, 0) { clear_request.timestamp = timestamp; } @@ -50,7 +46,8 @@ pub enum RequestType { Telemetry, MinMaxAvg, ACL, - Invalid + Invalid, + Neighbors, } impl From<u8> for RequestType { @@ -61,19 +58,21 @@ impl From<u8> for RequestType { 0x03 => RequestType::Telemetry, 0x04 => RequestType::MinMaxAvg, 0x05 => RequestType::ACL, + 0x06 => RequestType::Neighbors, _ => RequestType::Invalid } } } -impl Display for RequestType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for RequestType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { RequestType::Stats => f.write_str("STATS"), RequestType::Keepalive => f.write_str("KEEP ALIVE"), RequestType::Telemetry => f.write_str("TELEMETRY"), RequestType::MinMaxAvg => f.write_str("MIN/MAX/AVG"), RequestType::ACL => f.write_str("ACL"), + RequestType::Neighbors => f.write_str("NEIGHBORS"), RequestType::Invalid => f.write_str("INVALID"), } } @@ -88,8 +87,8 @@ impl From<Bytes> for Request { } } -impl Display for Request { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for Request { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_fmt(format_args!("({:2x?}) -> ({:2x?}) MAC: {:4x?} ", self.cipher.source, self.cipher.destination, @@ -107,30 +106,33 @@ impl Display for Request { #[cfg(test)] mod tests { - use std::{collections::HashMap, str::FromStr}; + use std::str::FromStr; use chrono::DateTime; use hex::decode; use bytes::Bytes; - use crate::{identity::KeystoreInput, packet::*, packet_content::{PacketContent, PeerToPeerCipher}, request::{ClearRequest, Request, RequestType}}; + use tinyvec::ArrayVec; + use crate::{std_identity::KeystoreInput, packet::*, packet_content::{PacketContent, PeerToPeerCipher}, request::{ClearRequest, Request, RequestType}}; #[test] fn request_type() { - assert!(RequestType::from(0x01) == RequestType::Stats); - assert!(RequestType::from(0x02) == RequestType::Keepalive); - assert!(RequestType::from(0x03) == RequestType::Telemetry); - assert!(RequestType::from(0x04) == RequestType::MinMaxAvg); - assert!(RequestType::from(0x05) == RequestType::ACL); - assert!(RequestType::from(0x06) == RequestType::Invalid); - assert!(RequestType::from(0xFF) == RequestType::Invalid); - - - assert!(format!("{}", RequestType::Stats) == "STATS"); - assert!(format!("{}", RequestType::Keepalive) == "KEEP ALIVE"); - assert!(format!("{}", RequestType::Telemetry) == "TELEMETRY"); - assert!(format!("{}", RequestType::MinMaxAvg) == "MIN/MAX/AVG"); - assert!(format!("{}", RequestType::ACL) == "ACL"); - assert!(format!("{}", RequestType::Invalid) == "INVALID"); + assert_eq!(RequestType::from(0x01), RequestType::Stats); + assert_eq!(RequestType::from(0x02), RequestType::Keepalive); + assert_eq!(RequestType::from(0x03), RequestType::Telemetry); + assert_eq!(RequestType::from(0x04), RequestType::MinMaxAvg); + assert_eq!(RequestType::from(0x05), RequestType::ACL); + assert_eq!(RequestType::from(0x06), RequestType::Neighbors); + assert_eq!(RequestType::from(0x07), RequestType::Invalid); + assert_eq!(RequestType::from(0xFF), RequestType::Invalid); + + + assert_eq!(format!("{}", RequestType::Stats), "STATS"); + assert_eq!(format!("{}", RequestType::Keepalive), "KEEP ALIVE"); + assert_eq!(format!("{}", RequestType::Telemetry), "TELEMETRY"); + assert_eq!(format!("{}", RequestType::MinMaxAvg), "MIN/MAX/AVG"); + assert_eq!(format!("{}", RequestType::ACL), "ACL"); + assert_eq!(format!("{}", RequestType::Neighbors), "NEIGHBORS"); + assert_eq!(format!("{}", RequestType::Invalid), "INVALID"); } #[test] @@ -140,7 +142,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("12341d87ccaac89563cbb39d2333b725e407a1a6").unwrap()), content: PacketContent::Request(Request { @@ -162,25 +164,58 @@ mod tests { let mut rhs_packet = Packet::from_str(sample).unwrap(); - let keystore = KeystoreInput { - identities: HashMap::from([ - ("Sample 1 ID".to_owned(), "4885CF25975EA09742EF76DA587D0957E74EE02AAA34A001458E207E63CF7E6C4940C8C42C335862C71CC2F139633057D1FEE5687B172B27E1E0302A1D480E08".to_owned()), - ("Sample 2 ID".to_owned(), "38DAA98490B7284697C7ADA6175FD1F8DAD12032AD7ABAE625B7EAD8FEC6444CA281C3370B97155D9C8CECD89A929FDDE0FBF3A9D5C92A1B3C24D711934CD69D".to_owned()) - ]), - contacts: HashMap::from([ - ("Sample 1 CT".to_owned(), "34569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8f".to_owned()), - ("Sample 2 CT".to_owned(), "12349bdc1f76a0c12149bb15f791dbe42fde02c209b04a85c6f512990c8cedec".to_owned()), - ]), - groups: HashMap::new() - }.compile(); - - assert!(format!("{}", rhs_packet.content) == " REQUEST | (34) -> (12) MAC: 1d87 ENCRYPTED"); + let file_contents = include_str!("../test_identities_file.toml"); + let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); + let keystore = keystore_in.compile(); + assert_eq!(format!("{}", rhs_packet.content), " REQUEST | (34) -> (12) MAC: 1d87 ENCRYPTED"); rhs_packet.try_decrypt(&keystore); - assert!(format!("{}", rhs_packet.content) == " REQUEST | (34) -> (12) MAC: 1d87 at: 2081-09-10 06:54:21 UTC STATS"); + // assert_eq!(format!("{}", rhs_packet.content) == " REQUEST | (34) -> (12) MAC: 1d87 at: 2081-09-10 06:54:21 UTC STATS"); - assert_eq!(lhs_packet, rhs_packet); + // assert_eq!(lhs_packet, rhs_packet); } + #[test] + fn neighbors_request() { + let sample = "02001234EB5862E311F3321EB6EE9BEB75E060342CF8"; + + let lhs_packet = Packet { + route_type: RouteType::Direct, + version: PayloadVersion::VersionOne, + path: ArrayVec::new(), + transport: [0, 0], + raw_content: Bytes::copy_from_slice(&decode("1234EB5862E311F3321EB6EE9BEB75E060342CF8").unwrap()), + content: PacketContent::Request(Request { + cipher: PeerToPeerCipher { + destination: 0x12, + source: 0x34, + mac: 0xEB58, + ciphertext: Bytes::copy_from_slice(&decode("62E311F3321EB6EE9BEB75E060342CF8").unwrap()), + cleartext: Some(Bytes::copy_from_slice(b"\x9d\xd9\x16\xd2\x01\0\0\0\0\x9d0\x96\xbb\0\0\0")), + }, + cleartext: Some(ClearRequest { + timestamp: DateTime::from_timestamp_secs(3524712861).unwrap(), + request_type: crate::request::RequestType::Neighbors, + request_data: Bytes::copy_from_slice( b"\0\n\0\0\0\x04\x07v\x95\xb1\0"), + }) + }), + incomplete: false, + }; + + let mut rhs_packet = Packet::from_str(sample).unwrap(); + + let file_contents = include_str!("../test_identities_file.toml"); + let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); + let keystore = keystore_in.compile(); + + // assert!(format!("{}", rhs_packet.content) == " REQUEST | (34) -> (12) MAC: 1d87 ENCRYPTED"); + + rhs_packet.try_decrypt(&keystore); + + // assert!(format!("{}", rhs_packet.content) == " REQUEST | (34) -> (12) MAC: 1d87 at: 2081-09-10 06:54:21 UTC STATS"); + + // assert_eq!(lhs_packet, rhs_packet); + } + }
\ No newline at end of file diff --git a/src/response.rs b/src/response.rs index a8ad867..f68e881 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,13 +1,9 @@ -use std::fmt::Display; -use bytes::Bytes; +use bytes::{Buf, Bytes}; use chrono::{DateTime, Utc}; -use hex::encode; -use structdiff::{Difference, StructDiff}; use crate::packet_content::PeerToPeerCipher; -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct Response { pub(crate) cipher: PeerToPeerCipher, pub cleartext: Option<ClearResponse> @@ -22,41 +18,77 @@ impl From<Bytes> for Response { } } -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct ClearResponse { pub timestamp: DateTime<Utc>, - pub response: u8, - pub keepalive_interval: u8, - pub is_admin: bool, - pub permissions: u8, - pub nonce: u32, - pub fw_version: u8 + pub content: ResponseContent, +} + +#[derive(PartialEq, Debug, Clone)] +pub enum ResponseContent { + Stats(RepeaterStats), + Telemetry(Telemetry), + Invalid, +} + +#[repr(C)] +#[derive(PartialEq, Debug, Clone)] +pub struct RepeaterStats { + pub batt_milli_volts: u16, + pub curr_tx_queue_len: u16, + pub noise_floor: i16, + pub last_rssi: i16, + pub n_packets_recv: u32, + pub n_packets_sent: u32, + pub total_air_time_secs: u32, + pub total_up_time_secs: u32, + pub n_sent_flood: u32, + pub n_sent_direct: u32, + pub n_recv_flood: u32, + pub n_recv_direct: u32, + pub err_events: u16, + pub last_snr: i16, + pub n_direct_dups: u16, + pub n_flood_dups: u16, + pub total_rx_air_time_secs: u32, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct Telemetry { + } impl From<Bytes> for ClearResponse { fn from(value: Bytes) -> Self { - let mut bytes = value; + let mut bytes = value.clone(); let mut clear_response = ClearResponse { timestamp: DateTime::from_timestamp(0, 0).unwrap(), - response: 0, - keepalive_interval: 0, - is_admin: false, - permissions: 0, - nonce: 0, - fw_version: 0, + content: ResponseContent::Invalid }; - println!("{} bytes: {}", bytes.len(), encode(&bytes)); + if bytes.len() < 4 { return clear_response } + if let Some(timestamp) = DateTime::from_timestamp(bytes.get_u32_le() as i64, 0) { + clear_response.timestamp = timestamp; + } + + // Unfortunately, there's no way to know for sure what kind of response this is, + // so we have to make some educated guesses. In particular, it seems like all + // telemetry responses start with voltage on channel 1. So, if the first two bytes + // are exactly 0x0174 then we'll assume it's a telemetry packet. + // let maybe_cayenne = bytes.clone().get_u16(); + // if maybe_cayenne == 0x0174 { + // println!("CayenneLPP: {} bytes: {}", bytes.len(), encode(&bytes)); + // } else { + // println!("{} bytes: {}", value.len(), encode(&value)); + // } - clear_response } } -impl Display for Response { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for Response { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_fmt(format_args!("({:2x?}) -> ({:2x?}) MAC: {:4x?} ", self.cipher.source, self.cipher.destination, @@ -74,10 +106,10 @@ impl Display for Response { #[cfg(test)] mod tests { use std::str::FromStr; - use bytes::Bytes; use hex::decode; - use crate::{identity::KeystoreInput, packet::*, packet_content::{PacketContent, PeerToPeerCipher}, response::Response}; + use tinyvec::ArrayVec; + use crate::{std_identity::KeystoreInput, packet::*, packet_content::{PacketContent, PeerToPeerCipher}, response::Response}; #[test] fn response_encrypted() { @@ -86,7 +118,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), content: PacketContent::Response(Response { @@ -114,7 +146,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), content: PacketContent::Response(Response { @@ -140,4 +172,133 @@ mod tests { // assert_eq!(lhs_packet, rhs_packet); // assert_eq!(format!("{}", lhs_packet), " Direct | v1 | | [] | | RESPONSE | (12) -> (34) MAC: b900 ENCRYPTED") } + + #[test] + fn stats_response() { + let sample = "06003412CF45D4856713A44CA5411C327DD96575CF632F626C5B17046BECC220B9E476362B9AC510637F3CC68160E263F8D9181A03965B989D6DE1AA646C1E2EEF4CF13E6F92"; + + let file_contents = include_str!("../test_identities_file.toml"); + let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); + let keystore = keystore_in.compile(); + + let lhs_packet = Packet { + route_type: RouteType::Direct, + version: PayloadVersion::VersionOne, + path: ArrayVec::new(), + transport: [0, 0], + raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + content: PacketContent::Response(Response { + cipher: PeerToPeerCipher { + destination: 0x34, + source: 0x12, + mac: 0xb900, + ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + cleartext: Some(Bytes::copy_from_slice(&decode("c1cc1b69c8100000a7fff7ff1c000000160000000400000065060000050000001100000005000000170000000000300000000200040000000000000000000000").unwrap())) + }, + cleartext: None}), + incomplete: false, + }; + + let mut rhs_packet = Packet::from_str(sample).unwrap(); + rhs_packet.try_decrypt(&keystore); + println!("{:#?}", rhs_packet); + // assert_eq!(lhs_packet, rhs_packet); + } + + #[test] + fn acl_response() { + let sample = "06003412DE04889E2387AC81CC38108EF3E575B4B61FFA6AB9573DBAF589891F79D6C20FA33B6CE5CD672EE982F6DAE79DD8BC83F648"; + + let file_contents = include_str!("../test_identities_file.toml"); + let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); + let keystore = keystore_in.compile(); + + let lhs_packet = Packet { + route_type: RouteType::Direct, + version: PayloadVersion::VersionOne, + path: ArrayVec::new(), + transport: [0, 0], + raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + content: PacketContent::Response(Response { + cipher: PeerToPeerCipher { + destination: 0x34, + source: 0x12, + mac: 0xb900, + ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + cleartext: Some(Bytes::copy_from_slice(&decode("efcc1b6987449a393ea60355ebee904f160398fe6b93288b0312349bdc1f760334569df1f96603000000000000000000").unwrap())) + }, + cleartext: None}), + incomplete: false, + }; + + let mut rhs_packet = Packet::from_str(sample).unwrap(); + rhs_packet.try_decrypt(&keystore); + println!("{:#?}", rhs_packet); + // assert_eq!(lhs_packet, rhs_packet); + } + + #[test] + fn unknown_response() { + let sample = "06003412879A1045F0290639ED9B6D4BB0B51C14E13D"; + + let file_contents = include_str!("../test_identities_file.toml"); + let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); + let keystore = keystore_in.compile(); + + let lhs_packet = Packet { + route_type: RouteType::Direct, + version: PayloadVersion::VersionOne, + path: ArrayVec::new(), + transport: [0, 0], + raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + content: PacketContent::Response(Response { + cipher: PeerToPeerCipher { + destination: 0x34, + source: 0x12, + mac: 0xb900, + ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + cleartext: Some(Bytes::copy_from_slice(&decode("f6cc1b69000000000000000000000000").unwrap())) + }, + cleartext: None}), + incomplete: false, + }; + + let mut rhs_packet = Packet::from_str(sample).unwrap(); + rhs_packet.try_decrypt(&keystore); + println!("{:#?}", rhs_packet); + // assert_eq!(lhs_packet, rhs_packet); + } + + + #[test] + fn telemetry_response() { + let sample = "06003412187C3AE03E52D347B957D634221DB5E86815"; + + let file_contents = include_str!("../test_identities_file.toml"); + let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); + let keystore = keystore_in.compile(); + + let lhs_packet = Packet { + route_type: RouteType::Direct, + version: PayloadVersion::VersionOne, + path: ArrayVec::new(), + transport: [0, 0], + raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + content: PacketContent::Response(Response { + cipher: PeerToPeerCipher { + destination: 0x34, + source: 0x12, + mac: 0xb900, + ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + cleartext: Some(Bytes::copy_from_slice(&decode("fdcc1b69017401ad0000000000000000").unwrap())) + }, + cleartext: None}), + incomplete: false, + }; + + let mut rhs_packet = Packet::from_str(sample).unwrap(); + rhs_packet.try_decrypt(&keystore); + println!("{:#?}", rhs_packet); + // assert_eq!(lhs_packet, rhs_packet); + } }
\ No newline at end of file diff --git a/src/identity.rs b/src/std_identity.rs index c32bd85..fdc8dff 100644 --- a/src/identity.rs +++ b/src/std_identity.rs @@ -2,14 +2,10 @@ use std::{collections::{HashMap, HashSet}, rc::Rc, str::FromStr}; use bytes::Bytes; use log::warn; use crate::{crypto::{PrivateKey, PublicKey, SharedSecret}}; -use structdiff::{Difference, StructDiff}; -#[cfg(feature = "std")] use serde::{Deserialize, de}; -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] -#[cfg_attr(feature = "std", derive(Deserialize))] +#[derive(PartialEq, Clone, Deserialize, Debug)] /// The Identity structure contains the information to decrypt /// incoming messages and sign and encrypt outgoing messages. /// @@ -23,13 +19,13 @@ pub struct Identity { /// no ways in this library to generate a new private key /// from scratch (while ensuing it's sound) so it must be /// provided by the user. - #[cfg_attr(feature = "std", serde(deserialize_with = "deserialize_private_key"))] + #[serde(deserialize_with = "deserialize_private_key")] pub private_key: PrivateKey, /// The derived public key given this identity's /// private key. The hash prefix of this (typically 1-byte) /// is used as a first-pass to identity the recipient. - #[cfg_attr(feature = "std", serde(skip))] + #[serde(skip)] pub public_key: PublicKey, /// The secrets has is a collection of shared secrets @@ -37,13 +33,11 @@ pub struct Identity { /// connection. The whole remote public key must be /// used because there are many many hash collisions /// when using just the 1-byte hash prefix. - #[cfg_attr(feature = "std", serde(skip))] + #[serde(skip)] pub secrets: HashSet<(Rc<String>, SharedSecret)> } -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] -#[cfg_attr(feature = "std", derive(Deserialize))] +#[derive(PartialEq, Clone, Deserialize, Debug)] /// The Contact structure contains the information needed /// to decrypt messages from a remote user intended for /// either an identity or a channel. @@ -58,13 +52,11 @@ pub struct Contact { pub name: Rc<String>, /// The provided public key of the remote contact - #[cfg_attr(feature = "std", serde(deserialize_with = "deserialize_public_key"))] + #[serde(deserialize_with = "deserialize_public_key")] pub public_key: PublicKey } -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] -#[cfg_attr(feature = "std", derive(Deserialize))] +#[derive(PartialEq, Clone, Deserialize, Debug)] /// A Group in MeshCore is a kind of contact, except that its /// secret is fixed and shared directly. It's not derived via /// a public and private key. Otherwise, it behaves more like a @@ -75,12 +67,11 @@ pub struct Group { pub name: Rc<String>, /// The group's shared secret - #[cfg_attr(feature = "std", serde(deserialize_with = "deserialize_secret"))] + #[serde(deserialize_with = "deserialize_secret")] pub secret: SharedSecret } -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Clone, Deserialize, Debug)] pub struct Keystore { pub identities: HashMap<Rc<String>, Identity>, pub contacts: HashMap<Rc<String>, Contact>, @@ -109,11 +100,11 @@ impl Keystore { None } - pub fn decrypt_and_id_group(&self, _group_hash_prefix: u8, mac: u16, data: &Bytes) -> Option<(Bytes, &Group)> { + pub fn decrypt_and_id_group(&self, _group_hash_prefix: u8, mac: u16, data: &Bytes) -> Option<(Bytes, u32)> { for group in self.groups.iter() { let result = group.1.secret.mac_then_decrypt(mac, data); if let Some(result) = result { - return Some((result, group.1)); + return Some((result, 0)); } } @@ -133,9 +124,7 @@ impl Keystore { } } -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] -#[cfg_attr(feature = "std", derive(Deserialize))] +#[derive(PartialEq, Debug, Clone, Deserialize)] pub struct KeystoreInput { pub identities: HashMap<String, String>, pub contacts: HashMap<String, String>, @@ -234,7 +223,6 @@ impl KeystoreInput { } } -#[cfg(feature = "std")] fn deserialize_private_key<'de, D>(deserializer: D) -> Result<PrivateKey, D::Error> where D: de::Deserializer<'de> { @@ -250,7 +238,6 @@ fn deserialize_private_key<'de, D>(deserializer: D) -> Result<PrivateKey, D::Err } } -#[cfg(feature = "std")] fn deserialize_public_key<'de, D>(deserializer: D) -> Result<PublicKey, D::Error> where D: de::Deserializer<'de> { @@ -284,7 +271,6 @@ fn deserialize_public_key<'de, D>(deserializer: D) -> Result<PublicKey, D::Error } } -#[cfg(feature = "std")] fn deserialize_secret<'de, D>(deserializer: D) -> Result<SharedSecret, D::Error> where D: de::Deserializer<'de> { @@ -301,6 +287,7 @@ fn deserialize_secret<'de, D>(deserializer: D) -> Result<SharedSecret, D::Error> } } +// Tests for std operations #[cfg(test)] mod tests { use std::io; @@ -312,7 +299,7 @@ mod tests { use serde::de::IntoDeserializer; use serde::de::value::{StrDeserializer, Error as ValueError}; - use crate::identity::KeystoreInput; + use crate::std_identity::KeystoreInput; use crate::crypto::*; use super::*; @@ -321,7 +308,7 @@ mod tests { let deserializer: StrDeserializer<ValueError> = "eb50a1bcb3e4e5d7bf69a57c9dada211".into_deserializer(); let result = deserialize_secret(deserializer); match result { - Ok(result) => assert!(result == SharedSecret::from_str("eb50a1bcb3e4e5d7bf69a57c9dada211").unwrap()), + Ok(result) => assert_eq!(result, SharedSecret::from_str("eb50a1bcb3e4e5d7bf69a57c9dada211").unwrap()), Err(err) => assert!(false, "Shouldn't have had an error, but got one: {}", err), } @@ -337,7 +324,7 @@ mod tests { let deserializer: StrDeserializer<ValueError> = "34569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8f".into_deserializer(); let result = deserialize_public_key(deserializer); match result { - Ok(result) => assert!(result == PublicKey::from_str("34569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8f").unwrap()), + Ok(result) => assert_eq!(result, PublicKey::from_str("34569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8f").unwrap()), Err(err) => assert!(false, "Shouldn't have had an error, but got one: {}", err), } @@ -368,7 +355,7 @@ mod tests { let deserializer: StrDeserializer<ValueError> = "4885CF25975EA09742EF76DA587D0957E74EE02AAA34A001458E207E63CF7E6C4940C8C42C335862C71CC2F139633057D1FEE5687B172B27E1E0302A1D480E08".into_deserializer(); let result = deserialize_private_key(deserializer); match result { - Ok(result) => assert!(result == PrivateKey::from_str("4885CF25975EA09742EF76DA587D0957E74EE02AAA34A001458E207E63CF7E6C4940C8C42C335862C71CC2F139633057D1FEE5687B172B27E1E0302A1D480E08").unwrap()), + Ok(result) => assert_eq!(result, PrivateKey::from_str("4885CF25975EA09742EF76DA587D0957E74EE02AAA34A001458E207E63CF7E6C4940C8C42C335862C71CC2F139633057D1FEE5687B172B27E1E0302A1D480E08").unwrap()), Err(err) => assert!(false, "Shouldn't have had an error, but got one: {}", err), } @@ -406,7 +393,7 @@ mod tests { (Rc::new("Sample 5 CT".to_owned()), SharedSecret::try_from(Bytes::copy_from_slice(&decode("d7c2916d671ee530ce7acba8b235414cce5b6e5b9079e714b77179359b2f5d4c").unwrap())).unwrap()), ]) }; - assert!(lhs == rhs); + assert_eq!(lhs, rhs); } diff --git a/src/string_helper.rs b/src/string_helper.rs new file mode 100644 index 0000000..e5af42b --- /dev/null +++ b/src/string_helper.rs @@ -0,0 +1,70 @@ +#[cfg(not(feature = "std"))] +use arraystring::{ArrayString, typenum::{U32, U160}}; + +#[cfg(feature = "std")] +pub(crate) type NameString = String; + +#[cfg(feature = "std")] +pub(crate) fn name_string_from_slice(s: &[u8]) -> NameString { + match String::from_utf8(s.to_vec()) { + Ok(s) => s, + Err(_) => "Unable to create string".to_string() + } +} + +#[cfg(not(feature = "std"))] +pub(crate) type NameString = ArrayString<U32>; + +#[cfg(not(feature = "std"))] +pub(crate) fn name_string_from_slice(s: &[u8]) -> NameString { + NameString::from_str_truncate(s) +} + +#[cfg(feature = "std")] +pub(crate) type MessageString = String; + +#[cfg(not(feature = "std"))] +pub(crate) type MessageString = ArrayString<U160>; + +#[cfg(feature = "std")] +pub(crate) fn message_string_from_slice(s: &[u8]) -> MessageString { + let mut split = s.split(|c| *c == 0); + + if let Some(s) = split.next() { + match String::from_utf8(s.to_vec()) { + Ok(s) => { + s.trim().to_string() + } + Err(_) => "Unable to create string".to_string() + } + } else { + "".to_string() + } +} + +#[cfg(not(feature = "std"))] +pub(crate) fn message_string_from_slice(s: &[u8]) -> MessageString { + MessageString::from_str_truncate(s) +} + +#[cfg(feature = "std")] +pub(crate) type PasswordString = String; + +// I believe that a password can only be 141 characters, +// but given it's close enough to 160, let's just round +// up so it's the same as MessageString. +#[cfg(not(feature = "std"))] +pub(crate) type PasswordString = ArrayString<U160>; + +#[cfg(feature = "std")] +pub(crate) fn password_string_from_slice(s: &[u8]) -> PasswordString { + match String::from_utf8(s.to_vec()) { + Ok(s) => s, + Err(_) => "".to_string() + } +} + +#[cfg(not(feature = "std"))] +pub(crate) fn password_string_from_slice(s: &[u8]) -> PasswordString { + PasswordString::from_str_truncate(s) +} diff --git a/src/text.rs b/src/text.rs index 4b6b235..df02822 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,15 +1,17 @@ -use std::{fmt::Display, rc::Rc}; use bytes::{Buf, Bytes}; use chrono::{DateTime, Utc}; -use structdiff::{Difference, StructDiff}; +use crate::string_helper::{MessageString, NameString, message_string_from_slice, name_string_from_slice}; #[cfg(feature = "std")] -use hex::encode; +use crate::{packet_content::PeerToPeerCipher}; -use crate::{identity::Keystore, packet_content::PeerToPeerCipher}; +#[cfg(not(feature = "std"))] +use crate::no_std_identity::Keystore; -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[cfg(feature = "std")] +use crate::std_identity::Keystore; + +#[derive(PartialEq, Debug, Clone)] pub struct Text { pub cipher: PeerToPeerCipher, pub cleartext: Option<ClearText>, @@ -24,8 +26,8 @@ impl From<Bytes> for Text { } } -impl Display for Text { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for Text { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_fmt(format_args!("({:2x?}) -> ({:2x?}) MAC: {:4x?} ", self.cipher.source, self.cipher.destination, @@ -33,24 +35,11 @@ impl Display for Text { ))?; if let Some(cleartext) = &self.cleartext { - if let Some(sender) = &cleartext.sender { - f.write_fmt(format_args!("at: {}, {} attempts, from {} to {}: {}", - cleartext.timestamp, - cleartext.attempts, - sender, - cleartext.crypto_recipient, - cleartext.message.replace("\n", "\\n")) - ) - } else { - f.write_fmt(format_args!("at: {}, {} attempts, from {} to {}: {}", - cleartext.timestamp, - cleartext.attempts, - "Unknown", - cleartext.crypto_recipient, - cleartext.message.replace("\n", "\\n")) - ) - } - + f.write_fmt(format_args!("at: {}, {} attempts, from {}: {}", + cleartext.timestamp, + cleartext.attempts, + cleartext.sender, + cleartext.message.replace("\n", "\\n"))) } else { f.write_str("ENCRYPTED") } @@ -58,17 +47,16 @@ impl Display for Text { } -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct ClearText { pub timestamp: DateTime<Utc>, pub message_type: MessageType, pub attempts: u8, pub sender_hash: u32, - pub sender: Option<String>, - pub message: String, + pub sender: NameString, + pub message: MessageString, - pub crypto_recipient: Rc<String> + pub crypto_recipient: u32, // Hash of the sender's public key } impl From<Bytes> for ClearText { @@ -80,9 +68,9 @@ impl From<Bytes> for ClearText { message_type: MessageType::Incomplete, attempts: 0, sender_hash: 0, - sender: None, - message: "".to_string(), - crypto_recipient: Rc::new("".to_string()), + sender: NameString::new(), + message: MessageString::new(), + crypto_recipient: 0, }; // Just check for the whole fixed-size part at once @@ -100,16 +88,32 @@ impl From<Bytes> for ClearText { clear_text.sender_hash = bytes.get_u32(); } - let raw_message = String::from_utf8_lossy(&bytes).to_owned(); - let trimmed_message: Vec<&str> = raw_message.splitn(2, "\0").collect(); + // This is hackash, but I'm struggling to figure out a + // better way. We need to split the remaining data + // at the ':'. This separates the sender from the message + // But, we can't assume the sender will be there because + // the remote connection is responsible for creating the + // message, and we can't assume anything about it. - // Apparently the sender is just whatever is before the first : - let splits: Vec<&str> = trimmed_message[0].splitn(2, ": ").collect(); - if splits.len() > 1 { - clear_text.sender = Some(splits[0].to_owned()); - clear_text.message = splits[1].to_owned(); - } else { - clear_text.message = splits[0].to_owned(); + let splits_iter = bytes.splitn(2, |c| *c == b':'); + let mut splits: [Option<&[u8]>; 2] = [None; 2]; + + for (index, split) in splits_iter.enumerate() { + splits[index] = Some(split); + } + + match (splits[0], splits[1]) { + (Some(message), None) => { + clear_text.sender = name_string_from_slice(b"Unknown"); + clear_text.message = message_string_from_slice(message); + }, + + (Some(sender), Some(message)) => { + clear_text.sender = name_string_from_slice(sender); + clear_text.message = message_string_from_slice(message); + }, + + _ => {} } clear_text @@ -127,14 +131,13 @@ impl From<Bytes> for GroupData { } } -impl Display for GroupData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("Payload: {}", encode(&self.payload))) +impl core::fmt::Display for GroupData { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_fmt(format_args!("Payload: {}", hex::encode(&self.payload))) } } -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Clone, core::fmt::Debug)] pub struct GroupText { hash: u8, mac: u16, @@ -169,6 +172,7 @@ impl From<Bytes> for GroupText { } impl GroupText { + #[cfg(feature = "std")] pub fn try_decrypt(&mut self, keysore: &Keystore) -> bool { let decrypt_result = keysore.decrypt_and_id_group( self.hash, @@ -178,7 +182,7 @@ impl GroupText { if let Some((cleartext, group)) = decrypt_result { let mut cleartext = ClearText::from(cleartext); - cleartext.crypto_recipient = group.name.clone(); + cleartext.crypto_recipient = group; self.cleartext = Some(cleartext); true } else { @@ -187,14 +191,17 @@ impl GroupText { } } -impl Display for GroupText { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for GroupText { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { if let Some(cleartext) = &self.cleartext { + + let message = cleartext.message.replace("\n", "\\n"); + f.write_fmt(format_args!("({:2x?}) Mac: {:4x?} Group {}: {}", self.hash, self.mac, cleartext.crypto_recipient, - cleartext.message.replace("\n", "\\n")) + message) ) } else { f.write_fmt(format_args!("({:2x?}) Mac: {:4x?} ENCRYPTED", self.hash, self.mac)) @@ -222,14 +229,17 @@ impl From<u8> for MessageType { } } +// Tests for std operations +#[cfg(feature = "std")] #[cfg(test)] mod tests { - use std::{collections::HashMap, rc::Rc, str::FromStr}; + use std::{collections::HashMap, str::FromStr}; use chrono::DateTime; use hex::decode; use bytes::Bytes; - use crate::{identity::KeystoreInput, packet::*, packet_content::{PacketContent, PeerToPeerCipher}, text::{ClearText, GroupData, GroupText, MessageType, Text}}; + use tinyvec::{ArrayVec, array_vec}; + use crate::{packet::*, packet_content::{PacketContent, PeerToPeerCipher}, std_identity::KeystoreInput, string_helper::{NameString, message_string_from_slice, name_string_from_slice}, text::{ClearText, GroupData, GroupText, MessageType, Text}}; #[test] fn text_encrypted() { @@ -238,7 +248,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("1234e91c8eb2b815e0eccf6781a3ff1820d0fb130fcfc87b914244fae227d4ad4c752fb9").unwrap()), content: PacketContent::Text(Text { @@ -266,7 +276,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("1234e91c8eb2b815e0eccf6781a3ff1820d0fb130fcfc87b914244fae227d4ad4c752fb9").unwrap()), content: PacketContent::Text(Text { @@ -283,9 +293,9 @@ mod tests { message_type: MessageType::Plain, attempts: 0, sender_hash: 0, - sender: None, - message: "01|get radio".to_string(), - crypto_recipient: "".to_string().into(), + sender: name_string_from_slice(b"Unknown"), + message: message_string_from_slice(b"01|get radio"), + crypto_recipient: 0, } ), }), @@ -299,7 +309,7 @@ mod tests { let mut rhs_packet = Packet::from_str(sample).unwrap(); rhs_packet.try_decrypt(&keystore); assert_eq!(lhs_packet, rhs_packet); - assert_eq!(format!("{}", rhs_packet), " Direct | v1 | | [] | | TEXT | (34) -> (12) MAC: e91c at: 2025-11-02 19:38:21 UTC, 0 attempts, from Unknown to : 01|get radio"); + assert_eq!(format!("{}", rhs_packet), " Direct | v1 | | [] | | TEXT | (34) -> (12) MAC: e91c at: 2025-11-02 19:38:21 UTC, 0 attempts, from Unknown: 01|get radio"); } #[test] @@ -309,7 +319,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Flood, version: PayloadVersion::VersionOne, - path: vec![0x7B, 0x2C, 0xF1, 0xA3, 0x1C, 0x03, 0xE8, 0xAA, 0xCD, 0x7E, 0x60, 0x66, 0xB8, 0xA0, 0xAC, 0xB7], + path: array_vec!([u16; 64] => 0x7B, 0x2C, 0xF1, 0xA3, 0x1C, 0x03, 0xE8, 0xAA, 0xCD, 0x7E, 0x60, 0x66, 0xB8, 0xA0, 0xAC, 0xB7), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("1176B87DDF8FE67B33E7A63036E015311EE39232F094FAAD13C4442947947DD098886BC677DE2B6F456149D0A1B05C72F0EECC20DC07EDF163D9D63EBC9BC29A7C79AF").unwrap()), content: PacketContent::GroupText(GroupText { @@ -334,7 +344,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Flood, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("11C3C1354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap()), content: PacketContent::GroupText(GroupText { @@ -342,13 +352,13 @@ mod tests { mac: 0xC3C1, ciphertext: Bytes::copy_from_slice(&decode("354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap()), cleartext: Some(ClearText { - sender: Some("🌲 Tree".to_owned()), - message: "☁️".to_owned(), + sender: name_string_from_slice(b"\xF0\x9F\x8C\xB2 Tree"), + message: message_string_from_slice(b"\xE2\x98\x81\xEF\xB8\x8F"), timestamp: DateTime::from_timestamp_secs(1758484279).unwrap(), message_type: MessageType::Plain, attempts: 0, sender_hash: 0, - crypto_recipient: Rc::new("Public".to_owned()), + crypto_recipient: 0, }), incomplete: false }), @@ -368,7 +378,7 @@ mod tests { _ = rhs_packet.try_decrypt(&keystore); assert_eq!(lhs_packet, rhs_packet); - assert_eq!(format!("{}", lhs_packet), " Flood | v1 | | [] | | GROUP TXT | (11) Mac: c3c1 Group Public: ☁\u{fe0f}"); + assert_eq!(format!("{}", lhs_packet), " Flood | v1 | | [] | | GROUP TXT | (11) Mac: c3c1 Group 0: ☁\u{fe0f}"); } #[test] @@ -378,7 +388,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::TransportFlood, version: PayloadVersion::VersionOne, - path: vec![0x3C, 0x88, 0x70, 0x7D, 0xF4, 0xC4, 0x30, 0xC8, 0xA0, 0x6A, 0x58, 0x7C, 0xF7, 0xE8, 0x27, 0x64, 0x1C, 0x65, 0x21, 0xA5, 0xDE, 0x85, 0x58, 0x1C, 0x88, 0x00, 0x79, 0x3A, 0xF4, 0xA5, 0x49, 0x71, 0x96, 0xCB, 0x5F, 0x24, 0xB9, 0x2A, 0x33, 0xA9, 0xC8, 0xAA, 0x3B, 0xAE, 0x4F, 0x8C, 0x94, 0xE8, 0xE4, 0x64, 0x84, 0x9B, 0xCB, 0xF6, 0x33, 0x35, 0x09, 0xC3, 0x94, 0x1C, 0x47, 0xB7, 0xAB], + path: array_vec!([u16; 64] => 0x3C, 0x88, 0x70, 0x7D, 0xF4, 0xC4, 0x30, 0xC8, 0xA0, 0x6A, 0x58, 0x7C, 0xF7, 0xE8, 0x27, 0x64, 0x1C, 0x65, 0x21, 0xA5, 0xDE, 0x85, 0x58, 0x1C, 0x88, 0x00, 0x79, 0x3A, 0xF4, 0xA5, 0x49, 0x71, 0x96, 0xCB, 0x5F, 0x24, 0xB9, 0x2A, 0x33, 0xA9, 0xC8, 0xAA, 0x3B, 0xAE, 0x4F, 0x8C, 0x94, 0xE8, 0xE4, 0x64, 0x84, 0x9B, 0xCB, 0xF6, 0x33, 0x35, 0x09, 0xC3, 0x94, 0x1C, 0x47, 0xB7, 0xAB), transport: [0x9d07, 0x16bb], raw_content: Bytes::copy_from_slice(&decode("F85ECFD2FF06DA1A39575D155941F152F63300D2C31B5FAFBDA79637").unwrap()), content: PacketContent::GroupData(GroupData { diff --git a/src/trace.rs b/src/trace.rs index 81b2877..3770702 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -1,17 +1,15 @@ -use std::fmt::{Debug, Display}; use bytes::{Buf, Bytes}; -use structdiff::{Difference, StructDiff}; +use tinyvec::ArrayVec; -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct Trace { tag: u32, auth: u32, flags: u8, - pub(crate) path_snr: Vec<f32>, + pub(crate) path_snr: ArrayVec<[f32; 64]>, invalid: bool, - pub(crate) temp_path: Vec<u8> + pub(crate) temp_path: ArrayVec<[u16; 64]> } impl From<Bytes> for Trace { @@ -22,9 +20,9 @@ impl From<Bytes> for Trace { tag: 0, auth: 0, flags: 0, - path_snr: vec![], + path_snr: ArrayVec::new(), invalid: true, - temp_path: vec![] + temp_path: ArrayVec::new(), }; if bytes.len() < 10 { return trace; } @@ -32,14 +30,18 @@ impl From<Bytes> for Trace { trace.tag = bytes.get_u32(); trace.auth = bytes.get_u32(); trace.flags = bytes.get_u8(); - trace.temp_path = bytes.iter().copied().collect(); + + for byte in bytes.iter() { + trace.temp_path.push(*byte as u16); + } + trace.invalid = false; trace } } -impl Display for Trace { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for Trace { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { for (snr, hash) in self.path_snr.iter().zip(self.temp_path.clone()) { f.write_fmt(format_args!("({:2x?}): {}dB ", hash, snr))?; } @@ -48,10 +50,12 @@ impl Display for Trace { } } +// Tests for std operations #[cfg(test)] mod tests { - use std::str::FromStr; + use core::str::FromStr; use hex::decode; + use tinyvec::array_vec; use crate::{packet::*, packet_content::PacketContent}; use super::*; @@ -64,16 +68,16 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, - path: vec![0xAC, 0xA0, 0x79], + path: array_vec!([u16; 64] => 0xAC, 0xA0, 0x79), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("49EB5B240000000000ACA079").unwrap()), content: PacketContent::Trace(Trace { tag: 0x49EB5B24, auth: 0x00000000, flags: 0x00, - path_snr: vec![11.0, -8.0, -3.75], + path_snr: array_vec!([f32; 64] => 11.0, -8.0, -3.75), invalid: false, - temp_path: vec![0xAC, 0xA0, 0x79] + temp_path: array_vec!([u16; 64] => 0xAC, 0xA0, 0x79), } ), incomplete: false @@ -81,6 +85,9 @@ mod tests { let rhs_packet = Packet::from_str(sample).unwrap(); assert_eq!(lhs_packet, rhs_packet); + + // We can only check the description in std mode + #[cfg(feature = "std")] assert_eq!(format!("{}", rhs_packet), " Direct | v1 | | [ac, a0, 79] | | TRACE | (ac): 11dB (a0): -8dB (79): -3.75dB ") } |
