aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorWill Dillon <william@housedillon.com>2025-11-06 06:14:58 +0000
committerWill Dillon <william@housedillon.com>2025-11-06 06:14:58 +0000
commita61fb3657bc978a7c724baaf79c1ae5cbc025c1e (patch)
treea129049b215dfe0e7ee09706d9e180befb0d56d3 /src
parentAdvert tests pass (diff)
downloadmeshcore-rs-a61fb3657bc978a7c724baaf79c1ae5cbc025c1e.tar.gz
meshcore-rs-a61fb3657bc978a7c724baaf79c1ae5cbc025c1e.zip
2/3 of packets pass tests
Diffstat (limited to 'src')
-rw-r--r--src/packet.rs342
1 files changed, 318 insertions, 24 deletions
diff --git a/src/packet.rs b/src/packet.rs
index dd43130..269835a 100644
--- a/src/packet.rs
+++ b/src/packet.rs
@@ -4,7 +4,7 @@ use chrono::{DateTime, Local, Utc};
use hex::decode;
use tokio_util::bytes::{Buf, Bytes};
use structdiff::{Difference, StructDiff};
-use crate::{crypto::SharedSecret, crypto::PublicKey};
+use crate::crypto::{PrivateKey, PublicKey, SharedSecret};
#[derive(PartialEq, Debug, Clone, Difference)]
#[difference(expose)]
@@ -125,39 +125,99 @@ impl From<u8> for NodeType {
}
}
+#[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
+ }
+}
+
+impl PeerToPeerCipher {
+ pub fn attempt_decrypt(&mut self, secret: SharedSecret) -> Option<(Bytes, PublicKey, PrivateKey)> {
+ None
+ }
+}
+
#[derive(PartialEq, Debug, Clone)]
-struct Request {}
+struct Request {
+ cipher: PeerToPeerCipher,
+}
impl From<Bytes> for Request {
fn from(value: Bytes) -> Self {
- todo!()
+ let request = Request {
+ cipher: PeerToPeerCipher::from(value)
+ };
+
+ request
}
}
-#[derive(PartialEq, Debug, Clone)]
-struct Response {}
+#[derive(PartialEq, Debug, Clone, Difference)]
+#[difference(expose)]
+struct Response {
+ cipher: PeerToPeerCipher,
+}
impl From<Bytes> for Response {
fn from(value: Bytes) -> Self {
- todo!()
+ let response = Response {
+ cipher: PeerToPeerCipher::from(value)
+ };
+
+ response
}
}
#[derive(PartialEq, Debug, Clone)]
-struct Text {}
+struct Text {
+ cipher: PeerToPeerCipher,
+}
impl From<Bytes> for Text {
fn from(value: Bytes) -> Self {
- todo!()
+ let mut text = Text {
+ cipher: PeerToPeerCipher::from(value)
+ };
+
+ text
}
}
#[derive(PartialEq, Debug, Clone)]
-struct Ack {}
+struct Ack {
+ checksum: u32
+}
impl From<Bytes> for Ack {
fn from(value: Bytes) -> Self {
- todo!()
+ let mut bytes = value;
+ Ack { checksum: bytes.get_u32() }
}
}
@@ -251,11 +311,36 @@ impl From<Bytes> for Advert {
}
#[derive(PartialEq, Debug, Clone)]
-struct GroupText {}
+struct GroupText {
+ hash: u8,
+ mac: u16,
+ ciphertext: Bytes,
+
+ cleartext: Option<String>,
+
+ incomplete: bool
+}
impl From<Bytes> for GroupText {
fn from(value: Bytes) -> Self {
- todo!()
+ 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
}
}
@@ -278,7 +363,9 @@ impl From<Bytes> for AnonReq {
}
#[derive(PartialEq, Debug, Clone)]
-struct Path {}
+struct Path {
+ cipher: PeerToPeerCipher,
+}
impl From<Bytes> for Path {
fn from(value: Bytes) -> Self {
@@ -287,11 +374,37 @@ impl From<Bytes> for Path {
}
#[derive(PartialEq, Debug, Clone)]
-struct Trace {}
+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 {
- todo!()
+ 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().map(|s| *s).collect();
+ trace.invalid = false;
+ trace
}
}
@@ -388,6 +501,24 @@ impl From<Bytes> for Packet {
packet.content = PacketContent::new(header, bytes);
+ // Unfortunately, the trace packet is a bit weird.
+ // So, if this is a trace packet, move things back into the right place.
+ match packet.content {
+ PacketContent::Trace(trace) => {
+ let mut trace = trace;
+ let path_snr = packet.path;
+ packet.path = trace.temp_path.iter().map(|i| *i as u16).collect();
+ trace.path_snr = path_snr.iter().map( |u| {
+ let i = *u as i8;
+ let f = i as f32;
+ f / 4.0
+ }).collect();
+ trace.temp_path = vec![];
+ packet.content = PacketContent::Trace(trace);
+ },
+ _ => {}
+ }
+
// Mark the packet as complete and valid
packet.incomplete = false;
@@ -395,13 +526,6 @@ impl From<Bytes> for Packet {
}
}
-impl Packet {
- pub fn attempt_decrypt(&mut self, secret: SharedSecret) -> bool {
- false
- }
-
-}
-
impl Default for Packet {
fn default() -> Self {
Packet {
@@ -463,7 +587,7 @@ mod tests {
}
fn print_compare(lhs: Packet, rhs: Packet) -> String {
- let mut output_string = format!("Differences between packets: \n");
+ 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));
@@ -519,7 +643,7 @@ mod tests {
}
#[test]
- fn advert() {
+ fn advert_with_lat_lon() {
// Real sample packet from the air
let sample = "110c015f7e9b60661da0f2671512460728508c17ef336412a223144d3a623215162682045c44fef7241af0161923d3af0769d2d976b687a506dd5325ef526bf3eb52ae687277fcbde9969a5b0087e0eb0f7c1760a50c6a88bec13cc30a2a9b681d713166515e3bbc2bc27f20c0e4d7b67e08910c29dc02c468b8f8484f574c";
@@ -551,7 +675,177 @@ mod tests {
}
#[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));
+ }
+
} \ No newline at end of file