Lots more sync work.

This commit is contained in:
Adam Ierymenko 2022-04-08 09:22:31 -04:00
parent aba212fd87
commit 6f7901a508
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
12 changed files with 679 additions and 438 deletions

View file

@ -6,3 +6,5 @@ control_brace_style = "AlwaysSameLine"
edition = "2021"
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
fn_single_line = true
struct_lit_width = 60

120
syncwhole/Cargo.lock generated
View file

@ -2,6 +2,17 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "async-trait"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -16,9 +27,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.9.0"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
dependencies = [
"generic-array",
]
@ -43,22 +54,39 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cpufeatures"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
dependencies = [
"libc",
]
[[package]]
name = "digest"
version = "0.9.0"
name = "crypto-common"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "futures-core"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
name = "generic-array"
version = "0.14.5"
@ -80,24 +108,25 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.119"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
[[package]]
name = "lock_api"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
dependencies = [
"cfg-if",
]
@ -110,14 +139,15 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "mio"
version = "0.8.0"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2"
checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"wasi",
"winapi",
]
@ -158,12 +188,6 @@ dependencies = [
"libc",
]
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "parking_lot"
version = "0.12.0"
@ -176,9 +200,9 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.1"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954"
checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37"
dependencies = [
"cfg-if",
"libc",
@ -204,18 +228,18 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.15"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
dependencies = [
"bitflags",
]
@ -278,15 +302,13 @@ dependencies = [
[[package]]
name = "sha2"
version = "0.9.9"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
dependencies = [
"block-buffer",
"cfg-if",
"cpufeatures",
"digest",
"opaque-debug",
]
[[package]]
@ -307,9 +329,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.86"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f"
dependencies = [
"proc-macro2",
"quote",
@ -320,6 +342,8 @@ dependencies = [
name = "syncwhole"
version = "0.1.0"
dependencies = [
"async-trait",
"futures-core",
"rmp",
"rmp-serde",
"serde",
@ -363,6 +387,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
@ -387,9 +417,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.32.0"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6"
checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
@ -400,30 +430,30 @@ dependencies = [
[[package]]
name = "windows_aarch64_msvc"
version = "0.32.0"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d"
[[package]]
name = "windows_i686_gnu"
version = "0.32.0"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed"
[[package]]
name = "windows_i686_msvc"
version = "0.32.0"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956"
[[package]]
name = "windows_x86_64_gnu"
version = "0.32.0"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4"
[[package]]
name = "windows_x86_64_msvc"
version = "0.32.0"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9"

View file

@ -29,6 +29,8 @@ serde_bytes = "^0"
rmp = "^0"
rmp-serde = "^1"
sha2 = { version = "^0", optional = true }
async-trait = "^0"
futures-core = "^0"
[features]
include_sha2_lib = ["sha2"]

View file

@ -6,49 +6,17 @@
* https://www.zerotier.com/
*/
use async_trait::async_trait;
/// Size of keys, which is the size of a 512-bit hash. This is a protocol constant.
pub const KEY_SIZE: usize = 64;
/// Minimum possible key (all zero).
/// Minimum possible value in a key range (all zero).
pub const MIN_KEY: [u8; KEY_SIZE] = [0; KEY_SIZE];
/// Maximum possible key (all 0xff).
/// Maximum possible value in a key range (all 0xff).
pub const MAX_KEY: [u8; KEY_SIZE] = [0xff; KEY_SIZE];
/// Generate a range of SHA512 hashes from a prefix and a number of bits.
/// The range will be inclusive and cover all keys under the prefix.
pub fn range_from_prefix(prefix: &[u8], prefix_bits: usize) -> Option<([u8; KEY_SIZE], [u8; KEY_SIZE])> {
let mut start = [0_u8; KEY_SIZE];
let mut end = [0xff_u8; KEY_SIZE];
if prefix_bits > (KEY_SIZE * 8) {
return None;
}
let whole_bytes = prefix_bits / 8;
let remaining_bits = prefix_bits % 8;
if prefix.len() < (whole_bytes + ((remaining_bits != 0) as usize)) {
return None;
}
start[0..whole_bytes].copy_from_slice(&prefix[0..whole_bytes]);
end[0..whole_bytes].copy_from_slice(&prefix[0..whole_bytes]);
if remaining_bits != 0 {
start[whole_bytes] |= prefix[whole_bytes];
end[whole_bytes] &= prefix[whole_bytes] | ((0xff_u8).wrapping_shr(remaining_bits as u32));
}
return Some((start, end));
}
/// Result returned by DataStore::load().
pub enum LoadResult<V: AsRef<[u8]> + Send> {
/// Object was found.
Ok(V),
/// Object was not found, including the case of being excluded due to the value of reference_time.
NotFound,
/// Supplied reference_time is outside what is available (usually too old).
TimeNotAvailable,
}
/// Result returned by DataStore::store().
pub enum StoreResult {
/// Entry was accepted.
@ -76,13 +44,15 @@ pub enum StoreResult {
/// what time I think it is" value to be considered locally so that data can be replicated
/// as of any given time.
///
/// The implementation must be thread safe and may be called concurrently.
/// In any call with a reference_time it should be ignored if it's zero. A zero reference
/// time should mean include all data that we have.
#[async_trait]
pub trait DataStore: Sync + Send {
/// Type to be enclosed in the Ok() enum value in LoadResult.
/// Container for values returned by load().
///
/// Allowing this type to be defined lets you use any type that makes sense with
/// your implementation. Examples include Box<[u8]>, Arc<[u8]>, Vec<u8>, etc.
type LoadResultValueType: AsRef<[u8]> + Send;
/// Making this a trait defined type lets you use Arc<[u8]>, etc. as well as obvious
/// ones like Box<[u8]> and Vec<u8>.
type ValueRef: AsRef<[u8]> + Sync + Send + Clone;
/// Key hash size, always 64 for SHA512.
const KEY_SIZE: usize = KEY_SIZE;
@ -101,17 +71,13 @@ pub trait DataStore: Sync + Send {
fn domain(&self) -> &str;
/// Get an item if it exists as of a given reference time.
fn load(&self, reference_time: i64, key: &[u8]) -> LoadResult<Self::LoadResultValueType>;
async fn load(&self, reference_time: i64, key: &[u8]) -> Option<Self::ValueRef>;
/// Check whether this data store contains a key.
///
/// The default implementation just uses load(). Override if you can provide a faster
/// version.
fn contains(&self, reference_time: i64, key: &[u8]) -> bool {
match self.load(reference_time, key) {
LoadResult::Ok(_) => true,
_ => false,
}
/// The default implementation just calls load(). Override if a faster version is possible.
async fn contains(&self, reference_time: i64, key: &[u8]) -> bool {
self.load(reference_time, key).await.is_some()
}
/// Store an item in the data store and return its status.
@ -137,22 +103,22 @@ pub trait DataStore: Sync + Send {
/// Rejected should only be returned if the value actually fails a validity check, signature
/// verification, proof of work check, or some other required criteria. Ignored must be
/// returned if the value is valid but is too old or was rejected for some other normal reason.
fn store(&self, key: &[u8], value: &[u8]) -> StoreResult;
async fn store(&self, key: &[u8], value: &[u8]) -> StoreResult;
/// Get the number of items in a range.
fn count(&self, reference_time: i64, key_range_start: &[u8], key_range_end: &[u8]) -> u64;
async fn count(&self, reference_time: i64, key_range_start: &[u8], key_range_end: &[u8]) -> u64;
/// Get the total number of records in this data store.
fn total_count(&self) -> u64;
async fn total_count(&self) -> u64;
/// Iterate through keys, stopping if the function returns false.
/// Iterate through a series of keys in a range (inclusive), stopping when function returns false.
///
/// The default implementation uses for_each(). This can be specialized if it's faster to
/// only load keys.
fn for_each_key<F: FnMut(&[u8]) -> bool>(&self, reference_time: i64, key_range_start: &[u8], key_range_end: &[u8], mut f: F) {
self.for_each(reference_time, key_range_start, key_range_end, |k, _| f(k));
/// The default implementation uses for_each() and just drops the value. Specialize if you can do it faster
/// by only retrieving keys.
async fn for_each_key<F: Send + FnMut(&[u8]) -> bool>(&self, reference_time: i64, key_range_start: &[u8], key_range_end: &[u8], mut f: F) {
self.for_each(reference_time, key_range_start, key_range_end, |k, _| f(k)).await;
}
/// Iterate through keys and values, stopping if the function returns false.
fn for_each<F: FnMut(&[u8], &[u8]) -> bool>(&self, reference_time: i64, key_range_start: &[u8], key_range_end: &[u8], f: F);
/// Iterate through a series of entries in a range (inclusive), stopping when function returns false.
async fn for_each<F: Send + FnMut(&[u8], &Self::ValueRef) -> bool>(&self, reference_time: i64, key_range_start: &[u8], key_range_end: &[u8], f: F);
}

View file

@ -39,6 +39,19 @@ pub struct Config {
pub contact: String,
}
impl Default for Config {
fn default() -> Self {
Self {
anchors: Vec::new(),
seeds: Vec::new(),
max_connection_count: 128,
desired_connection_count: 64,
name: String::new(),
contact: String::new(),
}
}
}
/// A trait that users of syncwhole implement to provide configuration information and listen for events.
pub trait Host: Sync + Send {
/// Get a copy of the current configuration for this syncwhole node.
@ -102,7 +115,7 @@ pub trait Host: Sync + Send {
for b in msg.iter() {
h.update(*b);
}
h.finalize_fixed().as_ref().try_into().unwrap()
h.finalize_fixed().try_into().unwrap()
}
/// Compute HMAC-SHA512 using key and input.

View file

@ -6,13 +6,26 @@
* https://www.zerotier.com/
*/
use crate::utils::*;
use std::borrow::Cow;
/// Total memory overhead of each bucket in bytes.
const BUCKET_SIZE_BYTES: usize = 13; // u64 key + u32 check + i8 count
/// Based on xorshift64 with endian conversion for BE systems.
#[inline(always)]
fn get_check_hash(mut x: u64) -> u32 {
x = u64::from_le(x);
x ^= x.wrapping_shl(13);
x ^= x.wrapping_shr(7);
x ^= x.wrapping_shl(17);
x.wrapping_add(x.wrapping_shr(32)).to_le() as u32
}
/// Called to get the next iteration index for each KEY_MAPPING_ITERATIONS table lookup.
/// (See IBLT papers, etc.)
/// A series of these implements the "series of different hashes" construct in IBLT.
#[inline(always)]
fn next_iteration_index(mut x: u64) -> u64 {
x = x.wrapping_add(1);
fn next_iteration_index(mut x: u64, hash_no: u64) -> u64 {
x = x.wrapping_add(hash_no);
x ^= x.wrapping_shr(30);
x = x.wrapping_mul(0xbf58476d1ce4e5b9);
x ^= x.wrapping_shr(27);
@ -26,60 +39,82 @@ fn next_iteration_index(mut x: u64) -> u64 {
/// Usage inspired by this paper:
///
/// https://dash.harvard.edu/bitstream/handle/1/14398536/GENTILI-SENIORTHESIS-2015.pdf
#[repr(C, packed)]
pub struct IBLT<const BUCKETS: usize> {
///
/// Note that an 8-bit counter that wraps is used instead of a much wider counter that
/// would probably never overflow. This saves space on the wire but means that there is
/// a very small (roughly 1/2^64) chance that this will list a value that is invalid in that
/// it was never added on either side. In our protocol that should not cause a problem as
/// it would just result in one key in a GetRecords query not being fulfilled, and in any
/// case it should be an extremely rare event.
///
/// BUCKETS is the maximum capacity in buckets, while HASHES is the number of
/// "different" (differently seeded) hash functions to use.
///
/// The best value for HASHES seems to be 3 for an optimal fill of 80%.
#[repr(C)]
pub struct IBLT<const BUCKETS: usize, const HASHES: usize> {
key: [u64; BUCKETS],
check_hash: [u64; BUCKETS],
check_hash: [u32; BUCKETS],
count: [i8; BUCKETS],
}
impl<const BUCKETS: usize> IBLT<BUCKETS> {
/// This was determined to be effective via empirical testing with random keys. This
/// is a protocol constant that can't be changed without upgrading all nodes in a domain.
const KEY_MAPPING_ITERATIONS: usize = 2;
impl<const BUCKETS: usize, const HASHES: usize> Clone for IBLT<BUCKETS, HASHES> {
#[inline(always)]
fn clone(&self) -> Self {
// NOTE: clone() is manually implemented here so it's tolerant of unaligned access on architectures not supporting it.
unsafe {
let mut tmp: Self = std::mem::MaybeUninit::uninit().assume_init();
std::ptr::copy_nonoverlapping((self as *const Self).cast::<u8>(), (&mut tmp as *mut Self).cast::<u8>(), Self::SIZE_BYTES);
tmp
}
}
}
impl<const BUCKETS: usize, const HASHES: usize> IBLT<BUCKETS, HASHES> {
/// Number of buckets in this IBLT.
#[allow(unused)]
pub const BUCKETS: usize = BUCKETS;
/// Size of this IBLT in bytes.
pub const SIZE_BYTES: usize = BUCKETS * (8 + 8 + 1);
#[inline(always)]
fn is_singular(&self, i: usize) -> bool {
let c = self.count[i];
if c == 1 || c == -1 {
xorshift64(self.key[i]) == self.check_hash[i]
} else {
false
}
}
pub const SIZE_BYTES: usize = BUCKETS * BUCKET_SIZE_BYTES;
/// Create a new zeroed IBLT.
#[inline(always)]
pub fn new() -> Self {
assert_eq!(Self::SIZE_BYTES, std::mem::size_of::<Self>());
assert!(Self::SIZE_BYTES <= std::mem::size_of::<Self>());
assert!(BUCKETS < (i32::MAX as usize));
unsafe { std::mem::zeroed() }
}
/// Cast a byte array to an IBLT if it is of the correct size.
pub fn ref_from_bytes(b: &[u8]) -> Option<&Self> {
if b.len() == Self::SIZE_BYTES {
Some(unsafe { &*b.as_ptr().cast() })
} else {
None
}
/// Get this IBLT as a byte slice (free cast operation).
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
unsafe { &*std::ptr::slice_from_raw_parts((self as *const Self).cast::<u8>(), Self::SIZE_BYTES) }
}
/// Compute the IBLT size in buckets to reconcile a given set difference, or return 0 if no advantage.
/// This returns zero if an IBLT would take up as much or more space than just sending local_set_size
/// hashes of hash_size_bytes.
/// Obtain an IBLT from bytes in memory.
///
/// If the architecture supports unaligned memory access or the memory is aligned, this returns a borrowed
/// Cow to 'b' that is just a cast. If re-alignment is necessary it returns an owned Cow containing a properly
/// aligned copy. This makes conversion a nearly free cast when alignment adjustment isn't needed.
#[inline(always)]
pub fn calc_iblt_parameters(hash_size_bytes: usize, local_set_size: u64, difference_size: u64) -> usize {
let b = (difference_size as f64) * 1.8; // factor determined experimentally for best bytes/item, can be tuned
if b > 64.0 && (b * (8.0 + 8.0 + 1.0)) < ((hash_size_bytes as f64) * (local_set_size as f64)) {
b.round() as usize
pub fn from_bytes<'a>(b: &'a [u8]) -> Option<Cow<'a, Self>> {
if b.len() == Self::SIZE_BYTES {
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "powerpc64", target_arch = "aarch64")))]
{
if b.as_ptr().align_offset(8) == 0 {
Some(Cow::Borrowed(unsafe { &*b.as_ptr().cast() }))
} else {
// NOTE: clone() is implemented above using a raw copy so that alignment doesn't matter.
Some(Cow::Owned(unsafe { &*b.as_ptr().cast::<Self>() }.clone()))
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "powerpc64", target_arch = "aarch64"))]
{
Some(Cow::Borrowed(unsafe { &*b.as_ptr().cast() }))
}
} else {
0
None
}
}
@ -91,17 +126,11 @@ impl<const BUCKETS: usize> IBLT<BUCKETS> {
}
}
/// Get this IBLT as a byte slice in place.
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
unsafe { &*std::ptr::slice_from_raw_parts((self as *const Self).cast::<u8>(), std::mem::size_of::<Self>()) }
}
fn ins_rem(&mut self, key: u64, delta: i8) {
let check_hash = xorshift64(key);
let check_hash = get_check_hash(key);
let mut iteration_index = u64::from_le(key);
for _ in 0..Self::KEY_MAPPING_ITERATIONS {
iteration_index = next_iteration_index(iteration_index);
for k in 0..(HASHES as u64) {
iteration_index = next_iteration_index(iteration_index, k);
let i = (iteration_index as usize) % BUCKETS;
self.key[i] ^= key;
self.check_hash[i] ^= check_hash;
@ -123,83 +152,84 @@ impl<const BUCKETS: usize> IBLT<BUCKETS> {
/// Subtract another IBLT from this one to get a set difference.
pub fn subtract(&mut self, other: &Self) {
for i in 0..BUCKETS {
self.key[i] ^= other.key[i];
}
for i in 0..BUCKETS {
self.check_hash[i] ^= other.check_hash[i];
}
for i in 0..BUCKETS {
self.count[i] = self.count[i].wrapping_sub(other.count[i]);
}
self.key.iter_mut().zip(other.key.iter()).for_each(|(a, b)| *a ^= *b);
self.check_hash.iter_mut().zip(other.check_hash.iter()).for_each(|(a, b)| *a ^= *b);
self.count.iter_mut().zip(other.count.iter()).for_each(|(a, b)| *a = a.wrapping_sub(*b));
}
/// List as many entries in this IBLT as can be extracted.
pub fn list<F: FnMut(u64)>(mut self, mut f: F) {
/// True is returned if extraction was 100% successful. False indicates that
/// some entries were not extractable.
pub fn list<F: FnMut(u64)>(mut self, mut f: F) -> bool {
let mut queue: Vec<u32> = Vec::with_capacity(BUCKETS);
for i in 0..BUCKETS {
if self.is_singular(i) {
let count = self.count[i];
if (count == 1 || count == -1) && get_check_hash(self.key[i]) == self.check_hash[i] {
queue.push(i as u32);
}
}
loop {
'list_main: loop {
let i = queue.pop();
let i = if i.is_some() {
i.unwrap() as usize
} else {
break;
break 'list_main;
};
if self.is_singular(i) {
let key = self.key[i];
let key = self.key[i];
let check_hash = self.check_hash[i];
let count = self.count[i];
if (count == 1 || count == -1) && check_hash == get_check_hash(key) {
f(key);
let check_hash = xorshift64(key);
let mut iteration_index = u64::from_le(key);
for _ in 0..Self::KEY_MAPPING_ITERATIONS {
iteration_index = next_iteration_index(iteration_index);
for k in 0..(HASHES as u64) {
iteration_index = next_iteration_index(iteration_index, k);
let i = (iteration_index as usize) % BUCKETS;
self.key[i] ^= key;
self.check_hash[i] ^= check_hash;
self.count[i] = self.count[i].wrapping_sub(1);
if self.is_singular(i) {
let key2 = self.key[i] ^ key;
let check_hash2 = self.check_hash[i] ^ check_hash;
let count2 = self.count[i].wrapping_sub(count);
self.key[i] = key2;
self.check_hash[i] = check_hash2;
self.count[i] = count2;
if (count2 == 1 || count2 == -1) && check_hash2 == get_check_hash(key2) {
if queue.len() > BUCKETS {
// sanity check, should be impossible
return;
break 'list_main;
}
queue.push(i as u32);
}
}
}
}
self.count.iter().all(|x| *x == 0) && self.key.iter().all(|x| *x == 0)
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
#[allow(unused_imports)]
use std::time::SystemTime;
use crate::iblt::*;
#[allow(unused_imports)]
use crate::utils::{splitmix64, xorshift64};
#[test]
fn splitmix_is_invertiblex() {
for i in 1..2000_u64 {
assert_eq!(i, splitmix64_inverse(splitmix64(i)))
}
}
const HASHES: usize = 3;
#[test]
fn fill_list_performance() {
let mut rn = xorshift64(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as u64);
const CAPACITY: usize = 4096;
//let mut rn = xorshift64(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as u64);
let mut rn = 31337;
let mut expected: HashSet<u64> = HashSet::with_capacity(4096);
let mut count = 64;
const CAPACITY: usize = 4096;
while count <= CAPACITY {
let mut test = IBLT::<CAPACITY>::new();
let mut test = IBLT::<CAPACITY, HASHES>::new();
expected.clear();
for _ in 0..count {
@ -215,43 +245,50 @@ mod tests {
assert!(expected.contains(&x));
});
//println!("inserted: {}\tlisted: {}\tcapacity: {}\tscore: {:.4}\tfill: {:.4}", count, list_count, CAPACITY, (list_count as f64) / (count as f64), (count as f64) / (CAPACITY as f64));
println!("inserted: {}\tlisted: {}\tcapacity: {}\tscore: {:.4}\tfill: {:.4}", count, list_count, CAPACITY, (list_count as f64) / (count as f64), (count as f64) / (CAPACITY as f64));
count += 64;
}
}
#[test]
fn merge_sets() {
let mut rn = xorshift64(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as u64);
const CAPACITY: usize = 16384;
const REMOTE_SIZE: usize = 1024 * 1024;
const REMOTE_SIZE: usize = 1024 * 1024 * 2;
const STEP: usize = 1024;
//let mut rn = xorshift64(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as u64);
let mut rn = 31337;
let mut missing_count = 1024;
let mut missing: HashSet<u64> = HashSet::with_capacity(CAPACITY);
let mut missing: HashSet<u64> = HashSet::with_capacity(CAPACITY * 2);
let mut all: HashSet<u64> = HashSet::with_capacity(REMOTE_SIZE);
while missing_count <= CAPACITY {
missing.clear();
let mut local = IBLT::<CAPACITY>::new();
let mut remote = IBLT::<CAPACITY>::new();
all.clear();
let mut local = IBLT::<CAPACITY, HASHES>::new();
let mut remote = IBLT::<CAPACITY, HASHES>::new();
for k in 0..REMOTE_SIZE {
if k >= missing_count {
local.insert(rn);
} else {
missing.insert(rn);
let mut k = 0;
while k < REMOTE_SIZE {
if all.insert(rn) {
if k >= missing_count {
local.insert(rn);
} else {
missing.insert(rn);
}
remote.insert(rn);
k += 1;
}
remote.insert(rn);
rn = splitmix64(rn);
}
local.subtract(&mut remote);
let bytes = local.as_bytes().len();
let mut cnt = 0;
local.list(|k| {
let all_success = local.list(|k| {
assert!(missing.contains(&k));
cnt += 1;
});
println!("total: {} missing: {:5} recovered: {:5} size: {:5} score: {:.4} bytes/item: {:.2}", REMOTE_SIZE, missing.len(), cnt, bytes, (cnt as f64) / (missing.len() as f64), (bytes as f64) / (cnt as f64));
println!("total: {} missing: {:5} recovered: {:5} size: {:5} score: {:.4} bytes/item: {:.2} extract(fill): {:.4} 100%: {}", REMOTE_SIZE, missing.len(), cnt, bytes, (cnt as f64) / (missing.len() as f64), (bytes as f64) / (cnt as f64), (cnt as f64) / (CAPACITY as f64), all_success);
missing_count += STEP;
}

View file

@ -6,11 +6,13 @@
* https://www.zerotier.com/
*/
pub(crate) mod varint;
pub(crate) mod protocol;
pub(crate) mod iblt;
pub(crate) mod protocol;
pub(crate) mod varint;
pub mod datastore;
pub mod node;
pub mod host;
pub mod node;
pub mod utils;
pub use async_trait;

View file

@ -1,18 +1,27 @@
extern crate core;
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c)2022 ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::collections::BTreeMap;
use std::io::{stdout, Write};
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::ops::Bound::Included;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
use async_trait::async_trait;
use sha2::digest::Digest;
use sha2::Sha512;
use syncwhole::datastore::{DataStore, LoadResult, StoreResult};
use syncwhole::host::Host;
use syncwhole::node::{Node, RemoteNodeInfo};
use syncwhole::datastore::*;
use syncwhole::host::*;
use syncwhole::node::*;
use syncwhole::utils::*;
const TEST_NODE_COUNT: usize = 8;
@ -36,16 +45,33 @@ fn get_random_bytes(mut buf: &mut [u8]) {
unsafe { RANDOM_CTR = ctr };
}
struct TestNodeHost {
name: String,
peers: Vec<SocketAddr>,
db: Mutex<BTreeMap<[u8; 64], Arc<[u8]>>>,
pub struct TestNodeHost {
pub name: String,
pub config: Config,
pub records: tokio::sync::Mutex<BTreeMap<[u8; 64], [u8; 64]>>,
}
impl TestNodeHost {
pub fn new_random(test_no: usize) -> Self {
let mut s = BTreeMap::new();
for _ in 0..TEST_STARTING_RECORDS_PER_NODE {
let mut v = [0_u8; 64];
get_random_bytes(&mut v);
let k = Self::sha512(&[&v]);
s.insert(k, v);
}
Self {
name: test_no.to_string(),
config: Config::default(),
records: tokio::sync::Mutex::new(s),
}
}
}
impl Host for TestNodeHost {
fn fixed_peers(&self) -> &[SocketAddr] { self.peers.as_slice() }
fn name(&self) -> Option<&str> { Some(self.name.as_str()) }
fn node_config(&self) -> Config {
self.config.clone()
}
fn on_connect_attempt(&self, _address: &SocketAddr) {
//println!("{:5}: connecting to {}", self.name, _address.to_string());
@ -56,7 +82,7 @@ impl Host for TestNodeHost {
}
fn on_connection_closed(&self, info: &RemoteNodeInfo, reason: String) {
println!("{:5}: closed connection to {}: {} ({}, {})", self.name, info.remote_address.to_string(), reason, if info.inbound { "inbound" } else { "outbound" }, if info.initialized { "initialized" } else { "not initialized" });
//println!("{:5}: closed connection to {}: {} ({}, {})", self.name, info.remote_address.to_string(), reason, if info.inbound { "inbound" } else { "outbound" }, if info.initialized { "initialized" } else { "not initialized" });
}
fn get_secure_random(&self, buf: &mut [u8]) {
@ -65,47 +91,68 @@ impl Host for TestNodeHost {
}
}
#[async_trait]
impl DataStore for TestNodeHost {
type LoadResultValueType = Arc<[u8]>;
type ValueRef = [u8; 64];
const MAX_VALUE_SIZE: usize = 1024;
fn clock(&self) -> i64 { ms_since_epoch() }
fn domain(&self) -> &str { "test" }
fn load(&self, _: i64, key: &[u8]) -> LoadResult<Self::LoadResultValueType> {
self.db.lock().unwrap().get(key).map_or(LoadResult::NotFound, |r| LoadResult::Ok(r.clone()))
fn clock(&self) -> i64 {
ms_since_epoch()
}
fn store(&self, key: &[u8], value: &[u8]) -> StoreResult {
assert_eq!(key.len(), 64);
let mut res = StoreResult::Ok(0);
self.db.lock().unwrap().entry(key.try_into().unwrap()).and_modify(|e| {
if e.as_ref().eq(value) {
res = StoreResult::Duplicate;
} else {
*e = Arc::from(value)
fn domain(&self) -> &str {
"test"
}
async fn load(&self, _: i64, key: &[u8]) -> Option<Self::ValueRef> {
let key = key.try_into();
if key.is_ok() {
let key: [u8; 64] = key.unwrap();
let records = self.records.lock().await;
let value = records.get(&key);
if value.is_some() {
return Some(value.unwrap().clone());
}
}).or_insert_with(|| {
Arc::from(value)
});
res
}
return None;
}
fn count(&self, _: i64, key_range_start: &[u8], key_range_end: &[u8]) -> u64 {
let s: [u8; 64] = key_range_start.try_into().unwrap();
let e: [u8; 64] = key_range_end.try_into().unwrap();
self.db.lock().unwrap().range((Included(s), Included(e))).count() as u64
async fn store(&self, key: &[u8], value: &[u8]) -> StoreResult {
let key = key.try_into();
if key.is_ok() && value.len() == 64 {
let key: [u8; 64] = key.unwrap();
let value: [u8; 64] = value.try_into().unwrap();
if key == Self::sha512(&[&value]) {
if self.records.lock().await.insert(key, value).is_none() {
StoreResult::Ok
} else {
StoreResult::Duplicate
}
} else {
StoreResult::Rejected
}
} else {
StoreResult::Rejected
}
}
fn total_count(&self) -> u64 { self.db.lock().unwrap().len() as u64 }
async fn count(&self, _: i64, key_range_start: &[u8], key_range_end: &[u8]) -> u64 {
let start: [u8; 64] = key_range_start.try_into().unwrap();
let end: [u8; 64] = key_range_end.try_into().unwrap();
self.records.lock().await.range((Included(start), Included(end))).count() as u64
}
fn for_each<F: FnMut(&[u8], &[u8]) -> bool>(&self, _: i64, key_range_start: &[u8], key_range_end: &[u8], mut f: F) {
let s: [u8; 64] = key_range_start.try_into().unwrap();
let e: [u8; 64] = key_range_end.try_into().unwrap();
for (k, v) in self.db.lock().unwrap().range((Included(s), Included(e))) {
if !f(k, v.as_ref()) {
async fn total_count(&self) -> u64 {
self.records.lock().await.len() as u64
}
async fn for_each<F: Send + FnMut(&[u8], &Self::ValueRef) -> bool>(&self, _reference_time: i64, key_range_start: &[u8], key_range_end: &[u8], mut f: F) {
let start: [u8; 64] = key_range_start.try_into().unwrap();
let end: [u8; 64] = key_range_end.try_into().unwrap();
let records = self.records.lock().await;
for (k, v) in records.range((Included(start), Included(end))) {
if !f(k, v) {
break;
}
}
@ -126,11 +173,10 @@ fn main() {
peers.push(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port2)));
}
}
let nh = Arc::new(TestNodeHost {
name: format!("{}", port),
peers,
db: Mutex::new(BTreeMap::new())
});
let mut th = TestNodeHost::new_random(port as usize);
th.config.anchors = peers;
th.config.name = port.to_string();
let nh = Arc::new(th);
//println!("Starting node on 127.0.0.1:{}...", port, nh.db.lock().unwrap().len());
nodes.push(Node::new(nh.clone(), nh.clone(), SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port))).await.unwrap());
}
@ -152,20 +198,6 @@ fn main() {
}
}
println!("Populating maps with data to be synchronized between nodes...");
let mut all_records = BTreeMap::new();
for n in nodes.iter_mut() {
for _ in 0..TEST_STARTING_RECORDS_PER_NODE {
let mut k = [0_u8; 64];
let mut v = [0_u8; 32];
get_random_bytes(&mut k);
get_random_bytes(&mut v);
let v: Arc<[u8]> = Arc::from(v);
all_records.insert(k.clone(), v.clone());
n.datastore().db.lock().unwrap().insert(k, v);
}
}
loop {
tokio::time::sleep(Duration::from_secs(1)).await;
}

View file

@ -29,11 +29,15 @@ use crate::protocol::*;
use crate::utils::*;
use crate::varint;
/// Period for running main housekeeping pass.
const HOUSEKEEPING_PERIOD: i64 = SYNC_STATUS_PERIOD;
/// Inactivity timeout for connections in milliseconds.
const CONNECTION_TIMEOUT: i64 = SYNC_STATUS_PERIOD * 4;
/// How often to run the housekeeping task's loop in milliseconds.
const HOUSEKEEPING_INTERVAL: i64 = SYNC_STATUS_PERIOD;
/// Announce when we get records from peers if sync status estimate is more than this threshold.
/// This is used to stop us from spamming with HaveRecords while catching up.
const ANNOUNCE_IF_SYNCED_MORE_THAN: f64 = 0.95;
/// Information about a remote node to which we are connected.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -63,19 +67,6 @@ pub struct RemoteNodeInfo {
pub initialized: bool,
}
fn configure_tcp_socket(socket: &TcpSocket) -> std::io::Result<()> {
let _ = socket.set_linger(None);
if socket.set_reuseport(true).is_ok() {
Ok(())
} else {
socket.set_reuseaddr(true)
}
}
fn decode_msgpack<'a, T: Deserialize<'a>>(b: &'a [u8]) -> std::io::Result<T> {
rmp_serde::from_slice(b).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("invalid msgpack object: {}", e.to_string())))
}
/// An instance of the syncwhole data set synchronization engine.
///
/// This holds a number of async tasks that are terminated or aborted if this object
@ -83,6 +74,7 @@ fn decode_msgpack<'a, T: Deserialize<'a>>(b: &'a [u8]) -> std::io::Result<T> {
pub struct Node<D: DataStore + 'static, H: Host + 'static> {
internal: Arc<NodeInternal<D, H>>,
housekeeping_task: JoinHandle<()>,
announce_task: JoinHandle<()>,
listener_task: JoinHandle<()>,
}
@ -102,11 +94,18 @@ impl<D: DataStore + 'static, H: Host + 'static> Node<D, H> {
datastore: db.clone(),
host: host.clone(),
connections: Mutex::new(HashMap::with_capacity(64)),
announce_queue: Mutex::new(HashMap::with_capacity(256)),
bind_address,
starting_instant: Instant::now(),
sync_completeness_estimate: AtomicU64::new((0.0_f64).to_bits()),
});
Ok(Self { internal: internal.clone(), housekeeping_task: tokio::spawn(internal.clone().housekeeping_task_main()), listener_task: tokio::spawn(internal.listener_task_main(listener)) })
Ok(Self {
internal: internal.clone(),
housekeeping_task: tokio::spawn(internal.clone().housekeeping_task_main()),
announce_task: tokio::spawn(internal.clone().announce_task_main()),
listener_task: tokio::spawn(internal.listener_task_main(listener)),
})
}
#[inline(always)]
@ -119,31 +118,66 @@ impl<D: DataStore + 'static, H: Host + 'static> Node<D, H> {
&self.internal.host
}
/// Broadcast a new record to the world.
///
/// This should be called when new records are added to the synchronized data store
/// that are created locally. If this isn't called it may take a while for normal
/// sync to pick up and propagate the record.
pub async fn broadcast_new_record(&self, key: &[u8], value: &[u8]) {}
/// Attempt to connect to an explicitly specified TCP endpoint.
pub async fn connect(&self, endpoint: &SocketAddr) -> std::io::Result<bool> {
self.internal.clone().connect(endpoint, Instant::now().add(Duration::from_millis(CONNECTION_TIMEOUT as u64))).await
}
/// Get open peer to peer connections.
pub async fn list_connections(&self) -> Vec<RemoteNodeInfo> {
let connections = self.internal.connections.lock().await;
let mut cl: Vec<RemoteNodeInfo> = Vec::with_capacity(connections.len());
for (_, c) in connections.iter() {
cl.push(c.info.lock().await.clone());
cl.push(c.info.lock().unwrap().clone());
}
cl
}
/// Get the number of open peer to peer connections.
pub async fn connection_count(&self) -> usize {
self.internal.connections.lock().await.len()
}
/// Get a value from 0.0 to 1.0 estimating how synchronized we are with the network.
///
/// This is an inexact estimate since it's based on record counts and it's possible for
/// two nodes to have the same count but disjoint sets. It tends to be fairly good in
/// practice though unless you have been disconnected for a very long time.
pub async fn sync_completeness_estimate(&self) -> f64 {
f64::from_bits(self.internal.sync_completeness_estimate.load(Ordering::Relaxed))
}
}
impl<D: DataStore + 'static, H: Host + 'static> Drop for Node<D, H> {
fn drop(&mut self) {
self.housekeeping_task.abort();
self.announce_task.abort();
self.listener_task.abort();
}
}
/********************************************************************************************************************/
fn configure_tcp_socket(socket: &TcpSocket) -> std::io::Result<()> {
let _ = socket.set_linger(None);
if socket.set_reuseport(true).is_ok() {
Ok(())
} else {
socket.set_reuseaddr(true)
}
}
fn decode_msgpack<'a, T: Deserialize<'a>>(b: &'a [u8]) -> std::io::Result<T> {
rmp_serde::from_slice(b).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("invalid msgpack object: {}", e.to_string())))
}
pub struct NodeInternal<D: DataStore + 'static, H: Host + 'static> {
// Secret used to perform HMAC to detect and drop loopback connections to self.
anti_loopback_secret: [u8; 64],
@ -155,11 +189,17 @@ pub struct NodeInternal<D: DataStore + 'static, H: Host + 'static> {
// Connections and their task join handles, by remote endpoint address.
connections: Mutex<HashMap<SocketAddr, Arc<Connection>>>,
// Records received since last announce and the endpoints that we know already have them.
announce_queue: Mutex<HashMap<[u8; ANNOUNCE_KEY_LEN], Vec<SocketAddr>>>,
// Local address to which this node is bound
bind_address: SocketAddr,
// Instant this node started.
starting_instant: Instant,
// Latest estimate of sync completeness.
sync_completeness_estimate: AtomicU64,
}
impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
@ -167,30 +207,46 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
Instant::now().duration_since(self.starting_instant).as_millis() as i64
}
/// Loop that constantly runs in the background to do cleanup and service things.
async fn housekeeping_task_main(self: Arc<Self>) {
let mut tasks: Vec<JoinHandle<()>> = Vec::new();
let mut counts: Vec<u64> = Vec::new();
let mut connected_to_addresses: HashSet<SocketAddr> = HashSet::new();
let mut sleep_until = Instant::now().add(Duration::from_millis(500));
loop {
tokio::time::sleep_until(sleep_until).await;
sleep_until = sleep_until.add(Duration::from_millis(HOUSEKEEPING_INTERVAL as u64));
sleep_until = sleep_until.add(Duration::from_millis(HOUSEKEEPING_PERIOD as u64));
tasks.clear();
counts.clear();
connected_to_addresses.clear();
let now = self.ms_monotonic();
// Drop dead connections, send SyncStatus, and populate counts for computing sync status.
let sync_status = Arc::new(
rmp_serde::encode::to_vec_named(&msg::SyncStatus {
record_count: self.datastore.total_count().await,
clock: self.datastore.clock() as u64,
})
.unwrap(),
);
self.connections.lock().await.retain(|sa, c| {
if !c.closed.load(Ordering::Relaxed) {
let cc = c.clone();
if (now - c.last_receive_time.load(Ordering::Relaxed)) < CONNECTION_TIMEOUT {
connected_to_addresses.insert(sa.clone());
if c.info.lock().unwrap().initialized {
counts.push(c.last_sync_status_record_count.load(Ordering::Relaxed));
let ss2 = sync_status.clone();
tasks.push(tokio::spawn(async move {
let _ = tokio::time::timeout_at(sleep_until, cc.send_msg(MessageType::SyncStatus, ss2.as_slice(), now)).await;
}));
}
true // keep connection
} else {
let _ = c.read_task.lock().unwrap().take().map(|j| j.abort());
let host = self.host.clone();
let cc = c.clone();
tasks.push(tokio::spawn(async move {
host.on_connection_closed(&*cc.info.lock().await, "timeout".to_string());
host.on_connection_closed(&*cc.info.lock().unwrap(), "timeout".to_string());
}));
false // discard connection
}
@ -203,18 +259,31 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
let e = j.unwrap().await;
if e.is_ok() {
let e = e.unwrap();
host.on_connection_closed(&*cc.info.lock().await, e.map_or_else(|e| e.to_string(), |_| "unknown error".to_string()));
host.on_connection_closed(&*cc.info.lock().unwrap(), e.map_or_else(|e| e.to_string(), |_| "unknown error".to_string()));
} else {
host.on_connection_closed(&*cc.info.lock().await, "remote host closed connection".to_string());
host.on_connection_closed(&*cc.info.lock().unwrap(), "remote host closed connection".to_string());
}
} else {
host.on_connection_closed(&*cc.info.lock().await, "remote host closed connection".to_string());
host.on_connection_closed(&*cc.info.lock().unwrap(), "remote host closed connection".to_string());
}
}));
false // discard connection
}
});
let sync_completness_estimate = if !counts.is_empty() {
counts.sort_unstable();
let twothirds = if counts.len() > 3 { *counts.get((counts.len() / 3) * 2).unwrap() } else { *counts.last().unwrap() };
if twothirds > 0 {
((self.datastore.total_count().await as f64) / (twothirds as f64)).min(1.0)
} else {
1.0
}
} else {
1.0
};
self.sync_completeness_estimate.store(sync_completness_estimate.to_bits(), Ordering::Relaxed);
let config = self.host.node_config();
// Always try to connect to anchor peers.
@ -257,7 +326,42 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
}
}
/// Incoming TCP acceptor task.
async fn announce_task_main(self: Arc<Self>) {
let mut sleep_until = Instant::now().add(Duration::from_millis(ANNOUNCE_PERIOD as u64));
let mut to_announce: Vec<([u8; ANNOUNCE_KEY_LEN], Vec<SocketAddr>)> = Vec::with_capacity(256);
let background_tasks = AsyncTaskReaper::new();
let announce_timeout = Duration::from_millis(CONNECTION_TIMEOUT as u64);
loop {
tokio::time::sleep_until(sleep_until).await;
sleep_until = sleep_until.add(Duration::from_millis(ANNOUNCE_PERIOD as u64));
for (key, already_has) in self.announce_queue.lock().await.drain() {
to_announce.push((key, already_has));
}
let now = self.ms_monotonic();
for c in self.connections.lock().await.iter() {
let mut have_records: Vec<u8> = Vec::with_capacity((to_announce.len() * ANNOUNCE_KEY_LEN) + 4);
have_records.push(ANNOUNCE_KEY_LEN as u8);
for (key, already_has) in to_announce.iter() {
if !already_has.contains(c.0) {
let _ = std::io::Write::write_all(&mut have_records, key);
}
}
if have_records.len() > 1 {
let c2 = c.1.clone();
background_tasks.spawn(async move {
// If the connection dies this will either fail or time out in 1s. Usually these execute instantly due to
// write buffering but a short timeout prevents them from building up too much.
let _ = tokio::time::timeout(announce_timeout, c2.send_msg(MessageType::HaveRecords, have_records.as_slice(), now));
})
}
}
to_announce.clear();
}
}
async fn listener_task_main(self: Arc<Self>, listener: TcpListener) {
loop {
let socket = listener.accept().await;
@ -273,7 +377,6 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
}
}
/// Initiate an outgoing connection with a deadline based timeout.
async fn connect(self: Arc<Self>, address: &SocketAddr, deadline: Instant) -> std::io::Result<bool> {
self.host.on_connect_attempt(address);
let stream = if address.is_ipv4() { TcpSocket::new_v4() } else { TcpSocket::new_v6() }?;
@ -287,28 +390,35 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
}
}
/// Sets up and spawns the task for a new TCP connection whether inbound or outbound.
async fn connection_start(self: &Arc<Self>, address: SocketAddr, stream: TcpStream, inbound: bool) -> bool {
let mut ok = false;
let _ = self.connections.lock().await.entry(address.clone()).or_insert_with(|| {
ok = true;
//let _ = stream.set_nodelay(true);
let _ = stream.set_nodelay(false);
let (reader, writer) = stream.into_split();
let now = self.ms_monotonic();
let connection = Arc::new(Connection {
writer: Mutex::new(writer),
last_send_time: AtomicI64::new(now),
last_receive_time: AtomicI64::new(now),
bytes_sent: AtomicU64::new(0),
bytes_received: AtomicU64::new(0),
info: Mutex::new(RemoteNodeInfo { name: String::new(), contact: String::new(), remote_address: address.clone(), explicit_addresses: Vec::new(), connect_time: ms_since_epoch(), connect_instant: now, inbound, initialized: false }),
last_sync_status_record_count: AtomicU64::new(0),
info: std::sync::Mutex::new(RemoteNodeInfo {
name: String::new(),
contact: String::new(),
remote_address: address.clone(),
explicit_addresses: Vec::new(),
connect_time: ms_since_epoch(),
connect_instant: now,
inbound,
initialized: false,
}),
read_task: std::sync::Mutex::new(None),
closed: AtomicBool::new(false),
});
let self2 = self.clone();
let c2 = connection.clone();
connection.read_task.lock().unwrap().replace(tokio::spawn(async move {
let result = self2.connection_io_task_main(&c2, reader).await;
let result = self2.connection_io_task_main(&c2, address, reader).await;
c2.closed.store(true, Ordering::Relaxed);
result
}));
@ -317,11 +427,7 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
ok
}
/// Main I/O task launched for each connection.
///
/// This handles reading from the connection and reacting to what it sends. Killing this
/// task is done when the connection is closed.
async fn connection_io_task_main(self: Arc<Self>, connection: &Arc<Connection>, mut reader: OwnedReadHalf) -> std::io::Result<()> {
async fn connection_io_task_main(self: Arc<Self>, connection: &Arc<Connection>, remote_address: SocketAddr, mut reader: OwnedReadHalf) -> std::io::Result<()> {
const BUF_CHUNK_SIZE: usize = 4096;
const READ_BUF_INITIAL_SIZE: usize = 65536; // should be a multiple of BUF_CHUNK_SIZE
@ -392,7 +498,7 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
}
}
}
let message = &read_buffer.as_slice()[header_size..total_size];
let mut message = &read_buffer.as_slice()[header_size..total_size];
let now = self.ms_monotonic();
connection.last_receive_time.store(now, Ordering::Relaxed);
@ -408,7 +514,7 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
let msg: msg::Init = decode_msgpack(message)?;
let (anti_loopback_response, domain_challenge_response, auth_challenge_response) = {
let mut info = connection.info.lock().await;
let mut info = connection.info.lock().unwrap();
info.name = msg.node_name.to_string();
info.contact = msg.node_contact.to_string();
let _ = msg.explicit_ipv4.map(|pv4| {
@ -417,22 +523,35 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
let _ = msg.explicit_ipv6.map(|pv6| {
info.explicit_addresses.push(SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(pv6.ip), pv6.port, 0, 0)));
});
let info = info.clone();
let auth_challenge_response = self.host.authenticate(&info, msg.auth_challenge);
if auth_challenge_response.is_none() {
return Err(std::io::Error::new(std::io::ErrorKind::Other, "authenticate() returned None, connection dropped"));
}
let auth_challenge_response = auth_challenge_response.unwrap();
(H::hmac_sha512(&self.anti_loopback_secret, msg.anti_loopback_challenge), H::hmac_sha512(&H::sha512(&[self.datastore.domain().as_bytes()]), msg.domain_challenge), auth_challenge_response.unwrap())
(H::hmac_sha512(&self.anti_loopback_secret, msg.anti_loopback_challenge), H::hmac_sha512(&H::sha512(&[self.datastore.domain().as_bytes()]), msg.domain_challenge), auth_challenge_response)
};
connection.send_obj(&mut write_buffer, MessageType::InitResponse, &msg::InitResponse { anti_loopback_response: &anti_loopback_response, domain_response: &domain_challenge_response, auth_response: &auth_challenge_response }, now).await?;
connection
.send_obj(
&mut write_buffer,
MessageType::InitResponse,
&msg::InitResponse {
anti_loopback_response: &anti_loopback_response,
domain_response: &domain_challenge_response,
auth_response: &auth_challenge_response,
},
now,
)
.await?;
}
MessageType::InitResponse => {
let msg: msg::InitResponse = decode_msgpack(message)?;
let mut info = connection.info.lock().unwrap();
let mut info = connection.info.lock().await;
if info.initialized {
return Err(std::io::Error::new(std::io::ErrorKind::Other, "duplicate init response"));
}
@ -447,10 +566,11 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
return Err(std::io::Error::new(std::io::ErrorKind::Other, "challenge/response authentication failed"));
}
info.initialized = true;
initialized = true;
let info = info.clone();
info.initialized = true;
let info = info.clone(); // also releases lock since info is replaced/destroyed
self.host.on_connect(&info);
}
@ -461,35 +581,103 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
}
match message_type {
_ => {}
MessageType::HaveRecords => {
let msg: msg::HaveRecords = decode_msgpack(message)?;
if message.len() > 1 {
let clock = self.datastore.clock();
let mut announce_queue_key = [0_u8; ANNOUNCE_KEY_LEN];
let mut start = [0_u8; KEY_SIZE];
let mut end = [0xff_u8; KEY_SIZE];
let key_prefix_len = message[0] as usize;
message = &message[1..];
if key_prefix_len > 0 && key_prefix_len <= KEY_SIZE {
write_buffer.clear();
write_buffer.push(key_prefix_len as u8);
while message.len() >= key_prefix_len {
let key_prefix = &message[..key_prefix_len];
if key_prefix_len >= ANNOUNCE_KEY_LEN {
// If the key prefix is appropriately sized, look up and add this remote endpoint
// to the list of endpoints that already have this record if it's in the announce
// queue. We don't add a new entry to the announce queue if one doesn't already
// exist because we did not just receive the actual record. This just avoids announcing
// to peers that just told us they have it.
announce_queue_key.copy_from_slice(&key_prefix[..ANNOUNCE_KEY_LEN]);
self.announce_queue.lock().await.get_mut(&announce_queue_key).map(|already_has| {
if !already_has.contains(&remote_address) {
already_has.push(remote_address.clone());
}
});
}
if if key_prefix_len < KEY_SIZE {
(&mut start[..key_prefix_len]).copy_from_slice(key_prefix);
(&mut end[..key_prefix_len]).copy_from_slice(key_prefix);
self.datastore.count(clock, &start, &end).await == 0
} else {
!self.datastore.contains(clock, key_prefix).await
} {
let _ = std::io::Write::write_all(&mut write_buffer, key_prefix);
}
message = &message[key_prefix_len..];
}
if write_buffer.len() > 1 {
let _ = connection.send_msg(MessageType::GetRecords, write_buffer.as_slice(), now).await?;
}
}
}
}
MessageType::GetRecords => {
let msg: msg::GetRecords = decode_msgpack(message)?;
if message.len() > 1 {
let mut start = [0_u8; KEY_SIZE];
let mut end = [0xff_u8; KEY_SIZE];
let key_prefix_len = message[0] as usize;
message = &message[1..];
if key_prefix_len > 0 && key_prefix_len <= KEY_SIZE {
while message.len() >= key_prefix_len {
let key_prefix = &message[..key_prefix_len];
if key_prefix_len < KEY_SIZE {
(&mut start[..key_prefix_len]).copy_from_slice(key_prefix);
(&mut end[..key_prefix_len]).copy_from_slice(key_prefix);
self.datastore
.for_each(0, &start, &end, |_, v| {
let v2 = v.clone();
let c2 = connection.clone();
background_tasks.spawn(async move {
let _ = c2.send_msg(MessageType::Record, v2.as_ref(), now).await;
});
true
})
.await;
} else {
let record = self.datastore.load(0, key_prefix).await;
if record.is_some() {
let record = record.unwrap();
let v: &[u8] = record.as_ref();
let _ = connection.send_msg(MessageType::Record, v, now).await?;
}
}
message = &message[key_prefix_len..];
}
}
}
}
MessageType::Record => {
let key = H::sha512(&[message]);
match self.datastore.store(&key, message) {
match self.datastore.store(&key, message).await {
StoreResult::Ok => {
// TODO: probably should not announce if way out of sync
let connections = self.connections.lock().await;
let mut announce_to: Vec<Arc<Connection>> = Vec::with_capacity(connections.len());
for (_, c) in connections.iter() {
if !Arc::ptr_eq(&connection, c) {
announce_to.push(c.clone());
if f64::from_bits(self.sync_completeness_estimate.load(Ordering::Relaxed)) >= ANNOUNCE_IF_SYNCED_MORE_THAN {
let announce_key: [u8; ANNOUNCE_KEY_LEN] = (&key[..ANNOUNCE_KEY_LEN]).try_into().unwrap();
let mut q = self.announce_queue.lock().await;
let ql = q.entry(announce_key).or_insert_with(|| Vec::with_capacity(2));
if !ql.contains(&remote_address) {
ql.push(remote_address.clone());
}
}
drop(connections); // release lock
background_tasks.spawn(async move {
for c in announce_to.iter() {
let _ = c.send_msg(MessageType::HaveRecord, &key[0..ANNOUNCE_KEY_LEN], now).await;
}
});
}
StoreResult::Rejected => {
return Err(std::io::Error::new(std::io::ErrorKind::Other, format!("record rejected by data store: {}", to_hex_string(&key))));
@ -500,6 +688,7 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
MessageType::SyncStatus => {
let msg: msg::SyncStatus = decode_msgpack(message)?;
connection.last_sync_status_record_count.store(msg.record_count, Ordering::Relaxed);
}
MessageType::SyncRequest => {
@ -509,14 +698,14 @@ impl<D: DataStore + 'static, H: Host + 'static> NodeInternal<D, H> {
MessageType::SyncResponse => {
let msg: msg::SyncResponse = decode_msgpack(message)?;
}
_ => {}
}
}
}
read_buffer.copy_within(total_size..buffer_fill, 0);
buffer_fill -= total_size;
connection.bytes_received.fetch_add(total_size as u64, Ordering::Relaxed);
}
}
}
@ -544,9 +733,8 @@ struct Connection {
writer: Mutex<OwnedWriteHalf>,
last_send_time: AtomicI64,
last_receive_time: AtomicI64,
bytes_sent: AtomicU64,
bytes_received: AtomicU64,
info: Mutex<RemoteNodeInfo>,
last_sync_status_record_count: AtomicU64,
info: std::sync::Mutex<RemoteNodeInfo>,
read_task: std::sync::Mutex<Option<JoinHandle<std::io::Result<()>>>>,
closed: AtomicBool,
}
@ -558,7 +746,6 @@ impl Connection {
let header_size = 1 + varint::encode(&mut header[1..], data.len() as u64);
if self.writer.lock().await.write_vectored(&[IoSlice::new(&header[0..header_size]), IoSlice::new(data)]).await? == (data.len() + header_size) {
self.last_send_time.store(now, Ordering::Relaxed);
self.bytes_sent.fetch_add((header_size + data.len()) as u64, Ordering::Relaxed);
Ok(())
} else {
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "write error"))

View file

@ -12,19 +12,38 @@ pub const ANNOUNCE_KEY_LEN: usize = 24;
/// Send SyncStatus this frequently, in milliseconds.
pub const SYNC_STATUS_PERIOD: i64 = 5000;
/// Check for and announce that we "have" records this often in milliseconds.
pub const ANNOUNCE_PERIOD: i64 = 100;
#[derive(Clone, Copy, Eq, PartialEq)]
#[repr(u8)]
pub enum MessageType {
/// No operation, payload ignored.
Nop = 0_u8,
/// msg::Init (msgpack)
Init = 1_u8,
/// msg::InitResponse (msgpack)
InitResponse = 2_u8,
HaveRecord = 3_u8,
HaveRecords = 4_u8,
GetRecords = 5_u8,
Record = 6_u8,
SyncStatus = 7_u8,
SyncRequest = 8_u8,
SyncResponse = 9_u8,
/// <u8 length of each key in bytes>[<key>...]
HaveRecords = 3_u8,
/// <u8 length of each key in bytes>[<key>...]
GetRecords = 4_u8,
/// <record>
Record = 5_u8,
/// msg::SyncStatus (msgpack)
SyncStatus = 6_u8,
/// msg::SyncRequest (msgpack)
SyncRequest = 7_u8,
/// msg::SyncResponse (msgpack)
SyncResponse = 8_u8,
}
impl From<u8> for MessageType {
@ -40,12 +59,12 @@ impl From<u8> for MessageType {
}
impl MessageType {
#[allow(unused)]
pub fn name(&self) -> &'static str {
match *self {
Self::Nop => "NOP",
Self::Init => "INIT",
Self::InitResponse => "INIT_RESPONSE",
Self::HaveRecord => "HAVE_RECORD",
Self::HaveRecords => "HAVE_RECORDS",
Self::GetRecords => "GET_RECORDS",
Self::Record => "RECORD",
@ -56,45 +75,8 @@ impl MessageType {
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
#[repr(u8)]
pub enum SyncResponseType {
/// No response, do nothing.
None = 0_u8,
/// Response is a msgpack-encoded HaveRecords message.
HaveRecords = 1_u8,
/// Response is a series of records prefixed by varint record sizes.
Records = 2_u8,
/// Response is an IBLT set summary.
IBLT = 3_u8,
}
impl From<u8> for SyncResponseType {
/// Get response type from a byte, returning None if the byte is out of range.
#[inline(always)]
fn from(b: u8) -> Self {
if b <= 3 {
unsafe { std::mem::transmute(b) }
} else {
Self::None
}
}
}
impl SyncResponseType {
pub fn as_str(&self) -> &'static str {
match *self {
SyncResponseType::None => "NONE",
SyncResponseType::HaveRecords => "HAVE_RECORDS",
SyncResponseType::Records => "RECORDS",
SyncResponseType::IBLT => "IBLT",
}
}
}
/// Msgpack serializable message types.
/// Some that are frequently transferred use shortened names to save bandwidth.
pub mod msg {
use serde::{Deserialize, Serialize};
@ -158,30 +140,6 @@ pub mod msg {
pub auth_response: &'a [u8],
}
#[derive(Serialize, Deserialize)]
pub struct HaveRecords<'a> {
/// Length of each key, chosen to ensure uniqueness.
#[serde(rename = "l")]
pub key_length: usize,
/// Keys whose existence is being announced, of 'key_length' length.
#[serde(with = "serde_bytes")]
#[serde(rename = "k")]
pub keys: &'a [u8],
}
#[derive(Serialize, Deserialize)]
pub struct GetRecords<'a> {
/// Length of each key, chosen to ensure uniqueness.
#[serde(rename = "l")]
pub key_length: usize,
/// Keys to retrieve, of 'key_length' bytes in length.
#[serde(with = "serde_bytes")]
#[serde(rename = "k")]
pub keys: &'a [u8],
}
#[derive(Serialize, Deserialize)]
pub struct SyncStatus {
/// Total number of records this node has in its data store.
@ -195,58 +153,69 @@ pub mod msg {
#[derive(Serialize, Deserialize)]
pub struct SyncRequest<'a> {
/// Query mask, a random string of KEY_SIZE bytes.
/// Key range start (length: KEY_SIZE)
#[serde(with = "serde_bytes")]
#[serde(rename = "q")]
pub query_mask: &'a [u8],
#[serde(rename = "s")]
pub range_start: &'a [u8],
/// Number of bits to match as a prefix in query_mask (0 for entire data set).
#[serde(rename = "b")]
pub query_mask_bits: u8,
/// Key range end (length: KEY_SIZE)
#[serde(with = "serde_bytes")]
#[serde(rename = "e")]
pub range_end: &'a [u8],
/// Number of records requesting node already holds under query mask prefix.
/// Number of records requesting node already has under key range
#[serde(rename = "c")]
pub record_count: u64,
/// Sender's reference time.
/// Reference time for query
#[serde(rename = "t")]
pub reference_time: u64,
/// Random salt
#[serde(rename = "s")]
pub salt: u64,
#[serde(rename = "x")]
pub salt: &'a [u8],
}
#[derive(Serialize, Deserialize)]
pub struct SyncResponse<'a> {
/// Query mask, a random string of KEY_SIZE bytes.
#[serde(with = "serde_bytes")]
#[serde(rename = "q")]
pub query_mask: &'a [u8],
/// Key range start (length: KEY_SIZE)
#[serde(rename = "s")]
pub range_start: &'a [u8],
/// Number of bits to match as a prefix in query_mask (0 for entire data set).
#[serde(rename = "b")]
pub query_mask_bits: u8,
/// Key range end (length: KEY_SIZE)
#[serde(rename = "e")]
pub range_end: &'a [u8],
/// Number of records sender has under this prefix.
/// Number of records responder has under key range
#[serde(rename = "c")]
pub record_count: u64,
/// Sender's reference time.
/// Reference time for query
#[serde(rename = "t")]
pub reference_time: u64,
/// Random salt
#[serde(rename = "s")]
pub salt: u64,
#[serde(rename = "x")]
pub salt: &'a [u8],
/// SyncResponseType determining content of 'data'.
#[serde(rename = "r")]
pub response_type: u8,
/// Data whose meaning depends on the response type.
/// IBLT set summary or empty if not included
///
/// If an IBLT is omitted it means the sender determined it was
/// more efficient to just send keys. In that case keys[] should have
/// an explicit list.
#[serde(with = "serde_bytes")]
#[serde(rename = "d")]
pub data: &'a [u8],
#[serde(rename = "i")]
pub iblt: &'a [u8],
/// Explicit list of keys (full key length).
///
/// This may still contain keys if an IBLT is present. In that case
/// keys included here will be any that have identical 64-bit prefixes
/// to keys already added to the IBLT and thus would collide. These
/// should be rare so it's most efficient to just explicitly name them.
/// Otherwise keys with identical 64-bit prefixes may never be synced.
#[serde(with = "serde_bytes")]
#[serde(rename = "k")]
pub keys: &'a [u8],
}
}

View file

@ -34,33 +34,30 @@ pub fn to_hex_string(b: &[u8]) -> String {
#[inline(always)]
pub fn xorshift64(mut x: u64) -> u64 {
x = u64::from_le(x);
x ^= x.wrapping_shl(13);
x ^= x.wrapping_shr(7);
x ^= x.wrapping_shl(17);
x.to_le()
x
}
#[inline(always)]
pub fn splitmix64(mut x: u64) -> u64 {
x = u64::from_le(x);
x ^= x.wrapping_shr(30);
x = x.wrapping_mul(0xbf58476d1ce4e5b9);
x ^= x.wrapping_shr(27);
x = x.wrapping_mul(0x94d049bb133111eb);
x ^= x.wrapping_shr(31);
x.to_le()
x
}
#[inline(always)]
pub fn splitmix64_inverse(mut x: u64) -> u64 {
x = u64::from_le(x);
x ^= x.wrapping_shr(31) ^ x.wrapping_shr(62);
x = x.wrapping_mul(0x319642b2d24d8ec3);
x ^= x.wrapping_shr(27) ^ x.wrapping_shr(54);
x = x.wrapping_mul(0x96de1b173f119089);
x ^= x.wrapping_shr(30) ^ x.wrapping_shr(60);
x.to_le()
x
}
static mut RANDOM_STATE_0: u64 = 0;
@ -73,7 +70,7 @@ pub fn random() -> u64 {
s0 = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as u64;
}
if s1 == 0 {
s1 = splitmix64(std::process::id() as u64);
s1 = splitmix64((std::process::id() as u64).wrapping_add((unsafe { &RANDOM_STATE_0 } as *const u64) as u64));
}
let s1_new = xorshift64(s1);
s0 = splitmix64(s0.wrapping_add(s1));
@ -93,7 +90,10 @@ pub struct AsyncTaskReaper {
impl AsyncTaskReaper {
pub fn new() -> Self {
Self { ctr: AtomicUsize::new(0), handles: Arc::new(std::sync::Mutex::new(HashMap::new())) }
Self {
ctr: AtomicUsize::new(0),
handles: Arc::new(std::sync::Mutex::new(HashMap::new())),
}
}
/// Spawn a new task.

View file

@ -6,6 +6,7 @@
* https://www.zerotier.com/
*/
#[allow(unused)]
pub const VARINT_MAX_SIZE_BYTES: usize = 10;
pub fn encode(b: &mut [u8], mut v: u64) -> usize {