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 | |
| parent | Packet header tests pass (diff) | |
| download | meshcore-rs-4e4cf9dff359a51465275c4840c8196c6513c9a9.tar.gz meshcore-rs-4e4cf9dff359a51465275c4840c8196c6513c9a9.zip | |
Advert tests pass
| -rw-r--r-- | .vscode/launch.json | 16 | ||||
| -rw-r--r-- | Cargo.lock | 228 | ||||
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | src/crypto.rs | 52 | ||||
| -rw-r--r-- | src/packet.rs | 154 |
5 files changed, 441 insertions, 12 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..52c34c7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "lldb", + "request": "launch", + "program": "${workspaceRoot}/<your program>", + "args": [], + "cwd": "${workspaceRoot}" + } + ] +}
\ No newline at end of file @@ -3,6 +3,21 @@ version = 4 [[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] name = "base64ct" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -18,24 +33,59 @@ dependencies = [ ] [[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] +name = "cc" +version = "1.2.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -144,6 +194,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -194,15 +250,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] name = "meshcore" version = "0.1.0" dependencies = [ + "chrono", "curve25519-dalek", "ed25519-dalek", "hex", @@ -212,6 +309,21 @@ dependencies = [ ] [[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -264,6 +376,12 @@ dependencies = [ ] [[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -311,6 +429,12 @@ dependencies = [ ] [[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -408,6 +532,110 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] +name = "wasm-bindgen" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] name = "x25519-dalek" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -9,4 +9,5 @@ curve25519-dalek = { version = "4.1.3", features = ["group"] } x25519-dalek = { version = "*", features = ["static_secrets"] } hex = "0.4" tokio-util = "*" -structdiff = { version = "0.7.3", features = ["debug_diffs"] }
\ No newline at end of file +structdiff = { version = "0.7.3", features = ["debug_diffs"] } +chrono = "*"
\ No newline at end of file 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 |
