aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorWill Dillon <william@housedillon.com>2025-12-04 17:07:12 +0000
committerWill Dillon <william@housedillon.com>2025-12-04 17:07:12 +0000
commitbf21ebaaae44b267cfefc87eee9608c09aab96b7 (patch)
tree67234092d3c85a9c58d1fa9ca51337ac480ebfb7 /src
parentBetter than 90% everywhere. (diff)
downloadmeshcore-rs-bf21ebaaae44b267cfefc87eee9608c09aab96b7.tar.gz
meshcore-rs-bf21ebaaae44b267cfefc87eee9608c09aab96b7.zip
Getting closer to no-std being done
Diffstat (limited to 'src')
-rw-r--r--src/ack.rs16
-rw-r--r--src/advert.rs128
-rw-r--r--src/anon_req.rs53
-rw-r--r--src/bin/packet_analyzer.rs2
-rw-r--r--src/crypto.rs117
-rw-r--r--src/lib.rs20
-rw-r--r--src/multipart.rs17
-rw-r--r--src/no_std_identity.rs108
-rw-r--r--src/packet.rs166
-rw-r--r--src/packet_content.rs71
-rw-r--r--src/path.rs18
-rw-r--r--src/request.rs125
-rw-r--r--src/response.rs219
-rw-r--r--src/std_identity.rs (renamed from src/identity.rs)49
-rw-r--r--src/string_helper.rs70
-rw-r--r--src/text.rs144
-rw-r--r--src/trace.rs37
17 files changed, 929 insertions, 431 deletions
diff --git a/src/ack.rs b/src/ack.rs
index 7b30205..236ddc4 100644
--- a/src/ack.rs
+++ b/src/ack.rs
@@ -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");
}
diff --git a/src/lib.rs b/src/lib.rs
index dfd777f..621dcf4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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 ")
}