use crate::packet_content::PeerToPeerCipher; use bytes::{Buf, Bytes}; use chrono::{DateTime, Utc}; #[derive(PartialEq, Debug, Clone)] pub struct Response { pub(crate) cipher: PeerToPeerCipher, pub cleartext: Option, } impl From for Response { fn from(value: Bytes) -> Self { Response { cipher: PeerToPeerCipher::from(value), cleartext: None, } } } #[derive(PartialEq, Debug, Clone)] pub struct ClearResponse { pub timestamp: DateTime, pub content: ResponseContent, } #[derive(PartialEq, Debug, Clone)] pub enum ResponseContent { Stats(RepeaterStats), Telemetry(Telemetry), Invalid, } #[repr(C)] #[derive(PartialEq, Debug, Clone)] pub struct RepeaterStats { pub batt_milli_volts: u16, pub curr_tx_queue_len: u16, pub noise_floor: i16, pub last_rssi: i16, pub n_packets_recv: u32, pub n_packets_sent: u32, pub total_air_time_secs: u32, pub total_up_time_secs: u32, pub n_sent_flood: u32, pub n_sent_direct: u32, pub n_recv_flood: u32, pub n_recv_direct: u32, pub err_events: u16, pub last_snr: i16, pub n_direct_dups: u16, pub n_flood_dups: u16, pub total_rx_air_time_secs: u32, } #[derive(PartialEq, Debug, Clone)] pub struct Telemetry {} impl From for ClearResponse { fn from(value: Bytes) -> Self { let mut bytes = value.clone(); let mut clear_response = ClearResponse { timestamp: DateTime::from_timestamp(0, 0).unwrap(), content: ResponseContent::Invalid, }; if bytes.len() < 4 { return clear_response; } if let Some(timestamp) = DateTime::from_timestamp(bytes.get_u32_le() as i64, 0) { clear_response.timestamp = timestamp; } // Unfortunately, there's no way to know for sure what kind of response this is, // so we have to make some educated guesses. In particular, it seems like all // telemetry responses start with voltage on channel 1. So, if the first two bytes // are exactly 0x0174 then we'll assume it's a telemetry packet. // let maybe_cayenne = bytes.clone().get_u16(); // if maybe_cayenne == 0x0174 { // println!("CayenneLPP: {} bytes: {}", bytes.len(), encode(&bytes)); // } else { // println!("{} bytes: {}", value.len(), encode(&value)); // } clear_response } } impl core::fmt::Display for Response { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_fmt(format_args!( "({:2x?}) -> ({:2x?}) MAC: {:4x?} ", self.cipher.source, self.cipher.destination, self.cipher.mac ))?; if let Some(_cleartext) = &self.cipher.cleartext { f.write_fmt(format_args!("")) } else { f.write_str("ENCRYPTED") } } } #[cfg(test)] mod tests { use crate::{ packet::*, packet_content::{PacketContent, PeerToPeerCipher}, response::Response, std_identity::KeystoreInput, }; use bytes::Bytes; use hex::decode; use std::str::FromStr; use tinyvec::ArrayVec; #[test] fn response_encrypted() { let sample = "06003412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92"; let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), content: PacketContent::Response(Response { cipher: PeerToPeerCipher { destination: 0x34, source: 0x12, mac: 0xb900, ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), cleartext: None }, cleartext: None }), incomplete: false }; let rhs_packet = Packet::from_str(sample).unwrap(); assert_eq!(lhs_packet, rhs_packet); assert_eq!( format!("{}", lhs_packet), " Direct | v1 | | [] | | RESPONSE | (12) -> (34) MAC: b900 ENCRYPTED" ) } #[test] fn response_decrypted() { let sample = "06003412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92"; let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), content: PacketContent::Response(Response { cipher: PeerToPeerCipher { destination: 0x34, source: 0x12, mac: 0xb900, ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), cleartext: Some(Bytes::copy_from_slice(b"\x9d\xd9\x16\xd2\x94\x10\0\0\xac\xff\xfe\xff\x12\0\0\0\x12\0\0\0\x05\0\0\0\xb7\x04\0\0\x12\0\0\0\0\0\0\0\x10\0\0\0\x02\0\0\0\0\03\0\0\0\x02\0\x04\0\0\0\0\0\0\0\0\0\0\0")) }, cleartext: None}), incomplete: false, }; let mut rhs_packet = Packet::from_str(sample).unwrap(); let file_contents = include_str!("../test_identities_file.toml"); let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); let keystore = keystore_in.compile(); rhs_packet.try_decrypt(&keystore); // assert_eq!(lhs_packet, rhs_packet); // assert_eq!(format!("{}", lhs_packet), " Direct | v1 | | [] | | RESPONSE | (12) -> (34) MAC: b900 ENCRYPTED") } #[test] fn stats_response() { let sample = "06003412CF45D4856713A44CA5411C327DD96575CF632F626C5B17046BECC220B9E476362B9AC510637F3CC68160E263F8D9181A03965B989D6DE1AA646C1E2EEF4CF13E6F92"; let file_contents = include_str!("../test_identities_file.toml"); let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); let keystore = keystore_in.compile(); let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), content: PacketContent::Response(Response { cipher: PeerToPeerCipher { destination: 0x34, source: 0x12, mac: 0xb900, ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), cleartext: Some(Bytes::copy_from_slice(&decode("c1cc1b69c8100000a7fff7ff1c000000160000000400000065060000050000001100000005000000170000000000300000000200040000000000000000000000").unwrap())) }, cleartext: None}), incomplete: false, }; let mut rhs_packet = Packet::from_str(sample).unwrap(); rhs_packet.try_decrypt(&keystore); println!("{:#?}", rhs_packet); // assert_eq!(lhs_packet, rhs_packet); } #[test] fn acl_response() { let sample = "06003412DE04889E2387AC81CC38108EF3E575B4B61FFA6AB9573DBAF589891F79D6C20FA33B6CE5CD672EE982F6DAE79DD8BC83F648"; let file_contents = include_str!("../test_identities_file.toml"); let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); let keystore = keystore_in.compile(); let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), content: PacketContent::Response(Response { cipher: PeerToPeerCipher { destination: 0x34, source: 0x12, mac: 0xb900, ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), cleartext: Some(Bytes::copy_from_slice(&decode("efcc1b6987449a393ea60355ebee904f160398fe6b93288b0312349bdc1f760334569df1f96603000000000000000000").unwrap())) }, cleartext: None}), incomplete: false, }; let mut rhs_packet = Packet::from_str(sample).unwrap(); rhs_packet.try_decrypt(&keystore); println!("{:#?}", rhs_packet); // assert_eq!(lhs_packet, rhs_packet); } #[test] fn unknown_response() { let sample = "06003412879A1045F0290639ED9B6D4BB0B51C14E13D"; let file_contents = include_str!("../test_identities_file.toml"); let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); let keystore = keystore_in.compile(); let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), content: PacketContent::Response(Response { cipher: PeerToPeerCipher { destination: 0x34, source: 0x12, mac: 0xb900, ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), cleartext: Some(Bytes::copy_from_slice(&decode("f6cc1b69000000000000000000000000").unwrap())) }, cleartext: None}), incomplete: false, }; let mut rhs_packet = Packet::from_str(sample).unwrap(); rhs_packet.try_decrypt(&keystore); println!("{:#?}", rhs_packet); // assert_eq!(lhs_packet, rhs_packet); } #[test] fn telemetry_response() { let sample = "06003412187C3AE03E52D347B957D634221DB5E86815"; let file_contents = include_str!("../test_identities_file.toml"); let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); let keystore = keystore_in.compile(); let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), content: PacketContent::Response(Response { cipher: PeerToPeerCipher { destination: 0x34, source: 0x12, mac: 0xb900, ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), cleartext: Some(Bytes::copy_from_slice(&decode("fdcc1b69017401ad0000000000000000").unwrap())) }, cleartext: None}), incomplete: false, }; let mut rhs_packet = Packet::from_str(sample).unwrap(); rhs_packet.try_decrypt(&keystore); println!("{:#?}", rhs_packet); // assert_eq!(lhs_packet, rhs_packet); } }