aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorWill Dillon <william@housedillon.com>2025-11-06 02:53:47 +0000
committerWill Dillon <william@housedillon.com>2025-11-06 02:53:47 +0000
commit4e4cf9dff359a51465275c4840c8196c6513c9a9 (patch)
treed84edacd07aea33414a30368502080f3b1d638c8 /src
parentPacket header tests pass (diff)
downloadmeshcore-rs-4e4cf9dff359a51465275c4840c8196c6513c9a9.tar.gz
meshcore-rs-4e4cf9dff359a51465275c4840c8196c6513c9a9.zip
Advert tests pass
Diffstat (limited to 'src')
-rw-r--r--src/crypto.rs52
-rw-r--r--src/packet.rs154
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