diff options
| author | Will Dillon <william@housedillon.com> | 2025-11-06 02:53:47 +0000 |
|---|---|---|
| committer | Will Dillon <william@housedillon.com> | 2025-11-06 02:53:47 +0000 |
| commit | 4e4cf9dff359a51465275c4840c8196c6513c9a9 (patch) | |
| tree | d84edacd07aea33414a30368502080f3b1d638c8 /src | |
| parent | Packet header tests pass (diff) | |
| download | meshcore-rs-4e4cf9dff359a51465275c4840c8196c6513c9a9.tar.gz meshcore-rs-4e4cf9dff359a51465275c4840c8196c6513c9a9.zip | |
Advert tests pass
Diffstat (limited to 'src')
| -rw-r--r-- | src/crypto.rs | 52 | ||||
| -rw-r--r-- | src/packet.rs | 154 |
2 files changed, 195 insertions, 11 deletions
diff --git a/src/crypto.rs b/src/crypto.rs index adcd4d8..e3b2917 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,16 +1,64 @@ -use std::{collections::HashMap, str::FromStr}; +use std::{array::TryFromSliceError, collections::HashMap, str::FromStr}; use ed25519_dalek::hazmat::ExpandedSecretKey; use curve25519_dalek::{constants, edwards::CompressedEdwardsY}; -use tokio_util::bytes::Bytes; +use tokio_util::bytes::{self, Buf, Bytes}; use x25519_dalek::StaticSecret; use hex::decode; +pub enum MeshcoreCryptoError { + KeyLengthError, + TryFromSliceError +} + pub struct PrivateKey(ExpandedSecretKey); + +#[derive(PartialEq, Debug, Clone)] pub struct PublicKey(CompressedEdwardsY); + pub struct SharedSecret([u8; 32]); +// This is just for creating placeholders +impl Default for PublicKey { + fn default() -> Self { + PublicKey(CompressedEdwardsY::from_slice(&[0 as u8; 32]).unwrap()) + } +} + +impl TryFrom<Bytes> for PublicKey { + type Error = MeshcoreCryptoError; + + fn try_from(mut value: Bytes) -> Result<Self, Self::Error> { + if value.len() < 32 { + return Err(MeshcoreCryptoError::TryFromSliceError) + } + + let mut slice = [0 as u8; 32]; + + if let Err(_) = value.try_copy_to_slice(&mut slice) { + return Err(MeshcoreCryptoError::TryFromSliceError) + } + + Ok(PublicKey(CompressedEdwardsY(slice))) + } + +} + +impl FromStr for PublicKey { + type Err = hex::FromHexError; + + fn from_str(hex_str: &str) -> Result<Self, Self::Err> { + let hex = decode(hex_str)?; + + if let Ok(edx) = CompressedEdwardsY::from_slice(&hex) { + Ok(PublicKey(edx)) + } else { + Err(hex::FromHexError::InvalidStringLength) + } + } +} + impl FromStr for PrivateKey { type Err = hex::FromHexError; diff --git a/src/packet.rs b/src/packet.rs index ecc13c6..dd43130 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,12 +1,13 @@ 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::SharedSecret, packet}; +use crate::{crypto::SharedSecret, crypto::PublicKey}; -#[difference(expose)] #[derive(PartialEq, Debug, Clone, Difference)] +#[difference(expose)] struct Packet { route_type: RouteType, version: PayloadVersion, @@ -103,6 +104,28 @@ impl PacketContent { } #[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)] struct Request {} impl From<Bytes> for Request { @@ -138,12 +161,92 @@ impl From<Bytes> for Ack { } } -#[derive(PartialEq, Debug, Clone)] -struct Advert {} +#[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 { - todo!() + let mut bytes = value; + + let mut advert = Advert { + public_key: PublicKey::default(), + timestamp: Local::now().into(), + signature: [0 as 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.len() == 0 { 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 ommitted 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 } } @@ -315,6 +418,8 @@ impl Default for Packet { #[cfg(test)] mod tests { + use chrono::DateTime; + use super::*; #[test] @@ -342,6 +447,21 @@ mod tests { assert!(PayloadVersion::VersionFour == PayloadVersion::from(0xFF)); } + #[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!("Differences between packets: \n"); @@ -391,10 +511,10 @@ mod tests { path: [0x06, 0x07, 0x08, 0x09, 0x0A].to_vec(), transport: [0x0102, 0x0304], raw_content: Bytes::copy_from_slice(&[0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19]), - content: PacketContent::Invalid, + content: PacketContent::Raw(Raw { bytes: Bytes::copy_from_slice(&[0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19])}), incomplete: false, }; - let rhs_packet = Packet::from_str("030102030405060708090A10111213141516171819").unwrap(); + let rhs_packet = Packet::from_str("3F0102030405060708090A10111213141516171819").unwrap(); assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet)); } @@ -403,14 +523,25 @@ mod tests { // 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: packet::PacketContent::Advert(Advert { - + 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, }; @@ -418,4 +549,9 @@ mod tests { assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet)); } + + #[test] + fn request() { + + } }
\ No newline at end of file |
