aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorWill Dillon <william@housedillon.com>2025-11-05 21:23:17 +0000
committerWill Dillon <william@housedillon.com>2025-11-05 21:23:17 +0000
commit7b79979ea4ba2968fad469174c3d9138e8a4a688 (patch)
tree189c9a6af24e3c5b3e42c629310015df40eff015 /src
downloadmeshcore-rs-7b79979ea4ba2968fad469174c3d9138e8a4a688.tar.gz
meshcore-rs-7b79979ea4ba2968fad469174c3d9138e8a4a688.zip
Initial commit
Diffstat (limited to 'src')
-rw-r--r--src/crypto.rs58
-rw-r--r--src/lib.rs3
-rw-r--r--src/packet.rs259
3 files changed, 320 insertions, 0 deletions
diff --git a/src/crypto.rs b/src/crypto.rs
new file mode 100644
index 0000000..adcd4d8
--- /dev/null
+++ b/src/crypto.rs
@@ -0,0 +1,58 @@
+use std::{collections::HashMap, str::FromStr};
+
+use ed25519_dalek::hazmat::ExpandedSecretKey;
+use curve25519_dalek::{constants, edwards::CompressedEdwardsY};
+use tokio_util::bytes::Bytes;
+use x25519_dalek::StaticSecret;
+
+use hex::decode;
+
+pub struct PrivateKey(ExpandedSecretKey);
+pub struct PublicKey(CompressedEdwardsY);
+pub struct SharedSecret([u8; 32]);
+
+impl FromStr for PrivateKey {
+ type Err = hex::FromHexError;
+
+ fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
+ let hex = decode(hex_str)?;
+
+ if let Ok(bytes) = TryInto::<[u8; 64]>::try_into(hex) {
+ let exp_sec_key = ExpandedSecretKey::from_bytes(&bytes);
+
+ Ok(PrivateKey(exp_sec_key))
+ } else {
+ Err(hex::FromHexError::InvalidStringLength)
+ }
+ }
+}
+
+impl From<PrivateKey> for PublicKey {
+ fn from(key: PrivateKey) -> Self {
+ PublicKey((key.0.scalar * constants::ED25519_BASEPOINT_POINT).compress())
+ }
+}
+
+impl PrivateKey {
+ pub fn create_secret(&self, other: &PublicKey) -> SharedSecret {
+ let private = StaticSecret::from(*self.0.scalar.as_bytes());
+ SharedSecret(private.diffie_hellman(&other.0.to_bytes().into()).to_bytes())
+ }
+}
+
+impl SharedSecret {
+ pub fn mac_then_decrypt(&self, mac: [u8; 2], data: Bytes) -> Option<Bytes> {
+ None
+ }
+
+ pub fn encrypt_then_mac(&self, data: Bytes) -> Option<([u8; 2], Bytes)> {
+ None
+ }
+}
+
+pub struct Keychain {
+ pub private_keys: Vec<PrivateKey>,
+ pub public_keys: Vec<PublicKey>,
+
+ // secrets: HashMap<String, Ha>
+} \ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..817b464
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,3 @@
+pub mod crypto;
+pub mod packet;
+
diff --git a/src/packet.rs b/src/packet.rs
new file mode 100644
index 0000000..d44aa66
--- /dev/null
+++ b/src/packet.rs
@@ -0,0 +1,259 @@
+use std::str::FromStr;
+
+use hex::decode;
+use tokio_util::bytes::{Buf, Bytes};
+
+use crate::{crypto::SharedSecret, packet};
+
+#[derive(PartialEq, Debug)]
+struct Packet {
+ route_type: RouteType,
+ version: PayloadVersion,
+ path: Vec<u16>,
+ transport: [u16; 2],
+ raw_content: Bytes,
+ content: PacketContent,
+ incomplete: bool
+}
+
+#[derive(PartialEq, Debug)]
+enum RouteType {
+ TransportFlood,
+ Flood,
+ Direct,
+ TransportDirect,
+ Invalid,
+}
+
+impl From<u8> for RouteType {
+ fn from(value: u8) -> Self {
+ match value & 0x03 {
+ 0x00 => RouteType::TransportFlood,
+ 0x01 => RouteType::Flood,
+ 0x02 => RouteType::Direct,
+ 0x03 => RouteType::TransportDirect,
+ _ => RouteType::Invalid
+ }
+ }
+}
+
+#[derive(PartialEq, Debug)]
+enum PayloadVersion {
+ VersionOne,
+ VersionTwo,
+ VersionThree,
+ VersionFour,
+ Invalid,
+}
+
+impl From<u8> for PayloadVersion {
+ fn from(value: u8) -> Self {
+ let value = (value & 0xC0) >> 6;
+ assert!(value < 4, "Programming error in masking and bitshifting");
+
+ match value {
+ 0x00 => PayloadVersion::VersionOne,
+ 0x01 => PayloadVersion::VersionTwo,
+ 0x02 => PayloadVersion::VersionThree,
+ 0x03 => PayloadVersion::VersionFour,
+ _ => PayloadVersion::Invalid
+ }
+ }
+}
+
+#[derive(PartialEq, Debug)]
+enum PacketContent {
+ Request(Request),
+ Response(Response),
+ Text(Text),
+ Ack(Ack),
+ Advert(Advert),
+ GroupText(GroupText),
+ GroupData(GroupData),
+ AnonReq(AnonReq),
+ Path(Path),
+ Trace(Trace),
+ Multipart(MultiPart),
+ Raw(Raw),
+
+ Invalid,
+}
+
+#[derive(PartialEq, Debug)]
+struct Request {}
+
+#[derive(PartialEq, Debug)]
+struct Response {}
+
+#[derive(PartialEq, Debug)]
+struct Text {}
+
+#[derive(PartialEq, Debug)]
+struct Ack {}
+
+#[derive(PartialEq, Debug)]
+struct Advert {}
+
+#[derive(PartialEq, Debug)]
+struct GroupText {}
+
+#[derive(PartialEq, Debug)]
+struct GroupData {}
+
+#[derive(PartialEq, Debug)]
+struct AnonReq {}
+
+#[derive(PartialEq, Debug)]
+struct Path {}
+
+#[derive(PartialEq, Debug)]
+struct Trace {}
+
+#[derive(PartialEq, Debug)]
+struct MultiPart {}
+
+#[derive(PartialEq, Debug)]
+struct Raw {}
+
+impl FromStr for Packet {
+ type Err = hex::FromHexError;
+
+ fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
+ let hex = decode(hex_str)?;
+ Ok(Packet::from(Bytes::copy_from_slice(&hex)))
+ }
+}
+
+impl From<Bytes> for Packet {
+ fn from(bytes: Bytes) -> Self {
+ let mut bytes = bytes;
+
+ // This is the packet we'll build as we begin to parse
+ let mut packet = Packet::default();
+
+ if bytes.len() == 0 {
+ return packet
+ }
+
+ let header = bytes.split_to(0).get_u8();
+
+ // Parse the header byte
+ packet.route_type = RouteType::from(header);
+ packet.version = PayloadVersion::from(header);
+
+ // Get the transport if it's provided
+ packet.transport = match packet.route_type {
+ RouteType::TransportFlood | RouteType::TransportDirect => {
+ // The packet isn't long enough to contain the transport
+ if bytes.len() < 4 { return packet; }
+
+ [bytes.split_to(2).get_u16(), bytes.split_to(2).get_u16()]
+ },
+ _ => {
+ [0, 0]
+ }
+ };
+
+ // Get the route
+ if bytes.len() == 0 { return packet; }
+ let path_length = bytes.split_to(1).get_u8() as usize;
+
+ packet.path = match packet.version {
+ PayloadVersion::VersionOne => {
+ // The packet isn't long enough for the indicated route
+ if bytes.len() < path_length { return packet; }
+
+ let route: Vec<u16> = bytes
+ .split_to(path_length)
+ .into_iter()
+ .map(|x| x as u16)
+ .collect();
+ route
+ },
+ PayloadVersion::VersionTwo => {
+ // The packet isn't long enough for the indicated route
+ if bytes.len() < path_length as usize * 2 { return packet; }
+
+ let route: Vec<u16> = bytes
+ .split_to(path_length * 2)
+ .chunks(2)
+ .map(|c| Bytes::copy_from_slice(c).get_u16())
+ .collect();
+
+ route
+ },
+ _ => {
+ return packet;
+ }
+ };
+
+ // Get the rest of the payload and subscript the parsing to the other structs
+ packet.raw_content = bytes;
+
+ // Mark the packet as complete and valid
+ packet.incomplete = false;
+
+ packet
+ }
+}
+
+impl Packet {
+ pub fn attempt_decrypt(&mut self, secret: SharedSecret) -> bool {
+ false
+ }
+
+}
+
+impl Default for Packet {
+ fn default() -> Self {
+ Packet {
+ route_type: RouteType::Invalid,
+ version: PayloadVersion::Invalid,
+ path: vec![],
+ transport: [0, 0],
+ raw_content: Bytes::new(),
+ content: PacketContent::Invalid,
+ incomplete: true
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn header_route_type() {
+ // The route type is the lowest-order two bits
+ assert!(RouteType::TransportFlood == RouteType::from(0x00));
+ assert!(RouteType::TransportFlood == RouteType::from(0xFC));
+ assert!(RouteType::Flood == RouteType::from(0x01));
+ assert!(RouteType::Flood == RouteType::from(0xFD));
+ assert!(RouteType::Direct == RouteType::from(0x02));
+ assert!(RouteType::Direct == RouteType::from(0xFE));
+ assert!(RouteType::TransportDirect == RouteType::from(0x03));
+ assert!(RouteType::TransportDirect == RouteType::from(0xFF));
+ }
+
+ #[test]
+ fn header_version() {
+ assert!(PayloadVersion::VersionOne == PayloadVersion::from(0x00));
+ assert!(PayloadVersion::VersionOne == PayloadVersion::from(0x3F));
+ assert!(PayloadVersion::VersionTwo == PayloadVersion::from(0x40));
+ assert!(PayloadVersion::VersionTwo == PayloadVersion::from(0x7F));
+ assert!(PayloadVersion::VersionThree == PayloadVersion::from(0x80));
+ assert!(PayloadVersion::VersionThree == PayloadVersion::from(0xBF));
+ assert!(PayloadVersion::VersionFour == PayloadVersion::from(0xC0));
+ assert!(PayloadVersion::VersionFour == PayloadVersion::from(0xFF));
+ }
+
+ #[test]
+ fn packet() {
+ // Check the hex decode errors
+ assert!(Err(hex::FromHexError::InvalidHexCharacter { c: 's', index: 0 }) == Packet::from_str("s0"));
+ assert!(Err(hex::FromHexError::OddLength) == Packet::from_str("0"));
+
+ // Check errors related to packet length issues
+ assert!(Packet::default() == Packet::from_str("").unwrap())
+ }
+} \ No newline at end of file