diff --git a/src/wireguard_config.rs b/src/wireguard_config.rs index 46a4078..ecc1a85 100644 --- a/src/wireguard_config.rs +++ b/src/wireguard_config.rs @@ -1,4 +1,5 @@ use crate::exporter_error::PeerEntryParseError; +use log::debug; use std::collections::HashMap; use std::convert::TryFrom; @@ -22,82 +23,96 @@ fn after_char(s: &str, c_split: char) -> &str { s } -fn parse_peer_entry<'a>(lines: &[&'a str]) -> Result, PeerEntryParseError> { - let mut public_key = ""; - let mut allowed_ips = ""; - let mut name = None; +impl<'a> TryFrom<&[&'a str]> for PeerEntry<'a> { + type Error = PeerEntryParseError; - for line in lines { - if line.starts_with("PublicKey") { - public_key = after_char(line, '=').trim(); - } else if line.starts_with("AllowedIPs") { - allowed_ips = after_char(line, '=').trim(); - } else if line.starts_with("#") { - name = Some(line[1..].trim()); + fn try_from(lines: &[&'a str]) -> Result, Self::Error> { + let mut public_key = ""; + let mut allowed_ips = ""; + let mut name = None; + + for line in lines { + if line.starts_with("PublicKey") { + public_key = after_char(line, '=').trim(); + } else if line.starts_with("AllowedIPs") { + allowed_ips = after_char(line, '=').trim(); + } else if line.starts_with("#") { + // since the pound sign is 1 byte the below slice will work + name = Some(line[1..].trim()); + } } - } - // Sanity checks - if public_key == "" { - let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); - Err(PeerEntryParseError::PublicKeyNotFound { lines: lines_owned }) - } else if allowed_ips == "" { - let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); - Err(PeerEntryParseError::AllowedIPsEntryNotFound { lines: lines_owned }) - } else { - Ok(PeerEntry { - public_key, - allowed_ips, - name, // name can be None - }) + // Sanity checks + // If there are more than one PublicKey or AllowedIPs we won't catch it. But + // WireGuard won't be working either so we can live with this simplification. + if public_key == "" { + // we return a owned String for ergonomics. This will allocate but it's ok since it's not supposed + // to happen :) + let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); + Err(PeerEntryParseError::PublicKeyNotFound { lines: lines_owned }) + } else if allowed_ips == "" { + let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); + Err(PeerEntryParseError::AllowedIPsEntryNotFound { lines: lines_owned }) + } else { + Ok(PeerEntry { + public_key, + allowed_ips, + name, // name can be None + }) + } } } -pub(crate) fn parse<'a>( - txt: &'a str, -) -> Result>, PeerEntryParseError> { - let mut ht = HashMap::new(); +#[derive(Debug, Default, Clone)] +pub(crate) struct PeerEntryHashMap<'a>(HashMap<&'a str, PeerEntry<'a>>); - let mut v_blocks = Vec::new(); - let mut cur_block: Option> = None; +impl<'a> TryFrom<&'a str> for PeerEntryHashMap<'a> { + type Error = PeerEntryParseError; - for line in txt.lines().into_iter() { - if line.starts_with("[") { - if let Some(inner_cur_block) = cur_block { - // close the block - v_blocks.push(inner_cur_block); - cur_block = None; - } + fn try_from(txt: &'a str) -> Result { + let mut hm = HashMap::new(); - if line == "[Peer]" { - // start a new block - cur_block = Some(Vec::new()); - } - } else { - // push the line if we are in a block (only if not empty) - if let Some(inner_cur_block) = &mut cur_block { - if line != "" { - inner_cur_block.push(line); + let mut v_blocks = Vec::new(); + let mut cur_block: Option> = None; + + for line in txt.lines().into_iter() { + if line.starts_with("[") { + if let Some(inner_cur_block) = cur_block { + // close the block + v_blocks.push(inner_cur_block); + cur_block = None; + } + + if line == "[Peer]" { + // start a new block + cur_block = Some(Vec::new()); + } + } else { + // push the line if we are in a block (only if not empty) + if let Some(inner_cur_block) = &mut cur_block { + if line != "" { + inner_cur_block.push(line); + } } } } + + if let Some(cur_block) = cur_block { + // we have a leftover block + v_blocks.push(cur_block); + } + + debug!("v_blocks == {:?}", v_blocks); + + for block in &v_blocks { + let p: PeerEntry = PeerEntry::try_from(&block as &[&str])?; + hm.insert(p.public_key, p); + } + + debug!("hm == {:?}", hm); + + Ok(PeerEntryHashMap(hm)) } - - if let Some(cur_block) = cur_block { - // we have a leftover block - v_blocks.push(cur_block); - } - - println!("v_blocks == {:?}", v_blocks); - - for block in &v_blocks { - let p = parse_peer_entry(block)?; - ht.insert(p.public_key, p); - } - - println!("ht == {:?}", ht); - - Ok(ht) } #[cfg(test)] @@ -139,14 +154,69 @@ AllowedIPs = 10.70.0.40/32 # OnePlus 5T PublicKey = 928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk= AllowedIPs = 10.70.0.80/32 +"; + + const TEXT_NOPK: &'static str = " +ListenPort = 51820 +PrivateKey = my_super_secret_private_key +# PreUp = iptables -t nat -A POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE +# PostDown = iptables -t nat -D POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE + +[Peer] +# OnePlus 6T +PublicKey = 2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk= +AllowedIPs = 10.70.0.2/32 + +[Peer] +# varch.local (laptop) +AllowedIPs = 10.70.0.3/32 + +[Peer] +# cantarch +PublicKey = L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008= +AllowedIPs = 10.70.0.4/32 +"; + + const TEXT_AIP: &'static str = " +ListenPort = 51820 +PrivateKey = my_super_secret_private_key +# PreUp = iptables -t nat -A POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE +# PostDown = iptables -t nat -D POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE + +[Peer] +# OnePlus 6T +PublicKey = 2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk= +AllowedIPs = 10.70.0.2/32 + +[Peer] +# varch.local (laptop) +AllowedIPs = 10.70.0.3/32 +PublicKey = 6S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk= + +[Peer] +# cantarch +PublicKey = L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008= "; #[test] - fn test_parse() { - let a = parse(TEXT); + fn test_parse_ok() { + let a: PeerEntryHashMap = PeerEntryHashMap::try_from(TEXT).unwrap(); println!("{:?}", a); } #[test] - fn test_parse_and_serialize() {} + #[should_panic( + expected = "PublicKeyNotFound { lines: [\"# varch.local (laptop)\", \"AllowedIPs = 10.70.0.3/32\"] }" + )] + fn test_parse_no_public_key() { + let _: PeerEntryHashMap = PeerEntryHashMap::try_from(TEXT_NOPK).unwrap(); + } + + #[test] + #[should_panic( + expected = "AllowedIPsEntryNotFound { lines: [\"# cantarch\", \"PublicKey = L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=\"] }" + )] + fn test_parse_no_allowed_ips() { + let _: PeerEntryHashMap = PeerEntryHashMap::try_from(TEXT_AIP).unwrap(); + } }