diff --git a/zerotier-network-hypervisor/Cargo.toml b/zerotier-network-hypervisor/Cargo.toml index 178179299..243591eec 100644 --- a/zerotier-network-hypervisor/Cargo.toml +++ b/zerotier-network-hypervisor/Cargo.toml @@ -28,9 +28,14 @@ serde = { version = "^1", features = ["derive"], default-features = false } rand = "*" serde_json = "*" serde_cbor = "*" +criterion = "0.3" [target."cfg(not(windows))".dependencies] libc = "^0" [target."cfg(windows)".dependencies] winapi = { version = "^0", features = ["ws2tcpip"] } + +[[bench]] +name = "benchmark_identity" +harness = false diff --git a/zerotier-network-hypervisor/benches/benchmark_identity.rs b/zerotier-network-hypervisor/benches/benchmark_identity.rs new file mode 100644 index 000000000..7e1822be7 --- /dev/null +++ b/zerotier-network-hypervisor/benches/benchmark_identity.rs @@ -0,0 +1,13 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use std::time::Duration; +use zerotier_network_hypervisor::vl1::Identity; + +pub fn criterion_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("basic"); + group.measurement_time(Duration::new(30, 0)); + group.bench_function("identity generation", |b| b.iter(|| Identity::generate())); + group.finish(); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/zerotier-network-hypervisor/src/util/buffer.rs b/zerotier-network-hypervisor/src/util/buffer.rs index 712c43841..0bedaebf3 100644 --- a/zerotier-network-hypervisor/src/util/buffer.rs +++ b/zerotier-network-hypervisor/src/util/buffer.rs @@ -356,22 +356,22 @@ impl Buffer { } #[inline(always)] - pub fn u16_at(&self, ptr: usize) -> std::io::Result { + pub fn u16_at(&self, ptr: usize) -> std::io::Result { let end = ptr + 2; debug_assert!(end <= L); if end <= self.0 { - Ok(u64::from_be(unsafe { self.read_obj_internal(ptr) })) + Ok(u16::from_be(unsafe { self.read_obj_internal(ptr) })) } else { Err(overflow_err()) } } #[inline(always)] - pub fn u32_at(&self, ptr: usize) -> std::io::Result { + pub fn u32_at(&self, ptr: usize) -> std::io::Result { let end = ptr + 4; debug_assert!(end <= L); if end <= self.0 { - Ok(u64::from_be(unsafe { self.read_obj_internal(ptr) })) + Ok(u32::from_be(unsafe { self.read_obj_internal(ptr) })) } else { Err(overflow_err()) } @@ -562,3 +562,168 @@ impl PoolFactory> for PooledBufferFactory { obj.clear(); } } + +#[cfg(test)] +mod tests { + use super::Buffer; + + #[test] + fn buffer_basic_u64() { + let mut b = Buffer::<8>::new(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + assert!(b.append_u64(1234).is_ok()); + assert_eq!(b.len(), 8); + assert!(!b.is_empty()); + assert_eq!(b.read_u64(&mut 0).unwrap(), 1234); + b.clear(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + } + + #[test] + fn buffer_basic_u32() { + let mut b = Buffer::<4>::new(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + assert!(b.append_u32(1234).is_ok()); + assert_eq!(b.len(), 4); + assert!(!b.is_empty()); + assert_eq!(b.read_u32(&mut 0).unwrap(), 1234); + b.clear(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + } + + #[test] + fn buffer_basic_u16() { + let mut b = Buffer::<2>::new(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + assert!(b.append_u16(1234).is_ok()); + assert_eq!(b.len(), 2); + assert!(!b.is_empty()); + assert_eq!(b.read_u16(&mut 0).unwrap(), 1234); + b.clear(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + } + + #[test] + fn buffer_basic_u8() { + let mut b = Buffer::<1>::new(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + assert!(b.append_u8(128).is_ok()); + assert_eq!(b.len(), 1); + assert!(!b.is_empty()); + assert_eq!(b.read_u8(&mut 0).unwrap(), 128); + b.clear(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + } + + #[test] + fn buffer_bytes() { + const SIZE: usize = 100; + + for _ in 0..1000 { + let mut v: Vec = Vec::with_capacity(SIZE); + v.fill_with(|| rand::random()); + + let mut b = Buffer::::new(); + assert!(b.append_bytes(&v).is_ok()); + assert_eq!(b.read_bytes(v.len(), &mut 0).unwrap(), &v); + + let mut v: [u8; SIZE] = [0u8; SIZE]; + v.fill_with(|| rand::random()); + + let mut b = Buffer::::new(); + assert!(b.append_bytes_fixed(&v).is_ok()); + assert_eq!(b.read_bytes_fixed(&mut 0).unwrap(), &v); + + // FIXME: append calls for _get_mut style do not accept anything to append, so we can't + // test them. + // + // let mut b = Buffer::::new(); + // let res = b.append_bytes_fixed_get_mut(&v); + // assert!(res.is_ok()); + // let byt = res.unwrap(); + // assert_eq!(byt, &v); + } + } + + #[test] + fn buffer_at() { + const SIZE: usize = 100; + + for _ in 0..1000 { + let mut v = [0u8; SIZE]; + let mut idx: usize = rand::random::() % SIZE; + v[idx] = 1; + + let mut b = Buffer::::new(); + assert!(b.append_bytes(&v).is_ok()); + + let res = b.bytes_fixed_at::<1>(idx); + assert!(res.is_ok()); + assert_eq!(res.unwrap()[0], 1); + + let res = b.bytes_fixed_mut_at::<1>(idx); + assert!(res.is_ok()); + assert_eq!(res.unwrap()[0], 1); + + // the uX integer tests require a little more massage. we're going to rewind the index + // by 8, correcting to 0 if necessary, and then write 1's in. our numbers will be + // consistent this way. + v[idx] = 0; + + if idx < 8 { + idx = 0; + } else if (idx + 7) >= SIZE { + idx -= 7; + } + + for i in idx..(idx + 8) { + v[i] = 1; + } + + let mut b = Buffer::::new(); + assert!(b.append_bytes(&v).is_ok()); + + let res = b.u8_at(idx); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), 1); + + let res = b.u16_at(idx); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), 257); + + let res = b.u32_at(idx); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), 16843009); + + let res = b.u64_at(idx); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), 72340172838076673); + } + } + + #[test] + fn buffer_sizing() { + const SIZE: usize = 100; + + for _ in 0..1000 { + let v = [0u8; SIZE]; + let mut b = Buffer::::new(); + assert!(b.append_bytes(&v).is_ok()); + assert_eq!(b.len(), SIZE); + b.set_size(10); + assert_eq!(b.len(), 10); + unsafe { + b.set_size_unchecked(8675309); + } + assert_eq!(b.len(), 8675309); + } + } +} diff --git a/zerotier-network-hypervisor/src/util/mod.rs b/zerotier-network-hypervisor/src/util/mod.rs index a99d46bdb..0bb686ac6 100644 --- a/zerotier-network-hypervisor/src/util/mod.rs +++ b/zerotier-network-hypervisor/src/util/mod.rs @@ -14,6 +14,9 @@ pub(crate) const ZEROES: [u8; 64] = [0_u8; 64]; /// A value for ticks that indicates that something never happened, and is thus very long before zero ticks. pub(crate) const NEVER_HAPPENED_TICKS: i64 = -2147483648; +#[cfg(test)] +pub mod testutil; + #[cfg(feature = "debug_events")] #[allow(unused_macros)] macro_rules! debug_event { diff --git a/zerotier-network-hypervisor/src/util/testutil.rs b/zerotier-network-hypervisor/src/util/testutil.rs new file mode 100644 index 000000000..15dafffdc --- /dev/null +++ b/zerotier-network-hypervisor/src/util/testutil.rs @@ -0,0 +1,4 @@ + // from zeronsd + pub fn randstring(len: u8) -> String { + (0..len).map(|_| (rand::random::() % 26) + 'a' as u8).map(|c| if rand::random::() { (c as char).to_ascii_uppercase() } else { c as char }).map(|c| c.to_string()).collect::>().join("") + } diff --git a/zerotier-network-hypervisor/src/vl1/careof.rs b/zerotier-network-hypervisor/src/vl1/careof.rs index ae6857c31..6f8349fac 100644 --- a/zerotier-network-hypervisor/src/vl1/careof.rs +++ b/zerotier-network-hypervisor/src/vl1/careof.rs @@ -61,10 +61,12 @@ impl CareOf { } } + #[allow(unused)] pub fn verify(&self, signer: &Identity) -> bool { signer.verify(self.to_bytes_internal(false).as_slice(), self.signature.as_slice()) } + #[allow(unused)] pub fn contains(&self, id: &Identity) -> bool { self.fingerprints.binary_search(&id.fingerprint).is_ok() } diff --git a/zerotier-network-hypervisor/src/vl1/dictionary.rs b/zerotier-network-hypervisor/src/vl1/dictionary.rs index 649ce3a24..4ecb7ff69 100644 --- a/zerotier-network-hypervisor/src/vl1/dictionary.rs +++ b/zerotier-network-hypervisor/src/vl1/dictionary.rs @@ -212,14 +212,9 @@ mod tests { type TypeMap = HashMap; - use std::collections::HashMap; - + use crate::util::testutil::randstring; use crate::vl1::dictionary::{Dictionary, BOOL_TRUTH}; - - // from zeronsd - pub fn randstring(len: u8) -> String { - (0..len).map(|_| (rand::random::() % 26) + 'a' as u8).map(|c| if rand::random::() { (c as char).to_ascii_uppercase() } else { c as char }).map(|c| c.to_string()).collect::>().join("") - } + use std::collections::HashMap; fn make_dictionary() -> (Dictionary, TypeMap) { let mut d = Dictionary::new(); diff --git a/zerotier-network-hypervisor/src/vl1/endpoint.rs b/zerotier-network-hypervisor/src/vl1/endpoint.rs index e28777e8a..8abf7e8ae 100644 --- a/zerotier-network-hypervisor/src/vl1/endpoint.rs +++ b/zerotier-network-hypervisor/src/vl1/endpoint.rs @@ -30,7 +30,7 @@ pub(crate) const MAX_MARSHAL_SIZE: usize = 1024; /// A communication endpoint on the network where a ZeroTier node can be reached. /// /// Currently only a few of these are supported. The rest are reserved for future use. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Debug)] pub enum Endpoint { /// A null endpoint. Nil, @@ -131,7 +131,7 @@ impl Marshalable for Endpoint { fn marshal(&self, buf: &mut Buffer) -> std::io::Result<()> { match self { - Endpoint::Nil => buf.append_u8(TYPE_NIL), + Endpoint::Nil => buf.append_u8(16 + TYPE_NIL), Endpoint::ZeroTier(a, h) => { buf.append_u8(16 + TYPE_ZEROTIER)?; buf.append_bytes_fixed(&a.to_bytes())?; @@ -310,7 +310,7 @@ impl ToString for Endpoint { Endpoint::Ip(ip) => format!("ip:{}", ip.to_ip_string()), Endpoint::IpUdp(ip) => format!("udp:{}", ip.to_string()), Endpoint::IpTcp(ip) => format!("tcp:{}", ip.to_string()), - Endpoint::Http(url) => url.clone(), // http or https + Endpoint::Http(url) => format!("url:{}", url.clone()), // http or https Endpoint::WebRTC(offer) => format!("webrtc:{}", base64::encode_config(offer.as_slice(), base64::URL_SAFE_NO_PAD)), Endpoint::ZeroTierEncap(a, ah) => format!("zte:{}-{}", a.to_string(), base64::encode_config(ah, base64::URL_SAFE_NO_PAD)), } @@ -354,7 +354,7 @@ impl FromStr for Endpoint { "ip" => return Ok(Endpoint::Ip(InetAddress::from_str(endpoint_data)?)), "udp" => return Ok(Endpoint::IpUdp(InetAddress::from_str(endpoint_data)?)), "tcp" => return Ok(Endpoint::IpTcp(InetAddress::from_str(endpoint_data)?)), - "http" | "https" => return Ok(Endpoint::Http(endpoint_data.into())), + "url" => return Ok(Endpoint::Http(endpoint_data.into())), "webrtc" => { let offer = base64::decode_config(endpoint_data, base64::URL_SAFE_NO_PAD); if offer.is_ok() { @@ -425,3 +425,274 @@ impl<'de> Deserialize<'de> for Endpoint { } } } + +#[cfg(test)] +mod tests { + use super::{Endpoint, MAX_MARSHAL_SIZE}; + use crate::{ + util::marshalable::Marshalable, + vl1::{ + protocol::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE, IDENTITY_FINGERPRINT_SIZE}, + Address, + }, + }; + + #[test] + fn endpoint_default() { + let e: Endpoint = Default::default(); + assert!(matches!(e, Endpoint::Nil)) + } + + #[test] + fn endpoint_from_bytes() { + let v = [0u8; MAX_MARSHAL_SIZE]; + assert!(Endpoint::from_bytes(&v).is_none()); + } + + #[test] + fn endpoint_marshal_nil() { + use crate::util::buffer::Buffer; + + let n = Endpoint::Nil; + + let mut buf = Buffer::<1>::new(); + + let res = n.marshal(&mut buf); + assert!(res.is_ok()); + + let res = Endpoint::unmarshal(&buf, &mut 0); + assert!(res.is_ok()); + + let n2 = res.unwrap(); + assert_eq!(n, n2); + } + + #[test] + fn endpoint_marshal_zerotier() { + use crate::util::buffer::Buffer; + + for _ in 0..1000 { + let mut hash = [0u8; IDENTITY_FINGERPRINT_SIZE]; + hash.fill_with(|| rand::random()); + + let mut v = [0u8; ADDRESS_SIZE]; + v.fill_with(|| rand::random()); + + // correct for situations where RNG generates a prefix which generates a None value. + while v[0] == ADDRESS_RESERVED_PREFIX { + v[0] = rand::random() + } + + let zte = Endpoint::ZeroTier(Address::from_bytes(&v).unwrap(), hash); + + const TMP: usize = IDENTITY_FINGERPRINT_SIZE + 8; + let mut buf = Buffer::::new(); + + let res = zte.marshal(&mut buf); + assert!(res.is_ok()); + + let res = Endpoint::unmarshal(&buf, &mut 0); + assert!(res.is_ok()); + + let zte2 = res.unwrap(); + assert_eq!(zte, zte2); + } + } + + #[test] + fn endpoint_marshal_zerotier_encap() { + use crate::util::buffer::Buffer; + + for _ in 0..1000 { + let mut hash = [0u8; IDENTITY_FINGERPRINT_SIZE]; + hash.fill_with(|| rand::random()); + + let mut v = [0u8; ADDRESS_SIZE]; + v.fill_with(|| rand::random()); + + // correct for situations where RNG generates a prefix which generates a None value. + while v[0] == ADDRESS_RESERVED_PREFIX { + v[0] = rand::random() + } + + let zte = Endpoint::ZeroTierEncap(Address::from_bytes(&v).unwrap(), hash); + + const TMP: usize = IDENTITY_FINGERPRINT_SIZE + 8; + let mut buf = Buffer::::new(); + + let res = zte.marshal(&mut buf); + assert!(res.is_ok()); + + let res = Endpoint::unmarshal(&buf, &mut 0); + assert!(res.is_ok()); + + let zte2 = res.unwrap(); + assert_eq!(zte, zte2); + } + } + + #[test] + fn endpoint_marshal_mac() { + use crate::util::buffer::Buffer; + + for _ in 0..1000 { + let mac = crate::vl1::MAC::from_u64(rand::random()).unwrap(); + + for e in [Endpoint::Ethernet(mac.clone()), Endpoint::WifiDirect(mac.clone()), Endpoint::Bluetooth(mac.clone())] { + let mut buf = Buffer::<7>::new(); + + let res = e.marshal(&mut buf); + assert!(res.is_ok()); + + let res = Endpoint::unmarshal(&buf, &mut 0); + assert!(res.is_ok()); + + let e2 = res.unwrap(); + assert_eq!(e, e2); + } + } + } + + #[test] + fn endpoint_marshal_inetaddress() { + use crate::util::buffer::Buffer; + + for _ in 0..1000 { + let mut v = [0u8; 16]; + v.fill_with(|| rand::random()); + + let inet = crate::vl1::InetAddress::from_ip_port(&v, 1234); + + for e in [Endpoint::Ip(inet.clone()), Endpoint::IpTcp(inet.clone()), Endpoint::IpUdp(inet.clone())] { + let mut buf = Buffer::<20>::new(); + + let res = e.marshal(&mut buf); + assert!(res.is_ok()); + + let res = Endpoint::unmarshal(&buf, &mut 0); + assert!(res.is_ok()); + + let e2 = res.unwrap(); + assert_eq!(e, e2); + } + } + } + + #[test] + fn endpoint_marshal_http() { + use crate::util::buffer::Buffer; + use crate::util::testutil::randstring; + + for _ in 0..1000 { + let http = Endpoint::Http(randstring(30)); + let mut buf = Buffer::<33>::new(); + + assert!(http.marshal(&mut buf).is_ok()); + + let res = Endpoint::unmarshal(&buf, &mut 0); + assert!(res.is_ok()); + + let http2 = res.unwrap(); + assert_eq!(http, http2); + } + } + + #[test] + fn endpoint_marshal_webrtc() { + use crate::util::buffer::Buffer; + + for _ in 0..1000 { + let mut v = Vec::with_capacity(100); + v.fill_with(|| rand::random()); + + let rtc = Endpoint::WebRTC(v); + let mut buf = Buffer::<102>::new(); + + assert!(rtc.marshal(&mut buf).is_ok()); + + let res = Endpoint::unmarshal(&buf, &mut 0); + assert!(res.is_ok()); + + let rtc2 = res.unwrap(); + assert_eq!(rtc, rtc2); + } + } + + #[test] + fn endpoint_to_from_string() { + use crate::util::testutil::randstring; + use std::str::FromStr; + + for _ in 0..1000 { + let mut v = Vec::with_capacity(100); + v.fill_with(|| rand::random()); + let rtc = Endpoint::WebRTC(v); + + assert_ne!(rtc.to_string().len(), 0); + assert!(rtc.to_string().starts_with("webrtc")); + + let rtc2 = Endpoint::from_str(&rtc.to_string()).unwrap(); + assert_eq!(rtc, rtc2); + + let http = Endpoint::Http(randstring(30)); + assert_ne!(http.to_string().len(), 0); + assert!(http.to_string().starts_with("url")); + + let http2 = Endpoint::from_str(&http.to_string()).unwrap(); + assert_eq!(http, http2); + + let mut v = [0u8; 16]; + v.fill_with(|| rand::random()); + + let inet = crate::vl1::InetAddress::from_ip_port(&v, 0); + + let ip = Endpoint::Ip(inet.clone()); + assert_ne!(ip.to_string().len(), 0); + assert!(ip.to_string().starts_with("ip")); + + let ip2 = Endpoint::from_str(&ip.to_string()).unwrap(); + assert_eq!(ip, ip2); + + let inet = crate::vl1::InetAddress::from_ip_port(&v, 1234); + + for e in [(Endpoint::IpTcp(inet.clone()), "tcp"), (Endpoint::IpUdp(inet.clone()), "udp")] { + assert_ne!(e.0.to_string().len(), 0); + assert!(e.0.to_string().starts_with(e.1)); + + let e2 = Endpoint::from_str(&e.0.to_string()).unwrap(); + assert_eq!(e.0, e2); + } + + let mac = crate::vl1::MAC::from_u64(rand::random()).unwrap(); + + for e in [(Endpoint::Ethernet(mac.clone()), "eth"), (Endpoint::WifiDirect(mac.clone()), "wifip2p"), (Endpoint::Bluetooth(mac.clone()), "bt")] { + assert_ne!(e.0.to_string().len(), 0); + assert!(e.0.to_string().starts_with(e.1)); + + let e2 = Endpoint::from_str(&e.0.to_string()).unwrap(); + assert_eq!(e.0, e2); + } + + let mut hash = [0u8; IDENTITY_FINGERPRINT_SIZE]; + hash.fill_with(|| rand::random()); + + let mut v = [0u8; ADDRESS_SIZE]; + v.fill_with(|| rand::random()); + + // correct for situations where RNG generates a prefix which generates a None value. + while v[0] == ADDRESS_RESERVED_PREFIX { + v[0] = rand::random() + } + + for e in [(Endpoint::ZeroTier(Address::from_bytes(&v).unwrap(), hash), "zt"), (Endpoint::ZeroTierEncap(Address::from_bytes(&v).unwrap(), hash), "zte")] { + assert_ne!(e.0.to_string().len(), 0); + assert!(e.0.to_string().starts_with(e.1)); + + let e2 = Endpoint::from_str(&e.0.to_string()).unwrap(); + assert_eq!(e.0, e2); + } + + assert_eq!(Endpoint::Nil.to_string(), "nil"); + } + } +} diff --git a/zerotier-network-hypervisor/src/vl1/identity.rs b/zerotier-network-hypervisor/src/vl1/identity.rs index 36a71287b..1801e4643 100644 --- a/zerotier-network-hypervisor/src/vl1/identity.rs +++ b/zerotier-network-hypervisor/src/vl1/identity.rs @@ -861,8 +861,6 @@ mod tests { use crate::util::marshalable::Marshalable; use crate::vl1::identity::*; use std::str::FromStr; - use std::time::{Duration, SystemTime}; - #[allow(unused_imports)] use zerotier_core_crypto::hex; #[test] @@ -974,24 +972,4 @@ mod tests { assert!(Identity::from_str(ids.as_str()).unwrap() == id); } } - - #[test] - fn benchmark_generate() { - let mut count = 0; - let run_time = Duration::from_secs(5); - let start = SystemTime::now(); - let mut end; - let mut duration; - loop { - let _id = Identity::generate(); - //println!("{}", _id.to_string()); - end = SystemTime::now(); - duration = end.duration_since(start).unwrap(); - count += 1; - if duration >= run_time { - break; - } - } - println!("benchmark: V1 identity generation: {} ms / identity (average)", (duration.as_millis() as f64) / (count as f64)); - } }