aboutsummaryrefslogtreecommitdiffstats
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
parentPacket header tests pass (diff)
downloadmeshcore-rs-4e4cf9dff359a51465275c4840c8196c6513c9a9.tar.gz
meshcore-rs-4e4cf9dff359a51465275c4840c8196c6513c9a9.zip
Advert tests pass
-rw-r--r--.vscode/launch.json16
-rw-r--r--Cargo.lock228
-rw-r--r--Cargo.toml3
-rw-r--r--src/crypto.rs52
-rw-r--r--src/packet.rs154
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
diff --git a/Cargo.lock b/Cargo.lock
index cef507a..f4e1c53 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 7a19a5b..32eeb1d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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