Initial commit of utils broken out of next-gen monorepo.

This commit is contained in:
Adam Ierymenko
2023-08-02 10:09:53 -04:00
commit 959815fb1e
23 changed files with 3861 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
/target
/**/target
/**/Cargo.lock
.DS_*
.Icon*
._*
*.o
*.so
*.dylib
*.dSYM
*.a
/.idea
/.nova
*.secret
+22
View File
@@ -0,0 +1,22 @@
[package]
authors = ["ZeroTier, Inc. <contact@zerotier.com>"]
edition = "2021"
license = "MPL-2.0"
name = "zerotier-common-utils"
version = "0.1.0"
[features]
[dependencies]
base64 = "^0"
serde = { version = "^1", features = ["derive"], default-features = false }
[dev-dependencies]
rand = "*"
[target."cfg(windows)".dependencies]
winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }
[target."cfg(not(windows))".dependencies]
libc = "^0"
signal-hook = "^0"
+16
View File
@@ -0,0 +1,16 @@
#unstable_features = true
max_width = 150
#use_small_heuristics = "Max"
edition = "2021"
#empty_item_single_line = true
newline_style = "Unix"
struct_lit_width = 60
tab_spaces = 4
use_small_heuristics = "Default"
#fn_single_line = true
#hex_literal_case = "Lower"
#merge_imports = false
group_imports = "StdExternalCrate"
single_line_if_else_max_width = 0
use_try_shorthand = true
imports_granularity = "Module"
+434
View File
@@ -0,0 +1,434 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::fmt::Debug;
use std::io::Write;
use std::mem::{needs_drop, size_of, MaybeUninit};
use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut};
use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone, Copy, Debug)]
pub struct OutOfCapacityError<T>(pub T);
impl<T> std::fmt::Display for OutOfCapacityError<T> {
fn fmt(&self, stream: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt("ArrayVec out of space", stream)
}
}
impl<T: std::fmt::Debug> ::std::error::Error for OutOfCapacityError<T> {
fn description(&self) -> &str {
"ArrayVec out of space"
}
}
/// A simple vector backed by a static sized array with no memory allocations and no overhead construction.
pub struct ArrayVec<T, const C: usize> {
pub(crate) s: usize,
pub(crate) a: [MaybeUninit<T>; C],
}
impl<T, const C: usize> Default for ArrayVec<T, C> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl<T: Clone, const C: usize> Clone for ArrayVec<T, C> {
#[inline]
fn clone(&self) -> Self {
debug_assert!(self.s <= C);
Self {
s: self.s,
a: unsafe {
let mut tmp: [MaybeUninit<T>; C] = MaybeUninit::uninit().assume_init();
for i in 0..self.s {
tmp.get_unchecked_mut(i).write(self.a[i].assume_init_ref().clone());
}
tmp
},
}
}
}
impl<T: Clone, const C: usize, const S: usize> From<[T; S]> for ArrayVec<T, C> {
#[inline]
fn from(v: [T; S]) -> Self {
if S <= C {
let mut tmp = Self::new();
for i in 0..S {
tmp.push(v[i].clone());
}
tmp
} else {
panic!();
}
}
}
impl<const C: usize> ToString for ArrayVec<u8, C> {
#[inline]
fn to_string(&self) -> String {
crate::hex::to_string(self.as_bytes())
}
}
impl<const C: usize> Write for ArrayVec<u8, C> {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
for i in buf.iter() {
if self.try_push(*i).is_err() {
return Err(std::io::Error::new(std::io::ErrorKind::Other, "ArrayVec out of space"));
}
}
Ok(buf.len())
}
#[inline(always)]
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<T, const C: usize> TryFrom<Vec<T>> for ArrayVec<T, C> {
type Error = OutOfCapacityError<T>;
#[inline(always)]
fn try_from(mut value: Vec<T>) -> Result<Self, Self::Error> {
let mut tmp = Self::new();
for x in value.drain(..) {
tmp.try_push(x)?;
}
Ok(tmp)
}
}
impl<T: Clone, const C: usize> TryFrom<&Vec<T>> for ArrayVec<T, C> {
type Error = OutOfCapacityError<T>;
#[inline(always)]
fn try_from(value: &Vec<T>) -> Result<Self, Self::Error> {
let mut tmp = Self::new();
for x in value.iter() {
tmp.try_push(x.clone())?;
}
Ok(tmp)
}
}
impl<T: Clone, const C: usize> TryFrom<&[T]> for ArrayVec<T, C> {
type Error = OutOfCapacityError<T>;
#[inline(always)]
fn try_from(value: &[T]) -> Result<Self, Self::Error> {
let mut tmp = Self::new();
for x in value.iter() {
tmp.try_push(x.clone())?;
}
Ok(tmp)
}
}
impl<T, const C: usize> ArrayVec<T, C> {
#[inline(always)]
pub fn new() -> Self {
assert_eq!(size_of::<[T; C]>(), size_of::<[MaybeUninit<T>; C]>());
Self { s: 0, a: unsafe { MaybeUninit::uninit().assume_init() } }
}
#[inline]
pub fn push(&mut self, v: T) {
let i = self.s;
if i < C {
unsafe { self.a.get_unchecked_mut(i).write(v) };
self.s = i + 1;
} else {
panic!();
}
}
#[inline]
pub fn try_push(&mut self, v: T) -> Result<(), OutOfCapacityError<T>> {
if self.s < C {
let i = self.s;
unsafe { self.a.get_unchecked_mut(i).write(v) };
self.s = i + 1;
Ok(())
} else {
Err(OutOfCapacityError(v))
}
}
/// Get a raw byte slice view of the contents of this vector.
/// This is only available for Copy types and will panic if the type needs_drop().
#[inline(always)]
pub fn as_bytes(&self) -> &[T]
where
T: Copy,
{
assert!(!std::mem::needs_drop::<T>());
unsafe { &*slice_from_raw_parts(self.a.as_ptr().cast(), self.s) }
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.s == 0
}
#[inline(always)]
pub fn len(&self) -> usize {
self.s
}
#[inline(always)]
pub const fn capacity(&self) -> usize {
C
}
#[inline(always)]
pub fn capacity_remaining(&self) -> usize {
C - self.s
}
#[inline(always)]
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &T> {
self.as_ref().iter()
}
#[inline(always)]
pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
self.as_mut().iter_mut()
}
#[inline(always)]
pub fn first(&self) -> Option<&T> {
if self.s != 0 {
Some(unsafe { self.a.get_unchecked(0).assume_init_ref() })
} else {
None
}
}
#[inline(always)]
pub fn last(&self) -> Option<&T> {
if self.s != 0 {
Some(unsafe { self.a.get_unchecked(self.s - 1).assume_init_ref() })
} else {
None
}
}
#[inline]
pub fn pop(&mut self) -> Option<T> {
if self.s > 0 {
let i = self.s - 1;
debug_assert!(i < C);
self.s = i;
Some(unsafe { self.a.get_unchecked(i).assume_init_read() })
} else {
None
}
}
#[inline]
pub fn clear(&mut self) {
if needs_drop::<T>() {
for i in 0..self.s {
unsafe { self.a.get_unchecked_mut(i).assume_init_drop() };
}
}
self.s = 0;
}
#[inline]
pub fn sort(&mut self)
where
T: Ord,
{
self.as_mut().sort();
}
#[inline]
pub fn sort_unstable(&mut self)
where
T: Ord,
{
self.as_mut().sort_unstable();
}
}
impl<T, const C: usize> ArrayVec<T, C>
where
T: Copy,
{
/// Push a slice of copyable objects, panic if capacity exceeded.
#[inline]
pub fn push_slice(&mut self, v: &[T]) {
let start = self.s;
let end = self.s + v.len();
if end <= C {
for i in start..end {
unsafe { self.a.get_unchecked_mut(i).write(*v.get_unchecked(i - start)) };
}
self.s = end;
} else {
panic!();
}
}
}
impl<T, const C: usize> Drop for ArrayVec<T, C> {
#[inline(always)]
fn drop(&mut self) {
if needs_drop::<T>() {
for i in 0..self.s {
unsafe { self.a.get_unchecked_mut(i).assume_init_drop() };
}
}
}
}
impl<T, const C: usize> AsRef<[T]> for ArrayVec<T, C> {
#[inline(always)]
fn as_ref(&self) -> &[T] {
unsafe { &*slice_from_raw_parts(self.a.as_ptr().cast(), self.s) }
}
}
impl<T, const C: usize> AsMut<[T]> for ArrayVec<T, C> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [T] {
unsafe { &mut *slice_from_raw_parts_mut(self.a.as_mut_ptr().cast(), self.s) }
}
}
impl<T, const C: usize> PartialEq for ArrayVec<T, C>
where
T: PartialEq,
{
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
let tmp: &[T] = self.as_ref();
tmp.eq(other.as_ref())
}
}
impl<T, const C: usize> Eq for ArrayVec<T, C> where T: Eq {}
impl<T, const L: usize> PartialOrd for ArrayVec<T, L>
where
T: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.iter().partial_cmp(other.iter())
}
}
impl<T, const L: usize> Ord for ArrayVec<T, L>
where
T: Ord,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.iter().cmp(other.iter())
}
}
impl<T, const L: usize> Debug for ArrayVec<T, L>
where
T: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("[")?;
for x in self.iter() {
x.fmt(f)?;
}
f.write_str("]")
}
}
impl<T, const L: usize> Serialize for ArrayVec<T, L>
where
T: Serialize,
{
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.len()))?;
let sl: &[T] = self.as_ref();
for i in 0..self.s {
seq.serialize_element(&sl[i])?;
}
seq.end()
}
}
struct ArrayVecVisitor<'de, T: Deserialize<'de>, const L: usize>(std::marker::PhantomData<&'de T>);
impl<'de, T, const L: usize> serde::de::Visitor<'de> for ArrayVecVisitor<'de, T, L>
where
T: Deserialize<'de>,
{
type Value = ArrayVec<T, L>;
#[inline]
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(format!("up to {} elements", L).as_str())
}
#[inline]
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut a = ArrayVec::<T, L>::new();
while let Some(x) = seq.next_element()? {
if !a.try_push(x).is_ok() {
return Err(serde::de::Error::custom("capacity exceeded"));
}
}
return Ok(a);
}
}
impl<'de, T: Deserialize<'de> + 'de, const L: usize> Deserialize<'de> for ArrayVec<T, L>
where
T: Deserialize<'de>,
{
#[inline]
fn deserialize<D>(deserializer: D) -> Result<ArrayVec<T, L>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(ArrayVecVisitor(std::marker::PhantomData::default()))
}
}
#[cfg(test)]
mod tests {
use super::ArrayVec;
#[test]
fn array_vec() {
let mut v = ArrayVec::<usize, 128>::new();
for i in 0..128 {
v.push(i);
}
assert_eq!(v.len(), 128);
assert!(v.try_push(1000).is_err());
assert_eq!(v.len(), 128);
for _ in 0..128 {
assert!(v.pop().is_some());
}
assert!(v.pop().is_none());
}
}
+53
View File
@@ -0,0 +1,53 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use crate::error::InvalidParameterError;
/// All unambiguous letters, thus easy to type on the alphabetic keyboards on phones without extra shift taps.
/// The letters 'l' and 'u' are skipped.
const BASE24_ALPHABET: [u8; 24] = [
b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'v', b'w', b'x', b'y', b'z',
];
/// Reverse table for BASE24 alphabet, indexed relative to 'a' or 'A'.
const BASE24_ALPHABET_INV: [u8; 26] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 255, 11, 12, 13, 14, 15, 16, 17, 18, 255, 19, 20, 21, 22, 23,
];
/// Encode 4 binary bytes into 7 base24 characters.
pub fn encode_4to7(b: &[u8], s: &mut String) {
let mut n = u32::from_be_bytes(b[..4].try_into().unwrap());
for _ in 0..6 {
s.push(BASE24_ALPHABET[(n % 24) as usize] as char);
n /= 24;
}
s.push(BASE24_ALPHABET[n as usize] as char);
}
pub fn decode_7to4(mut s: &[u8]) -> Result<[u8; 4], InvalidParameterError> {
let mut n = 0u32;
if s.len() > 7 {
s = &s[..7];
}
for c in s.iter().rev() {
let mut c = *c;
if c >= 97 && c <= 122 {
c -= 97;
} else if c >= 65 && c <= 90 {
c -= 65;
} else {
return Err(InvalidParameterError("invalid base24 character"));
}
let i = BASE24_ALPHABET_INV[c as usize];
if i == 255 {
return Err(InvalidParameterError("invalid base24 character"));
}
n *= 24;
n = n.wrapping_add(i as u32);
}
return Ok(n.to_be_bytes());
}
+21
View File
@@ -0,0 +1,21 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use base64::{engine::general_purpose, Engine as _};
/// Encode a byte slice as Base64 using the URL-safe alphabet without padding
#[inline(always)]
pub fn to_string(b: &[u8]) -> String {
general_purpose::URL_SAFE_NO_PAD.encode(b)
}
/// Decode a byte slcie using the URL-safe alphabet without padding
#[inline(always)]
pub fn from_string(s: &[u8]) -> Option<Vec<u8>> {
general_purpose::URL_SAFE_NO_PAD.decode(s).ok()
}
+160
View File
@@ -0,0 +1,160 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::array::TryFromSliceError;
use std::fmt::Debug;
use std::hash::Hash;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::base64;
use crate::hex;
/// Fixed size Serde serializable byte array.
/// This makes it easier to deal with blobs larger than 32 bytes (due to serde array limitations)
#[repr(transparent)]
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Blob<const L: usize>([u8; L]);
impl<const L: usize> Blob<L> {
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; L] {
&self.0
}
#[inline(always)]
pub const fn len(&self) -> usize {
L
}
}
impl<const L: usize> From<Blob<L>> for [u8; L] {
#[inline(always)]
fn from(value: Blob<L>) -> Self {
value.0
}
}
impl<const L: usize> From<[u8; L]> for Blob<L> {
#[inline(always)]
fn from(a: [u8; L]) -> Self {
Self(a)
}
}
impl<const L: usize> From<&[u8; L]> for &Blob<L> {
#[inline(always)]
fn from(a: &[u8; L]) -> Self {
// Blob is a transparent wrapper around an array, so this is just a type cast.
unsafe { std::mem::transmute(a) }
}
}
impl<const L: usize> TryFrom<&[u8]> for Blob<L> {
type Error = TryFromSliceError;
#[inline(always)]
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
value.try_into().map(|b| Self(b))
}
}
impl<const L: usize> Default for Blob<L> {
#[inline(always)]
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
impl<const L: usize> AsRef<[u8; L]> for Blob<L> {
#[inline(always)]
fn as_ref(&self) -> &[u8; L] {
&self.0
}
}
impl<const L: usize> AsMut<[u8; L]> for Blob<L> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8; L] {
&mut self.0
}
}
impl<const L: usize> ToString for Blob<L> {
#[inline(always)]
fn to_string(&self) -> String {
hex::to_string(&self.0)
}
}
impl<const L: usize> Debug for Blob<L> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.to_string().as_str())
}
}
impl<const L: usize> Serialize for Blob<L> {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
base64::to_string(&self.0).serialize(serializer)
} else {
serializer.serialize_bytes(&self.0)
}
}
}
struct BlobVisitor<const L: usize>;
impl<'de, const L: usize> serde::de::Visitor<'de> for BlobVisitor<L> {
type Value = Blob<L>;
#[inline]
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(format!("{} bytes", L).as_str())
}
#[inline]
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let b = base64::from_string(v.trim().as_bytes()).ok_or(serde::de::Error::custom("invalid base64"))?;
b.as_slice()
.try_into()
.map(|b| Blob::<L>(b))
.map_err(|_| serde::de::Error::invalid_length(b.len(), &self))
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.try_into()
.map(|b| Blob::<L>(b))
.map_err(|_| serde::de::Error::invalid_length(v.len(), &self))
}
}
impl<'de, const L: usize> Deserialize<'de> for Blob<L> {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
deserializer.deserialize_str(BlobVisitor::<L>)
} else {
deserializer.deserialize_bytes(BlobVisitor::<L>)
}
}
}
+703
View File
File diff suppressed because it is too large Load Diff
+36
View File
@@ -0,0 +1,36 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::any::TypeId;
use std::mem::size_of;
/// Returns true if two types are in fact the same type.
#[inline(always)]
pub fn same_type<U: 'static, V: 'static>() -> bool {
TypeId::of::<U>() == TypeId::of::<V>() && size_of::<U>() == size_of::<V>()
}
/// Cast a reference if the types are equal, such as from a specific type to a generic that it implements.
#[inline(always)]
pub fn cast_ref<U: 'static, V: 'static>(u: &U) -> Option<&V> {
if same_type::<U, V>() {
Some(unsafe { std::mem::transmute::<&U, &V>(u) })
} else {
None
}
}
/// Cast a reference if the types are equal, such as from a specific type to a generic that it implements.
#[inline(always)]
pub fn cast_mut<U: 'static, V: 'static>(u: &mut U) -> Option<&mut V> {
if same_type::<U, V>() {
Some(unsafe { std::mem::transmute::<&mut U, &mut V>(u) })
} else {
None
}
}
+209
View File
@@ -0,0 +1,209 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::collections::BTreeMap;
use std::io::Write;
use crate::hex;
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(Debug, Clone, PartialEq, Eq)]
pub struct Dictionary(pub(crate) BTreeMap<String, Vec<u8>>);
fn write_escaped<W: Write>(mut b: &[u8], w: &mut W) -> std::io::Result<()> {
while !b.is_empty() {
match b[0] {
0 => {
w.write_all(&[b'\\', b'0'])?;
}
b'\n' => {
w.write_all(&[b'\\', b'n'])?;
}
b'\r' => {
w.write_all(&[b'\\', b'r'])?;
}
b'=' => {
w.write_all(&[b'\\', b'e'])?;
}
b'\\' => {
w.write_all(&[b'\\', b'\\'])?;
}
_ => {
w.write_all(&b[..1])?;
}
}
b = &b[1..];
}
Ok(())
}
fn append_printable(s: &mut String, b: &[u8]) {
for c in b {
let c = *c as char;
if c.is_alphanumeric() || c.is_whitespace() {
s.push(c);
} else {
s.push('\\');
s.push('x');
s.push(hex::HEX_CHARS[((c as u8) >> 4) as usize] as char);
s.push(hex::HEX_CHARS[((c as u8) & 0xf) as usize] as char);
}
}
}
impl Dictionary {
pub fn new() -> Self {
Self(BTreeMap::new())
}
pub fn clear(&mut self) {
self.0.clear()
}
#[inline(always)]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get_str(&self, k: &str) -> Option<&str> {
self.0.get(k).and_then(|v| std::str::from_utf8(v.as_slice()).ok())
}
pub fn get_bytes(&self, k: &str) -> Option<&[u8]> {
self.0.get(k).map(|v| v.as_slice())
}
pub fn get_u64(&self, k: &str) -> Option<u64> {
self.get_str(k).and_then(|s| u64::from_str_radix(s, 16).ok())
}
pub fn get_i64(&self, k: &str) -> Option<i64> {
self.get_str(k).and_then(|s| i64::from_str_radix(s, 16).ok())
}
pub fn get_bool(&self, k: &str) -> Option<bool> {
self.0
.get(k)
.and_then(|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) {
let _ = self.0.insert(String::from(k), v.as_bytes().to_vec());
}
pub fn set_u64(&mut self, k: &str, v: u64) {
let _ = self.0.insert(String::from(k), hex::to_vec_u64(v, true));
}
pub fn set_bytes(&mut self, k: &str, v: Vec<u8>) {
let _ = self.0.insert(String::from(k), v);
}
pub fn set_bool(&mut self, k: &str, v: bool) {
let _ = self.0.insert(
String::from(k),
vec![if v {
b'1'
} else {
b'0'
}],
);
}
pub fn write_to<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
for kv in self.0.iter() {
write_escaped(kv.0.as_bytes(), w)?;
w.write_all(&[b'='])?;
write_escaped(kv.1.as_slice(), w)?;
w.write_all(&[b'\n'])?;
}
Ok(())
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut b: Vec<u8> = Vec::with_capacity(32 * self.0.len());
let _ = self.write_to(&mut b);
b
}
pub fn from_bytes(b: &[u8]) -> Option<Dictionary> {
let mut d = Dictionary::new();
let mut kv: [Vec<u8>; 2] = [Vec::new(), Vec::new()];
let mut state = 0;
let mut escape = false;
for c in b {
let c = *c;
if escape {
escape = false;
kv[state].push(match c {
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'\\' {
escape = true;
} else if c == b'=' {
if state != 0 {
return None;
}
state = 1;
} else if c == b'\n' {
if state != 1 {
return None;
}
state = 0;
if !kv[0].is_empty()
&& String::from_utf8(kv[0].clone()).map_or(true, |key| {
d.0.insert(key, kv[1].clone());
false
})
{
return None;
}
kv[0].clear();
kv[1].clear();
} else if c != b'\r' {
kv[state].push(c);
}
}
Some(d)
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &Vec<u8>)> {
self.0.iter()
}
}
impl ToString for Dictionary {
/// Get the dictionary in an always readable format with non-printable characters replaced by '\xXX'.
/// This is not a serializable output that can be re-imported. Use write_to() for that.
fn to_string(&self) -> String {
let mut s = String::new();
for kv in self.0.iter() {
append_printable(&mut s, kv.0.as_bytes());
s.push('=');
append_printable(&mut s, kv.1.as_slice());
s.push('\n');
}
s
}
}
+44
View File
@@ -0,0 +1,44 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::error::Error;
use std::fmt::{Debug, Display};
pub struct InvalidFormatError;
impl Display for InvalidFormatError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("InvalidFormatError")
}
}
impl Debug for InvalidFormatError {
#[inline(always)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<Self as Display>::fmt(self, f)
}
}
impl Error for InvalidFormatError {}
pub struct InvalidParameterError(pub &'static str);
impl Display for InvalidParameterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "InvalidParameterError: {}", self.0)
}
}
impl Debug for InvalidParameterError {
#[inline(always)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<Self as Display>::fmt(self, f)
}
}
impl Error for InvalidParameterError {}
+22
View File
@@ -0,0 +1,22 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
// These were taken from BSD sysexits.h to provide some standard for process exit codes.
pub const OK: i32 = 0;
pub const ERR_USAGE: i32 = 64;
pub const ERR_DATA_FORMAT: i32 = 65;
pub const ERR_NO_INPUT: i32 = 66;
pub const ERR_SERVICE_UNAVAILABLE: i32 = 69;
pub const ERR_INTERNAL: i32 = 70;
pub const ERR_OSERR: i32 = 71;
pub const ERR_OSFILE: i32 = 72;
pub const ERR_IOERR: i32 = 74;
pub const ERR_NOPERM: i32 = 77;
pub const ERR_CONFIG: i32 = 78;
+35
View File
@@ -0,0 +1,35 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
/// Boolean rate limiter with normal (non-atomic) semantics.
#[repr(transparent)]
pub struct IntervalGate<const FREQ: i64>(i64);
impl<const FREQ: i64> Default for IntervalGate<FREQ> {
#[inline(always)]
fn default() -> Self {
Self(crate::NEVER_HAPPENED_TICKS)
}
}
impl<const FREQ: i64> IntervalGate<FREQ> {
#[inline(always)]
pub fn new(initial_ts: i64) -> Self {
Self(initial_ts)
}
#[inline(always)]
pub fn gate(&mut self, time: i64) -> bool {
if (time - self.0) >= FREQ {
self.0 = time;
true
} else {
false
}
}
}
+124
View File
@@ -0,0 +1,124 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
pub const HEX_CHARS: [u8; 16] = [
b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f',
];
/// Encode a byte slice to a hexadecimal string.
pub fn to_string(b: &[u8]) -> String {
let mut s = String::with_capacity(b.len() * 2);
s.reserve(b.len() * 2);
for c in b {
let x = *c as usize;
s.push(HEX_CHARS[x >> 4] as char);
s.push(HEX_CHARS[x & 0xf] as char);
}
s
}
/// Encode an unsigned 64-bit value as a hexadecimal string.
pub fn to_string_u64(mut i: u64, skip_leading_zeroes: bool) -> String {
let mut s = String::with_capacity(16);
for _ in 0..16 {
let ii = i >> 60;
if ii != 0 || !s.is_empty() || !skip_leading_zeroes {
s.push(HEX_CHARS[ii as usize] as char);
}
i = i.wrapping_shl(4);
}
s
}
/// Encode an unsigned 64-bit value as a hexadecimal ASCII string.
pub fn to_vec_u64(mut i: u64, skip_leading_zeroes: bool) -> Vec<u8> {
let mut s = Vec::with_capacity(16);
for _ in 0..16 {
let ii = i >> 60;
if ii != 0 || !s.is_empty() || !skip_leading_zeroes {
s.push(HEX_CHARS[ii as usize]);
}
i = i.wrapping_shl(4);
}
s
}
/// Decode a hex string, ignoring all non-hexadecimal characters.
pub fn from_string(s: &str) -> Vec<u8> {
let mut b: Vec<u8> = Vec::with_capacity((s.len() / 2) + 1);
let mut byte = 0_u8;
let mut have_8: bool = false;
for cc in s.as_bytes() {
let c = *cc;
if (48..=57).contains(&c) {
byte = (byte.wrapping_shl(4)) | (c - 48);
if have_8 {
b.push(byte);
}
have_8 = !have_8;
} else if (65..=70).contains(&c) {
byte = (byte.wrapping_shl(4)) | (c - 55);
if have_8 {
b.push(byte);
}
have_8 = !have_8;
} else if (97..=102).contains(&c) {
byte = (byte.wrapping_shl(4)) | (c - 87);
if have_8 {
b.push(byte);
}
have_8 = !have_8;
}
}
b
}
pub fn from_string_u64(s: &str) -> u64 {
let mut n = 0u64;
let mut byte = 0_u8;
let mut have_8: bool = false;
for cc in s.as_bytes() {
let c = *cc;
if (48..=57).contains(&c) {
byte = (byte.wrapping_shl(4)) | (c - 48);
if have_8 {
n = n.wrapping_shl(8);
n |= byte as u64;
}
have_8 = !have_8;
} else if (65..=70).contains(&c) {
byte = (byte.wrapping_shl(4)) | (c - 55);
if have_8 {
n = n.wrapping_shl(8);
n |= byte as u64;
}
have_8 = !have_8;
} else if (97..=102).contains(&c) {
byte = (byte.wrapping_shl(4)) | (c - 87);
if have_8 {
n = n.wrapping_shl(8);
n |= byte as u64;
}
have_8 = !have_8;
}
}
n
}
/// Encode bytes from 'b' into hex characters in 'dest' and return the number of hex characters written.
/// This will panic if the destination slice is smaller than twice the length of the source.
pub fn to_hex_bytes(b: &[u8], dest: &mut [u8]) -> usize {
let mut j = 0;
for c in b {
let x = *c as usize;
dest[j] = HEX_CHARS[x >> 4];
dest[j + 1] = HEX_CHARS[x & 0xf];
j += 2;
}
j
}
+1191
View File
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
/// Default sanity limit parameter for read_limit() used throughout the service.
pub const DEFAULT_FILE_IO_READ_LIMIT: usize = 262144;
/// Convenience function to read up to limit bytes from a file.
///
/// If the file is larger than limit, the excess is not read.
pub fn read_limit<P: AsRef<Path>>(path: P, limit: usize) -> std::io::Result<Vec<u8>> {
let mut f = File::open(path)?;
let bytes = f.metadata()?.len().min(limit as u64) as usize;
let mut v: Vec<u8> = Vec::with_capacity(bytes);
v.resize(bytes, 0);
f.read_exact(v.as_mut_slice())?;
Ok(v)
}
/// Set permissions on a file or directory to be most restrictive (visible only to the service's user).
#[cfg(unix)]
pub fn fs_restrict_permissions<P: AsRef<Path>>(path: P) -> bool {
unsafe {
let c_path = std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap();
libc::chmod(
c_path.as_ptr(),
if path.as_ref().is_dir() {
0o700
} else {
0o600
},
) == 0
}
}
#[inline]
pub fn write_all_multi<W: Write>(w: &mut W, s: &[&[u8]]) -> std::io::Result<()> {
for ss in s {
w.write_all(*ss)?;
}
Ok(())
}
/// Set permissions on a file or directory to be most restrictive (visible only to the service's user).
#[cfg(windows)]
pub fn fs_restrict_permissions<P: AsRef<Path>>(path: P) -> bool {
todo!()
}
+119
View File
@@ -0,0 +1,119 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
pub mod arrayvec;
pub mod base24;
pub mod base64;
pub mod blob;
pub mod buffer;
pub mod cast;
pub mod dictionary;
pub mod error;
pub mod exitcode;
pub mod gate;
pub mod hex;
pub mod inetaddress;
pub mod io;
pub mod memory;
pub mod pool;
pub mod ringbuffer;
pub mod str;
pub mod sync;
pub mod tofrombytes;
/// Initial value that should be used for monotonic tick time variables.
pub const NEVER_HAPPENED_TICKS: i64 = i64::MIN / 2;
/// Get milliseconds since unix epoch.
#[inline]
pub fn ms_since_epoch() -> i64 {
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64
}
/// Get an estimate of the number of CPU cores in the system.
/// This defaults to 1 if information is not available.
pub fn parallelism() -> usize {
static mut PARALLELISM: usize = 0;
let mut p = unsafe { PARALLELISM };
// It's perfectly fine if this runs more than once due to concurrent calls as it should always yield the same value.
if p == 0 {
p = std::thread::available_parallelism().map(|p| p.get()).unwrap_or(1);
unsafe {
PARALLELISM = p;
}
}
p
}
/// Get milliseconds since an arbitrary time in the past, guaranteed to monotonically increase within a given process.
#[inline]
pub fn ms_monotonic() -> i64 {
static STARTUP_INSTANT: std::sync::RwLock<Option<std::time::Instant>> = std::sync::RwLock::new(None);
let si = *STARTUP_INSTANT.read().unwrap();
if let Some(si) = si {
si.elapsed().as_millis() as i64
} else {
STARTUP_INSTANT
.write()
.unwrap()
.get_or_insert(std::time::Instant::now())
.elapsed()
.as_millis() as i64
}
}
/// Wait for a kill signal (e.g. SIGINT or OS-equivalent) sent to this process and return when received.
#[cfg(unix)]
pub fn wait_for_process_abort() {
if let Ok(mut signals) = signal_hook::iterator::Signals::new([libc::SIGINT, libc::SIGTERM, libc::SIGQUIT]) {
'wait_for_exit: loop {
for signal in signals.wait() {
match signal as libc::c_int {
libc::SIGINT | libc::SIGTERM | libc::SIGQUIT => {
break 'wait_for_exit;
}
_ => {}
}
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
} else {
panic!("unable to listen for OS signals");
}
}
/// Helper for use in serde directives
#[inline(always)]
pub fn slice_is_empty<T>(s: &[T]) -> bool {
s.is_empty()
}
#[cold]
#[inline(never)]
pub extern "C" fn unlikely_branch() {}
#[cfg(test)]
mod tests {
use super::ms_monotonic;
use std::time::Duration;
#[test]
fn monotonic_clock_sanity_check() {
let start = ms_monotonic();
assert!(start >= 0);
std::thread::sleep(Duration::from_millis(500));
let end = ms_monotonic();
// per docs:
//
// The thread may sleep longer than the duration specified due to scheduling specifics or
// platform-dependent functionality. It will never sleep less.
//
assert!((end - start).abs() >= 500);
assert!((end - start).abs() < 750);
}
}
+80
View File
@@ -0,0 +1,80 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
// This is a collection of functions that use "unsafe" to do things with memory that should in fact
// be safe. Some of these may eventually get stable standard library replacements.
#[allow(unused_imports)]
use std::mem::{needs_drop, size_of, MaybeUninit};
#[allow(unused_imports)]
use std::ptr::copy_nonoverlapping;
/// Implement this trait to mark a struct as safe to cast (in place) from a byte array.
/// To be safe it must contain alignment-neutral objects which basically means bytes. For
/// integers they should be represented as byte arrays e.g. [u8; 4] for u32.
pub unsafe trait FlatBuffer: Sized {}
/// Our version of the not-yet-stable array_chunks method in slice.
#[inline(always)]
pub fn array_chunks_exact<T, const S: usize>(a: &[T]) -> impl Iterator<Item = &[T; S]> {
let mut i = 0;
let l = a.len();
std::iter::from_fn(move || {
let j = i + S;
if j <= l {
let next = unsafe { &*a.as_ptr().add(i).cast() };
i = j;
Some(next)
} else {
None
}
})
}
/// Obtain a view into an array cast as another array.
/// This will panic if the template parameters would result in out of bounds access.
#[inline(always)]
pub fn array_range<T, const S: usize, const START: usize, const LEN: usize>(a: &[T; S]) -> &[T; LEN] {
assert!((START + LEN) <= S);
unsafe { &*a.as_ptr().add(START).cast::<[T; LEN]>() }
}
/// Get a reference to a raw object as a byte array.
/// The template parameter S must be less than or equal to the size of the object in bytes or this will panic.
#[inline(always)]
pub fn as_byte_array<T: Copy, const S: usize>(o: &T) -> &[u8; S] {
assert!(S <= size_of::<T>());
unsafe { &*(o as *const T).cast() }
}
/// Get a reference to a raw object as a byte array.
/// The template parameter S must be less than or equal to the size of the object in bytes or this will panic.
#[inline(always)]
pub fn as_byte_array_mut<T: Copy, const S: usize>(o: &mut T) -> &mut [u8; S] {
assert!(S <= size_of::<T>());
unsafe { &mut *(o as *mut T).cast() }
}
/// Transmute an object to a byte array.
/// The template parameter S must equal the size of the object in bytes or this will panic.
#[inline(always)]
pub fn to_byte_array<T: Copy, const S: usize>(o: T) -> [u8; S] {
assert_eq!(S, size_of::<T>());
assert!(!std::mem::needs_drop::<T>());
unsafe { *(&o as *const T).cast() }
}
/// Cast a byte slice into a flat struct.
/// This will panic if the slice is too small or the struct requires drop.
#[inline(always)]
pub fn cast_to_struct<T: FlatBuffer>(b: &[u8]) -> &T {
assert!(b.len() >= size_of::<T>());
assert!(!std::mem::needs_drop::<T>());
unsafe { &*b.as_ptr().cast() }
}
+251
View File
@@ -0,0 +1,251 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
use std::sync::{Arc, Mutex, Weak};
/// Each pool requires a factory that creates and resets (for re-use) pooled objects.
pub trait PoolFactory<O> {
fn create(&self) -> O;
fn reset(&self, obj: &mut O);
}
/// Container for pooled objects that have been checked out of the pool.
///
/// Objects are automagically returned to the pool when Pooled<> is dropped if the pool still exists.
/// If the pool itself is gone objects are freed. Two methods for conversion to/from raw pointers are
/// available for interoperation with foreign APIs.
#[repr(transparent)]
pub struct Pooled<O, F: PoolFactory<O>>(NonNull<PoolEntry<O, F>>);
#[repr(C)]
struct PoolEntry<O, F: PoolFactory<O>> {
obj: O, // must be first
return_pool: Weak<PoolInner<O, F>>,
}
impl<O, F: PoolFactory<O>> Pooled<O, F> {
/// Create a pooled object wrapper around an object but with no pool to return it to.
/// The object will be freed when this pooled container is dropped.
#[inline]
pub fn naked(o: O) -> Self {
unsafe {
Self(NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry::<O, F> {
obj: o,
return_pool: Weak::new(),
}))))
}
}
/// Get a raw pointer to the object wrapped by this pooled object container.
///
/// The returned pointer MUST be returned to the pooling system with from_raw() or memory
/// will leak.
#[inline]
pub unsafe fn into_raw(self) -> *mut O {
// Verify that the structure is not padded before 'obj'.
assert_eq!(
(&self.0.as_ref().obj as *const O).cast::<u8>(),
(self.0.as_ref() as *const PoolEntry<O, F>).cast::<u8>()
);
let ptr = self.0.as_ptr().cast::<O>();
std::mem::forget(self);
ptr
}
/// Restore a raw pointer from into_raw() into a Pooled object.
///
/// The supplied pointer MUST have been obtained from a Pooled object. None is returned
/// if the pointer is null.
#[inline]
pub unsafe fn from_raw(raw: *mut O) -> Option<Self> {
if !raw.is_null() {
Some(Self(NonNull::new_unchecked(raw.cast())))
} else {
None
}
}
}
impl<O, F: PoolFactory<O>> Clone for Pooled<O, F>
where
O: Clone,
{
#[inline]
fn clone(&self) -> Self {
let internal = unsafe { &mut *self.0.as_ptr() };
if let Some(p) = internal.return_pool.upgrade() {
if let Some(o) = p.pool.lock().unwrap().pop() {
let mut o = Self(o);
*o.as_mut() = self.as_ref().clone();
o
} else {
Pooled::<O, F>(unsafe {
NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry::<O, F> {
obj: self.as_ref().clone(),
return_pool: Arc::downgrade(&p),
})))
})
}
} else {
Self::naked(self.as_ref().clone())
}
}
}
unsafe impl<O, F: PoolFactory<O>> Send for Pooled<O, F> where O: Send {}
unsafe impl<O, F: PoolFactory<O>> Sync for Pooled<O, F> where O: Sync {}
impl<O, F: PoolFactory<O>> Deref for Pooled<O, F> {
type Target = O;
#[inline(always)]
fn deref(&self) -> &Self::Target {
unsafe { &self.0.as_ref().obj }
}
}
impl<O, F: PoolFactory<O>> DerefMut for Pooled<O, F> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut self.0.as_mut().obj }
}
}
impl<O, F: PoolFactory<O>> AsRef<O> for Pooled<O, F> {
#[inline(always)]
fn as_ref(&self) -> &O {
unsafe { &self.0.as_ref().obj }
}
}
impl<O, F: PoolFactory<O>> AsMut<O> for Pooled<O, F> {
#[inline(always)]
fn as_mut(&mut self) -> &mut O {
unsafe { &mut self.0.as_mut().obj }
}
}
impl<O, F: PoolFactory<O>> Drop for Pooled<O, F> {
#[inline]
fn drop(&mut self) {
let internal = unsafe { &mut *self.0.as_ptr() };
if let Some(p) = internal.return_pool.upgrade() {
p.factory.reset(&mut internal.obj);
p.pool.lock().unwrap().push(self.0);
} else {
drop(unsafe { Box::from_raw(self.0.as_ptr()) });
}
}
}
/// An object pool for Reusable objects.
/// Checked out objects are held by a guard object that returns them when dropped if
/// the pool still exists or drops them if the pool has itself been dropped.
pub struct Pool<O, F: PoolFactory<O>>(Arc<PoolInner<O, F>>);
struct PoolInner<O, F: PoolFactory<O>> {
factory: F,
pool: Mutex<Vec<NonNull<PoolEntry<O, F>>>>,
}
impl<O, F: PoolFactory<O>> Pool<O, F> {
#[inline]
pub fn new(initial_stack_capacity: usize, factory: F) -> Self {
Self(Arc::new(PoolInner::<O, F> {
factory,
pool: Mutex::new(Vec::with_capacity(initial_stack_capacity)),
}))
}
/// Get a pooled object, or allocate one if the pool is empty.
#[inline]
pub fn get(&self) -> Pooled<O, F> {
if let Some(o) = self.0.pool.lock().unwrap().pop() {
return Pooled::<O, F>(o);
}
Pooled::<O, F>(unsafe {
NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry::<O, F> {
obj: self.0.factory.create(),
return_pool: Arc::downgrade(&self.0),
})))
})
}
/// Dispose of all pooled objects, freeing any memory they use.
///
/// If get() is called after this new objects will be allocated, and any outstanding
/// objects will still be returned on drop unless the pool itself is dropped. This can
/// be done to free some memory if there has been a spike in memory use.
#[inline]
pub fn purge(&self) {
for o in self.0.pool.lock().unwrap().drain(..) {
drop(unsafe { Box::from_raw(o.as_ptr()) })
}
}
}
impl<O, F: PoolFactory<O>> Drop for Pool<O, F> {
#[inline(always)]
fn drop(&mut self) {
self.purge();
}
}
unsafe impl<O: Send, F: PoolFactory<O>> Send for Pool<O, F> {}
unsafe impl<O: Send, F: PoolFactory<O>> Sync for Pool<O, F> {}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use super::*;
struct TestPoolFactory;
impl PoolFactory<String> for TestPoolFactory {
fn create(&self) -> String {
String::new()
}
fn reset(&self, obj: &mut String) {
obj.clear();
}
}
#[test]
fn threaded_pool_use() {
let p: Arc<Pool<String, TestPoolFactory>> = Arc::new(Pool::new(2, TestPoolFactory {}));
let ctr = Arc::new(AtomicUsize::new(0));
for _ in 0..64 {
let p2 = p.clone();
let ctr2 = ctr.clone();
let _ = std::thread::spawn(move || {
for _ in 0..16384 {
let mut o1 = p2.get();
o1.push('a');
let o2 = p2.get();
drop(o1);
let mut o2 = unsafe { Pooled::<String, TestPoolFactory>::from_raw(o2.into_raw()).unwrap() };
o2.push('b');
ctr2.fetch_add(1, Ordering::Relaxed);
}
});
}
loop {
std::thread::sleep(Duration::from_millis(100));
if ctr.load(Ordering::Relaxed) >= 16384 * 64 {
break;
}
}
}
}
+120
View File
@@ -0,0 +1,120 @@
/* 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) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::mem::MaybeUninit;
/// A FIFO ring buffer.
pub struct RingBuffer<T, const C: usize> {
a: [MaybeUninit<T>; C],
p: usize,
}
impl<T, const C: usize> RingBuffer<T, C> {
#[inline]
pub fn new() -> Self {
#[allow(invalid_value)]
let mut tmp: Self = unsafe { MaybeUninit::uninit().assume_init() };
tmp.p = 0;
tmp
}
/// Add an element to the buffer, replacing old elements if full.
#[inline]
pub fn add(&mut self, o: T) {
let p = self.p;
if p < C {
unsafe { self.a.get_unchecked_mut(p).write(o) };
} else {
unsafe { *self.a.get_unchecked_mut(p % C).assume_init_mut() = o };
}
self.p = p.wrapping_add(1);
}
/// Clear the buffer and drop all elements.
#[inline]
pub fn clear(&mut self) {
for i in 0..C.min(self.p) {
unsafe { self.a.get_unchecked_mut(i).assume_init_drop() };
}
self.p = 0;
}
/// Gets an iterator that dumps the contents of the buffer in FIFO order.
#[inline]
pub fn iter(&self) -> RingBufferIterator<'_, T, C> {
let s = C.min(self.p);
RingBufferIterator { b: self, s, i: self.p.wrapping_sub(s) }
}
}
impl<T, const C: usize> Default for RingBuffer<T, C> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl<T, const C: usize> Drop for RingBuffer<T, C> {
#[inline(always)]
fn drop(&mut self) {
self.clear();
}
}
pub struct RingBufferIterator<'a, T, const C: usize> {
b: &'a RingBuffer<T, C>,
s: usize,
i: usize,
}
impl<'a, T, const C: usize> Iterator for RingBufferIterator<'a, T, C> {
type Item = &'a T;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let s = self.s;
if s > 0 {
let i = self.i;
self.s = s.wrapping_sub(1);
self.i = i.wrapping_add(1);
Some(unsafe { self.b.a.get_unchecked(i % C).assume_init_ref() })
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fifo() {
let mut tmp: RingBuffer<i32, 8> = RingBuffer::new();
let mut tmp2 = Vec::new();
for i in 0..4 {
tmp.add(i);
tmp2.push(i);
}
for (i, j) in tmp.iter().zip(tmp2.iter()) {
assert_eq!(*i, *j);
}
tmp.clear();
tmp2.clear();
for i in 0..23 {
tmp.add(i);
tmp2.push(i);
}
while tmp2.len() > 8 {
tmp2.remove(0);
}
for (i, j) in tmp.iter().zip(tmp2.iter()) {
assert_eq!(*i, *j);
}
}
}

Some files were not shown because too many files have changed in this diff Show More