mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-12 23:43:44 +02:00
commit
897aa113c9
3 changed files with 261 additions and 5 deletions
|
@ -24,6 +24,11 @@ parking_lot = { version = "^0", features = [], default-features = false }
|
|||
lazy_static = "^1"
|
||||
serde = { version = "^1", features = ["derive"], default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "*"
|
||||
serde_json = "*"
|
||||
serde_cbor = "*"
|
||||
|
||||
[target."cfg(not(windows))".dependencies]
|
||||
libc = "^0"
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::util::marshalable::Marshalable;
|
|||
use crate::vl1::protocol::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE};
|
||||
|
||||
/// A unique address on the global ZeroTier VL1 network.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct Address(NonZeroU64);
|
||||
|
||||
|
@ -145,3 +145,120 @@ impl<'de> Deserialize<'de> for Address {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn safe_address() -> super::Address {
|
||||
let mut addr: Option<super::Address>;
|
||||
|
||||
'retry: loop {
|
||||
let rawaddr: u64 = rand::random();
|
||||
addr = super::Address::from_u64(rawaddr);
|
||||
|
||||
if addr.is_some() {
|
||||
break 'retry;
|
||||
}
|
||||
}
|
||||
|
||||
addr.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_marshal_u64() {
|
||||
let mut rawaddr: u64 = rand::random();
|
||||
let addr = super::Address::from_u64(rawaddr);
|
||||
assert!(addr.is_some());
|
||||
assert_eq!(addr.unwrap().to_u64(), rawaddr & 0xffffffffff);
|
||||
|
||||
rawaddr = 0;
|
||||
assert!(super::Address::from_u64(rawaddr).is_none());
|
||||
|
||||
rawaddr = (crate::vl1::protocol::ADDRESS_RESERVED_PREFIX as u64) << 32;
|
||||
assert!(super::Address::from_u64(rawaddr).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_marshal_bytes() {
|
||||
use crate::vl1::protocol::ADDRESS_SIZE;
|
||||
let mut v: Vec<u8> = Vec::with_capacity(ADDRESS_SIZE);
|
||||
let mut i = 0;
|
||||
while i < ADDRESS_SIZE {
|
||||
v.push(rand::random());
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let addr = super::Address::from_bytes(v.as_slice());
|
||||
assert!(addr.is_some());
|
||||
assert_eq!(addr.unwrap().to_bytes(), v.as_slice());
|
||||
|
||||
let empty: Vec<u8> = Vec::new();
|
||||
let emptyaddr = super::Address::from_bytes(empty.as_slice());
|
||||
assert!(emptyaddr.is_none());
|
||||
|
||||
let mut v2: [u8; ADDRESS_SIZE] = [0u8; ADDRESS_SIZE];
|
||||
let mut i = 0;
|
||||
while i < ADDRESS_SIZE {
|
||||
v2[i] = v[i];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let addr2 = super::Address::from_bytes_fixed(&v2);
|
||||
assert!(addr2.is_some());
|
||||
assert_eq!(addr2.unwrap().to_bytes(), v2);
|
||||
|
||||
assert_eq!(addr.unwrap(), addr2.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_to_from_string() {
|
||||
use std::str::FromStr;
|
||||
|
||||
for _ in 0..1000 {
|
||||
let rawaddr: u64 = rand::random();
|
||||
let addr = super::Address::from_u64(rawaddr);
|
||||
|
||||
// NOTE: a regression here is covered by other tests and should not break this test
|
||||
// accidentally.
|
||||
if addr.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let addr = addr.unwrap();
|
||||
assert_ne!(addr.to_string(), "");
|
||||
assert_eq!(addr.to_string().len(), 10);
|
||||
|
||||
assert_eq!(super::Address::from_str(&addr.to_string()).unwrap(), addr);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_hash() {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
|
||||
let addr = safe_address();
|
||||
addr.hash(&mut hasher);
|
||||
let result1 = hasher.finish();
|
||||
|
||||
// this loop is mostly to ensure that hash returns a consistent result every time.
|
||||
for _ in 0..1000 {
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
addr.hash(&mut hasher);
|
||||
let result2 = hasher.finish();
|
||||
assert_ne!(result2.to_string(), "");
|
||||
assert_eq!(result1.to_string(), result2.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_serialize() {
|
||||
let addr = safe_address();
|
||||
|
||||
for _ in 0..1000 {
|
||||
assert_eq!(serde_json::from_str::<super::Address>(&serde_json::to_string(&addr).unwrap()).unwrap(), addr);
|
||||
assert_eq!(serde_cbor::from_slice::<super::Address>(&serde_cbor::to_vec(&addr).unwrap()).unwrap(), addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,15 @@ use std::io::Write;
|
|||
|
||||
use crate::util::hex::HEX_CHARS;
|
||||
|
||||
const BOOL_TRUTH: &str = "1tTyY";
|
||||
|
||||
/// Dictionary is an extremely simple key=value serialization format.
|
||||
///
|
||||
/// It's designed for extreme parsing simplicity and is human readable if keys and values are strings.
|
||||
/// It also supports binary keys and values which will be minimally escaped but render the result not
|
||||
/// entirely human readable. Keys are serialized in natural sort order so the result can be consistently
|
||||
/// checksummed or hashed.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Dictionary(pub(crate) BTreeMap<String, Vec<u8>>);
|
||||
|
||||
fn write_escaped<W: Write>(b: &[u8], w: &mut W) -> std::io::Result<()> {
|
||||
|
@ -94,7 +96,7 @@ impl Dictionary {
|
|||
}
|
||||
|
||||
pub fn get_bool(&self, k: &str) -> Option<bool> {
|
||||
self.0.get(k).map_or(None, |v| v.first().map_or(Some(false), |c| Some("1tTyY".contains(*c as char))))
|
||||
self.0.get(k).map_or(None, |v| v.first().map_or(Some(false), |c| Some(BOOL_TRUTH.contains(*c as char))))
|
||||
}
|
||||
|
||||
pub fn set_str(&mut self, k: &str, v: &str) {
|
||||
|
@ -146,6 +148,7 @@ impl Dictionary {
|
|||
b'0' => 0,
|
||||
b'n' => b'\n',
|
||||
b'r' => b'\r',
|
||||
b'e' => b'=',
|
||||
_ => c, // =, \, and escapes before other characters are unnecessary but not errors
|
||||
});
|
||||
} else if c == b'\\' {
|
||||
|
@ -176,6 +179,10 @@ impl Dictionary {
|
|||
}
|
||||
Some(d)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&String, &Vec<u8>)> {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Dictionary {
|
||||
|
@ -195,10 +202,58 @@ impl ToString for Dictionary {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::vl1::dictionary::Dictionary;
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
enum Type {
|
||||
String,
|
||||
Bytes,
|
||||
U64,
|
||||
Bool,
|
||||
}
|
||||
|
||||
type TypeMap = HashMap<String, Type>;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::vl1::dictionary::{Dictionary, BOOL_TRUTH};
|
||||
|
||||
// from zeronsd
|
||||
pub fn randstring(len: u8) -> String {
|
||||
(0..len).map(|_| (rand::random::<u8>() % 26) + 'a' as u8).map(|c| if rand::random::<bool>() { (c as char).to_ascii_uppercase() } else { c as char }).map(|c| c.to_string()).collect::<Vec<String>>().join("")
|
||||
}
|
||||
|
||||
fn make_dictionary() -> (Dictionary, TypeMap) {
|
||||
let mut d = Dictionary::new();
|
||||
let mut tm = TypeMap::new();
|
||||
|
||||
for _ in 0..(rand::random::<usize>() % 20) + 1 {
|
||||
// NOTE: just doing this twice because I want to keep the code a little cleaner.
|
||||
let selection = rand::random::<usize>() % 4;
|
||||
|
||||
let key = randstring(10);
|
||||
|
||||
// set the key
|
||||
match selection {
|
||||
0 => d.set_str(&key, &randstring(10)),
|
||||
1 => d.set_u64(&key, rand::random()),
|
||||
2 => d.set_bytes(&key, (0..((rand::random::<usize>() % 10) + 1)).into_iter().map(|_| rand::random()).collect::<Vec<u8>>()),
|
||||
3 => d.set_bool(&key, rand::random::<bool>()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match selection {
|
||||
0 => tm.insert(key, Type::String),
|
||||
1 => tm.insert(key, Type::U64),
|
||||
2 => tm.insert(key, Type::Bytes),
|
||||
3 => tm.insert(key, Type::Bool),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
||||
(d, tm)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dictionary() {
|
||||
fn dictionary_basic() {
|
||||
let mut d = Dictionary::new();
|
||||
d.set_str("foo", "bar");
|
||||
d.set_u64("bar", 0xfeedcafebabebeef);
|
||||
|
@ -209,4 +264,83 @@ mod tests {
|
|||
let d2 = Dictionary::from_bytes(bytes.as_slice()).unwrap();
|
||||
assert!(d.eq(&d2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dictionary_to_string() {
|
||||
for _ in 0..1000 {
|
||||
let (d, _) = make_dictionary();
|
||||
assert_ne!(d.to_string().len(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dictionary_clear() {
|
||||
for _ in 0..1000 {
|
||||
let (mut d, _) = make_dictionary();
|
||||
assert_ne!(d.len(), 0);
|
||||
assert!(!d.is_empty());
|
||||
d.clear();
|
||||
assert!(d.is_empty());
|
||||
assert_eq!(d.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dictionary_io() {
|
||||
for _ in 0..1000 {
|
||||
let (d, _) = make_dictionary();
|
||||
assert_ne!(d.len(), 0);
|
||||
assert!(!d.is_empty());
|
||||
|
||||
let mut v = Vec::new();
|
||||
let mut cursor = std::io::Cursor::new(&mut v);
|
||||
assert!(d.write_to(&mut cursor).is_ok());
|
||||
drop(cursor);
|
||||
assert!(!v.is_empty());
|
||||
|
||||
let d2 = super::Dictionary::from_bytes(v.as_slice());
|
||||
assert!(d2.is_some());
|
||||
let d2 = d2.unwrap();
|
||||
assert_eq!(d, d2);
|
||||
|
||||
let d2 = super::Dictionary::from_bytes(&d.to_bytes());
|
||||
assert!(d2.is_some());
|
||||
let d2 = d2.unwrap();
|
||||
assert_eq!(d, d2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dictionary_accessors() {
|
||||
for _ in 0..1000 {
|
||||
let (d, tm) = make_dictionary();
|
||||
|
||||
for (k, v) in d.iter() {
|
||||
match tm.get(k).unwrap() {
|
||||
Type::String => {
|
||||
let v2 = d.get_str(k);
|
||||
assert!(v2.is_some());
|
||||
assert_eq!(String::from_utf8(v.to_vec()).unwrap(), String::from(v2.unwrap()));
|
||||
}
|
||||
Type::Bytes => {
|
||||
let v2 = d.get_bytes(k);
|
||||
assert!(v2.is_some());
|
||||
assert_eq!(v, v2.unwrap());
|
||||
}
|
||||
Type::Bool => {
|
||||
let v2 = d.get_bool(k);
|
||||
assert!(v2.is_some());
|
||||
// FIXME move this lettering to a constant
|
||||
assert_eq!(BOOL_TRUTH.contains(*v.iter().nth(0).unwrap() as char), v2.unwrap());
|
||||
}
|
||||
Type::U64 => {
|
||||
let v2 = d.get_u64(k);
|
||||
assert!(v2.is_some());
|
||||
|
||||
assert_eq!(u64::from_str_radix(d.get_str(k).unwrap(), 16).unwrap(), v2.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue