mirror of
https://github.com/zerotier/common-utils.git
synced 2026-05-22 16:28:56 -07:00
Initial commit of utils broken out of next-gen monorepo.
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
/target
|
||||
/**/target
|
||||
/**/Cargo.lock
|
||||
|
||||
.DS_*
|
||||
.Icon*
|
||||
._*
|
||||
*.o
|
||||
*.so
|
||||
*.dylib
|
||||
*.dSYM
|
||||
*.a
|
||||
/.idea
|
||||
/.nova
|
||||
*.secret
|
||||
+22
@@ -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"
|
||||
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+36
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user