aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorWill Dillon <william@housedillon.com>2025-11-07 14:19:22 +0000
committerWill Dillon <william@housedillon.com>2025-11-07 14:19:22 +0000
commitc9103391c3baf2e43e071b7899a45132c95d2683 (patch)
tree385269e6e5a6875e272bff0373d00f5803943397 /src
parentMoving back to laptop (diff)
downloadmeshcore-rs-c9103391c3baf2e43e071b7899a45132c95d2683.tar.gz
meshcore-rs-c9103391c3baf2e43e071b7899a45132c95d2683.zip
Decryption is working!
Diffstat (limited to 'src')
-rw-r--r--src/crypto.rs211
-rw-r--r--src/identity.rs30
-rw-r--r--src/lib.rs4
-rw-r--r--src/packet.rs813
-rw-r--r--src/packet_content.rs897
5 files changed, 1097 insertions, 858 deletions
diff --git a/src/crypto.rs b/src/crypto.rs
index 31d2cb7..8215f94 100644
--- a/src/crypto.rs
+++ b/src/crypto.rs
@@ -1,4 +1,5 @@
use std::str::FromStr;
+use ed25519_dalek::{VerifyingKey, hazmat::ExpandedSecretKey};
use tokio_util::bytes::{Buf, BufMut, Bytes, BytesMut};
use hex::{decode, decode_to_slice, encode};
@@ -6,12 +7,9 @@ use hex::{decode, decode_to_slice, encode};
// but I can't seem to figure out how to pull it out of this
// stack of software
#[allow(deprecated)]
-use aes::cipher::{
- BlockCipher, BlockEncrypt, BlockDecrypt,
- generic_array::GenericArray,
-};
+use aes::cipher::{BlockEncrypt, BlockDecrypt, generic_array::GenericArray};
use curve25519_dalek::MontgomeryPoint;
-use aes::Aes256;
+use aes::Aes128;
use sha2::{Sha256};
use hmac::{Hmac, Mac};
type HmacSha256 = Hmac<Sha256>;
@@ -21,35 +19,37 @@ type HmacSha256 = Hmac<Sha256>;
pub enum MeshcoreCryptoError {
KeyLengthError,
TryFromSliceError,
- HexDecodeError
+ HexDecodeError,
+ KeyCreationError,
}
-pub struct PrivateKey(x25519_dalek::StaticSecret);
+#[derive(PartialEq)]
+pub struct PrivateKey(ExpandedSecretKey);
-impl PartialEq for PrivateKey {
- fn eq(&self, other: &Self) -> bool {
- self.0.as_bytes() == other.0.as_bytes()
- }
-
- fn ne(&self, other: &Self) -> bool {
- !self.eq(other)
+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 std::fmt::Debug for PrivateKey {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_tuple("PrivateKey").field(&encode(self.0.as_bytes())).finish()
+impl Clone for PrivateKey {
+ fn clone(&self) -> Self {
+ Self(ExpandedSecretKey {
+ scalar: self.0.scalar,
+ hash_prefix: self.0.hash_prefix
+ })
}
}
#[derive(PartialEq, Clone)]
-pub struct PublicKey(x25519_dalek::PublicKey);
+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()
}
}
+#[derive(Clone)]
pub struct SharedSecret(MontgomeryPoint);
impl PartialEq for SharedSecret {
@@ -82,6 +82,11 @@ impl FromStr for SharedSecret {
}
impl SharedSecret {
+ fn get_key(&self) -> &[u8; 16] {
+ // Safety: The size of the slice ensures that this will never be wrong.
+ (&self.0.as_bytes()[0..16]).try_into().unwrap()
+ }
+
pub fn new_from_group_secret(bytes: Bytes) -> Self {
// The group secret is 16-bytes of key with the last 16-bytes set to zero.
let mut group_secret = BytesMut::from(bytes);
@@ -94,23 +99,23 @@ impl SharedSecret {
SharedSecret(MontgomeryPoint(slice))
}
- pub fn get_hmac(&self, ciphertext: Bytes) -> Result<u16, MeshcoreCryptoError> {
- if let Ok(mut mac) = HmacSha256::new_from_slice(self.0.as_bytes()) {
- mac.update(&ciphertext);
- let result = mac.finalize();
- let mut bytes = Bytes::copy_from_slice(&result.into_bytes());
-
- Ok(bytes.get_u16())
- } else {
- Err(MeshcoreCryptoError::KeyLengthError)
- }
+ pub fn get_hmac(&self, ciphertext: &Bytes) -> u16 {
+ // At this point, we're sure that the key is an appropriate size for
+ // the Hmac. So, I don't think we should complicate the API with making
+ // this failable.
+ let mut mac = HmacSha256::new_from_slice(self.0.as_bytes())
+ .expect("Programming error in hmac");
+ mac.update(&ciphertext);
+ let result = mac.finalize();
+ let mut bytes = Bytes::copy_from_slice(&result.into_bytes());
+ bytes.get_u16()
}
- pub fn decrypt(&self, ciphertext: Bytes) -> Result<Bytes, MeshcoreCryptoError> {
+ pub fn decrypt(&self, ciphertext: &Bytes) -> Bytes {
use aes::cipher::KeyInit;
- if let Ok(aes) = Aes256::new_from_slice(self.0.as_bytes()) {
- let mut text = BytesMut::from(ciphertext);
+ if let Ok(aes) = Aes128::new_from_slice(self.get_key()) {
+ let mut text = BytesMut::from(ciphertext.clone());
// The decryption function works on a 16-byte block of data.
// we need to break the input ciphertext into blocks of this size
@@ -139,16 +144,17 @@ impl SharedSecret {
// Drop the padding
text.truncate(length);
- Ok(Bytes::from(text))
+ Bytes::from(text)
} else {
- Err(MeshcoreCryptoError::KeyLengthError)
+ assert!(false, "Key length error in decrypt");
+ Bytes::new()
}
}
pub fn encrypt(&self, plaintext: Bytes) -> Result<Bytes, MeshcoreCryptoError> {
use aes::cipher::KeyInit;
- if let Ok(aes) = Aes256::new_from_slice(self.0.as_bytes()) {
+ if let Ok(aes) = Aes128::new_from_slice(self.get_key()) {
let mut text = BytesMut::from(plaintext);
// The decryption function works on a 16-byte block of data.
@@ -185,7 +191,7 @@ impl SharedSecret {
// This is just for creating placeholders
impl Default for PublicKey {
fn default() -> Self {
- PublicKey(x25519_dalek::PublicKey::from([0_u8; 32]))
+ PublicKey(VerifyingKey::from_bytes(&[0_u8; 32]).unwrap())
}
}
@@ -203,21 +209,31 @@ impl TryFrom<Bytes> for PublicKey {
return Err(MeshcoreCryptoError::TryFromSliceError)
}
- Ok(PublicKey(x25519_dalek::PublicKey::from(slice)))
+ if let Ok(key) = VerifyingKey::from_bytes(&slice) {
+ Ok(PublicKey(key))
+ } else {
+ Err(MeshcoreCryptoError::KeyCreationError)
+ }
}
}
impl FromStr for PublicKey {
- type Err = hex::FromHexError;
+ type Err = MeshcoreCryptoError;
fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
- let hex = decode(hex_str)?;
-
- if let Ok(bytes) = TryInto::<[u8; 32]>::try_into(hex) {
- Ok(PublicKey(x25519_dalek::PublicKey::from(bytes)))
+ if let Ok(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))
+ } else {
+ Err(MeshcoreCryptoError::KeyCreationError)
+ }
+ } else {
+ Err(MeshcoreCryptoError::KeyLengthError)
+ }
} else {
- Err(hex::FromHexError::InvalidStringLength)
+ Err(MeshcoreCryptoError::HexDecodeError)
}
}
}
@@ -226,9 +242,9 @@ impl FromStr for PrivateKey {
type Err = MeshcoreCryptoError;
fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
- if let Ok(mut hex) = decode(hex_str) {
- if let Ok(bytes) = TryInto::<[u8; 32]>::try_into(hex.split_off(32)) {
- Ok(PrivateKey(x25519_dalek::StaticSecret::from(bytes)))
+ if let Ok(hex) = decode(hex_str) {
+ if let Ok(bytes) = TryInto::<[u8; 64]>::try_into(hex) {
+ Ok(PrivateKey(ExpandedSecretKey::from_bytes(&bytes)))
} else {
Err(MeshcoreCryptoError::HexDecodeError)
}
@@ -240,23 +256,30 @@ impl FromStr for PrivateKey {
impl From<&PrivateKey> for PublicKey {
fn from(key: &PrivateKey) -> Self {
- let key = x25519_dalek::PublicKey::from(&key.0);
- PublicKey(key)
+ println!("Scalar: {}", encode(key.0.scalar.to_bytes()));
+ // let key = key.0.verifying_key();
+ let key = (key.0.scalar * curve25519_dalek::constants::ED25519_BASEPOINT_POINT).compress();
+ PublicKey(VerifyingKey::from_bytes(key.as_bytes()).unwrap())
}
}
impl PrivateKey {
pub fn create_secret(&self, other: &PublicKey) -> SharedSecret {
- SharedSecret(MontgomeryPoint(*self.0.diffie_hellman(&other.0).as_bytes()))
+ SharedSecret(self.0.scalar * other.0.to_montgomery())
}
}
impl SharedSecret {
- pub fn mac_then_decrypt(&self, mac: [u8; 2], data: Bytes) -> Option<Bytes> {
- None
+ pub fn mac_then_decrypt(&self, mac: u16, data: Bytes) -> Option<Bytes> {
+ // Get the MAC of the message and key to check vailidity
+ let our_mac = self.get_hmac(&data);
+ if our_mac != mac { return None }
+
+ // Attempt to decrypt the packet itself
+ Some(self.decrypt(&data))
}
- pub fn encrypt_then_mac(&self, data: Bytes) -> Option<([u8; 2], Bytes)> {
+ pub fn encrypt_then_mac(&self, _data: Bytes) -> Option<(u16, Bytes)> {
None
}
}
@@ -278,7 +301,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(x25519_dalek::PublicKey::from(slice))) == public_key);
+ assert!(Ok(PublicKey(VerifyingKey::from_bytes(&slice).unwrap())) == public_key);
}
#[test]
@@ -292,14 +315,14 @@ mod tests {
#[test]
fn shared_secret() {
// We'll make a public/private pair for alice and bob and make sure the shared secret is the same and expected
- let alice_private = PrivateKey::from_str("58f5052c13275c8a3f4863a082555fed7ea08b9dec2eb00dd86f6b5412174458").unwrap();
- let bob_private = PrivateKey::from_str("586c4cc29635af5865abe4f231bafbd373969725493c07271e02c7fec8ff3b5f").unwrap();
+ let alice_private = PrivateKey::from_str("4885CF25975EA09742EF76DA587D0957E74EE02AAA34A001458E207E63CF7E6C4940C8C42C335862C71CC2F139633057D1FEE5687B172B27E1E0302A1D480E08").unwrap();
+ let bob_private = PrivateKey::from_str("38DAA98490B7284697C7ADA6175FD1F8DAD12032AD7ABAE625B7EAD8FEC6444CA281C3370B97155D9C8CECD89A929FDDE0FBF3A9D5C92A1B3C24D711934CD69D").unwrap();
let alice_public = PublicKey::from(&alice_private);
let bob_public = PublicKey::from(&bob_private);
- assert!(alice_public.0.as_bytes().to_vec() == decode("f673f022cf0466a95db086adf19084d4cf06e56fdabb202dc833bab227a78561").unwrap());
- assert!( bob_public.0.as_bytes().to_vec() == decode("47cb9867a8df6d2637eabe399b70f58f30f250da3911a4add950e7fac6aa3e39").unwrap());
+ assert!(alice_public.0.as_bytes().to_vec() == decode("34569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8f").unwrap());
+ assert!( 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()));
@@ -307,7 +330,7 @@ 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("ddbb8b5e70099817db83b48caa73f44a120b12a26072e5c29a023f16a2cd8b2a").unwrap());
+ assert!(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()));
@@ -324,7 +347,7 @@ mod tests {
// 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).unwrap();
+ let mac = group_secret.get_hmac(&sample_data);
assert!(0xC3C1 == mac);
}
@@ -332,15 +355,15 @@ mod tests {
fn decrypt() {
let ciphertext = Bytes::copy_from_slice(&decode("354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap());
let secret = SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("8b3387e9c5cdea6ac9e5edbaa115cd72").unwrap()));
- let cleartext = secret.decrypt(ciphertext).unwrap();
+ let cleartext = secret.decrypt(&ciphertext);
println!("Cleartext: {}", encode(&cleartext));
}
#[test]
fn decrypt_online_example() {
- let ciphertext = Bytes::copy_from_slice(&decode("95CCD78CB3FE2DE57774D552558AE954").unwrap());
- let secret = SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("FB05C2FF72C9E7F931E5FA1232AFA962EC04367D016F10493F82884823A6B529").unwrap()));
- let cleartext = secret.decrypt(ciphertext).unwrap();
+ let ciphertext = Bytes::copy_from_slice(&decode("9A1FD57EDFE7E4369F9FD9420C48FFAD").unwrap());
+ let secret = SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("949E911CA6A6196275FF319B28C3A143").unwrap()));
+ let cleartext = secret.decrypt(&ciphertext);
let vec = cleartext.to_vec();
let string = String::from_utf8_lossy(&vec);
assert!("Hello my world!!" == string);
@@ -349,9 +372,9 @@ mod tests {
#[test]
fn encrypt_online_example() {
let plaintext = Bytes::copy_from_slice("Meshcore!".as_bytes());
- let secret = SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("C8ED5DFD3DC316A7FA22450D6DC7097F52255F91E4E30F90F8CA3D25AC3E90E4").unwrap()));
+ let secret = SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("44A6F78DAD2E54D73A32CDE3ECAA9E75").unwrap()));
let ciphertext = secret.encrypt(plaintext).unwrap();
- assert!(ciphertext == decode("FE952E7104F0AC1CFC42CA5EE3783961").unwrap());
+ assert!(ciphertext == decode("62374852B6A11405A081F87356C88861").unwrap());
}
#[test]
@@ -389,48 +412,28 @@ mod tests {
#[test]
fn aes_test() {
use aes::Aes128;
- use aes::cipher::{
- BlockCipher, BlockEncrypt, BlockDecrypt, KeyInit,
- generic_array::GenericArray,
- };
-
- let key = GenericArray::from([0u8; 16]);
- let mut block = GenericArray::from([42u8; 16]);
+ #[allow(deprecated)]
+ use aes::cipher::{BlockEncrypt, BlockDecrypt, KeyInit};
// Initialize cipher
- let cipher = Aes128::new(&key);
+ let key: [u8; 16] = decode("0A1BB8C05063D9941F0F1019D001B743").unwrap().try_into().unwrap();
+ let cipher = Aes128::new(&key.into());
- let block_copy = block.clone();
+ let message: [u8; 16] = decode("7D69AB072E09AF74EBA47EB95BF00AE3").unwrap().try_into().unwrap();
+ let message_copy = message.clone();
// Encrypt block in-place
- println!("Before: {}", encode(&block));
- cipher.encrypt_block(&mut block);
- println!("Crypted: {}", encode(&block));
- // And decrypt it back
- cipher.decrypt_block(&mut block);
- println!("Decrypted: {}", encode(&block));
-
- assert_eq!(block, block_copy);
-
- // Implementation supports parallel block processing. Number of blocks
- // processed in parallel depends in general on hardware capabilities.
- // This is achieved by instruction-level parallelism (ILP) on a single
- // CPU core, which is differen from multi-threaded parallelism.
- let mut blocks = [block; 100];
- cipher.encrypt_blocks(&mut blocks);
-
- for block in blocks.iter_mut() {
- cipher.decrypt_block(block);
- assert_eq!(block, &block_copy);
- }
+ println!("Before: {}", encode(&message));
- // `decrypt_blocks` also supports parallel block processing.
- cipher.decrypt_blocks(&mut blocks);
+ cipher.encrypt_block(&mut message.into());
- for block in blocks.iter_mut() {
- cipher.encrypt_block(block);
- assert_eq!(block, &block_copy);
- }
+ println!("Crypted: {}", encode(&message));
+
+ cipher.decrypt_block(&mut message.into());
+
+ println!("Decrypted: {}", encode(&message));
+
+ assert_eq!(message, message_copy);
}
#[test]
@@ -450,4 +453,14 @@ mod tests {
assert_eq!(alice_shared_secret.as_bytes(), bob_shared_secret.as_bytes());
}
+
+ #[test]
+ fn mac_then_decrypt() {
+ let group_secret = SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("8b3387e9c5cdea6ac9e5edbaa115cd72").unwrap()));
+ let sample_data = Bytes::copy_from_slice(&decode("354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap());
+ let mac = 0xC3C1;
+
+ let cleartext = group_secret.mac_then_decrypt(mac, sample_data).unwrap();
+ assert!(cleartext == decode("3757d06800f09f8cb220547265653a20e29881efb88f00000000000000000000").unwrap());
+ }
} \ No newline at end of file
diff --git a/src/identity.rs b/src/identity.rs
new file mode 100644
index 0000000..ea88164
--- /dev/null
+++ b/src/identity.rs
@@ -0,0 +1,30 @@
+use std::collections::{HashMap};
+
+use x25519_dalek::PublicKey;
+
+use crate::crypto::{PrivateKey, SharedSecret};
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct Identity {
+ pub name: String,
+ pub private_key: PrivateKey
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct Contact {
+ pub name: String,
+ pub public_key: PublicKey
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct Group {
+ pub name: String,
+ pub secret: SharedSecret
+}
+
+pub struct Keystore {
+ identities: HashMap<String, Identity>,
+ contacts: HashMap<String, Contact>,
+ groups: HashMap<String, Group>
+}
+
diff --git a/src/lib.rs b/src/lib.rs
index 817b464..20b8e6b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,5 @@
pub mod crypto;
-pub mod packet;
+pub mod identity;
+pub mod packet;
+pub mod packet_content;
diff --git a/src/packet.rs b/src/packet.rs
index cd403fd..c4e3809 100644
--- a/src/packet.rs
+++ b/src/packet.rs
@@ -1,21 +1,20 @@
use std::str::FromStr;
-use chrono::{DateTime, Local, Utc};
use hex::decode;
use tokio_util::bytes::{Buf, Bytes};
use structdiff::{Difference, StructDiff};
-use crate::crypto::{PublicKey, SharedSecret};
+use crate::{crypto::SharedSecret, packet_content::PacketContent};
#[derive(PartialEq, Debug, Clone, Difference)]
#[difference(expose)]
-struct Packet {
- route_type: RouteType,
- version: PayloadVersion,
- path: Vec<u16>,
- transport: [u16; 2],
- raw_content: Bytes,
- content: PacketContent,
- incomplete: bool
+pub struct Packet {
+ pub route_type: RouteType,
+ pub version: PayloadVersion,
+ pub path: Vec<u16>,
+ pub transport: [u16; 2],
+ pub raw_content: Bytes,
+ pub content: PacketContent,
+ pub incomplete: bool
}
impl FromStr for Packet {
@@ -134,14 +133,48 @@ impl Default for Packet {
}
impl Packet {
- fn try_decrypt(&mut self, key: SharedSecret) -> bool {
+ pub fn try_decrypt(&mut self, key: SharedSecret) -> bool {
- false
+ match self.content {
+ // Encrypted packet types
+ PacketContent::Path(ref mut path) => {
+ path.cipher.try_decrypt(key)
+ },
+ PacketContent::Request(ref mut request) => {
+ request.cipher.try_decrypt(key)
+ },
+ PacketContent::Response(ref mut response) => {
+ response.cipher.try_decrypt(key)
+ },
+ PacketContent::Text(ref mut text) => {
+ text.cipher.try_decrypt(key)
+ },
+
+ PacketContent::AnonReq(ref mut anon_req) => {
+ let decrypt_reault = key.mac_then_decrypt(
+ anon_req.mac,
+ anon_req.ciphertext.clone()
+ );
+
+ if let Some(cleartext) = decrypt_reault {
+ true
+ } else {
+ false
+ }
+ }
+
+ PacketContent::GroupText(ref mut group_text) => {
+ group_text.try_decrypt(key)
+ }
+
+ // None of the other packets implement any encryption
+ _ => false
+ }
}
}
#[derive(PartialEq, Debug, Clone)]
-enum RouteType {
+pub enum RouteType {
TransportFlood,
Flood,
Direct,
@@ -162,7 +195,7 @@ impl From<u8> for RouteType {
}
#[derive(PartialEq, Debug, Clone)]
-enum PayloadVersion {
+pub enum PayloadVersion {
VersionOne,
VersionTwo,
VersionThree,
@@ -185,406 +218,20 @@ impl From<u8> for PayloadVersion {
}
}
-#[derive(PartialEq, Debug, Clone, Difference)]
-#[difference(expose)]
-enum PacketContent {
- Request(Request),
- Response(Response),
- Text(Text),
- Ack(Ack),
- Advert(Advert),
- GroupText(GroupText),
- GroupData(GroupData),
- AnonReq(AnonReq),
- Path(Path),
- Trace(Trace),
- Multipart(MultiPart),
- Raw(Raw),
-
- Invalid,
-}
-
-impl PacketContent {
- fn new(header: u8, bytes: Bytes) -> PacketContent {
- // Specialize based on the Payload Type from the header
- match (header & 0x3C) >> 2 {
- 0x00 => PacketContent::Request( Request::from(bytes)),
- 0x01 => PacketContent::Response( Response::from(bytes)),
- 0x02 => PacketContent::Text( Text::from(bytes)),
- 0x03 => PacketContent::Ack( Ack::from(bytes)),
- 0x04 => PacketContent::Advert( Advert::from(bytes)),
- 0x05 => PacketContent::GroupText(GroupText::from(bytes)),
- 0x06 => PacketContent::GroupData(GroupData::from(bytes)),
- 0x07 => PacketContent::AnonReq( AnonReq::from(bytes)),
- 0x08 => PacketContent::Path( Path::from(bytes)),
- 0x09 => PacketContent::Trace( Trace::from(bytes)),
- 0x0A => PacketContent::Multipart(MultiPart::from(bytes)),
- 0x0F => PacketContent::Raw( Raw { bytes }),
-
- _ => PacketContent::Invalid
- }
- }
-}
-
-#[derive(PartialEq, Debug, Clone)]
-enum NodeType {
- Chat,
- Room,
- Repeater,
- Sensor,
- Invalid
-}
-
-impl From<u8> for NodeType {
- fn from(value: u8) -> NodeType {
- match value & 0x07 {
- 0x00 => NodeType::Invalid,
- 0x01 => NodeType::Chat,
- 0x02 => NodeType::Repeater,
- 0x03 => NodeType::Room,
- 0x04 => NodeType::Sensor,
- _ => NodeType::Invalid
- }
- }
-}
-
-#[derive(PartialEq, Debug, Clone, Difference)]
-#[difference(expose)]
-struct PeerToPeerCipher {
- destination: u8,
- source: u8,
- mac: u16,
- ciphertext: Bytes
-}
-
-impl From<Bytes> for PeerToPeerCipher {
- fn from(value: Bytes) -> Self {
- let mut bytes = value;
-
- let mut response = PeerToPeerCipher {
- destination: 0x00,
- source: 0x00,
- mac: 0x0000,
- ciphertext: Bytes::new()
- };
-
- // Just check for the whole fixed-size part at once
- if bytes.len() < 4 { return response }
- response.destination = bytes.get_u8();
- response.source = bytes.get_u8();
- response.mac = bytes.get_u16();
-
- response.ciphertext = bytes;
-
- response
- }
-}
-
-#[derive(PartialEq, Debug, Clone)]
-struct Request {
- cipher: PeerToPeerCipher,
-}
-
-impl From<Bytes> for Request {
- fn from(value: Bytes) -> Self {
-
-
- Request {
- cipher: PeerToPeerCipher::from(value)
- }
- }
-}
-
-#[derive(PartialEq, Debug, Clone, Difference)]
-#[difference(expose)]
-struct Response {
- cipher: PeerToPeerCipher,
-}
-
-impl From<Bytes> for Response {
- fn from(value: Bytes) -> Self {
-
-
- Response {
- cipher: PeerToPeerCipher::from(value)
- }
- }
-}
-
-#[derive(PartialEq, Debug, Clone)]
-struct Text {
- cipher: PeerToPeerCipher,
-}
-
-impl From<Bytes> for Text {
- fn from(value: Bytes) -> Self {
- Text { cipher: PeerToPeerCipher::from(value) }
- }
-}
-
-#[derive(PartialEq, Debug, Clone)]
-struct Ack {
- checksum: u32
-}
-
-impl From<Bytes> for Ack {
- fn from(value: Bytes) -> Self {
- let mut bytes = value;
- Ack { checksum: bytes.get_u32() }
- }
-}
-
-#[derive(PartialEq, Debug, Clone, Difference)]
-#[difference(expose)]
-struct Advert {
- pub public_key: PublicKey,
- pub timestamp: DateTime<Utc>,
- pub signature: [u8; 64],
-
- pub node_type: NodeType,
- pub latitude: Option<f32>,
- pub longitude: Option<f32>,
- pub feature1: Option<u16>,
- pub feature2: Option<u16>,
- pub name: String
-}
-
-impl From<Bytes> for Advert {
- fn from(value: Bytes) -> Self {
- let mut bytes = value;
-
- let mut advert = Advert {
- 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(),
- };
-
- if bytes.len() < 32 { return advert }
- if let Ok(key) = PublicKey::try_from(bytes.split_to(32)) {
- advert.public_key = key;
- } else {
- return advert
- }
-
- if bytes.len() < 4 { return advert }
- if let Some(time) = DateTime::from_timestamp(bytes.get_u32_le() as i64, 0) {
- advert.timestamp = time;
- }
-
- if bytes.len() < 64 { return advert }
- _ = bytes.try_copy_to_slice(&mut advert.signature);
-
- if bytes.is_empty() { return advert }
- let flags = bytes.get_u8();
- advert.node_type = NodeType::from(flags);
-
- if (flags & 0x10) != 0 {
- // The location is 8 bytes (4 each for lat and lon)
- if bytes.len() < 8 { return advert }
-
- advert.latitude = Some(bytes.get_i32_le() as f32 / 1_000_000.0);
- advert.longitude = Some(bytes.get_i32_le() as f32 / 1_000_000.0);
- }
-
- if (flags & 0x20) != 0 {
- // Feature 1 is 2 bytes when it's included
- if bytes.len() < 2 { return advert }
- advert.feature1 = Some(bytes.get_u16_le());
- }
-
- if (flags & 0x40) != 0 {
- // Feature 2 is the same
- if bytes.len() < 2 { return advert }
- advert.feature2 = Some(bytes.get_u16_le());
- }
-
- // Lastly the name... The flag is a little
- // irrelevant because the only different 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();
- }
-
- advert
- }
-}
-
-#[derive(PartialEq, Debug, Clone, Difference)]
-#[difference(expose)]
-struct GroupText {
- hash: u8,
- mac: u16,
- ciphertext: Bytes,
-
- cleartext: Option<ClearGroupText>,
-
- incomplete: bool
-}
-
-#[derive(PartialEq, Debug, Clone, Difference)]
-#[difference(expose)]
-struct ClearGroupText {
- sender: String,
- message: String,
- timestamp: DateTime<Utc>
-}
-
-impl From<Bytes> for GroupText {
- fn from(value: Bytes) -> Self {
- let mut bytes = value;
-
- let mut group_text = GroupText {
- hash: 0x00,
- mac: 0x0000,
- ciphertext: Bytes::new(),
- cleartext: None,
- incomplete: true,
- };
-
- // Just check for the whole fixed-size part at once
- if bytes.len() < 3 { return group_text }
- group_text.hash = bytes.get_u8();
- group_text.mac = bytes.get_u16();
-
- group_text.ciphertext = bytes;
- group_text.incomplete = false;
- group_text
- }
-}
-
-#[derive(PartialEq, Debug, Clone)]
-struct GroupData {
- payload: Bytes
-}
-
-impl From<Bytes> for GroupData {
- fn from(value: Bytes) -> Self {
- GroupData { payload: value }
- }
-}
-
-#[derive(PartialEq, Debug, Clone, Difference)]
-#[difference(expose)]
-struct AnonReq {
- destination: u8,
- public_key: PublicKey,
- mac: u16,
- ciphertext: Bytes,
- incomplete: bool
-}
-
-impl From<Bytes> for AnonReq {
- fn from(value: Bytes) -> Self {
- let mut bytes = value;
-
- let mut anon_req = AnonReq {
- destination: 0x00,
- public_key: PublicKey::default(),
- mac: 0x0000,
- ciphertext: Bytes::new(),
- incomplete: false,
- };
-
- if bytes.is_empty() { return anon_req; }
- anon_req.destination = bytes.get_u8();
-
- if bytes.len() < 32 { return anon_req; }
- if let Ok(pub_key) = PublicKey::try_from(bytes.split_to(32)) {
- anon_req.public_key = pub_key;
- }
-
- if bytes.len() < 2 { return anon_req; }
- anon_req.mac = bytes.get_u16();
-
- anon_req.ciphertext = bytes;
- anon_req.incomplete = false;
- anon_req
- }
-}
-
-#[derive(PartialEq, Debug, Clone)]
-struct Path {
- cipher: PeerToPeerCipher,
-}
-
-impl From<Bytes> for Path {
- fn from(value: Bytes) -> Self {
- Path {
- cipher: PeerToPeerCipher::from(value)
- }
- }
-}
-
-#[derive(PartialEq, Debug, Clone)]
-struct Trace {
- tag: u32,
- auth: u32,
- flags: u8,
- path_snr: Vec<f32>,
- invalid: bool,
-
- temp_path: Vec<u8>
-}
-
-impl From<Bytes> for Trace {
- fn from(value: Bytes) -> Self {
- let mut bytes = value;
-
- let mut trace = Trace {
- tag: 0,
- auth: 0,
- flags: 0,
- path_snr: vec![],
- invalid: true,
- temp_path: vec![]
- };
-
- if bytes.len() < 10 { return trace; }
-
- trace.tag = bytes.get_u32();
- trace.auth = bytes.get_u32();
- trace.flags = bytes.get_u8();
- trace.temp_path = bytes.iter().copied().collect();
- trace.invalid = false;
- trace
+#[allow(dead_code)]
+pub(crate) fn print_compare(lhs: Packet, rhs: Packet) -> String {
+ let mut output_string = format!("Left hand side: \n{:#?}\nRight hand side: \n{:#?}\nDifferences: \n", lhs, rhs);
+
+ for diff in lhs.diff(&rhs) {
+ output_string.push_str(&format!("{:#?}", diff));
}
-}
-
-#[derive(PartialEq, Debug, Clone)]
-struct MultiPart {
- payload: Bytes
-}
-
-impl From<Bytes> for MultiPart {
- fn from(value: Bytes) -> Self {
- MultiPart { payload: value }
- }
-}
-
-#[derive(PartialEq, Debug, Clone)]
-struct Raw {
- bytes: Bytes
+ output_string
}
#[cfg(test)]
mod tests {
- use chrono::DateTime;
-
+ use crate::packet_content::{PacketContent, Raw};
use super::*;
#[test]
@@ -613,30 +260,6 @@ mod tests {
}
#[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));
- }
-
- fn print_compare(lhs: Packet, rhs: Packet) -> String {
- let mut output_string = format!("Left hand side: \n{:#?}\nRight hand side: \n{:#?}\nDifferences: \n", lhs, rhs);
-
- for diff in lhs.diff(&rhs) {
- output_string.push_str(&format!("{:#?}", diff));
- }
- output_string
- }
-
- #[test]
fn packet() {
// Check the hex decode errors
assert!(Err(hex::FromHexError::InvalidHexCharacter { c: 's', index: 0 }) == Packet::from_str("s0"));
@@ -682,330 +305,4 @@ mod tests {
let rhs_packet = Packet::from_str("3F0201040305060708090A10111213141516171819").unwrap();
assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
}
-
- #[test]
- fn advert_with_lat_lon() {
- // Real sample packet from the air
- let sample = "110c015f7e9b60661da0f2671512460728508c17ef336412a223144d3a623215162682045c44fef7241af0161923d3af0769d2d976b687a506dd5325ef526bf3eb52ae687277fcbde9969a5b0087e0eb0f7c1760a50c6a88bec13cc30a2a9b681d713166515e3bbc2bc27f20c0e4d7b67e08910c29dc02c468b8f8484f574c";
-
- let mut signature_slice = [0 as u8; 64];
- hex::decode_to_slice("d2d976b687a506dd5325ef526bf3eb52ae687277fcbde9969a5b0087e0eb0f7c1760a50c6a88bec13cc30a2a9b681d713166515e3bbc2bc27f20c0e4d7b67e08", &mut signature_slice).unwrap();
-
- 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],
- transport: [0x00, 0x00],
- raw_content: Bytes::copy_from_slice(&decode("460728508c17ef336412a223144d3a623215162682045c44fef7241af0161923d3af0769d2d976b687a506dd5325ef526bf3eb52ae687277fcbde9969a5b0087e0eb0f7c1760a50c6a88bec13cc30a2a9b681d713166515e3bbc2bc27f20c0e4d7b67e08910c29dc02c468b8f8484f574c").unwrap()),
- content: PacketContent::Advert(Advert {
- public_key: PublicKey::from_str("460728508c17ef336412a223144d3a623215162682045c44fef7241af0161923").unwrap(),
- timestamp: DateTime::from_timestamp(1762111443, 0).unwrap(),
- signature: signature_slice,
- node_type: NodeType::Chat,
- latitude: Some(47.98286),
- longitude: Some(-122.132286),
- feature1: None,
- feature2: None,
- name: "HOWL".to_owned(),
- }),
- incomplete: false,
- };
- let rhs_packet = Packet::from_str(sample).unwrap();
-
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn advert() {
- // Another on-air sample
- let sample = "12007890b8573a6ba4a05b173d6ccfdfa73ac8ec4a12bf3c745ace636e1d191e132a4eb407695601f50907999735f3699a3c73ace2d8acc385ed209606abef6b914a346fbb88648c540ae794586b4d54eb08b473c8571a00c6b8d3dbcc77699afa169811fa098168707578373335";
-
- let mut signature_slice = [0 as u8; 64];
- hex::decode_to_slice("5601f50907999735f3699a3c73ace2d8acc385ed209606abef6b914a346fbb88648c540ae794586b4d54eb08b473c8571a00c6b8d3dbcc77699afa169811fa09", &mut signature_slice).unwrap();
-
- let lhs_packet = Packet {
- route_type: RouteType::Direct,
- version: PayloadVersion::VersionOne,
- path: vec![],
- transport: [0x00, 0x00],
- raw_content: Bytes::copy_from_slice(&decode("7890b8573a6ba4a05b173d6ccfdfa73ac8ec4a12bf3c745ace636e1d191e132a4eb407695601f50907999735f3699a3c73ace2d8acc385ed209606abef6b914a346fbb88648c540ae794586b4d54eb08b473c8571a00c6b8d3dbcc77699afa169811fa098168707578373335").unwrap()),
- content: PacketContent::Advert(Advert {
- public_key: PublicKey::from_str("7890b8573a6ba4a05b173d6ccfdfa73ac8ec4a12bf3c745ace636e1d191e132a").unwrap(),
- timestamp: DateTime::from_timestamp(1762112590, 0).unwrap(),
- signature: signature_slice,
- node_type: NodeType::Chat,
- latitude: None,
- longitude: None,
- feature1: None,
- feature2: None,
- name: "hpux735".to_owned(),
- }),
- incomplete: false,
- };
-
- let rhs_packet = Packet::from_str(sample).unwrap();
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn response() {
- let sample = "06003412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92";
-
- let lhs_packet = Packet {
- route_type: RouteType::Direct,
- version: PayloadVersion::VersionOne,
- path: vec![],
- 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())
- }}),
- incomplete: false,
- };
-
- let rhs_packet = Packet::from_str(sample).unwrap();
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn request() {
- let sample = "020012341d87ccaac89563cbb39d2333b725e407a1a6";
-
- let lhs_packet = Packet {
- route_type: RouteType::Direct,
- version: PayloadVersion::VersionOne,
- path: vec![],
- transport: [0, 0],
- raw_content: Bytes::copy_from_slice(&decode("12341d87ccaac89563cbb39d2333b725e407a1a6").unwrap()),
- content: PacketContent::Request(Request {
- cipher: PeerToPeerCipher {
- destination: 0x12,
- source: 0x34,
- mac: 0x1d87,
- ciphertext: Bytes::copy_from_slice(&decode("ccaac89563cbb39d2333b725e407a1a6").unwrap())
- }}),
- incomplete: false,
- };
-
- let rhs_packet = Packet::from_str(sample).unwrap();
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn text() {
- let sample = "0a001234e91c8eb2b815e0eccf6781a3ff1820d0fb130fcfc87b914244fae227d4ad4c752fb9";
-
- let lhs_packet = Packet {
- route_type: RouteType::Direct,
- version: PayloadVersion::VersionOne,
- path: vec![],
- transport: [0, 0],
- raw_content: Bytes::copy_from_slice(&decode("1234e91c8eb2b815e0eccf6781a3ff1820d0fb130fcfc87b914244fae227d4ad4c752fb9").unwrap()),
- content: PacketContent::Text(Text {
- cipher: PeerToPeerCipher {
- destination: 0x12,
- source: 0x34,
- mac: 0xe91c,
- ciphertext: Bytes::copy_from_slice(&decode("8eb2b815e0eccf6781a3ff1820d0fb130fcfc87b914244fae227d4ad4c752fb9").unwrap())
- }}),
- incomplete: false,
- };
-
- let rhs_packet = Packet::from_str(sample).unwrap();
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn ack() {
- let sample = "0D0E78B5561B03CD70DA326066B8A0CF24F3214D";
-
- 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],
- transport: [0, 0],
- raw_content: Bytes::copy_from_slice(&decode("24F3214D").unwrap()),
- content: PacketContent::Ack(Ack {
- checksum: 0x24F3214D
- }),
- incomplete: false
- };
-
- let rhs_packet = Packet::from_str(sample).unwrap();
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn trace() {
- let sample = "26032CE0F149EB5B240000000000ACA079";
-
- let lhs_packet = Packet {
- route_type: RouteType::Direct,
- version: PayloadVersion::VersionOne,
- path: vec![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],
- invalid: false,
- temp_path: vec![]
- }
- ),
- incomplete: false
- };
-
- let rhs_packet = Packet::from_str(sample).unwrap();
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn group_text() {
- let sample = "15107B2CF1A31C03E8AACD7E6066B8A0ACB71176B87DDF8FE67B33E7A63036E015311EE39232F094FAAD13C4442947947DD098886BC677DE2B6F456149D0A1B05C72F0EECC20DC07EDF163D9D63EBC9BC29A7C79AF";
-
- 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],
- transport: [0, 0],
- raw_content: Bytes::copy_from_slice(&decode("1176B87DDF8FE67B33E7A63036E015311EE39232F094FAAD13C4442947947DD098886BC677DE2B6F456149D0A1B05C72F0EECC20DC07EDF163D9D63EBC9BC29A7C79AF").unwrap()),
- content: PacketContent::GroupText(GroupText {
- hash: 0x11,
- mac: 0x76B8,
- ciphertext: Bytes::copy_from_slice(&decode("7DDF8FE67B33E7A63036E015311EE39232F094FAAD13C4442947947DD098886BC677DE2B6F456149D0A1B05C72F0EECC20DC07EDF163D9D63EBC9BC29A7C79AF").unwrap()),
- cleartext: None, // "Just need some more high repeaters".to_owned()
- incomplete: false
- }),
- incomplete: false
- };
-
- let rhs_packet = Packet::from_str(sample).unwrap();
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn group_text_decrypted() {
- let sample = "150011C3C1354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D";
-
- let lhs_packet = Packet {
- route_type: RouteType::Flood,
- version: PayloadVersion::VersionOne,
- path: vec![],
- transport: [0, 0],
- raw_content: Bytes::copy_from_slice(&decode("11C3C1354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap()),
- content: PacketContent::GroupText(GroupText {
- hash: 0x11,
- mac: 0xC3C1,
- ciphertext: Bytes::copy_from_slice(&decode("354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap()),
- cleartext: Some(ClearGroupText {
- sender: "🌲 Tree".to_owned(),
- message: "☁️".to_owned(),
- timestamp: DateTime::from_timestamp_secs(1758484279).unwrap()
- }),
- incomplete: false
- }),
- incomplete: false
- };
-
- let mut rhs_packet = Packet::from_str(sample).unwrap();
- _ = rhs_packet.try_decrypt(SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("8b3387e9c5cdea6ac9e5edbaa115cd72").unwrap())));
-
- // assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn multipart() {
- let sample = "2A0013E8C59624";
-
- let lhs_packet = Packet {
- route_type: RouteType::Direct,
- version: PayloadVersion::VersionOne,
- path: vec![],
- transport: [0, 0],
- raw_content: Bytes::copy_from_slice(&decode("13E8C59624").unwrap()),
- content: PacketContent::Multipart(MultiPart {
- payload: Bytes::copy_from_slice(&decode("13E8C59624").unwrap())
- }),
- incomplete: false
- };
-
- let rhs_packet = Packet::from_str(sample).unwrap();
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn group_data() {
- let sample = "18079DBB163F3C88707DF4C430C8A06A587CF7E827641C6521A5DE85581C8800793AF4A5497196CB5F24B92A33A9C8AA3BAE4F8C94E8E464849BCBF6333509C3941C47B7ABF85ECFD2FF06DA1A39575D155941F152F63300D2C31B5FAFBDA79637";
-
- 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],
- transport: [0x9d07, 0x16bb],
- raw_content: Bytes::copy_from_slice(&decode("F85ECFD2FF06DA1A39575D155941F152F63300D2C31B5FAFBDA79637").unwrap()),
- content: PacketContent::GroupData(GroupData {
- payload: Bytes::copy_from_slice(&decode("F85ECFD2FF06DA1A39575D155941F152F63300D2C31B5FAFBDA79637").unwrap())
- }),
- incomplete: false
- };
-
- let rhs_packet = Packet::from_str(sample).unwrap();
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn anon_req() {
- let sample = "1d001234569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8fd2db15a7138098557cc291928b4358fa7522ddd41d35c99fb78f0def2b3e673d73d2";
-
- let lhs_packet = Packet {
- route_type: RouteType::Flood,
- version: PayloadVersion::VersionOne,
- path: vec![],
- transport: [0, 0],
- raw_content: Bytes::copy_from_slice(&decode("1234569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8fd2db15a7138098557cc291928b4358fa7522ddd41d35c99fb78f0def2b3e673d73d2").unwrap()),
- content: PacketContent::AnonReq(AnonReq {
- destination: 0x12,
- public_key: PublicKey::try_from(Bytes::copy_from_slice(&decode("34569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8f").unwrap())).unwrap(),
- mac: 0xd2db,
- ciphertext: Bytes::copy_from_slice(&decode("15a7138098557cc291928b4358fa7522ddd41d35c99fb78f0def2b3e673d73d2").unwrap()),
- incomplete: false
- }),
- incomplete: false
- };
-
- let rhs_packet = Packet::from_str(sample).unwrap();
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
- #[test]
- fn path() {
- let sample = "2107BA03127F7EA9221351768DD2DF32E1D02F5851379F5AFCC667AB273442FAB2943673F26DDBEB9595027474";
-
- let lhs_packet = Packet {
- route_type: RouteType::Flood,
- version: PayloadVersion::VersionOne,
- path: vec![0xBA, 0x03, 0x12, 0x7F, 0x7E, 0xA9, 0x22],
- transport: [0, 0],
- raw_content: Bytes::copy_from_slice(&decode("1351768DD2DF32E1D02F5851379F5AFCC667AB273442FAB2943673F26DDBEB9595027474").unwrap()),
- content: PacketContent::Path(Path {
- cipher: PeerToPeerCipher {
- destination: 0x13,
- source: 0x51,
- mac: 0x768D,
- ciphertext: Bytes::copy_from_slice(&decode("D2DF32E1D02F5851379F5AFCC667AB273442FAB2943673F26DDBEB9595027474").unwrap())
- }
- }),
- incomplete: false
- };
-
- let rhs_packet = Packet::from_str(sample).unwrap();
- assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
- }
-
} \ No newline at end of file
diff --git a/src/packet_content.rs b/src/packet_content.rs
new file mode 100644
index 0000000..3897f2d
--- /dev/null
+++ b/src/packet_content.rs
@@ -0,0 +1,897 @@
+use chrono::{DateTime, Local, Utc};
+use tokio_util::bytes::{Buf, Bytes};
+use structdiff::{Difference, StructDiff};
+use crate::crypto::{PublicKey, SharedSecret};
+
+#[derive(PartialEq, Debug, Clone, Difference)]
+#[difference(expose)]
+pub enum PacketContent {
+ Request(Request),
+ Response(Response),
+ Text(Text),
+ Ack(Ack),
+ Advert(Advert),
+ GroupText(GroupText),
+ GroupData(GroupData),
+ AnonReq(AnonReq),
+ Path(Path),
+ Trace(Trace),
+ Multipart(MultiPart),
+ Raw(Raw),
+
+ Invalid,
+}
+
+impl PacketContent {
+ pub fn new(header: u8, bytes: Bytes) -> PacketContent {
+ // Specialize based on the Payload Type from the header
+ match (header & 0x3C) >> 2 {
+ 0x00 => PacketContent::Request( Request::from(bytes)),
+ 0x01 => PacketContent::Response( Response::from(bytes)),
+ 0x02 => PacketContent::Text( Text::from(bytes)),
+ 0x03 => PacketContent::Ack( Ack::from(bytes)),
+ 0x04 => PacketContent::Advert( Advert::from(bytes)),
+ 0x05 => PacketContent::GroupText(GroupText::from(bytes)),
+ 0x06 => PacketContent::GroupData(GroupData::from(bytes)),
+ 0x07 => PacketContent::AnonReq( AnonReq::from(bytes)),
+ 0x08 => PacketContent::Path( Path::from(bytes)),
+ 0x09 => PacketContent::Trace( Trace::from(bytes)),
+ 0x0A => PacketContent::Multipart(MultiPart::from(bytes)),
+ 0x0F => PacketContent::Raw( Raw { bytes }),
+
+ _ => PacketContent::Invalid
+ }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub enum NodeType {
+ Chat,
+ Room,
+ Repeater,
+ Sensor,
+ Invalid
+}
+
+impl From<u8> for NodeType {
+ fn from(value: u8) -> NodeType {
+ match value & 0x07 {
+ 0x00 => NodeType::Invalid,
+ 0x01 => NodeType::Chat,
+ 0x02 => NodeType::Repeater,
+ 0x03 => NodeType::Room,
+ 0x04 => NodeType::Sensor,
+ _ => NodeType::Invalid
+ }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone, Difference)]
+#[difference(expose)]
+pub struct PeerToPeerCipher {
+ destination: u8,
+ source: u8,
+ mac: u16,
+ ciphertext: Bytes,
+ cleartext: Option<Bytes>
+}
+
+impl From<Bytes> for PeerToPeerCipher {
+ fn from(value: Bytes) -> Self {
+ let mut bytes = value;
+
+ let mut response = PeerToPeerCipher {
+ destination: 0x00,
+ source: 0x00,
+ mac: 0x0000,
+ ciphertext: Bytes::new(),
+ cleartext: None
+ };
+
+ // Just check for the whole fixed-size part at once
+ if bytes.len() < 4 { return response }
+ response.destination = bytes.get_u8();
+ response.source = bytes.get_u8();
+ response.mac = bytes.get_u16();
+
+ response.ciphertext = bytes;
+
+ response
+ }
+}
+
+impl PeerToPeerCipher {
+ pub fn try_decrypt(&mut self, key: SharedSecret) -> bool {
+ let decrypt = key.mac_then_decrypt(self.mac, self.ciphertext.clone());
+
+ if let Some(cleartext) = decrypt {
+ self.cleartext = Some(cleartext);
+ true
+ } else {
+ false
+ }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct Request {
+ pub(crate) cipher: PeerToPeerCipher,
+}
+
+impl From<Bytes> for Request {
+ fn from(value: Bytes) -> Self {
+
+
+ Request {
+ cipher: PeerToPeerCipher::from(value)
+ }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone, Difference)]
+#[difference(expose)]
+pub struct Response {
+ pub(crate) cipher: PeerToPeerCipher,
+}
+
+impl From<Bytes> for Response {
+ fn from(value: Bytes) -> Self {
+
+
+ Response {
+ cipher: PeerToPeerCipher::from(value)
+ }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct Text {
+ pub(crate) cipher: PeerToPeerCipher,
+}
+
+impl From<Bytes> for Text {
+ fn from(value: Bytes) -> Self {
+ Text { cipher: PeerToPeerCipher::from(value) }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct Ack {
+ checksum: u32
+}
+
+impl From<Bytes> for Ack {
+ fn from(value: Bytes) -> Self {
+ let mut bytes = value;
+ Ack { checksum: bytes.get_u32() }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone, Difference)]
+#[difference(expose)]
+pub struct Advert {
+ pub public_key: PublicKey,
+ pub timestamp: DateTime<Utc>,
+ pub signature: [u8; 64],
+
+ pub node_type: NodeType,
+ pub latitude: Option<f32>,
+ pub longitude: Option<f32>,
+ pub feature1: Option<u16>,
+ pub feature2: Option<u16>,
+ pub name: String
+}
+
+impl From<Bytes> for Advert {
+ fn from(value: Bytes) -> Self {
+ let mut bytes = value;
+
+ let mut advert = Advert {
+ 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(),
+ };
+
+ if bytes.len() < 32 { return advert }
+ if let Ok(key) = PublicKey::try_from(bytes.split_to(32)) {
+ advert.public_key = key;
+ } else {
+ return advert
+ }
+
+ if bytes.len() < 4 { return advert }
+ if let Some(time) = DateTime::from_timestamp(bytes.get_u32_le() as i64, 0) {
+ advert.timestamp = time;
+ }
+
+ if bytes.len() < 64 { return advert }
+ _ = bytes.try_copy_to_slice(&mut advert.signature);
+
+ if bytes.is_empty() { return advert }
+ let flags = bytes.get_u8();
+ advert.node_type = NodeType::from(flags);
+
+ if (flags & 0x10) != 0 {
+ // The location is 8 bytes (4 each for lat and lon)
+ if bytes.len() < 8 { return advert }
+
+ advert.latitude = Some(bytes.get_i32_le() as f32 / 1_000_000.0);
+ advert.longitude = Some(bytes.get_i32_le() as f32 / 1_000_000.0);
+ }
+
+ if (flags & 0x20) != 0 {
+ // Feature 1 is 2 bytes when it's included
+ if bytes.len() < 2 { return advert }
+ advert.feature1 = Some(bytes.get_u16_le());
+ }
+
+ if (flags & 0x40) != 0 {
+ // Feature 2 is the same
+ if bytes.len() < 2 { return advert }
+ advert.feature2 = Some(bytes.get_u16_le());
+ }
+
+ // Lastly the name... The flag is a little
+ // irrelevant because the only different 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();
+ }
+
+ advert
+ }
+}
+
+#[derive(PartialEq, Debug, Clone, Difference)]
+#[difference(expose)]
+pub struct GroupText {
+ hash: u8,
+ mac: u16,
+ ciphertext: Bytes,
+
+ cleartext: Option<ClearText>,
+
+ incomplete: bool
+}
+
+impl From<Bytes> for GroupText {
+ fn from(value: Bytes) -> Self {
+ let mut bytes = value;
+
+ let mut group_text = GroupText {
+ hash: 0x00,
+ mac: 0x0000,
+ ciphertext: Bytes::new(),
+ cleartext: None,
+ incomplete: true,
+ };
+
+ // Just check for the whole fixed-size part at once
+ if bytes.len() < 3 { return group_text }
+ group_text.hash = bytes.get_u8();
+ group_text.mac = bytes.get_u16();
+
+ group_text.ciphertext = bytes;
+ group_text.incomplete = false;
+ group_text
+ }
+}
+
+impl GroupText {
+ pub fn try_decrypt(&mut self, key: SharedSecret) -> bool {
+ let decrypt_reault = key.mac_then_decrypt(
+ self.mac,
+ self.ciphertext.clone()
+ );
+
+ if let Some(cleartext) = decrypt_reault {
+ self.cleartext = Some(ClearText::from(cleartext));
+
+ true
+ } else {
+ false
+ }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub enum MessageType {
+ Plain,
+ CLI,
+ SignedPlain,
+ Invalid,
+ Incomplete,
+}
+
+impl From<u8> for MessageType {
+ fn from(value: u8) -> Self {
+ match (value & 0xFC) >> 3 {
+ 0x00 => MessageType::Plain,
+ 0x01 => MessageType::CLI,
+ 0x02 => MessageType::SignedPlain,
+ _ => MessageType::Invalid
+ }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone, Difference)]
+#[difference(expose)]
+pub struct ClearText {
+ timestamp: DateTime<Utc>,
+ message_type: MessageType,
+ attempts: u8,
+ sender_hash: u32,
+ sender: Option<String>,
+ message: String,
+}
+
+impl From<Bytes> for ClearText {
+ fn from(value: Bytes) -> Self {
+ let mut bytes = value;
+
+ let mut clear_text = ClearText {
+ timestamp: DateTime::from_timestamp(0, 0).unwrap(),
+ message_type: MessageType::Incomplete,
+ attempts: 0,
+ sender_hash: 0,
+ sender: None,
+ message: "".to_string(),
+ };
+
+ // Just check for the whole fixed-size part at once
+ if bytes.len() < 5 { return clear_text }
+ if let Some(timestamp) = DateTime::from_timestamp(bytes.get_u32_le() as i64, 0) {
+ clear_text.timestamp = timestamp;
+ }
+
+ let flags = bytes.get_u8();
+ clear_text.message_type = MessageType::from(flags);
+ clear_text.attempts = flags & 0x03;
+
+ if clear_text.message_type == MessageType::SignedPlain &&
+ bytes.len() > 4 {
+ 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();
+
+ // 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();
+ }
+
+ clear_text
+ }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct GroupData {
+ payload: Bytes
+}
+
+impl From<Bytes> for GroupData {
+ fn from(value: Bytes) -> Self {
+ GroupData { payload: value }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone, Difference)]
+#[difference(expose)]
+pub struct AnonReq {
+ destination: u8,
+ public_key: PublicKey,
+ pub(crate) mac: u16,
+ pub(crate) ciphertext: Bytes,
+ request: Option<ClearAnonRequest>,
+ incomplete: bool
+}
+
+impl From<Bytes> for AnonReq {
+ fn from(value: Bytes) -> Self {
+ let mut bytes = value;
+
+ let mut anon_req = AnonReq {
+ destination: 0x00,
+ public_key: PublicKey::default(),
+ mac: 0x0000,
+ ciphertext: Bytes::new(),
+ request: None,
+ incomplete: false,
+ };
+
+ if bytes.is_empty() { return anon_req; }
+ anon_req.destination = bytes.get_u8();
+
+ if bytes.len() < 32 { return anon_req; }
+ if let Ok(pub_key) = PublicKey::try_from(bytes.split_to(32)) {
+ anon_req.public_key = pub_key;
+ }
+
+ if bytes.len() < 2 { return anon_req; }
+ anon_req.mac = bytes.get_u16();
+
+ anon_req.ciphertext = bytes;
+ anon_req.incomplete = false;
+ anon_req
+ }
+}
+
+#[derive(PartialEq, Debug, Clone, Difference)]
+#[difference(expose)]
+pub struct ClearAnonRequest {
+ timestamp: DateTime<Utc>,
+ sync_timestamp: Option<DateTime<Utc>>,
+ password: String
+}
+
+impl From<Bytes> for ClearAnonRequest {
+ fn from(value: Bytes) -> Self {
+ let mut bytes = value;
+
+ let mut anon_req = ClearAnonRequest {
+ timestamp: DateTime::from_timestamp(0, 0).unwrap(),
+ sync_timestamp: None,
+ password: "".to_string(),
+ };
+
+ // Just check for the whole fixed-size part at once
+ if bytes.len() < 4 { return anon_req }
+ if let Some(timestamp) = DateTime::from_timestamp(bytes.get_u32() as i64, 0) {
+ anon_req.timestamp = timestamp;
+ }
+
+ if bytes.len() < 4 { return anon_req; }
+ if let Some(timestamp) = DateTime::from_timestamp(bytes.get_u32() as i64, 0) {
+ anon_req.sync_timestamp = Some(timestamp);
+ }
+
+ anon_req.password = String::from_utf8_lossy(&bytes).to_string();
+ anon_req
+ }
+}
+
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct Path {
+ pub(crate) cipher: PeerToPeerCipher,
+}
+
+impl From<Bytes> for Path {
+ fn from(value: Bytes) -> Self {
+ Path {
+ cipher: PeerToPeerCipher::from(value)
+ }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct Trace {
+ tag: u32,
+ auth: u32,
+ flags: u8,
+ pub(crate) path_snr: Vec<f32>,
+ invalid: bool,
+
+ pub(crate) temp_path: Vec<u8>
+}
+
+impl From<Bytes> for Trace {
+ fn from(value: Bytes) -> Self {
+ let mut bytes = value;
+
+ let mut trace = Trace {
+ tag: 0,
+ auth: 0,
+ flags: 0,
+ path_snr: vec![],
+ invalid: true,
+ temp_path: vec![]
+ };
+
+ if bytes.len() < 10 { return trace; }
+
+ trace.tag = bytes.get_u32();
+ trace.auth = bytes.get_u32();
+ trace.flags = bytes.get_u8();
+ trace.temp_path = bytes.iter().copied().collect();
+ trace.invalid = false;
+ trace
+ }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct MultiPart {
+ payload: Bytes
+}
+
+impl From<Bytes> for MultiPart {
+ fn from(value: Bytes) -> Self {
+ MultiPart { payload: value }
+ }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct Raw {
+ pub(crate) bytes: Bytes
+}
+
+#[cfg(test)]
+mod tests {
+ use std::str::FromStr;
+
+ use chrono::DateTime;
+ use hex::decode;
+
+ use crate::packet::*;
+ use crate::crypto::*;
+
+ 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));
+ }
+
+ #[test]
+ fn advert_with_lat_lon() {
+ // Real sample packet from the air
+ let sample = "110c015f7e9b60661da0f2671512460728508c17ef336412a223144d3a623215162682045c44fef7241af0161923d3af0769d2d976b687a506dd5325ef526bf3eb52ae687277fcbde9969a5b0087e0eb0f7c1760a50c6a88bec13cc30a2a9b681d713166515e3bbc2bc27f20c0e4d7b67e08910c29dc02c468b8f8484f574c";
+
+ let mut signature_slice = [0 as u8; 64];
+ hex::decode_to_slice("d2d976b687a506dd5325ef526bf3eb52ae687277fcbde9969a5b0087e0eb0f7c1760a50c6a88bec13cc30a2a9b681d713166515e3bbc2bc27f20c0e4d7b67e08", &mut signature_slice).unwrap();
+
+ 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],
+ transport: [0x00, 0x00],
+ raw_content: Bytes::copy_from_slice(&decode("460728508c17ef336412a223144d3a623215162682045c44fef7241af0161923d3af0769d2d976b687a506dd5325ef526bf3eb52ae687277fcbde9969a5b0087e0eb0f7c1760a50c6a88bec13cc30a2a9b681d713166515e3bbc2bc27f20c0e4d7b67e08910c29dc02c468b8f8484f574c").unwrap()),
+ content: PacketContent::Advert(Advert {
+ public_key: PublicKey::from_str("460728508c17ef336412a223144d3a623215162682045c44fef7241af0161923").unwrap(),
+ timestamp: DateTime::from_timestamp(1762111443, 0).unwrap(),
+ signature: signature_slice,
+ node_type: NodeType::Chat,
+ latitude: Some(47.98286),
+ longitude: Some(-122.132286),
+ feature1: None,
+ feature2: None,
+ name: "HOWL".to_owned(),
+ }),
+ incomplete: false,
+ };
+ let rhs_packet = Packet::from_str(sample).unwrap();
+
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn advert() {
+ // Another on-air sample
+ let sample = "12007890b8573a6ba4a05b173d6ccfdfa73ac8ec4a12bf3c745ace636e1d191e132a4eb407695601f50907999735f3699a3c73ace2d8acc385ed209606abef6b914a346fbb88648c540ae794586b4d54eb08b473c8571a00c6b8d3dbcc77699afa169811fa098168707578373335";
+
+ let mut signature_slice = [0 as u8; 64];
+ hex::decode_to_slice("5601f50907999735f3699a3c73ace2d8acc385ed209606abef6b914a346fbb88648c540ae794586b4d54eb08b473c8571a00c6b8d3dbcc77699afa169811fa09", &mut signature_slice).unwrap();
+
+ let lhs_packet = Packet {
+ route_type: RouteType::Direct,
+ version: PayloadVersion::VersionOne,
+ path: vec![],
+ transport: [0x00, 0x00],
+ raw_content: Bytes::copy_from_slice(&decode("7890b8573a6ba4a05b173d6ccfdfa73ac8ec4a12bf3c745ace636e1d191e132a4eb407695601f50907999735f3699a3c73ace2d8acc385ed209606abef6b914a346fbb88648c540ae794586b4d54eb08b473c8571a00c6b8d3dbcc77699afa169811fa098168707578373335").unwrap()),
+ content: PacketContent::Advert(Advert {
+ public_key: PublicKey::from_str("7890b8573a6ba4a05b173d6ccfdfa73ac8ec4a12bf3c745ace636e1d191e132a").unwrap(),
+ timestamp: DateTime::from_timestamp(1762112590, 0).unwrap(),
+ signature: signature_slice,
+ node_type: NodeType::Chat,
+ latitude: None,
+ longitude: None,
+ feature1: None,
+ feature2: None,
+ name: "hpux735".to_owned(),
+ }),
+ incomplete: false,
+ };
+
+ let rhs_packet = Packet::from_str(sample).unwrap();
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn response() {
+ let sample = "06003412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92";
+
+ let lhs_packet = Packet {
+ route_type: RouteType::Direct,
+ version: PayloadVersion::VersionOne,
+ path: vec![],
+ 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: None
+ }}),
+ incomplete: false,
+ };
+
+ let rhs_packet = Packet::from_str(sample).unwrap();
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn request() {
+ let sample = "020012341d87ccaac89563cbb39d2333b725e407a1a6";
+
+ let lhs_packet = Packet {
+ route_type: RouteType::Direct,
+ version: PayloadVersion::VersionOne,
+ path: vec![],
+ transport: [0, 0],
+ raw_content: Bytes::copy_from_slice(&decode("12341d87ccaac89563cbb39d2333b725e407a1a6").unwrap()),
+ content: PacketContent::Request(Request {
+ cipher: PeerToPeerCipher {
+ destination: 0x12,
+ source: 0x34,
+ mac: 0x1d87,
+ ciphertext: Bytes::copy_from_slice(&decode("ccaac89563cbb39d2333b725e407a1a6").unwrap()),
+ cleartext: None
+ }}),
+ incomplete: false,
+ };
+
+ let rhs_packet = Packet::from_str(sample).unwrap();
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn text() {
+ let sample = "0a001234e91c8eb2b815e0eccf6781a3ff1820d0fb130fcfc87b914244fae227d4ad4c752fb9";
+
+ let lhs_packet = Packet {
+ route_type: RouteType::Direct,
+ version: PayloadVersion::VersionOne,
+ path: vec![],
+ transport: [0, 0],
+ raw_content: Bytes::copy_from_slice(&decode("1234e91c8eb2b815e0eccf6781a3ff1820d0fb130fcfc87b914244fae227d4ad4c752fb9").unwrap()),
+ content: PacketContent::Text(Text {
+ cipher: PeerToPeerCipher {
+ destination: 0x12,
+ source: 0x34,
+ mac: 0xe91c,
+ ciphertext: Bytes::copy_from_slice(&decode("8eb2b815e0eccf6781a3ff1820d0fb130fcfc87b914244fae227d4ad4c752fb9").unwrap()),
+ cleartext: None
+ }}),
+ incomplete: false,
+ };
+
+ let rhs_packet = Packet::from_str(sample).unwrap();
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn ack() {
+ let sample = "0D0E78B5561B03CD70DA326066B8A0CF24F3214D";
+
+ 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],
+ transport: [0, 0],
+ raw_content: Bytes::copy_from_slice(&decode("24F3214D").unwrap()),
+ content: PacketContent::Ack(Ack {
+ checksum: 0x24F3214D
+ }),
+ incomplete: false
+ };
+
+ let rhs_packet = Packet::from_str(sample).unwrap();
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn trace() {
+ let sample = "26032CE0F149EB5B240000000000ACA079";
+
+ let lhs_packet = Packet {
+ route_type: RouteType::Direct,
+ version: PayloadVersion::VersionOne,
+ path: vec![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],
+ invalid: false,
+ temp_path: vec![]
+ }
+ ),
+ incomplete: false
+ };
+
+ let rhs_packet = Packet::from_str(sample).unwrap();
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn group_text() {
+ let sample = "15107B2CF1A31C03E8AACD7E6066B8A0ACB71176B87DDF8FE67B33E7A63036E015311EE39232F094FAAD13C4442947947DD098886BC677DE2B6F456149D0A1B05C72F0EECC20DC07EDF163D9D63EBC9BC29A7C79AF";
+
+ 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],
+ transport: [0, 0],
+ raw_content: Bytes::copy_from_slice(&decode("1176B87DDF8FE67B33E7A63036E015311EE39232F094FAAD13C4442947947DD098886BC677DE2B6F456149D0A1B05C72F0EECC20DC07EDF163D9D63EBC9BC29A7C79AF").unwrap()),
+ content: PacketContent::GroupText(GroupText {
+ hash: 0x11,
+ mac: 0x76B8,
+ ciphertext: Bytes::copy_from_slice(&decode("7DDF8FE67B33E7A63036E015311EE39232F094FAAD13C4442947947DD098886BC677DE2B6F456149D0A1B05C72F0EECC20DC07EDF163D9D63EBC9BC29A7C79AF").unwrap()),
+ cleartext: None, // "Just need some more high repeaters".to_owned()
+ incomplete: false
+ }),
+ incomplete: false
+ };
+
+ let rhs_packet = Packet::from_str(sample).unwrap();
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn group_text_decrypted() {
+ let sample = "150011C3C1354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D";
+
+ let lhs_packet = Packet {
+ route_type: RouteType::Flood,
+ version: PayloadVersion::VersionOne,
+ path: vec![],
+ transport: [0, 0],
+ raw_content: Bytes::copy_from_slice(&decode("11C3C1354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap()),
+ content: PacketContent::GroupText(GroupText {
+ hash: 0x11,
+ mac: 0xC3C1,
+ ciphertext: Bytes::copy_from_slice(&decode("354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap()),
+ cleartext: Some(ClearText {
+ sender: Some("🌲 Tree".to_owned()),
+ message: "☁️".to_owned(),
+ timestamp: DateTime::from_timestamp_secs(1758484279).unwrap(),
+ message_type: MessageType::Plain,
+ attempts: 0,
+ sender_hash: 0,
+ }),
+ incomplete: false
+ }),
+ incomplete: false
+ };
+
+ let mut rhs_packet = Packet::from_str(sample).unwrap();
+ _ = rhs_packet.try_decrypt(SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("8b3387e9c5cdea6ac9e5edbaa115cd72").unwrap())));
+
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn multipart() {
+ let sample = "2A0013E8C59624";
+
+ let lhs_packet = Packet {
+ route_type: RouteType::Direct,
+ version: PayloadVersion::VersionOne,
+ path: vec![],
+ transport: [0, 0],
+ raw_content: Bytes::copy_from_slice(&decode("13E8C59624").unwrap()),
+ content: PacketContent::Multipart(MultiPart {
+ payload: Bytes::copy_from_slice(&decode("13E8C59624").unwrap())
+ }),
+ incomplete: false
+ };
+
+ let rhs_packet = Packet::from_str(sample).unwrap();
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn group_data() {
+ let sample = "18079DBB163F3C88707DF4C430C8A06A587CF7E827641C6521A5DE85581C8800793AF4A5497196CB5F24B92A33A9C8AA3BAE4F8C94E8E464849BCBF6333509C3941C47B7ABF85ECFD2FF06DA1A39575D155941F152F63300D2C31B5FAFBDA79637";
+
+ 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],
+ transport: [0x9d07, 0x16bb],
+ raw_content: Bytes::copy_from_slice(&decode("F85ECFD2FF06DA1A39575D155941F152F63300D2C31B5FAFBDA79637").unwrap()),
+ content: PacketContent::GroupData(GroupData {
+ payload: Bytes::copy_from_slice(&decode("F85ECFD2FF06DA1A39575D155941F152F63300D2C31B5FAFBDA79637").unwrap())
+ }),
+ incomplete: false
+ };
+
+ let rhs_packet = Packet::from_str(sample).unwrap();
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn anon_req() {
+ let sample = "1d001234569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8fd2db15a7138098557cc291928b4358fa7522ddd41d35c99fb78f0def2b3e673d73d2";
+
+ let lhs_packet = Packet {
+ route_type: RouteType::Flood,
+ version: PayloadVersion::VersionOne,
+ path: vec![],
+ transport: [0, 0],
+ raw_content: Bytes::copy_from_slice(&decode("1234569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8fd2db15a7138098557cc291928b4358fa7522ddd41d35c99fb78f0def2b3e673d73d2").unwrap()),
+ content: PacketContent::AnonReq(AnonReq {
+ destination: 0x12,
+ public_key: PublicKey::try_from(Bytes::copy_from_slice(&decode("34569df1f9661916901669666fb8025eccb9ddb0499cddad4c164fec219c8b8f").unwrap())).unwrap(),
+ mac: 0xd2db,
+ ciphertext: Bytes::copy_from_slice(&decode("15a7138098557cc291928b4358fa7522ddd41d35c99fb78f0def2b3e673d73d2").unwrap()),
+ incomplete: false,
+ request: None
+ }),
+ incomplete: false
+ };
+
+ let rhs_packet = Packet::from_str(sample).unwrap();
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+ #[test]
+ fn path() {
+ let sample = "2107BA03127F7EA9221351768DD2DF32E1D02F5851379F5AFCC667AB273442FAB2943673F26DDBEB9595027474";
+
+ let lhs_packet = Packet {
+ route_type: RouteType::Flood,
+ version: PayloadVersion::VersionOne,
+ path: vec![0xBA, 0x03, 0x12, 0x7F, 0x7E, 0xA9, 0x22],
+ transport: [0, 0],
+ raw_content: Bytes::copy_from_slice(&decode("1351768DD2DF32E1D02F5851379F5AFCC667AB273442FAB2943673F26DDBEB9595027474").unwrap()),
+ content: PacketContent::Path(Path {
+ cipher: PeerToPeerCipher {
+ destination: 0x13,
+ source: 0x51,
+ mac: 0x768D,
+ ciphertext: Bytes::copy_from_slice(&decode("D2DF32E1D02F5851379F5AFCC667AB273442FAB2943673F26DDBEB9595027474").unwrap()),
+ cleartext: None
+ }
+ }),
+ incomplete: false
+ };
+
+ let rhs_packet = Packet::from_str(sample).unwrap();
+ assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet));
+ }
+
+} \ No newline at end of file