diff options
Diffstat (limited to 'src/response.rs')
| -rw-r--r-- | src/response.rs | 219 |
1 files changed, 190 insertions, 29 deletions
diff --git a/src/response.rs b/src/response.rs index a8ad867..f68e881 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,13 +1,9 @@ -use std::fmt::Display; -use bytes::Bytes; +use bytes::{Buf, Bytes}; use chrono::{DateTime, Utc}; -use hex::encode; -use structdiff::{Difference, StructDiff}; use crate::packet_content::PeerToPeerCipher; -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct Response { pub(crate) cipher: PeerToPeerCipher, pub cleartext: Option<ClearResponse> @@ -22,41 +18,77 @@ impl From<Bytes> for Response { } } -#[derive(PartialEq, Debug, Clone, Difference)] -#[difference(expose)] +#[derive(PartialEq, Debug, Clone)] pub struct ClearResponse { pub timestamp: DateTime<Utc>, - pub response: u8, - pub keepalive_interval: u8, - pub is_admin: bool, - pub permissions: u8, - pub nonce: u32, - pub fw_version: u8 + pub content: ResponseContent, +} + +#[derive(PartialEq, Debug, Clone)] +pub enum ResponseContent { + Stats(RepeaterStats), + Telemetry(Telemetry), + Invalid, +} + +#[repr(C)] +#[derive(PartialEq, Debug, Clone)] +pub struct RepeaterStats { + pub batt_milli_volts: u16, + pub curr_tx_queue_len: u16, + pub noise_floor: i16, + pub last_rssi: i16, + pub n_packets_recv: u32, + pub n_packets_sent: u32, + pub total_air_time_secs: u32, + pub total_up_time_secs: u32, + pub n_sent_flood: u32, + pub n_sent_direct: u32, + pub n_recv_flood: u32, + pub n_recv_direct: u32, + pub err_events: u16, + pub last_snr: i16, + pub n_direct_dups: u16, + pub n_flood_dups: u16, + pub total_rx_air_time_secs: u32, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct Telemetry { + } impl From<Bytes> for ClearResponse { fn from(value: Bytes) -> Self { - let mut bytes = value; + let mut bytes = value.clone(); let mut clear_response = ClearResponse { timestamp: DateTime::from_timestamp(0, 0).unwrap(), - response: 0, - keepalive_interval: 0, - is_admin: false, - permissions: 0, - nonce: 0, - fw_version: 0, + content: ResponseContent::Invalid }; - println!("{} bytes: {}", bytes.len(), encode(&bytes)); + if bytes.len() < 4 { return clear_response } + if let Some(timestamp) = DateTime::from_timestamp(bytes.get_u32_le() as i64, 0) { + clear_response.timestamp = timestamp; + } + + // Unfortunately, there's no way to know for sure what kind of response this is, + // so we have to make some educated guesses. In particular, it seems like all + // telemetry responses start with voltage on channel 1. So, if the first two bytes + // are exactly 0x0174 then we'll assume it's a telemetry packet. + // let maybe_cayenne = bytes.clone().get_u16(); + // if maybe_cayenne == 0x0174 { + // println!("CayenneLPP: {} bytes: {}", bytes.len(), encode(&bytes)); + // } else { + // println!("{} bytes: {}", value.len(), encode(&value)); + // } - clear_response } } -impl Display for Response { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for Response { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_fmt(format_args!("({:2x?}) -> ({:2x?}) MAC: {:4x?} ", self.cipher.source, self.cipher.destination, @@ -74,10 +106,10 @@ impl Display for Response { #[cfg(test)] mod tests { use std::str::FromStr; - use bytes::Bytes; use hex::decode; - use crate::{identity::KeystoreInput, packet::*, packet_content::{PacketContent, PeerToPeerCipher}, response::Response}; + use tinyvec::ArrayVec; + use crate::{std_identity::KeystoreInput, packet::*, packet_content::{PacketContent, PeerToPeerCipher}, response::Response}; #[test] fn response_encrypted() { @@ -86,7 +118,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), content: PacketContent::Response(Response { @@ -114,7 +146,7 @@ mod tests { let lhs_packet = Packet { route_type: RouteType::Direct, version: PayloadVersion::VersionOne, - path: vec![], + path: ArrayVec::new(), transport: [0, 0], raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), content: PacketContent::Response(Response { @@ -140,4 +172,133 @@ mod tests { // assert_eq!(lhs_packet, rhs_packet); // assert_eq!(format!("{}", lhs_packet), " Direct | v1 | | [] | | RESPONSE | (12) -> (34) MAC: b900 ENCRYPTED") } + + #[test] + fn stats_response() { + let sample = "06003412CF45D4856713A44CA5411C327DD96575CF632F626C5B17046BECC220B9E476362B9AC510637F3CC68160E263F8D9181A03965B989D6DE1AA646C1E2EEF4CF13E6F92"; + + let file_contents = include_str!("../test_identities_file.toml"); + let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); + let keystore = keystore_in.compile(); + + let lhs_packet = Packet { + route_type: RouteType::Direct, + version: PayloadVersion::VersionOne, + path: ArrayVec::new(), + transport: [0, 0], + raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + content: PacketContent::Response(Response { + cipher: PeerToPeerCipher { + destination: 0x34, + source: 0x12, + mac: 0xb900, + ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + cleartext: Some(Bytes::copy_from_slice(&decode("c1cc1b69c8100000a7fff7ff1c000000160000000400000065060000050000001100000005000000170000000000300000000200040000000000000000000000").unwrap())) + }, + cleartext: None}), + incomplete: false, + }; + + let mut rhs_packet = Packet::from_str(sample).unwrap(); + rhs_packet.try_decrypt(&keystore); + println!("{:#?}", rhs_packet); + // assert_eq!(lhs_packet, rhs_packet); + } + + #[test] + fn acl_response() { + let sample = "06003412DE04889E2387AC81CC38108EF3E575B4B61FFA6AB9573DBAF589891F79D6C20FA33B6CE5CD672EE982F6DAE79DD8BC83F648"; + + let file_contents = include_str!("../test_identities_file.toml"); + let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); + let keystore = keystore_in.compile(); + + let lhs_packet = Packet { + route_type: RouteType::Direct, + version: PayloadVersion::VersionOne, + path: ArrayVec::new(), + transport: [0, 0], + raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + content: PacketContent::Response(Response { + cipher: PeerToPeerCipher { + destination: 0x34, + source: 0x12, + mac: 0xb900, + ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + cleartext: Some(Bytes::copy_from_slice(&decode("efcc1b6987449a393ea60355ebee904f160398fe6b93288b0312349bdc1f760334569df1f96603000000000000000000").unwrap())) + }, + cleartext: None}), + incomplete: false, + }; + + let mut rhs_packet = Packet::from_str(sample).unwrap(); + rhs_packet.try_decrypt(&keystore); + println!("{:#?}", rhs_packet); + // assert_eq!(lhs_packet, rhs_packet); + } + + #[test] + fn unknown_response() { + let sample = "06003412879A1045F0290639ED9B6D4BB0B51C14E13D"; + + let file_contents = include_str!("../test_identities_file.toml"); + let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); + let keystore = keystore_in.compile(); + + let lhs_packet = Packet { + route_type: RouteType::Direct, + version: PayloadVersion::VersionOne, + path: ArrayVec::new(), + transport: [0, 0], + raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + content: PacketContent::Response(Response { + cipher: PeerToPeerCipher { + destination: 0x34, + source: 0x12, + mac: 0xb900, + ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + cleartext: Some(Bytes::copy_from_slice(&decode("f6cc1b69000000000000000000000000").unwrap())) + }, + cleartext: None}), + incomplete: false, + }; + + let mut rhs_packet = Packet::from_str(sample).unwrap(); + rhs_packet.try_decrypt(&keystore); + println!("{:#?}", rhs_packet); + // assert_eq!(lhs_packet, rhs_packet); + } + + + #[test] + fn telemetry_response() { + let sample = "06003412187C3AE03E52D347B957D634221DB5E86815"; + + let file_contents = include_str!("../test_identities_file.toml"); + let keystore_in: KeystoreInput = toml::from_str(file_contents).unwrap(); + let keystore = keystore_in.compile(); + + let lhs_packet = Packet { + route_type: RouteType::Direct, + version: PayloadVersion::VersionOne, + path: ArrayVec::new(), + transport: [0, 0], + raw_content: Bytes::copy_from_slice(&decode("3412b9000cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + content: PacketContent::Response(Response { + cipher: PeerToPeerCipher { + destination: 0x34, + source: 0x12, + mac: 0xb900, + ciphertext: Bytes::copy_from_slice(&decode("0cf1641739f7e4d49bff88bf5695b304111b15277de3a6031b9af3a2b6371cf75615ffefe7dcc0bbea2856c4e798a72d5b989d6de1aa646c1e2eef4cf13e6f92").unwrap()), + cleartext: Some(Bytes::copy_from_slice(&decode("fdcc1b69017401ad0000000000000000").unwrap())) + }, + cleartext: None}), + incomplete: false, + }; + + let mut rhs_packet = Packet::from_str(sample).unwrap(); + rhs_packet.try_decrypt(&keystore); + println!("{:#?}", rhs_packet); + // assert_eq!(lhs_packet, rhs_packet); + } }
\ No newline at end of file |
