diff options
| author | Will Dillon <william@housedillon.com> | 2025-11-07 14:19:22 +0000 |
|---|---|---|
| committer | Will Dillon <william@housedillon.com> | 2025-11-07 14:19:22 +0000 |
| commit | c9103391c3baf2e43e071b7899a45132c95d2683 (patch) | |
| tree | 385269e6e5a6875e272bff0373d00f5803943397 /src | |
| parent | Moving back to laptop (diff) | |
| download | meshcore-rs-c9103391c3baf2e43e071b7899a45132c95d2683.tar.gz meshcore-rs-c9103391c3baf2e43e071b7899a45132c95d2683.zip | |
Decryption is working!
Diffstat (limited to 'src')
| -rw-r--r-- | src/crypto.rs | 211 | ||||
| -rw-r--r-- | src/identity.rs | 30 | ||||
| -rw-r--r-- | src/lib.rs | 4 | ||||
| -rw-r--r-- | src/packet.rs | 813 | ||||
| -rw-r--r-- | src/packet_content.rs | 897 |
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> +} + @@ -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 |
