move to rustsvc branch

This commit is contained in:
Adam Ierymenko 2021-09-16 13:22:25 -04:00
parent 4f70873a46
commit 1fc4bc96d1
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
23 changed files with 8 additions and 4380 deletions

8
.gitignore vendored
View file

@ -6,3 +6,11 @@
*.so *.so
*.dylib *.dylib
*.a *.a
/.idea
# Added by cargo
#
# already existing elements were commented out
#/target
Cargo.lock

View file

@ -1,116 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64-serde"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e964e3e0a930303c7c0bdb28ebf691dd98d9eee4b8b68019d2c995710b58a18"
dependencies = [
"base64",
"serde",
]
[[package]]
name = "hex"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "zerotier-core"
version = "0.1.0"
dependencies = [
"base64",
"base64-serde",
"hex",
"num-derive",
"num-traits",
"serde",
]

View file

@ -1,20 +0,0 @@
[package]
name = "zerotier-core"
version = "0.1.0"
authors = ["Adam Ierymenko <adam.ierymenko@zerotier.com>"]
edition = "2018"
build = "build.rs"
[profile.release]
opt-level = 'z'
lto = true
codegen-units = 1
panic = 'abort'
[dependencies]
num-traits = "0.2"
num-derive = "0.3"
serde = { version = "1", features = ["derive"] }
hex = "0.4"
base64-serde = "0"
base64 = "0"

View file

@ -1,13 +0,0 @@
#[allow(unused_assignments)]
#[allow(unused_mut)]
fn main() {
let d = env!("CARGO_MANIFEST_DIR");
println!("cargo:rustc-link-search=native={}/../build/core", d);
println!("cargo:rustc-link-lib=static=zt_core");
let mut cpplib = "c++";
#[cfg(target_os = "linux")] {
cpplib = "stdc++";
}
println!("cargo:rustc-link-lib={}", cpplib);
}

View file

@ -1,100 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::hash::{Hash, Hasher};
#[derive(PartialEq, Eq, Clone, Copy, Ord, PartialOrd)]
pub struct Address(pub u64);
impl Default for Address {
#[inline(always)]
fn default() -> Address {
Address(0)
}
}
impl Address {
#[inline(always)]
fn to_bytes(&self) -> [u8; 5] {
[(self.0 >> 32) as u8, (self.0 >> 24) as u8, (self.0 >> 16) as u8, (self.0 >> 8) as u8, self.0 as u8]
}
}
impl Hash for Address {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl ToString for Address {
fn to_string(&self) -> String {
format!("{:0>10x}", self.0)
}
}
impl From<u64> for Address {
#[inline(always)]
fn from(i: u64) -> Self {
Address(i & 0xffffffffff)
}
}
impl From<&str> for Address {
#[inline(always)]
fn from(s: &str) -> Self {
Address::from(u64::from_str_radix(s, 16).unwrap_or(0))
}
}
impl From<&[u8]> for Address {
#[inline(always)]
fn from(bytes: &[u8]) -> Self {
if bytes.len() >= 5 {
Address(((bytes[0] as u64) << 32) | ((bytes[1] as u64) << 24) | ((bytes[2] as u64) << 16) | ((bytes[3] as u64) << 8) | (bytes[4] as u64))
} else {
Address(0)
}
}
}
impl serde::Serialize for Address {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
if serializer.is_human_readable() {
serializer.serialize_str(self.to_string().as_str())
} else {
let b = self.to_bytes();
serializer.serialize_bytes(b.as_ref())
}
}
}
struct AddressVisitor;
impl<'de> serde::de::Visitor<'de> for AddressVisitor {
type Value = Address;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("ZeroTier Address") }
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: serde::de::Error { Ok(Address::from(v)) }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error { Ok(Address::from(s)) }
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> where E: serde::de::Error { Ok(Address::from(v)) }
}
impl<'de> serde::Deserialize<'de> for Address {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
if deserializer.is_human_readable() {
deserializer.deserialize_str(AddressVisitor)
} else {
deserializer.deserialize_bytes(AddressVisitor)
}
}
}

View file

@ -1,89 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::os::raw::c_void;
use crate::capi as ztcore;
/// A reusable buffer for I/O to/from the ZeroTier core.
///
/// The core allocates and manages a pool of these. This provides a Rust
/// interface to that pool. ZT core buffers are used to reduce the need for
/// memory copying by passing buffers around instead of memcpy'ing when
/// packet data is passed into and out of the core.
///
/// IMPORTANT NOTE: when these are fed into the ZeroTier core, drop() is
/// elided via std::mem::forget(). Node does this automatically so usually
/// users of this API do not need to be aware of it, but it's worth mentioning
/// in case someone re-implements calls directly into the core. Dropping this
/// after handing it back to the core could result in mytserious corruption
/// bugs or double-free.
///
/// This does not implement copy or clone because that would result in this
/// memory being dropped more than once. Use Rc or Arc to share.
pub struct Buffer {
pub(crate) zt_core_buf: *mut u8,
pub(crate) data_size: usize,
}
impl Buffer {
/// Maximum capacity of a ZeroTier reusable buffer.
pub const CAPACITY: usize = ztcore::ZT_BUF_SIZE as usize;
/// Obtain a new buffer from the core and set the size of its data to CAPACITY.
/// The contents of the buffer are not defined.
#[inline(always)]
pub fn new() -> Buffer {
let b = unsafe { ztcore::ZT_getBuffer() as *mut u8 };
if b.is_null() {
panic!("out of memory calling ZT_getBuffer()");
}
Buffer {
zt_core_buf: b,
data_size: ztcore::ZT_BUF_SIZE as usize
}
}
/// Get the current size of the data held by this buffer.
/// Initially this is equal to CAPACITY.
#[inline(always)]
pub fn len(&self) -> usize {
self.data_size
}
#[inline(always)]
pub fn as_ptr(&self) -> *const u8 {
self.zt_core_buf
}
#[inline(always)]
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.zt_core_buf
}
/// Set the size of the data held by this buffer.
/// This is usually called after writing data into the buffer.
#[inline(always)]
pub fn set_len(&mut self, s: usize) {
// CAPACITY will always be a power of two, so we can just mask this
// to make this safe. This is a sanity check to make it impossible to
// set this to an invalid size.
self.data_size = s & (Buffer::CAPACITY - 1);
}
}
impl Drop for Buffer {
#[inline(always)]
fn drop(&mut self) {
unsafe { ztcore::ZT_freeBuffer(self.zt_core_buf as *mut c_void); }
}
}

View file

@ -1,818 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::ffi::CString;
use std::hash::{Hash, Hasher};
use std::mem::zeroed;
use std::os::raw::{c_char, c_uint, c_void};
use std::pin::Pin;
use std::ptr::{copy_nonoverlapping, null, null_mut, read_unaligned};
use num_derive::{FromPrimitive, ToPrimitive};
#[allow(unused_imports)]
use num_traits::{FromPrimitive, ToPrimitive};
use serde::{Deserialize, Serialize};
use crate::*;
use crate::capi as ztcore;
/// Maximum length of a string in a certificate (mostly for the certificate name fields).
pub const CERTIFICATE_MAX_STRING_LENGTH: isize = ztcore::ZT_CERTIFICATE_MAX_STRING_LENGTH as isize;
/// Certificate local trust bit field flag: this certificate self-signs a root CA.
pub const CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA: u32 = ztcore::ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA;
pub const CERTIFICATE_LOCAL_TRUST_FLAG_CONFIG: u32 = ztcore::ZT_CERTIFICATE_LOCAL_TRUST_FLAG_CONFIG;
pub const CERTIFICATE_USAGE_DIGITAL_SIGNATURE: u64 = ztcore::ZT_CERTIFICATE_USAGE_DIGITAL_SIGNATURE as u64;
pub const CERTIFICATE_USAGE_NON_REPUDIATION: u64 = ztcore::ZT_CERTIFICATE_USAGE_NON_REPUDIATION as u64;
pub const CERTIFICATE_USAGE_KEY_ENCIPHERMENT: u64 = ztcore::ZT_CERTIFICATE_USAGE_KEY_ENCIPHERMENT as u64;
pub const CERTIFICATE_USAGE_DATA_ENCIPHERMENT: u64 = ztcore::ZT_CERTIFICATE_USAGE_DATA_ENCIPHERMENT as u64;
pub const CERTIFICATE_USAGE_KEY_AGREEMENT: u64 = ztcore::ZT_CERTIFICATE_USAGE_KEY_AGREEMENT as u64;
pub const CERTIFICATE_USAGE_CERTIFICATE_SIGNING: u64 = ztcore::ZT_CERTIFICATE_USAGE_CERTIFICATE_SIGNING as u64;
pub const CERTIFICATE_USAGE_CRL_SIGNING: u64 = ztcore::ZT_CERTIFICATE_USAGE_CRL_SIGNING as u64;
pub const CERTIFICATE_USAGE_EXECUTABLE_SIGNATURE: u64 = ztcore::ZT_CERTIFICATE_USAGE_EXECUTABLE_SIGNATURE as u64;
pub const CERTIFICATE_USAGE_TIMESTAMPING: u64 = ztcore::ZT_CERTIFICATE_USAGE_TIMESTAMPING as u64;
pub const CERTIFICATE_USAGE_ZEROTIER_ROOT_SET: u64 = ztcore::ZT_CERTIFICATE_USAGE_ZEROTIER_ROOT_SET as u64;
#[inline(always)]
fn vec_to_array<const L: usize>(v: &Vec<u8>) -> [u8; L] {
let mut a = [0_u8; L];
let vl = v.len();
if vl >= L {
a.copy_from_slice(&v.as_slice()[0..L]);
} else {
a.copy_from_slice(v.as_slice());
}
a
}
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum CertificatePublicKeyAlgorithm {
None = ztcore::ZT_CertificatePublicKeyAlgorithm_ZT_CERTIFICATE_PUBLIC_KEY_ALGORITHM_NONE as isize,
ECDSANistP384 = ztcore::ZT_CertificatePublicKeyAlgorithm_ZT_CERTIFICATE_PUBLIC_KEY_ALGORITHM_ECDSA_NIST_P_384 as isize,
}
impl From<i32> for CertificatePublicKeyAlgorithm {
#[inline(always)]
fn from(n: i32) -> CertificatePublicKeyAlgorithm {
CertificatePublicKeyAlgorithm::from_i32(n).unwrap_or(CertificatePublicKeyAlgorithm::None)
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct CertificateSerialNo(pub [u8; 48]);
impl CertificateSerialNo {
pub const SIZE: usize = 48;
pub const SIZE_HEX: usize = 96;
#[inline(always)]
pub fn new() -> CertificateSerialNo { CertificateSerialNo([0_u8; 48]) }
/// Create a new certificate serial from a hex string, returning None if invalid.
/// The from() alternative for converting from a string returns an a nil (all zero) serial on error.
pub fn new_from_string(s: &str) -> Option<CertificateSerialNo> {
hex::decode(s).map_or(None, |b| {
if b.len() == Self::SIZE {
Some(CertificateSerialNo(vec_to_array::<48>(&b)))
} else {
None
}
})
}
/// Returns true if serial is all zeroes.
#[inline(always)]
pub fn is_nil(&self) -> bool {
is_all_zeroes(self.0)
}
/// Returns true if this serial indicates a self-signed certificate.
/// This is meaningful for the serial in the issuer field of Certificate,
/// which will be all 0xff in self-signed certificates.
#[inline(always)]
pub fn is_self_signed(&self) -> bool {
for x in self.0.iter() {
if *x != 0xff {
return false;
}
}
return true;
}
/// Set this to all 0xff which in the issuer field means self-signed.
#[inline(always)]
pub fn set_self_signed(&mut self) {
self.0.fill(0xff);
}
}
impl Hash for CertificateSerialNo {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
// Serials are SHA384 hashes, so we can just use the first 8 bytes as-is.
state.write_u64(unsafe { read_unaligned::<u64>(self.0.as_ptr().cast()) })
}
}
impl<S: AsRef<str>> From<S> for CertificateSerialNo {
#[inline(always)]
fn from(s: S) -> CertificateSerialNo {
Self::new_from_string(s.as_ref()).unwrap_or_else(|| CertificateSerialNo::new())
}
}
impl ToString for CertificateSerialNo {
#[inline(always)]
fn to_string(&self) -> String { hex::encode(self.0) }
}
impl serde::Serialize for CertificateSerialNo {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { serializer.serialize_str(self.to_string().as_str()) }
}
struct CertificateSerialNoVisitor;
impl<'de> serde::de::Visitor<'de> for CertificateSerialNoVisitor {
type Value = CertificateSerialNo;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("object") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error { Self::Value::new_from_string(s).map_or_else(|| { Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)) }, |serial| { Ok(serial as Self::Value) }) }
}
impl<'de> serde::Deserialize<'de> for CertificateSerialNo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { deserializer.deserialize_str(CertificateSerialNoVisitor) }
}
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum CertificateError {
None = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_NONE as isize,
InvalidFormat = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_FORMAT as isize,
InvalidIdentity = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_IDENTITY as isize,
InvalidPrimarySignature = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE as isize,
InvalidChain = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_CHAIN as isize,
InvalidComponentSignature = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_COMPONENT_SIGNATURE as isize,
InvalidUniqueIdProof = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF as isize,
MissingRequiredFields = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS as isize,
OutOfValidTimeWindow = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW as isize,
Revoked = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_REVOKED as isize,
}
impl CertificateError {
pub fn to_str(&self) -> &'static str {
match self {
CertificateError::None => "None",
CertificateError::InvalidFormat => "InvalidFormat",
CertificateError::InvalidIdentity => "InvalidIdentity",
CertificateError::InvalidPrimarySignature => "InvalidPrimarySignature",
CertificateError::InvalidChain => "InvalidChain",
CertificateError::InvalidComponentSignature => "InvalidComponentSignature",
CertificateError::InvalidUniqueIdProof => "InvalidUniqueIdProof",
CertificateError::MissingRequiredFields => "MissingRequiredFields",
CertificateError::OutOfValidTimeWindow => "OutOfValidTimeWindow",
CertificateError::Revoked => "Revoked",
}
}
}
impl ToString for CertificateError {
#[inline(always)]
fn to_string(&self) -> String {
String::from(self.to_str())
}
}
impl<S: AsRef<str>> From<S> for CertificateError {
fn from(s: S) -> CertificateError {
match s.as_ref().to_ascii_lowercase().as_str() {
"invalidformat" => CertificateError::InvalidFormat,
"invalididentity" => CertificateError::InvalidIdentity,
"invalidprimarysignature" => CertificateError::InvalidPrimarySignature,
"invalidchain" => CertificateError::InvalidChain,
"invalidcomponentsignature" => CertificateError::InvalidComponentSignature,
"invaliduniqueidproof" => CertificateError::InvalidUniqueIdProof,
"missingrequiredfields" => CertificateError::MissingRequiredFields,
"outofvalidtimewindow" => CertificateError::OutOfValidTimeWindow,
"revoked" => CertificateError::Revoked,
_ => CertificateError::None
}
}
}
impl serde::Serialize for CertificateError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { serializer.serialize_str(self.to_string().as_str()) }
}
struct CertificateErrorVisitor;
impl<'de> serde::de::Visitor<'de> for CertificateErrorVisitor {
type Value = CertificateError;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("object") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error { return Ok(CertificateError::from(s)); }
}
impl<'de> serde::Deserialize<'de> for CertificateError {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { deserializer.deserialize_str(CertificateErrorVisitor) }
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct CertificateName {
#[serde(rename = "serialNo")]
pub serial_no: String,
#[serde(rename = "commonName")]
pub common_name: String,
pub country: String,
pub organization: String,
pub unit: String,
pub locality: String,
pub province: String,
#[serde(rename = "streetAddress")]
pub street_address: String,
#[serde(rename = "postalCode")]
pub postal_code: String,
pub email: String,
pub url: String,
pub host: String,
}
impl CertificateName {
pub fn new() -> CertificateName {
CertificateName {
serial_no: String::new(),
common_name: String::new(),
country: String::new(),
organization: String::new(),
unit: String::new(),
locality: String::new(),
province: String::new(),
street_address: String::new(),
postal_code: String::new(),
email: String::new(),
url: String::new(),
host: String::new(),
}
}
pub(crate) unsafe fn new_from_capi(cn: &ztcore::ZT_Certificate_Name) -> CertificateName {
return CertificateName {
serial_no: cstr_to_string(cn.serialNo.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
common_name: cstr_to_string(cn.commonName.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
country: cstr_to_string(cn.country.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
organization: cstr_to_string(cn.organization.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
unit: cstr_to_string(cn.unit.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
locality: cstr_to_string(cn.locality.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
province: cstr_to_string(cn.province.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
street_address: cstr_to_string(cn.streetAddress.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
postal_code: cstr_to_string(cn.postalCode.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
email: cstr_to_string(cn.email.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
url: cstr_to_string(cn.url.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
host: cstr_to_string(cn.host.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
};
}
fn str_to_cert_cstr(s: &String, cs: &mut [c_char; 128]) {
let mut l = s.len();
if l == 0 {
cs[0] = 0;
return;
}
if l > 126 {
l = 126;
}
unsafe { copy_nonoverlapping(s.as_ptr(), cs.as_mut_ptr() as *mut u8, l); }
cs[l + 1] = 0;
}
pub(crate) unsafe fn to_capi(&self) -> ztcore::ZT_Certificate_Name {
let mut cn: ztcore::ZT_Certificate_Name = zeroed();
Self::str_to_cert_cstr(&self.serial_no, &mut cn.serialNo);
Self::str_to_cert_cstr(&self.common_name, &mut cn.commonName);
Self::str_to_cert_cstr(&self.country, &mut cn.country);
Self::str_to_cert_cstr(&self.organization, &mut cn.organization);
Self::str_to_cert_cstr(&self.unit, &mut cn.unit);
Self::str_to_cert_cstr(&self.locality, &mut cn.locality);
Self::str_to_cert_cstr(&self.province, &mut cn.province);
Self::str_to_cert_cstr(&self.street_address, &mut cn.streetAddress);
Self::str_to_cert_cstr(&self.postal_code, &mut cn.postalCode);
Self::str_to_cert_cstr(&self.email, &mut cn.email);
Self::str_to_cert_cstr(&self.url, &mut cn.url);
Self::str_to_cert_cstr(&self.host, &mut cn.host);
return cn;
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct CertificateNetwork {
pub id: NetworkId,
pub controller: Option<Fingerprint>,
}
impl CertificateNetwork {
pub(crate) fn new_from_capi(cn: &ztcore::ZT_Certificate_Network) -> CertificateNetwork {
if is_all_zeroes(cn.controller.hash) {
CertificateNetwork {
id: NetworkId(cn.id),
controller: None,
}
} else {
CertificateNetwork {
id: NetworkId(cn.id),
controller: Some(Fingerprint {
address: Address(cn.controller.address),
hash: cn.controller.hash,
}),
}
}
}
pub(crate) fn to_capi(&self) -> ztcore::ZT_Certificate_Network {
self.controller.as_ref().map_or_else(|| {
ztcore::ZT_Certificate_Network {
id: self.id.0,
controller: ztcore::ZT_Fingerprint {
address: 0,
hash: [0_u8; 48],
},
}
}, |controller| {
ztcore::ZT_Certificate_Network {
id: self.id.0,
controller: ztcore::ZT_Fingerprint {
address: controller.address.0,
hash: controller.hash,
},
}
})
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct CertificateIdentity {
pub identity: Identity,
pub locator: Option<Locator>,
}
impl CertificateIdentity {
pub(crate) unsafe fn new_from_capi(ci: &ztcore::ZT_Certificate_Identity) -> Option<CertificateIdentity> {
if ci.identity.is_null() {
return None;
}
Some(CertificateIdentity {
identity: Identity::new_from_capi(ci.identity, false).clone(),
locator: if ci.locator.is_null() { None } else { Some(Locator::new_from_capi(ci.locator, false).clone()) },
})
}
pub(crate) unsafe fn to_capi(&self) -> ztcore::ZT_Certificate_Identity {
ztcore::ZT_Certificate_Identity {
identity: self.identity.capi,
locator: if self.locator.is_some() { self.locator.as_ref().unwrap().capi } else { null() },
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct CertificateSubject {
pub timestamp: i64,
pub identities: Vec<CertificateIdentity>,
pub networks: Vec<CertificateNetwork>,
#[serde(rename = "updateURLs")]
pub update_urls: Vec<String>,
pub name: CertificateName,
#[serde(with = "Base64URLSafeNoPad")]
#[serde(rename = "uniqueId")]
pub unique_id: Vec<u8>,
#[serde(with = "Base64URLSafeNoPad")]
#[serde(rename = "uniqueIdSignature")]
pub unique_id_signature: Vec<u8>,
}
#[allow(unused)]
pub(crate) struct CertificateSubjectCAPIContainer {
pub(crate) subject: ztcore::ZT_Certificate_Subject,
subject_identities: Option<Pin<Box<[ztcore::ZT_Certificate_Identity]>>>,
subject_networks: Option<Pin<Box<[ztcore::ZT_Certificate_Network]>>>,
subject_urls: Option<Pin<Box<[*const c_char]>>>,
subject_urls_strs: Option<Pin<Box<[CString]>>>,
}
impl CertificateSubject {
pub fn new() -> CertificateSubject {
CertificateSubject {
timestamp: 0,
identities: Vec::new(),
networks: Vec::new(),
update_urls: Vec::new(),
name: CertificateName::new(),
unique_id: Vec::new(),
unique_id_signature: Vec::new(),
}
}
pub(crate) unsafe fn new_from_capi(cs: &ztcore::ZT_Certificate_Subject) -> CertificateSubject {
let mut identities: Vec<CertificateIdentity> = Vec::new();
if !cs.identities.is_null() && cs.identityCount > 0 {
let cidentities: &[ztcore::ZT_Certificate_Identity] = std::slice::from_raw_parts(cs.identities, cs.identityCount as usize);
for i in cidentities.iter() {
let ci = CertificateIdentity::new_from_capi(i);
if ci.is_some() {
identities.push(ci.unwrap());
}
}
}
let mut networks: Vec<CertificateNetwork> = Vec::new();
if !cs.networks.is_null() && cs.networkCount > 0 {
let cnetworks: &[ztcore::ZT_Certificate_Network] = std::slice::from_raw_parts(cs.networks, cs.networkCount as usize);
for i in cnetworks.iter() {
networks.push(CertificateNetwork::new_from_capi(i));
}
}
let mut update_urls: Vec<String> = Vec::new();
if !cs.updateURLs.is_null() && cs.updateURLCount > 0 {
let cupdate_urls: &[*const c_char] = std::slice::from_raw_parts(cs.updateURLs, cs.updateURLCount as usize);
for i in cupdate_urls.iter() {
update_urls.push(cstr_to_string(*i, CERTIFICATE_MAX_STRING_LENGTH - 1));
}
}
return CertificateSubject {
timestamp: cs.timestamp,
identities,
networks,
update_urls,
name: CertificateName::new_from_capi(&cs.name),
unique_id: cs.uniqueId[0..(cs.uniqueIdSize as usize)].to_vec(),
unique_id_signature: cs.uniqueIdSignature[0..cs.uniqueIdSignatureSize as usize].to_vec(),
};
}
pub(crate) unsafe fn to_capi(&self) -> CertificateSubjectCAPIContainer {
let mut identity_count: c_uint = 0;
let mut network_count: c_uint = 0;
let mut update_url_count: c_uint = 0;
let mut capi_identities = if self.identities.is_empty() {
None
} else {
let mut capi_identities: Vec<ztcore::ZT_Certificate_Identity> = Vec::new();
capi_identities.reserve(self.identities.len());
for i in self.identities.iter() {
capi_identities.push((*i).to_capi());
}
identity_count = capi_identities.len() as c_uint;
Some(Pin::from(capi_identities.into_boxed_slice()))
};
let mut capi_networks = if self.networks.is_empty() {
None
} else {
let mut capi_networks: Vec<ztcore::ZT_Certificate_Network> = Vec::new();
capi_networks.reserve(self.networks.len());
for i in self.networks.iter() {
capi_networks.push((*i).to_capi());
}
network_count = capi_networks.len() as c_uint;
Some(Pin::from(capi_networks.into_boxed_slice()))
};
let (mut capi_urls, capi_urls_strs) = if self.update_urls.is_empty() {
(None, None)
} else {
let mut capi_urls_strs: Vec<CString> = Vec::new();
let mut capi_urls: Vec<*const c_char> = Vec::new();
capi_urls_strs.reserve(self.update_urls.len());
capi_urls.reserve(self.update_urls.len());
for i in self.update_urls.iter() {
let cs = CString::new((*i).as_str());
if cs.is_ok() {
capi_urls_strs.push(cs.unwrap());
}
}
let capi_urls_strs = Pin::from(capi_urls_strs.into_boxed_slice());
for i in capi_urls_strs.iter() {
capi_urls.push((*i).as_ptr());
}
update_url_count = capi_urls.len() as c_uint;
(Some(Pin::from(capi_urls.into_boxed_slice())), Some(capi_urls_strs))
};
CertificateSubjectCAPIContainer {
subject: ztcore::ZT_Certificate_Subject {
timestamp: self.timestamp,
identities: capi_identities.as_mut().map_or(null_mut(), |v| v.as_mut_ptr()),
networks: capi_networks.as_mut().map_or(null_mut(), |v| v.as_mut_ptr()),
updateURLs: capi_urls.as_mut().map_or(null_mut(), |v| v.as_mut_ptr()),
identityCount: identity_count,
networkCount: network_count,
updateURLCount: update_url_count,
name: self.name.to_capi(),
uniqueId: vec_to_array(&self.unique_id),
uniqueIdSignature: vec_to_array(&self.unique_id_signature),
uniqueIdSize: self.unique_id.len() as c_uint,
uniqueIdSignatureSize: self.unique_id_signature.len() as c_uint,
},
subject_identities: capi_identities,
subject_networks: capi_networks,
subject_urls: capi_urls,
subject_urls_strs: capi_urls_strs,
}
}
/// Create a new certificate signing request.
/// A CSR is a Certificate containing only the subject (with optional unique ID and signature)
/// and its private key. Other fields must be filled in by the owner of the signing certificate.
pub fn new_csr(&self, certifcate_private_key: &[u8], subject_unique_id_private_key: Option<&[u8]>) -> Result<Vec<u8>, ResultCode> {
let mut csr: Vec<u8> = Vec::new();
csr.resize(65536, 0);
let mut csr_size: c_int = 65536;
let (uid, uid_size) = subject_unique_id_private_key.map_or((null::<u8>(), 0 as c_int), |b| (b.as_ptr(), b.len() as c_int));
let r = unsafe {
let s = self.to_capi();
ztcore::ZT_Certificate_newCSR(&s.subject, certifcate_private_key.as_ptr().cast(), certifcate_private_key.len() as c_int, uid.cast(), uid_size, csr.as_mut_ptr().cast(), &mut csr_size)
};
if r == 0 {
csr.resize(csr_size as usize, 0);
csr.shrink_to_fit();
Ok(csr)
} else {
Err(ResultCode::from_i32(r as i32).unwrap_or(ResultCode::ErrorInternalNonFatal))
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Certificate {
#[serde(rename = "serialNo")]
pub serial_no: CertificateSerialNo,
#[serde(rename = "usageFlags")]
pub usage_flags: u64,
pub timestamp: i64,
pub validity: [i64; 2],
pub subject: CertificateSubject,
pub issuer: CertificateSerialNo,
#[serde(rename = "issuerPublicKey")]
pub issuer_public_key: Vec<u8>,
#[serde(rename = "publicKey")]
pub public_key: Vec<u8>,
#[serde(rename = "subjectSignature")]
pub subject_signature: Vec<u8>,
#[serde(rename = "extendedAttributes")]
pub extended_attributes: Vec<u8>,
#[serde(with = "Base64URLSafeNoPad")]
pub signature: Vec<u8>,
#[serde(rename = "maxPathLength")]
pub max_path_length: u32,
}
#[allow(unused)]
pub(crate) struct CertificateCAPIContainer {
pub(crate) certificate: ztcore::ZT_Certificate,
subject_container: CertificateSubjectCAPIContainer,
}
impl Certificate {
/// Create a new public/private key pair for use in certificate signing or subject unique IDs.
/// This returns a pair of (public, private) or an error. The first byte of both the
/// public and private are the type.
pub fn new_key_pair(alg: CertificatePublicKeyAlgorithm) -> Result<(Vec<u8>, Vec<u8>), ResultCode> {
let mut public_key = [0_u8; ztcore::ZT_CERTIFICATE_MAX_PUBLIC_KEY_SIZE as usize];
let mut private_key = [0_u8; ztcore::ZT_CERTIFICATE_MAX_PRIVATE_KEY_SIZE as usize];
let mut public_key_size: c_int = 0;
let mut private_key_size: c_int = 0;
let r = unsafe { ztcore::ZT_Certificate_newKeyPair(alg.to_i32().unwrap() as ztcore::ZT_CertificatePublicKeyAlgorithm, public_key.as_mut_ptr(), &mut public_key_size, private_key.as_mut_ptr(), &mut private_key_size) };
if r == 0 {
if public_key_size > 0 && private_key_size > 0 {
Ok((public_key[0..public_key_size as usize].to_vec(), private_key[0..private_key_size as usize].to_vec()))
} else {
Err(ResultCode::ErrorInternalNonFatal)
}
} else {
Err(ResultCode::from_i32(r as i32).unwrap_or(ResultCode::ErrorBadParameter))
}
}
/// Create an empty certificate structure.
pub fn new() -> Certificate {
Certificate {
serial_no: CertificateSerialNo::new(),
usage_flags: 0,
timestamp: 0,
validity: [0, i64::MAX],
subject: CertificateSubject::new(),
issuer: CertificateSerialNo::new(),
issuer_public_key: Vec::new(),
public_key: Vec::new(),
subject_signature: Vec::new(),
extended_attributes: Vec::new(),
max_path_length: 0,
signature: Vec::new(),
}
}
pub(crate) unsafe fn new_from_capi(c: &ztcore::ZT_Certificate) -> Certificate {
return Certificate {
serial_no: CertificateSerialNo(c.serialNo),
usage_flags: c.usageFlags,
timestamp: c.timestamp,
validity: c.validity,
subject: CertificateSubject::new_from_capi(&c.subject),
issuer: CertificateSerialNo(c.issuer),
issuer_public_key: c.issuerPublicKey[0..(c.issuerPublicKeySize as usize)].to_vec(),
public_key: c.publicKey[0..(c.publicKeySize as usize)].to_vec(),
subject_signature: c.subjectSignature[0..(c.subjectSignatureSize as usize)].to_vec(),
extended_attributes: Vec::from(std::slice::from_raw_parts(c.extendedAttributes, c.extendedAttributesSize as usize)),
max_path_length: c.maxPathLength as u32,
signature: c.signature[0..(c.signatureSize as usize)].to_vec(),
};
}
pub(crate) unsafe fn to_capi(&self) -> CertificateCAPIContainer {
let subject = self.subject.to_capi();
CertificateCAPIContainer {
certificate: ztcore::ZT_Certificate {
serialNo: self.serial_no.0,
usageFlags: self.usage_flags,
timestamp: self.timestamp,
validity: self.validity,
subject: subject.subject,
issuer: self.issuer.0,
issuerPublicKey: vec_to_array(&self.issuer_public_key),
publicKey: vec_to_array(&self.public_key),
subjectSignature: vec_to_array(&self.subject_signature),
issuerPublicKeySize: self.issuer_public_key.len() as c_uint,
publicKeySize: self.public_key.len() as c_uint,
subjectSignatureSize: self.subject_signature.len() as c_uint,
extendedAttributes: self.extended_attributes.as_ptr(),
extendedAttributesSize: self.extended_attributes.len() as c_uint,
maxPathLength: self.max_path_length as c_uint,
signature: vec_to_array(&self.signature),
signatureSize: self.signature.len() as c_uint,
},
subject_container: subject,
}
}
pub fn new_from_bytes(b: &[u8], verify: bool) -> Result<Certificate, CertificateError> {
let mut capi_cert: *const ztcore::ZT_Certificate = null_mut();
let capi_verify: c_int = if verify { 1 } else { 0 };
let result = unsafe { ztcore::ZT_Certificate_decode(&mut capi_cert as *mut *const ztcore::ZT_Certificate, b.as_ptr() as *const c_void, b.len() as c_int, capi_verify) };
if result != ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_NONE {
return Err(CertificateError::from_i32(result as i32).unwrap_or(CertificateError::InvalidFormat));
}
if capi_cert.is_null() {
return Err(CertificateError::InvalidFormat);
}
unsafe {
let cert = Certificate::new_from_capi(&*capi_cert);
ztcore::ZT_Certificate_delete(capi_cert);
return Ok(cert);
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, ResultCode> {
let mut cert: Vec<u8> = Vec::new();
cert.resize(16384, 0);
let mut cert_size: c_int = 16384;
unsafe {
let capi = self.to_capi();
if ztcore::ZT_Certificate_encode(&capi.certificate as *const ztcore::ZT_Certificate, cert.as_mut_ptr() as *mut c_void, &mut cert_size) != 0 {
return Err(ResultCode::ErrorInternalNonFatal);
}
}
cert.resize(cert_size as usize, 0);
return Ok(cert);
}
/// Sign this certificate, returning new signed certificate.
pub fn sign(&self, issuer: &CertificateSerialNo, issuer_private_key: &[u8]) -> Result<Certificate, ResultCode> {
let signed_cert = unsafe {
let c = self.to_capi();
ztcore::ZT_Certificate_sign(&c.certificate, issuer.0.as_ptr(), issuer_private_key.as_ptr().cast(), issuer_private_key.len() as c_int)
};
if signed_cert.is_null() {
Err(ResultCode::ErrorBadParameter)
} else {
let signed_cert2 = unsafe { Certificate::new_from_capi(&*signed_cert) };
unsafe { ztcore::ZT_Certificate_delete(signed_cert) };
Ok(signed_cert2)
}
}
/// Verify certificate structure and signatures.
/// This does not verify the full certificate chain, just what can be verified
/// by looking at the certificate itself.
pub fn verify(&self, clock: i64) -> CertificateError {
unsafe {
let capi = self.to_capi();
return CertificateError::from_i32(ztcore::ZT_Certificate_verify(&capi.certificate as *const ztcore::ZT_Certificate, clock) as i32).unwrap_or(CertificateError::InvalidFormat);
}
}
/// Returns true if this is a self-signed certificate.
pub fn is_self_signed(&self) -> bool {
for b in self.issuer.0.iter() {
if *b != 0xff {
return false;
}
}
return true;
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn generate_key_pair() {
let (pubk, privk) = Certificate::new_key_pair(CertificatePublicKeyAlgorithm::ECDSANistP384).ok().unwrap();
println!("key pair public: {}", hex::encode(pubk).as_str());
println!("key pair private: {}", hex::encode(privk).as_str());
}
#[test]
fn cert() {
let (issuer_pubk, issuer_privk) = Certificate::new_key_pair(CertificatePublicKeyAlgorithm::ECDSANistP384).ok().unwrap();
let (pubk, _) = Certificate::new_key_pair(CertificatePublicKeyAlgorithm::ECDSANistP384).ok().unwrap();
let (_, unique_id_private) = Certificate::new_key_pair(CertificatePublicKeyAlgorithm::ECDSANistP384).ok().unwrap();
let id0 = Identity::new_generate(IdentityType::Curve25519).ok().unwrap();
let mut cert = Certificate {
serial_no: CertificateSerialNo::new(),
usage_flags: 1,
timestamp: 2,
validity: [1, 10],
subject: CertificateSubject::new(),
issuer: CertificateSerialNo::new(),
issuer_public_key: issuer_pubk,
public_key: pubk.clone(),
subject_signature: Vec::new(),
extended_attributes: Vec::new(),
max_path_length: 123,
signature: Vec::new(),
};
cert.serial_no.0[1] = 99;
cert.issuer.0[1] = 199;
cert.subject.timestamp = 5;
cert.subject.identities.push(CertificateIdentity {
identity: id0.clone(),
locator: None,
});
cert.subject.networks.push(CertificateNetwork {
id: NetworkId(0xdeadbeef),
controller: Some(id0.fingerprint()),
});
cert.subject.update_urls.push(String::from("http://foo.bar"));
cert.subject.name = CertificateName {
serial_no: String::from("12345"),
common_name: String::from("foo"),
country: String::from("bar"),
organization: String::from("baz"),
unit: String::from("asdf"),
locality: String::from("qwerty"),
province: String::from("province"),
street_address: String::from("street address"),
postal_code: String::from("postal code"),
email: String::from("nobody@nowhere.org"),
url: String::from("https://www.zerotier.com/"),
host: String::from("zerotier.com"),
};
unsafe {
let cert_capi = cert.to_capi();
let cert2 = Certificate::new_from_capi(&cert_capi.certificate);
assert!(cert == cert2);
}
let csr = cert.subject.new_csr(pubk.as_ref(), Some(unique_id_private.as_ref()));
assert!(csr.is_ok());
let csr = csr.ok().unwrap();
let csr_decoded = Certificate::new_from_bytes(csr.as_ref(), false);
assert!(csr_decoded.is_ok());
let mut csr_decoded = csr_decoded.ok().unwrap();
csr_decoded.validity = cert.validity;
let cert_signed = csr_decoded.sign(&cert.issuer, issuer_privk.as_ref());
assert!(cert_signed.is_ok());
let cert_signed = cert_signed.ok().unwrap();
assert!(cert_signed.verify(-1) == CertificateError::None);
assert!(cert_signed.verify(5) == CertificateError::None);
assert!(cert_signed.verify(15) != CertificateError::None);
}
}

View file

@ -1,97 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::collections::HashMap;
use std::ffi::c_void;
use std::os::raw::{c_char, c_uint};
use crate::{cstr_to_string, ResultCode};
/// Rust interface to the Dictionary data structure.
#[derive(Clone)]
pub struct Dictionary {
data: HashMap<String, Vec<u8>>,
}
// Callback called by ZeroTier core to parse a Dictionary, populates a Rust Dictionary object.
extern "C" fn populate_dict_callback(arg: *mut c_void, c_key: *const c_char, key_len: c_uint, c_value: *const c_void, value_len: c_uint) {
let d = unsafe { &mut *(arg.cast::<Dictionary>()) };
let k = unsafe { cstr_to_string(c_key, key_len as isize) };
if !k.is_empty() {
let mut v: Vec<u8> = Vec::new();
if value_len > 0 {
let vp = c_value.cast::<u8>();
v.reserve(value_len as usize);
for i in 0..(value_len as isize) {
unsafe { v.push(*(vp.offset(i))) };
}
}
let _ = d.data.insert(k, v);
}
}
impl Dictionary {
#[inline(always)]
pub fn new() -> Dictionary {
Dictionary { data: HashMap::new() }
}
pub fn new_from_bytes(dict: &[u8]) -> Result<Dictionary, ResultCode> {
let mut d = Dictionary{ data: HashMap::new() };
if unsafe { crate::capi::ZT_Dictionary_parse(dict.as_ptr().cast(), dict.len() as c_uint, (&mut d as *mut Dictionary).cast(), Some(populate_dict_callback)) != 0 } {
Ok(d)
} else {
Err(ResultCode::ErrorBadParameter)
}
}
pub fn get<K: AsRef<[u8]>>(&self, k: K) -> Option<&Vec<u8>> {
let ks = String::from(String::from_utf8_lossy(k.as_ref()));
self.data.get(&ks)
}
pub fn get_or_empty<K: AsRef<[u8]>>(&self, k: K) -> Vec<u8> {
let ks = String::from(String::from_utf8_lossy(k.as_ref()));
self.data.get(&ks).map_or_else(|| -> Vec<u8> { Vec::new() }, |d| -> Vec<u8> { d.clone() })
}
pub fn get_str<K: AsRef<[u8]>>(&self, k: K) -> Option<&str> {
let ks = String::from(String::from_utf8_lossy(k.as_ref()));
let v = self.data.get(&ks);
v.map_or(None, |v: &Vec<u8>| {
let vs = std::str::from_utf8(v.as_slice());
vs.map_or(None, |v: &str| {
Some(v)
})
})
}
pub fn get_string_or_empty<K: AsRef<[u8]>>(&self, k: K) -> String {
self.get_str(k).map_or_else(|| { String::new() },|s| { String::from(s) })
}
pub fn get_ui<K: AsRef<[u8]>>(&self, k: K) -> Option<u64> {
let v = self.get_str(k);
v.map_or(None, |v: &str| {
let vi = u64::from_str_radix(v, 16);
vi.map_or(None, |i: u64| {
Some(i)
})
})
}
#[inline(always)]
pub fn len(&self) -> usize {
self.data.len()
}
}

View file

@ -1,140 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::ffi::CString;
use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_uint};
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use crate::*;
use crate::capi as ztcore;
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum EndpointType {
Nil = ztcore::ZT_EndpointType_ZT_ENDPOINT_TYPE_NIL as isize,
ZeroTier = ztcore::ZT_EndpointType_ZT_ENDPOINT_TYPE_ZEROTIER as isize,
Ethernet = ztcore::ZT_EndpointType_ZT_ENDPOINT_TYPE_ETHERNET as isize,
WifiDirect = ztcore::ZT_EndpointType_ZT_ENDPOINT_TYPE_WIFI_DIRECT as isize,
Bluetooth = ztcore::ZT_EndpointType_ZT_ENDPOINT_TYPE_BLUETOOTH as isize,
Ip = ztcore::ZT_EndpointType_ZT_ENDPOINT_TYPE_IP as isize,
IpUdp = ztcore::ZT_EndpointType_ZT_ENDPOINT_TYPE_IP_UDP as isize,
IpTcp = ztcore::ZT_EndpointType_ZT_ENDPOINT_TYPE_IP_TCP as isize,
IpTcpWs = ztcore::ZT_EndpointType_ZT_ENDPOINT_TYPE_IP_TCP_WS as isize,
}
#[derive(Clone)]
pub struct Endpoint {
pub type_: EndpointType,
pub(crate) capi: ztcore::ZT_Endpoint
}
impl Endpoint {
pub(crate) fn new_from_capi(ep: &ztcore::ZT_Endpoint) -> Endpoint {
return Endpoint{
type_: EndpointType::from_i32(ep.type_ as i32).unwrap(),
capi: *ep
};
}
pub fn new_from_bytes(bytes: &[u8]) -> Result<Endpoint, ResultCode> {
unsafe {
let mut cep: MaybeUninit<ztcore::ZT_Endpoint> = MaybeUninit::uninit();
let ec = ztcore::ZT_Endpoint_fromBytes(cep.as_mut_ptr(), bytes.as_ptr().cast(), bytes.len() as c_uint);
if ec == 0 {
let epi = cep.assume_init();
return Ok(Endpoint{
type_: EndpointType::from_i32(epi.type_ as i32).unwrap(),
capi: epi
});
}
return Err(ResultCode::from_i32(ec).unwrap());
}
}
pub fn new_from_string(s: &str) -> Result<Endpoint, ResultCode> {
let cs = CString::new(s);
if cs.is_err() {
return Err(ResultCode::ErrorBadParameter);
}
let cs = cs.unwrap();
unsafe {
let mut cep: MaybeUninit<ztcore::ZT_Endpoint> = MaybeUninit::uninit();
let ec = ztcore::ZT_Endpoint_fromString(cep.as_mut_ptr(), cs.as_ptr()) as i32;
if ec == 0 {
let epi = cep.assume_init();
return Ok(Endpoint{
type_: EndpointType::from_i32(epi.type_ as i32).unwrap(),
capi: epi
});
}
return Err(ResultCode::from_i32(ec).unwrap());
}
}
/// Get a reference to the InetAddress in this endpoint or None if this is not of a relevant type.
pub fn as_inetaddress(&self) -> Option<&InetAddress> {
match self.type_ {
EndpointType::Ip | EndpointType::IpUdp | EndpointType::IpTcp | EndpointType::IpTcpWs => {
unsafe {
Some(InetAddress::transmute_capi(&self.capi.value.ia))
}
},
_ => None
}
}
}
impl ToString for Endpoint {
fn to_string(&self) -> String {
unsafe {
let mut buf: MaybeUninit<[c_char; 1024]> = MaybeUninit::uninit();
if ztcore::ZT_Endpoint_toString(&(self.capi) as *const ztcore::ZT_Endpoint, (*buf.as_mut_ptr()).as_mut_ptr(), 1024).is_null() {
return String::from("(invalid)");
}
return cstr_to_string((*buf.as_ptr()).as_ptr(), 1024);
}
}
}
impl PartialEq for Endpoint {
fn eq(&self, other: &Endpoint) -> bool {
if self.type_ == other.type_ {
self.to_string() == other.to_string()
} else {
false
}
}
}
impl Eq for Endpoint {}
impl serde::Serialize for Endpoint {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { serializer.serialize_str(self.to_string().as_str()) }
}
struct EndpointVisitor;
impl<'de> serde::de::Visitor<'de> for EndpointVisitor {
type Value = Endpoint;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("Endpoint value in string form") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error {
let id = Endpoint::new_from_string(s);
if id.is_err() {
return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self));
}
return Ok(id.ok().unwrap() as Self::Value);
}
}
impl<'de> serde::Deserialize<'de> for Endpoint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { deserializer.deserialize_str(EndpointVisitor) }
}

View file

@ -1,118 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::ffi::CString;
use std::hash::{Hash, Hasher};
use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_int};
use std::ptr::copy_nonoverlapping;
use crate::*;
use crate::capi as ztcore;
#[derive(PartialEq, Eq, Clone)]
pub struct Fingerprint {
pub address: Address,
pub hash: [u8; 48],
}
impl Fingerprint {
#[inline(always)]
pub(crate) fn new_from_capi(fp: &ztcore::ZT_Fingerprint) -> Fingerprint {
Fingerprint {
address: Address(fp.address),
hash: fp.hash,
}
}
pub fn new_from_string(s: &str) -> Result<Fingerprint, ResultCode> {
let cs = CString::new(s);
if cs.is_err() {
return Err(ResultCode::ErrorBadParameter);
}
let cs = cs.unwrap();
let mut cfp: MaybeUninit<ztcore::ZT_Fingerprint> = MaybeUninit::uninit();
unsafe {
if ztcore::ZT_Fingerprint_fromString(cfp.as_mut_ptr(), cs.as_ptr()) != 0 {
let fp = cfp.assume_init();
return Ok(Fingerprint {
address: Address(fp.address),
hash: fp.hash,
});
}
}
return Err(ResultCode::ErrorBadParameter);
}
pub fn new_from_bytes(bytes: &[u8]) -> Result<Fingerprint, ResultCode> {
if bytes.len() >= (5 + 48) {
Ok(Fingerprint {
address: Address::from(bytes),
hash: {
let mut h: MaybeUninit<[u8; 48]> = MaybeUninit::uninit();
unsafe {
copy_nonoverlapping(bytes.as_ptr().offset(5), h.as_mut_ptr().cast::<u8>(), 48);
h.assume_init()
}
},
})
} else {
Err(ResultCode::ErrorBadParameter)
}
}
}
impl Hash for Fingerprint {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.address.0.hash(state)
}
}
impl ToString for Fingerprint {
fn to_string(&self) -> String {
let mut buf: [u8; 256] = [0; 256];
unsafe {
if ztcore::ZT_Fingerprint_toString(&ztcore::ZT_Fingerprint { address: self.address.0, hash: self.hash }, buf.as_mut_ptr() as *mut c_char, buf.len() as c_int).is_null() {
return String::from("(invalid)");
}
return cstr_to_string(buf.as_ptr() as *const c_char, 256);
}
}
}
impl serde::Serialize for Fingerprint {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
serializer.serialize_str(self.to_string().as_str())
}
}
struct FingerprintVisitor;
impl<'de> serde::de::Visitor<'de> for FingerprintVisitor {
type Value = Fingerprint;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("ZeroTier Fingerprint in string format") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error {
let id = Fingerprint::new_from_string(s);
if id.is_err() {
return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self));
}
return Ok(id.ok().unwrap() as Self::Value);
}
}
impl<'de> serde::Deserialize<'de> for Fingerprint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
deserializer.deserialize_str(FingerprintVisitor)
}
}

View file

@ -1,304 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::cmp::Ordering;
use std::ffi::CString;
use std::mem::{MaybeUninit, transmute};
use std::os::raw::*;
use std::hash::{Hash, Hasher};
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::{FromPrimitive, ToPrimitive};
use crate::*;
use crate::capi as ztcore;
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum IdentityType {
Curve25519 = ztcore::ZT_IdentityType_ZT_IDENTITY_TYPE_C25519 as isize,
NistP384 = ztcore::ZT_IdentityType_ZT_IDENTITY_TYPE_P384 as isize,
}
impl IdentityType {
fn to_str(&self) -> &'static str {
if *self == IdentityType::Curve25519 { "c25519" } else { "p384" }
}
}
impl ToString for IdentityType {
fn to_string(&self) -> String {
String::from(self.to_str())
}
}
pub struct Identity {
pub type_: IdentityType,
pub address: Address,
pub(crate) capi: *const ztcore::ZT_Identity,
requires_delete: bool,
}
impl Identity {
pub(crate) fn new_from_capi(id: *const ztcore::ZT_Identity, requires_delete: bool) -> Identity {
unsafe {
Identity {
type_: FromPrimitive::from_i32(ztcore::ZT_Identity_type(id) as i32).unwrap(),
address: Address(ztcore::ZT_Identity_address(id)),
capi: id,
requires_delete,
}
}
}
/// Sync type and address fields with C API.
/// This isn't truly unsafe because these are primitive types, but it's marked as such
/// because it breaks the rules slightly. It's used in Node to make sure its identity
/// wrapper matches the one in C++-land even if the latter changes.
pub(crate) unsafe fn sync_type_and_address_with_capi(&self) {
let s: *mut Identity = transmute(self as *const Identity);
(*s).type_ = FromPrimitive::from_i32(ztcore::ZT_Identity_type(self.capi) as i32).unwrap();
(*s).address.0 = ztcore::ZT_Identity_address(self.capi);
}
/// Generate a new identity.
/// This is time consuming due to one time proof of work. It can take several seconds.
pub fn new_generate(id_type: IdentityType) -> Result<Identity, ResultCode> {
unsafe {
let id = ztcore::ZT_Identity_new(id_type.to_u32().unwrap());
if id.is_null() {
return Err(ResultCode::ErrorBadParameter); // this only really happens if type is invalid
}
return Ok(Identity::new_from_capi(id, true));
}
}
/// Construct from a string representation of this identity.
pub fn new_from_string(s: &str) -> Result<Identity, ResultCode> {
unsafe {
let cs = CString::new(s);
if cs.is_err() {
return Err(ResultCode::ErrorBadParameter);
}
let cs = cs.unwrap();
let id = ztcore::ZT_Identity_fromString(cs.as_ptr());
if id.is_null() {
return Err(ResultCode::ErrorBadParameter);
}
return Ok(Identity::new_from_capi(id, true));
}
}
fn intl_to_string(&self, include_private: bool) -> String {
let mut buf: MaybeUninit<[c_char; 4096]> = MaybeUninit::uninit();
unsafe {
let bufptr = (*buf.as_mut_ptr()).as_mut_ptr();
if ztcore::ZT_Identity_toString(self.capi, bufptr, 4096, if include_private { 1 } else { 0 }).is_null() {
return String::from("(invalid)");
}
return cstr_to_string(bufptr, 4096);
}
}
/// Convert to a string and include the private key if present.
/// If the private key is not present this is the same as to_string().
#[inline(always)]
pub fn to_secret_string(&self) -> String {
self.intl_to_string(true)
}
/// Validate this identity, which can be slightly time consuming in some cases (20-40ms).
#[inline(always)]
pub fn validate(&self) -> bool {
unsafe { ztcore::ZT_Identity_validate(self.capi) != 0 }
}
/// Returns true if this Identity includes its corresponding private key.
#[inline(always)]
pub fn has_private(&self) -> bool {
unsafe { ztcore::ZT_Identity_hasPrivate(self.capi) != 0 }
}
/// Obtain the full fingerprint of this identity, which includes a SHA384 hash of the public key.
pub fn fingerprint(&self) -> Fingerprint {
unsafe {
let cfp = ztcore::ZT_Identity_fingerprint(self.capi);
return Fingerprint {
address: Address((*cfp).address),
hash: (*cfp).hash,
};
}
}
/// Sign some data with this identity.
pub fn sign(&self, data: &[u8]) -> Result<Box<[u8]>, ResultCode> {
unsafe {
let mut sig: Vec<u8> = vec!(0; 128);
let siglen = ztcore::ZT_Identity_sign(self.capi, data.as_ptr() as *const c_void, data.len() as c_uint, sig.as_mut_ptr() as *mut c_void, sig.len() as u32);
if siglen > 0 {
sig.resize(siglen as usize, 0);
return Ok(sig.into_boxed_slice());
}
return Err(ResultCode::ErrorBadParameter);
}
}
/// Verify a signature by this identity.
#[inline(always)]
pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool {
unsafe { signature.len() > 0 && ztcore::ZT_Identity_verify(self.capi, data.as_ptr() as *const c_void, data.len() as c_uint, signature.as_ptr() as *const c_void, signature.len() as c_uint) != 0 }
}
}
impl Hash for Identity {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.address.0.hash(state)
}
}
impl PartialEq for Identity {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
unsafe { ztcore::ZT_Identity_compare(self.capi, other.capi) == 0 }
}
}
impl Eq for Identity {}
impl Ord for Identity {
fn cmp(&self, b: &Self) -> Ordering {
let c = unsafe { ztcore::ZT_Identity_compare(self.capi, b.capi) };
if c < 0 {
Ordering::Less
} else if c > 0 {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
impl PartialOrd for Identity {
#[inline(always)]
fn partial_cmp(&self, b: &Self) -> Option<Ordering> {
Some(self.cmp(b))
}
}
impl Clone for Identity {
#[inline(always)]
fn clone(&self) -> Identity {
unsafe {
return Identity::new_from_capi(ztcore::ZT_Identity_clone(self.capi), true);
}
}
}
impl Drop for Identity {
#[inline(always)]
fn drop(&mut self) {
if self.requires_delete {
unsafe {
ztcore::ZT_Identity_delete(self.capi);
}
}
}
}
impl ToString for Identity {
#[inline(always)]
fn to_string(&self) -> String {
self.intl_to_string(false)
}
}
impl serde::Serialize for Identity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
serializer.serialize_str(self.intl_to_string(false).as_str())
}
}
struct IdentityVisitor;
impl<'de> serde::de::Visitor<'de> for IdentityVisitor {
type Value = Identity;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("ZeroTier Identity in string format") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error {
let id = Identity::new_from_string(s);
if id.is_err() {
return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self));
}
return Ok(id.ok().unwrap() as Self::Value);
}
}
impl<'de> serde::Deserialize<'de> for Identity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
deserializer.deserialize_str(IdentityVisitor)
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn identity() {
let test1 = Identity::new_generate(IdentityType::Curve25519);
assert!(test1.is_ok());
let test1 = test1.ok().unwrap();
assert!(test1.has_private());
let test2 = Identity::new_generate(IdentityType::NistP384);
assert!(test2.is_ok());
let test2 = test2.ok().unwrap();
println!("test type 0: {}", test1.to_secret_string());
println!("test type 1: {}", test2.to_secret_string());
assert!(test1.clone() == test1);
let test12 = Identity::new_from_string(test1.to_string().as_str());
assert!(test12.is_ok());
let test12 = test12.ok().unwrap();
assert!(!test12.has_private());
let test22 = Identity::new_from_string(test2.to_string().as_str());
assert!(test22.is_ok());
let test22 = test22.ok().unwrap();
assert!(test1 == test12);
assert!(test2 == test22);
println!("test type 0, from string: {}", test12.to_string());
println!("test type 1, from string: {}", test22.to_string());
let from_str_fail = Identity::new_from_string("asdf:foo:invalid");
assert!(from_str_fail.is_err());
let mut to_sign: [u8; 4] = [1, 2, 3, 4];
let signed = test1.sign(&to_sign);
assert!(signed.is_ok());
let signed = signed.ok().unwrap();
assert!(test1.verify(&to_sign, signed.as_ref()));
to_sign[0] = 2;
assert!(!test1.verify(&to_sign, signed.as_ref()));
to_sign[0] = 1;
let signed = test2.sign(&to_sign);
assert!(signed.is_ok());
let signed = signed.ok().unwrap();
assert!(test2.verify(&to_sign, signed.as_ref()));
to_sign[0] = 2;
assert!(!test2.verify(&to_sign, signed.as_ref()));
}
}

View file

@ -1,375 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::cmp::Ordering;
use std::ffi::CString;
use std::mem::{MaybeUninit, transmute};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::os::raw::{c_uint, c_void};
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use crate::*;
use crate::capi as ztcore;
// WARNING: here be dragons! This defines an opaque blob in Rust that shadows
// and is of the exact size as an opaque blob in C that shadows and is the
// exact size of struct sockaddr_storage. This Rust code makes use of a good
// deal of transmute() magic to save copying and allow these identically sized
// blobs to be freely cast to one another. That the sizes are correct is
// checked statically in the C++ code and in the tests in the Rust code.
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq)]
pub enum IpScope {
None = ztcore::ZT_InetAddress_IpScope_ZT_IP_SCOPE_NONE as isize,
Multicast = ztcore::ZT_InetAddress_IpScope_ZT_IP_SCOPE_MULTICAST as isize,
Loopback = ztcore::ZT_InetAddress_IpScope_ZT_IP_SCOPE_LOOPBACK as isize,
PseudoPrivate = ztcore::ZT_InetAddress_IpScope_ZT_IP_SCOPE_PSEUDOPRIVATE as isize,
Global = ztcore::ZT_InetAddress_IpScope_ZT_IP_SCOPE_GLOBAL as isize,
LinkLocal = ztcore::ZT_InetAddress_IpScope_ZT_IP_SCOPE_LINK_LOCAL as isize,
Shared = ztcore::ZT_InetAddress_IpScope_ZT_IP_SCOPE_SHARED as isize,
Private = ztcore::ZT_InetAddress_IpScope_ZT_IP_SCOPE_PRIVATE as isize
}
impl IpScope {
pub fn to_str(&self) -> &'static str {
match *self {
IpScope::None => "None",
IpScope::Multicast => "Multicast",
IpScope::Loopback => "Loopback",
IpScope::PseudoPrivate => "PseudoPrivate",
IpScope::Global => "Global",
IpScope::LinkLocal => "LinkLocal",
IpScope::Shared => "Shared",
IpScope::Private => "Private",
}
}
}
#[derive(PartialEq, Eq)]
pub enum InetAddressFamily {
Nil,
IPv4,
IPv6
}
impl InetAddressFamily {
pub fn to_str(&self) -> &'static str {
match *self {
InetAddressFamily::Nil => "Nil",
InetAddressFamily::IPv4 => "IPv4",
InetAddressFamily::IPv6 => "IPv6",
}
}
}
pub const IPV4_INADDR_ANY: [u8; 4] = [0; 4];
pub const IPV6_INADDR_ANY: [u8; 16] = [0; 16];
pub const IPV4_LOOPBACK: [u8; 4] = [127, 0, 0, 1];
pub const IPV6_LOOPBACK: [u8; 16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
/// Opaque structure that can hold an IPv4 or IPv6 address.
pub struct InetAddress {
// This must be the same size as ZT_InetAddress in zerotier.h. This is
// checked in tests.
bits: [u64; (ztcore::ZT_SOCKADDR_STORAGE_SIZE / 8) as usize]
}
impl InetAddress {
/// Create a new empty and "nil" InetAddress.
pub fn new() -> InetAddress {
InetAddress {
bits: [0; (ztcore::ZT_SOCKADDR_STORAGE_SIZE / 8) as usize]
}
}
/// Create an IPv4 0.0.0.0 InetAddress
pub fn new_ipv4_any(port: u16) -> InetAddress {
let mut ia = InetAddress::new();
unsafe {
ztcore::ZT_InetAddress_setIpBytes(ia.as_capi_mut_ptr(), IPV4_INADDR_ANY.as_ptr().cast(), 4, port as c_uint);
}
ia
}
/// Create an IPv6 ::0 InetAddress
pub fn new_ipv6_any(port: u16) -> InetAddress {
let mut ia = InetAddress::new();
unsafe {
ztcore::ZT_InetAddress_setIpBytes(ia.as_capi_mut_ptr(), IPV6_INADDR_ANY.as_ptr().cast(), 16, port as c_uint);
}
ia
}
/// Create 127.0.0.1/port
pub fn new_ipv4_loopback(port: u16) -> InetAddress {
let mut ia = InetAddress::new();
unsafe {
ztcore::ZT_InetAddress_setIpBytes(ia.as_capi_mut_ptr(), IPV4_LOOPBACK.as_ptr().cast(), 4, port as c_uint);
}
ia
}
/// Create ::1/port
pub fn new_ipv6_loopback(port: u16) -> InetAddress {
let mut ia = InetAddress::new();
unsafe {
ztcore::ZT_InetAddress_setIpBytes(ia.as_capi_mut_ptr(), IPV6_LOOPBACK.as_ptr().cast(), 16, port as c_uint);
}
ia
}
/// Create from a 4-byte IPv4 IP or a 16-byte IPv6 IP.
/// Returns None if ip is not 4 or 16 bytes.
pub fn new_from_ip_bytes(ip: &[u8], port: u16) -> Option<InetAddress> {
if ip.len() != 4 && ip.len() != 16 {
return None;
}
let mut a = InetAddress::new();
unsafe {
ztcore::ZT_InetAddress_setIpBytes(a.as_capi_mut_ptr(), ip.as_ptr() as *const c_void, ip.len() as c_uint, port as c_uint);
}
Some(a)
}
/// Create from an InetAddress in string form.
/// Returns None if the string is not valid.
pub fn new_from_string(s: &str) -> Option<InetAddress> {
let mut a = InetAddress::new();
let cs = CString::new(s);
if cs.is_ok() {
let cs = cs.unwrap();
unsafe {
if ztcore::ZT_InetAddress_fromString(a.as_capi_mut_ptr(), cs.as_ptr()) == 0 {
return None
}
}
}
Some(a)
}
/// Unsafely transmute a raw sockaddr_storage structure into an InetAddress.
/// The type S MUST have a size equal to the size of this type and the
/// OS's sockaddr_storage. If not, this may crash.
#[inline(always)]
pub unsafe fn transmute_raw_sockaddr_storage<S>(ss: &S) -> &InetAddress {
transmute(ss)
}
/// Transmute a ZT_InetAddress from the core into a reference to a Rust
/// InetAddress containing exactly the same data. The returned reference
/// of course only remains valid so long as the ZT_InetAddress remains
/// valid.
#[inline(always)]
pub(crate) fn transmute_capi(a: &ztcore::ZT_InetAddress) -> &InetAddress {
unsafe {
transmute(a)
}
}
/// Create an InetAddress by copying a ZT_InetAddress instead of transmuting.
/// Returns None if the input is a nil address.
pub(crate) fn new_from_capi(a: &ztcore::ZT_InetAddress) -> Option<InetAddress> {
if a.bits[0] != 0 {
Some(InetAddress {
bits: a.bits
})
} else {
None
}
}
/// Clear and set to the "nil" value.
pub fn clear(&mut self) {
for i in self.bits.iter_mut() {
*i = 0;
}
}
/// Returns true if this InetAddress holds nothing.
#[inline(always)]
pub fn is_nil(&self) -> bool {
self.bits[0] == 0 // if ss_family != 0, this will not be zero
}
#[inline(always)]
pub(crate) fn as_capi_ptr(&self) -> *const ztcore::ZT_InetAddress {
unsafe {
transmute(self as *const InetAddress)
}
}
#[inline(always)]
pub(crate) fn as_capi_mut_ptr(&mut self) -> *mut ztcore::ZT_InetAddress {
unsafe {
transmute(self as *mut InetAddress)
}
}
#[inline(always)]
pub fn port(&self) -> u16 {
unsafe {
ztcore::ZT_InetAddress_port(self.as_capi_ptr()) as u16
}
}
#[inline(always)]
pub fn set_port(&mut self, port: u16) {
unsafe {
ztcore::ZT_InetAddress_setPort(self.as_capi_mut_ptr(), port as c_uint);
}
}
/// Get the network scope of the IP in this object.
#[inline(always)]
pub fn ip_scope(&self) -> IpScope {
unsafe {
IpScope::from_i32(ztcore::ZT_InetAddress_ipScope(self.as_capi_ptr()) as i32).unwrap_or(IpScope::None)
}
}
#[inline(always)]
pub fn is_v4(&self) -> bool {
unsafe {
ztcore::ZT_InetAddress_isV4(self.as_capi_ptr()) != 0
}
}
#[inline(always)]
pub fn is_v6(&self) -> bool {
unsafe {
ztcore::ZT_InetAddress_isV6(self.as_capi_ptr()) != 0
}
}
/// Get the address family of this InetAddress.
pub fn family(&self) -> InetAddressFamily {
if !self.is_nil() {
unsafe {
if ztcore::ZT_InetAddress_isV4(self.as_capi_ptr()) != 0 {
return InetAddressFamily::IPv4;
}
if ztcore::ZT_InetAddress_isV6(self.as_capi_ptr()) != 0 {
return InetAddressFamily::IPv6;
}
}
}
InetAddressFamily::Nil
}
/// Convert to std::net::SocketAddr for use with std::net APIs
pub fn to_socketaddr(&self) -> Option<SocketAddr> {
let mut buf: MaybeUninit<[u8; 16]> = MaybeUninit::uninit();
let len;
let buf = unsafe {
len = ztcore::ZT_InetAddress_ipBytes(self.as_capi_ptr(), buf.as_mut_ptr().cast());
buf.assume_init()
};
if len == 4 {
Some(SocketAddr::new(IpAddr::from(Ipv4Addr::new(buf[0], buf[1], buf[2], buf[3])), self.port()))
} else if len == 16 {
Some(SocketAddr::new(IpAddr::from(buf), self.port()))
} else {
None
}
}
}
impl ToString for InetAddress {
fn to_string(&self) -> String {
let mut buf: MaybeUninit<[c_char; 128]> = MaybeUninit::uninit();
unsafe {
return cstr_to_string(ztcore::ZT_InetAddress_toString(self.as_capi_ptr(), (*buf.as_mut_ptr()).as_mut_ptr(), 128), 128);
}
}
}
impl From<&str> for InetAddress {
fn from(s: &str) -> InetAddress {
let a = InetAddress::new_from_string(s);
if a.is_none() {
return InetAddress::new();
}
a.unwrap()
}
}
impl Clone for InetAddress {
#[inline(always)]
fn clone(&self) -> Self {
InetAddress{
bits: self.bits
}
}
}
impl Ord for InetAddress {
fn cmp(&self, other: &Self) -> Ordering {
let c = unsafe { ztcore::ZT_InetAddress_compare(self.as_capi_ptr(), other.as_capi_ptr()) };
if c < 0 {
Ordering::Less
} else if c > 0 {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
impl PartialOrd for InetAddress {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for InetAddress {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
unsafe { ztcore::ZT_InetAddress_compare(self.as_capi_ptr(), other.as_capi_ptr()) == 0 }
}
}
impl Eq for InetAddress {}
impl serde::Serialize for InetAddress {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { serializer.serialize_str(self.to_string().as_str()) }
}
struct InetAddressVisitor;
impl<'de> serde::de::Visitor<'de> for InetAddressVisitor {
type Value = InetAddress;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("InetAddress value in string form") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error {
let id = InetAddress::new_from_string(s);
if id.is_none() {
return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self));
}
return Ok(id.unwrap() as Self::Value);
}
}
impl<'de> serde::Deserialize<'de> for InetAddress {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { deserializer.deserialize_str(InetAddressVisitor) }
}
#[cfg(test)]
mod tests {
use std::mem::size_of;
use crate::*;
#[test]
fn type_sizes() {
assert_eq!(size_of::<ztcore::ZT_InetAddress>(), size_of::<InetAddress>());
}
}

View file

@ -1,235 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::os::raw::{c_char, c_int};
use num_derive::{FromPrimitive, ToPrimitive};
#[macro_use] extern crate base64_serde;
#[allow(non_snake_case,non_upper_case_globals,non_camel_case_types,dead_code,improper_ctypes)]
mod capi; // bindgen generated
mod identity;
mod address;
mod fingerprint;
mod endpoint;
mod certificate;
mod networkid;
mod inetaddress;
mod locator;
mod path;
mod peer;
mod node;
mod mac;
mod buffer;
mod portableatomici64;
mod virtualnetworkconfig;
mod multicastgroup;
mod dictionary;
pub mod trace;
use crate::capi as ztcore;
pub use identity::*;
pub use address::Address;
pub use fingerprint::Fingerprint;
pub use endpoint::*;
pub use certificate::*;
pub use networkid::NetworkId;
pub use inetaddress::*;
pub use locator::*;
pub use path::Path;
pub use peer::Peer;
pub use node::*;
pub use mac::MAC;
pub use buffer::Buffer;
pub use portableatomici64::PortableAtomicI64;
pub use virtualnetworkconfig::*;
pub use multicastgroup::MulticastGroup;
pub use dictionary::*;
base64_serde_type!(Base64URLSafeNoPad, base64::URL_SAFE_NO_PAD);
/// Recommended minimum thread stack size for background threads.
pub const RECOMMENDED_THREAD_STACK_SIZE: usize = 524288;
/// Default TCP and UDP port.
pub const DEFAULT_PORT: u16 = ztcore::ZT_DEFAULT_PORT as u16;
/// Default secondary UDP port if enabled.
pub const DEFAULT_SECONDARY_PORT: u16 = ztcore::ZT_DEFAULT_SECONDARY_PORT as u16;
/// Size of a ZeroTier core "Buffer" in bytes.
pub const BUF_SIZE: usize = ztcore::ZT_BUF_SIZE as usize;
/// Minimum physical MTU.
pub const MIN_MTU: u32 = ztcore::ZT_MIN_MTU;
/// Maximum physical MTU.
pub const MAX_MTU: u32 = ztcore::ZT_MAX_MTU;
/// Default physica UDP MTU (not including IP or UDP headers).
pub const DEFAULT_UDP_MTU: u32 = ztcore::ZT_DEFAULT_UDP_MTU;
/// Maximum UDP MTU (we never actually get this high).
pub const MAX_UDP_MTU: u32 = ztcore::ZT_MAX_UDP_MTU;
/// Base64 encode using the URL-safe alphabet with no padding.
pub fn base64_encode<T: AsRef<[u8]>>(t: &T) -> String {
base64::encode_config(t, base64::URL_SAFE_NO_PAD)
}
/// Base64 decode using the URL-safe alphabet with no padding.
pub fn base64_decode<T: AsRef<[u8]>>(t: &T) -> Result<Vec<u8>, base64::DecodeError> {
base64::decode_config(t, base64::URL_SAFE_NO_PAD)
}
#[allow(non_snake_case,non_upper_case_globals)]
pub mod RulePacketCharacteristicFlags {
pub const Inbound: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_INBOUND as u64;
pub const Multicast: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST as u64;
pub const Broadcast: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST as u64;
pub const SenderIpAuthenticated: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED as u64;
pub const SenderMacAuthenticated: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED as u64;
pub const TcpFlagNS: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_TCP_NS as u64;
pub const TcpFlagCWR: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_TCP_CWR as u64;
pub const TcpFlagECE: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_TCP_ECE as u64;
pub const TcpFlagURG: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_TCP_URG as u64;
pub const TcpFlagACK: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_TCP_ACK as u64;
pub const TcpFlagPSH: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_TCP_PSH as u64;
pub const TcpFlagRST: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_TCP_RST as u64;
pub const TcpFlagSYN: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN as u64;
pub const TcpFlagFIN: u64 = crate::capi::ZT_RULE_PACKET_CHARACTERISTICS_TCP_FIN as u64;
}
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq)]
pub enum CredentialType {
Null = ztcore::ZT_CredentialType_ZT_CREDENTIAL_TYPE_NULL as isize,
CertificateOfMembership = ztcore::ZT_CredentialType_ZT_CREDENTIAL_TYPE_COM as isize,
Capability = ztcore::ZT_CredentialType_ZT_CREDENTIAL_TYPE_CAPABILITY as isize,
Tag = ztcore::ZT_CredentialType_ZT_CREDENTIAL_TYPE_TAG as isize,
CertificateOfOwnership = ztcore::ZT_CredentialType_ZT_CREDENTIAL_TYPE_COO as isize,
Revocation = ztcore::ZT_CredentialType_ZT_CREDENTIAL_TYPE_REVOCATION as isize,
}
impl CredentialType {
pub fn to_str(&self) -> &'static str {
match *self {
CredentialType::Null => "Null",
CredentialType::CertificateOfMembership => "CertificateOfMembership",
CredentialType::Capability => "Capability",
CredentialType::Tag => "Tag",
CredentialType::CertificateOfOwnership => "CertificateOfOwnership",
CredentialType::Revocation => "Revocation",
}
}
}
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq)]
pub enum ResultCode {
Ok = ztcore::ZT_ResultCode_ZT_RESULT_OK as isize,
FatalErrorOutOfMemory = ztcore::ZT_ResultCode_ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY as isize,
FatalErrorDataStoreFailed = ztcore::ZT_ResultCode_ZT_RESULT_FATAL_ERROR_DATA_STORE_FAILED as isize,
FatalErrorInternal = ztcore::ZT_ResultCode_ZT_RESULT_FATAL_ERROR_INTERNAL as isize,
ErrorNetworkNotFound = ztcore::ZT_ResultCode_ZT_RESULT_ERROR_NETWORK_NOT_FOUND as isize,
ErrorUnsupportedOperation = ztcore::ZT_ResultCode_ZT_RESULT_ERROR_UNSUPPORTED_OPERATION as isize,
ErrorBadParameter = ztcore::ZT_ResultCode_ZT_RESULT_ERROR_BAD_PARAMETER as isize,
ErrorInvalidCredential = ztcore::ZT_ResultCode_ZT_RESULT_ERROR_INVALID_CREDENTIAL as isize,
ErrorCollidingObject = ztcore::ZT_ResultCode_ZT_RESULT_ERROR_COLLIDING_OBJECT as isize,
ErrorInternalNonFatal = ztcore::ZT_ResultCode_ZT_RESULT_ERROR_INTERNAL as isize,
}
impl ResultCode {
pub fn to_str(&self) -> &'static str {
match *self {
ResultCode::Ok => "Ok",
ResultCode::FatalErrorOutOfMemory => "FatalErrorOutOfMemory",
ResultCode::FatalErrorDataStoreFailed => "FatalErrorDataStoreFailed",
ResultCode::FatalErrorInternal => "FatalErrorInternal",
ResultCode::ErrorNetworkNotFound => "ErrorNetworkNotFound",
ResultCode::ErrorUnsupportedOperation => "ErrorUnsupportedOperation",
ResultCode::ErrorBadParameter => "ErrorBadParameter",
ResultCode::ErrorInvalidCredential => "ErrorInvalidCredential",
ResultCode::ErrorCollidingObject => "ErrorCollidingObject",
ResultCode::ErrorInternalNonFatal => "ErrorInternalNonFatal",
}
}
}
/// Returns a tuple of major, minor, revision, and build version numbers from the ZeroTier core.
pub fn version() -> (i32, i32, i32, i32) {
let mut major: c_int = 0;
let mut minor: c_int = 0;
let mut revision: c_int = 0;
let mut build: c_int = 0;
unsafe {
ztcore::ZT_version(&mut major as *mut c_int, &mut minor as *mut c_int, &mut revision as *mut c_int, &mut build as *mut c_int);
}
(major as i32, minor as i32, revision as i32, build as i32)
}
/// Get a random 64-bit integer using the non-cryptographic PRNG in the ZeroTier core.
#[inline(always)]
pub fn random() -> u64 {
unsafe {
return ztcore::ZT_random();
}
}
/// Test whether this byte array or slice is all zeroes.
pub fn is_all_zeroes<B: AsRef<[u8]>>(b: B) -> bool {
for c in b.as_ref().iter() {
if *c != 0 {
return false;
}
}
true
}
/// The CStr stuff is cumbersome, so this is an easier to use function to turn a C string into a String.
/// This returns an empty string on a null pointer or invalid UTF-8. It's unsafe because it can crash if
/// the string is not zero-terminated. A size limit can be passed in if available to reduce this risk, or
/// the max_len parameter can be -1 if there is no known limit.
pub unsafe fn cstr_to_string(cstr: *const c_char, max_len: isize) -> String {
if !cstr.is_null() {
let mut cstr_len: isize = 0;
while max_len < 0 || cstr_len < max_len {
if *cstr.offset(cstr_len) == 0 {
break;
}
cstr_len += 1;
}
return String::from(std::str::from_utf8(std::slice::from_raw_parts(cstr as *const u8, cstr_len as usize)).unwrap_or(""));
}
String::new()
}
/*
#[macro_export(crate)]
macro_rules! enum_str {
(enum $name:ident {
$($variant:ident = $val:expr),*,
}) => {
enum $name {
$($variant = $val),*
}
impl $name {
fn name(&self) -> &'static str {
match self {
$($name::$variant => stringify!($variant)),*
}
}
}
};
}
*/

View file

@ -1,145 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::ffi::CString;
use std::os::raw::{c_int, c_uint};
use std::mem::MaybeUninit;
use std::ptr::null;
use crate::*;
use crate::capi as ztcore;
pub struct Locator {
pub(crate) capi: *const ztcore::ZT_Locator,
requires_delete: bool
}
impl Locator {
/// Create and sign a new locator.
/// The signer must include its secret key.
pub fn new(signer: &Identity, revision: i64, endpoints: &Vec<Endpoint>) -> Result<Locator, ResultCode> {
let mut capi_endpoints: Vec<ztcore::ZT_Endpoint> = Vec::new();
capi_endpoints.reserve(endpoints.len());
for ep in endpoints.iter() {
capi_endpoints.push(ep.capi);
}
let loc = unsafe { ztcore::ZT_Locator_create(revision, capi_endpoints.as_ptr(), null(), capi_endpoints.len() as c_uint, signer.capi) };
if loc.is_null() {
Err(ResultCode::ErrorBadParameter)
} else {
Ok(Locator::new_from_capi(loc, true))
}
}
#[inline(always)]
pub(crate) fn new_from_capi(l: *const ztcore::ZT_Locator, requires_delete: bool) -> Locator {
Locator{
capi: l,
requires_delete
}
}
pub fn new_from_string(s: &str) -> Result<Locator, ResultCode> {
let cs = CString::new(s);
if cs.is_err() {
return Err(ResultCode::ErrorBadParameter);
}
let cs = cs.unwrap();
let l = unsafe { ztcore::ZT_Locator_fromString(cs.as_ptr()) };
if l.is_null() {
return Err(ResultCode::ErrorBadParameter);
}
return Ok(Locator::new_from_capi(l, true));
}
#[inline(always)]
pub fn revision(&self) -> i64 {
unsafe { ztcore::ZT_Locator_revision(self.capi) as i64 }
}
#[inline(always)]
pub fn signer(&self) -> Address {
unsafe { Address(ztcore::ZT_Locator_signer(self.capi)) }
}
pub fn endpoints(&self) -> Vec<Endpoint> {
let mut eps: Vec<Endpoint> = Vec::new();
let ep_count = unsafe { ztcore::ZT_Locator_endpointCount(self.capi) as usize };
eps.reserve(ep_count as usize);
for i in 0..ep_count {
eps.push(Endpoint::new_from_capi(unsafe { &*ztcore::ZT_Locator_endpoint(self.capi, i as c_uint) }));
}
eps
}
#[inline(always)]
pub fn verify(&self, id: &Identity) -> bool {
unsafe { ztcore::ZT_Locator_verify(self.capi, id.capi) != 0 }
}
}
impl Drop for Locator {
#[inline(always)]
fn drop(&mut self) {
if self.requires_delete {
unsafe { ztcore::ZT_Locator_delete(self.capi); }
}
}
}
impl Clone for Locator {
#[inline(always)]
fn clone(&self) -> Locator {
Locator::new_from_string(self.to_string().as_str()).ok().unwrap()
}
}
impl ToString for Locator {
fn to_string(&self) -> String {
const LOCATOR_STRING_BUF_LEN: usize = 16384;
let mut buf: MaybeUninit<[u8; LOCATOR_STRING_BUF_LEN]> = MaybeUninit::uninit();
if unsafe { ztcore::ZT_Locator_toString(self.capi, buf.as_mut_ptr().cast(), LOCATOR_STRING_BUF_LEN as c_int).is_null() }{
"(invalid)".to_owned()
} else {
unsafe { cstr_to_string(buf.as_ptr().cast(), LOCATOR_STRING_BUF_LEN as isize) }
}
}
}
impl PartialEq for Locator {
#[inline(always)]
fn eq(&self, other: &Locator) -> bool {
unsafe { ztcore::ZT_Locator_equals(self.capi, other.capi) != 0 }
}
}
impl Eq for Locator {}
impl serde::Serialize for Locator {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { serializer.serialize_str(self.to_string().as_str()) }
}
struct LocatorVisitor;
impl<'de> serde::de::Visitor<'de> for LocatorVisitor {
type Value = Locator;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("Locator value in string form") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error {
let id = Locator::new_from_string(s);
if id.is_err() {
return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self));
}
return Ok(id.ok().unwrap() as Self::Value);
}
}
impl<'de> serde::Deserialize<'de> for Locator {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { deserializer.deserialize_str(LocatorVisitor) }
}

View file

@ -1,92 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::hash::{Hash, Hasher};
#[derive(PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub struct MAC(pub u64);
impl Default for MAC {
#[inline(always)]
fn default() -> MAC {
MAC(0)
}
}
impl MAC {
#[inline(always)]
fn to_bytes(&self) -> [u8; 6] {
[(self.0 >> 40) as u8, (self.0 >> 32) as u8, (self.0 >> 24) as u8, (self.0 >> 16) as u8, (self.0 >> 8) as u8, self.0 as u8]
}
}
impl Hash for MAC {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl ToString for MAC {
fn to_string(&self) -> String {
let x = self.0;
format!("{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}", (x >> 40) & 0xff, (x >> 32) & 0xff, (x >> 24) & 0xff, (x >> 16) & 0xff, (x >> 8) & 0xff, x & 0xff)
}
}
impl From<&[u8]> for MAC {
#[inline(always)]
fn from(bytes: &[u8]) -> Self {
if bytes.len() >= 6 {
MAC(((bytes[0] as u64) << 40) | ((bytes[1] as u64) << 32) | ((bytes[2] as u64) << 24) | ((bytes[3] as u64) << 16) | ((bytes[4] as u64) << 8) | (bytes[5] as u64))
} else {
MAC(0)
}
}
}
impl From<&str> for MAC {
fn from(s: &str) -> MAC {
MAC(u64::from_str_radix(s.replace(":", "").as_str(), 16).unwrap_or(0))
}
}
impl serde::Serialize for MAC {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
if serializer.is_human_readable() {
serializer.serialize_str(self.to_string().as_str())
} else {
let b = self.to_bytes();
serializer.serialize_bytes(b.as_ref())
}
}
}
struct AddressVisitor;
impl<'de> serde::de::Visitor<'de> for AddressVisitor {
type Value = MAC;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("Ethernet MAC address in string format (with or without : separators)") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error { Ok(MAC::from(s)) }
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> where E: serde::de::Error { Ok(MAC::from(v)) }
}
impl<'de> serde::Deserialize<'de> for MAC {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
if deserializer.is_human_readable() {
deserializer.deserialize_str(AddressVisitor)
} else {
deserializer.deserialize_bytes(AddressVisitor)
}
}
}

View file

@ -1,61 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use crate::MAC;
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
#[derive(Clone, PartialEq, Eq)]
pub struct MulticastGroup {
pub mac: MAC,
pub adi: u32,
}
impl ToString for MulticastGroup {
fn to_string(&self) -> String {
format!("{}/{}", self.mac.to_string(), self.adi)
}
}
impl Hash for MulticastGroup {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.mac.0.hash(state);
self.adi.hash(state);
}
}
impl Ord for MulticastGroup {
fn cmp(&self, other: &Self) -> Ordering {
if self.mac.0 < other.mac.0 {
Ordering::Less
} else if self.mac.0 > other.mac.0 {
Ordering::Greater
} else {
if self.adi < other.adi {
Ordering::Less
} else if self.adi > other.adi {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
}
impl PartialOrd for MulticastGroup {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

View file

@ -1,80 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::hash::{Hash, Hasher};
#[derive(PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub struct NetworkId(pub u64);
impl Default for NetworkId {
#[inline(always)]
fn default() -> NetworkId {
NetworkId(0)
}
}
impl Hash for NetworkId {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl ToString for NetworkId {
fn to_string(&self) -> String {
format!("{:0>16x}", self.0)
}
}
impl From<u64> for NetworkId {
#[inline(always)]
fn from(n: u64) -> Self {
NetworkId(n)
}
}
impl From<&str> for NetworkId {
#[inline(always)]
fn from(s: &str) -> Self {
NetworkId(u64::from_str_radix(s, 16).unwrap_or(0))
}
}
impl serde::Serialize for NetworkId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
if serializer.is_human_readable() {
serializer.serialize_str(self.to_string().as_str())
} else {
serializer.serialize_u64(self.0)
}
}
}
struct NetworkIdVisitor;
impl<'de> serde::de::Visitor<'de> for NetworkIdVisitor {
type Value = NetworkId;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("ZeroTier network ID") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error { Ok(NetworkId::from(s)) }
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: serde::de::Error { Ok(NetworkId(v)) }
}
impl<'de> serde::Deserialize<'de> for NetworkId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
if deserializer.is_human_readable() {
deserializer.deserialize_str(NetworkIdVisitor)
} else {
deserializer.deserialize_u64(NetworkIdVisitor)
}
}
}

View file

@ -1,557 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::collections::hash_map::HashMap;
use std::marker::PhantomData;
use std::mem::{MaybeUninit, transmute};
use std::os::raw::{c_int, c_uint, c_ulong, c_void};
use std::pin::Pin;
use std::ptr::{null_mut, slice_from_raw_parts, copy_nonoverlapping};
use std::sync::Mutex;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use serde::{Deserialize, Serialize};
use crate::*;
use crate::capi as ztcore;
pub const NODE_BACKGROUND_TASKS_MAX_INTERVAL: i64 = 200;
const EMPTY_BYTE_ARRAY: [u8; 0] = [];
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum Event {
Up = ztcore::ZT_Event_ZT_EVENT_UP as isize,
Offline = ztcore::ZT_Event_ZT_EVENT_OFFLINE as isize,
Online = ztcore::ZT_Event_ZT_EVENT_ONLINE as isize,
Down = ztcore::ZT_Event_ZT_EVENT_DOWN as isize,
Trace = ztcore::ZT_Event_ZT_EVENT_TRACE as isize,
UserMessage = ztcore::ZT_Event_ZT_EVENT_USER_MESSAGE as isize,
}
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum StateObjectType {
IdentityPublic = ztcore::ZT_StateObjectType_ZT_STATE_OBJECT_IDENTITY_PUBLIC as isize,
IdentitySecret = ztcore::ZT_StateObjectType_ZT_STATE_OBJECT_IDENTITY_SECRET as isize,
Locator = ztcore::ZT_StateObjectType_ZT_STATE_OBJECT_LOCATOR as isize,
Peer = ztcore::ZT_StateObjectType_ZT_STATE_OBJECT_PEER as isize,
NetworkConfig = ztcore::ZT_StateObjectType_ZT_STATE_OBJECT_NETWORK_CONFIG as isize,
TrustStore = ztcore::ZT_StateObjectType_ZT_STATE_OBJECT_TRUST_STORE as isize,
}
impl StateObjectType {
/// True if this state object should be protected in a data store.
/// This could mean its file permissions should be locked down so they're only readable by the service, for example.
#[inline(always)]
pub fn is_secret(&self) -> bool {
*self == StateObjectType::IdentitySecret || *self == StateObjectType::TrustStore
}
}
/// The status of a ZeroTier node.
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct NodeStatus {
pub address: Address,
pub identity: Identity,
#[serde(rename = "publicIdentity")]
pub public_identity: String,
#[serde(rename = "secretIdentity")]
pub secret_identity: String,
pub online: bool,
}
/// An event handler that receives events, frames, and packets from the core.
/// Note that if multiple threads are calling into Node these may be called by any of these threads
/// at any time and must be thread safe.
pub trait NodeEventHandler<N: Sync + Send + 'static> {
/// Called when a configuration change or update should be applied to a network.
fn virtual_network_config(&self, network_id: NetworkId, network_obj: &N, config_op: VirtualNetworkConfigOperation, config: Option<&VirtualNetworkConfig>);
/// Called when a frame should be injected into the virtual network (physical -> virtual).
fn virtual_network_frame(&self, network_id: NetworkId, network_obj: &N, source_mac: MAC, dest_mac: MAC, ethertype: u16, vlan_id: u16, data: &[u8]);
/// Called when a core ZeroTier event occurs.
fn event(&self, event: Event, event_data: &[u8]);
/// Called to store an object into the object store.
fn state_put(&self, obj_type: StateObjectType, obj_id: &[u64], obj_data: &[u8]) -> std::io::Result<()>;
/// Called to retrieve an object from the object store.
fn state_get(&self, obj_type: StateObjectType, obj_id: &[u64]) -> std::io::Result<Vec<u8>>;
/// Called to send a packet over the physical network (virtual -> physical).
fn wire_packet_send(&self, local_socket: i64, sock_addr: &InetAddress, data: &[u8], packet_ttl: u32) -> i32;
/// Called to check and see if a physical address should be used for ZeroTier traffic.
fn path_check(&self, address: Address, id: &Identity, local_socket: i64, sock_addr: &InetAddress) -> bool;
/// Called to look up a path to a known node, allowing out of band lookup methods for physical paths to nodes.
fn path_lookup(&self, address: Address, id: &Identity, desired_family: InetAddressFamily) -> Option<InetAddress>;
}
pub(crate) struct NodeIntl<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>> {
event_handler: T,
capi: *mut ztcore::ZT_Node,
networks_by_id: Mutex<HashMap<u64, Pin<Box<N>>>>,
recent_clock: PortableAtomicI64,
recent_ticks: PortableAtomicI64,
event_handler_placeholder: PhantomData<H>,
}
/// An instance of the ZeroTier core.
///
/// The event handler is templated as AsRef<H> where H is the concrete type of the actual
/// handler. This allows the handler to be an Arc<>, Box<>, or similar. We do this instead
/// of templating it on "dyn NodeEventHandler" because we want the types to all be concrete
/// to avoid dynamic call overhead. Unfortunately it makes the types here a tad more
/// verbose.
///
/// In most cases you will want the handler to be an Arc<> anyway since most uses will be
/// multithreaded or async.
pub struct Node<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>> {
intl: Pin<Box<NodeIntl<T, N, H>>>,
identity_wrapper: Option<Identity>,
event_handler_placeholder: PhantomData<H>,
}
/********************************************************************************************************************/
extern "C" fn zt_virtual_network_config_function<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>>(
_: *mut ztcore::ZT_Node,
uptr: *mut c_void,
_: *mut c_void,
nwid: u64,
nptr: *mut *mut c_void,
op: ztcore::ZT_VirtualNetworkConfigOperation,
conf: *const ztcore::ZT_VirtualNetworkConfig,
) {
let _ = VirtualNetworkConfigOperation::from_i32(op as i32).map(|op| {
let n = unsafe { &*(uptr.cast::<NodeIntl<T, N, H>>()) };
if conf.is_null() {
n.event_handler.as_ref().virtual_network_config(NetworkId(nwid), unsafe { &*(nptr.cast::<N>()) }, op, None);
} else {
let conf2 = unsafe { VirtualNetworkConfig::new_from_capi(&*conf) };
n.event_handler.as_ref().virtual_network_config(NetworkId(nwid), unsafe { &*(nptr.cast::<N>()) }, op, Some(&conf2));
}
});
}
extern "C" fn zt_virtual_network_frame_function<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>>(
_: *mut ztcore::ZT_Node,
uptr: *mut c_void,
_: *mut c_void,
nwid: u64,
nptr: *mut *mut c_void,
source_mac: u64,
dest_mac: u64,
ethertype: c_uint,
vlan_id: c_uint,
data: *const c_void,
data_size: c_uint,
) {
if !nptr.is_null() {
unsafe { &*(uptr.cast::<NodeIntl<T, N, H>>()) }.event_handler.as_ref().virtual_network_frame(
NetworkId(nwid),
unsafe { &*(nptr.cast::<N>()) },
MAC(source_mac),
MAC(dest_mac),
ethertype as u16,
vlan_id as u16,
unsafe { &*slice_from_raw_parts(data.cast::<u8>(), data_size as usize) });
}
}
extern "C" fn zt_event_callback<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>>(
_: *mut ztcore::ZT_Node,
uptr: *mut c_void,
_: *mut c_void,
ev: ztcore::ZT_Event,
data: *const c_void,
data_size: c_uint,
) {
let _ = Event::from_i32(ev as i32).map(|ev: Event| {
let n = unsafe { &*(uptr.cast::<NodeIntl<T, N, H>>()) };
if data.is_null() {
n.event_handler.as_ref().event(ev, &EMPTY_BYTE_ARRAY);
} else {
n.event_handler.as_ref().event(ev, unsafe { &*slice_from_raw_parts(data.cast::<u8>(), data_size as usize) });
}
});
}
extern "C" fn zt_state_put_function<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>>(
_: *mut ztcore::ZT_Node,
uptr: *mut c_void,
_: *mut c_void,
obj_type: ztcore::ZT_StateObjectType,
obj_id: *const u64,
obj_id_len: c_uint,
obj_data: *const c_void,
obj_data_len: c_int,
) {
let _ = StateObjectType::from_i32(obj_type as i32).map(|obj_type| {
let n = unsafe { &*(uptr.cast::<NodeIntl<T, N, H>>()) };
let _ = n.event_handler.as_ref().state_put(obj_type, unsafe { &*slice_from_raw_parts(obj_id, obj_id_len as usize) }, unsafe { &*slice_from_raw_parts(obj_data.cast::<u8>(), obj_data_len as usize) });
});
}
extern "C" fn zt_state_get_function<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>>(
_: *mut ztcore::ZT_Node,
uptr: *mut c_void,
_: *mut c_void,
obj_type: ztcore::ZT_StateObjectType,
obj_id: *const u64,
obj_id_len: c_uint,
obj_data: *mut *mut c_void,
obj_data_free_function: *mut *mut c_void,
) -> c_int {
if obj_data.is_null() || obj_data_free_function.is_null() {
-1 as c_int
} else {
unsafe {
*obj_data = null_mut();
*obj_data_free_function = transmute(ztcore::free as *const ());
}
StateObjectType::from_i32(obj_type as i32).map_or_else(|| {
-1 as c_int
}, |obj_type| {
unsafe { &*(uptr.cast::<NodeIntl<T, N, H>>()) }.event_handler.as_ref().state_get(obj_type, unsafe { &*slice_from_raw_parts(obj_id, obj_id_len as usize) }).map_or_else(|_| {
-1 as c_int
}, |obj_data_result| {
let obj_data_len = obj_data_result.len() as c_int;
if obj_data_len > 0 {
unsafe {
let obj_data_raw = ztcore::malloc(obj_data_len as c_ulong);
if obj_data_raw.is_null() {
-1 as c_int
} else {
copy_nonoverlapping(obj_data_result.as_ptr(), obj_data_raw.cast::<u8>(), obj_data_len as usize);
*obj_data = obj_data_raw;
obj_data_len
}
}
} else {
-1 as c_int
}
})
})
}
}
extern "C" fn zt_wire_packet_send_function<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>>(
_: *mut ztcore::ZT_Node,
uptr: *mut c_void,
_: *mut c_void,
local_socket: i64,
sock_addr: *const ztcore::ZT_InetAddress,
data: *const c_void,
data_size: c_uint,
packet_ttl: c_uint,
) -> c_int {
unsafe { &*(uptr.cast::<NodeIntl<T, N, H>>()) }.event_handler.as_ref().wire_packet_send(local_socket, InetAddress::transmute_capi(unsafe { &*sock_addr }), unsafe { &*slice_from_raw_parts(data.cast::<u8>(), data_size as usize) }, packet_ttl as u32) as c_int
}
extern "C" fn zt_path_check_function<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>>(
_: *mut ztcore::ZT_Node,
uptr: *mut c_void,
_: *mut c_void,
address: u64,
identity: *const ztcore::ZT_Identity,
local_socket: i64,
sock_addr: *const ztcore::ZT_InetAddress,
) -> c_int {
let id = Identity::new_from_capi(identity, false);
unsafe { &*(uptr.cast::<NodeIntl<T, N, H>>()) }.event_handler.as_ref().path_check(Address(address), &id, local_socket, InetAddress::transmute_capi(unsafe { &*sock_addr })) as c_int
}
extern "C" fn zt_path_lookup_function<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>>(
_: *mut ztcore::ZT_Node,
uptr: *mut c_void,
_: *mut c_void,
address: u64,
identity: *const ztcore::ZT_Identity,
sock_family: c_int,
sock_addr: *mut ztcore::ZT_InetAddress,
) -> c_int {
if sock_addr.is_null() {
return 0;
}
let sock_family2: InetAddressFamily;
unsafe {
if sock_family == ztcore::ZT_AF_INET {
sock_family2 = InetAddressFamily::IPv4;
} else if sock_family == ztcore::ZT_AF_INET6 {
sock_family2 = InetAddressFamily::IPv6;
} else {
return 0;
}
}
let id = Identity::new_from_capi(identity, false);
unsafe { &*(uptr.cast::<NodeIntl<T, N, H>>()) }.event_handler.as_ref().path_lookup(Address(address), &id, sock_family2).map_or_else(|| {
0 as c_int
}, |result| {
let result_ptr = &result as *const InetAddress;
unsafe {
copy_nonoverlapping(result_ptr.cast::<ztcore::ZT_InetAddress>(), sock_addr, 1);
}
1 as c_int
})
}
/********************************************************************************************************************/
impl<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>> Node<T, N, H> {
/// Create a new Node with a given event handler.
#[allow(unused_mut)]
pub fn new(event_handler: T, clock: i64, ticks: i64) -> Result<Node<T, N, H>, ResultCode> {
let mut n = Node {
intl: Box::pin(NodeIntl {
event_handler: event_handler.clone(),
capi: null_mut(),
networks_by_id: Mutex::new(HashMap::new()),
event_handler_placeholder: PhantomData::default(),
recent_clock: PortableAtomicI64::new(clock),
recent_ticks: PortableAtomicI64::new(ticks),
}),
identity_wrapper: None,
event_handler_placeholder: PhantomData::default(),
};
let rc = unsafe {
let callbacks = ztcore::ZT_Node_Callbacks {
statePutFunction: transmute(zt_state_put_function::<T, N, H> as *const ()),
stateGetFunction: transmute(zt_state_get_function::<T, N, H> as *const ()),
wirePacketSendFunction: transmute(zt_wire_packet_send_function::<T, N, H> as *const ()),
virtualNetworkFrameFunction: transmute(zt_virtual_network_frame_function::<T, N, H> as *const ()),
virtualNetworkConfigFunction: transmute(zt_virtual_network_config_function::<T, N, H> as *const ()),
eventCallback: transmute(zt_event_callback::<T, N, H> as *const ()),
pathCheckFunction: transmute(zt_path_check_function::<T, N, H> as *const ()),
pathLookupFunction: transmute(zt_path_lookup_function::<T, N, H> as *const ()),
};
ztcore::ZT_Node_new(transmute(&(n.intl.capi) as *const *mut ztcore::ZT_Node), clock, ticks, null_mut(), transmute(&*n.intl as *const NodeIntl<T, N, H>), &callbacks)
};
if rc == 0 {
assert!(!n.intl.capi.is_null());
n.identity_wrapper.replace(Identity::new_from_capi(unsafe { ztcore::ZT_Node_identity(n.intl.capi) }, false));
Ok(n)
} else {
Err(ResultCode::from_i32(rc as i32).unwrap_or(ResultCode::FatalErrorInternal))
}
}
/// Perform periodic background tasks.
/// The first call should happen no more than NODE_BACKGROUND_TASKS_MAX_INTERVAL milliseconds
/// since the node was created, and after this runs it returns the amount of time the caller
/// should wait before calling it again.
#[inline(always)]
pub fn process_background_tasks(&self, clock: i64, ticks: i64) -> i64 {
self.intl.recent_clock.set(clock);
self.intl.recent_ticks.set(ticks);
let mut next_task_deadline: i64 = ticks;
unsafe {
ztcore::ZT_Node_processBackgroundTasks(self.intl.capi, clock, ticks, null_mut(), (&mut next_task_deadline as *mut i64).cast());
}
(next_task_deadline - ticks).clamp(1_i64, NODE_BACKGROUND_TASKS_MAX_INTERVAL)
}
/// Join a network, associating network_obj with it.
/// If a fingerprint is supplied it will be used as a full sha384 fingerprint of the
/// network's controller.
pub fn join(&self, clock: i64, ticks: i64, nwid: NetworkId, controller_fingerprint: Option<Fingerprint>, network_obj: N) -> ResultCode {
let mut cfp: MaybeUninit<ztcore::ZT_Fingerprint> = MaybeUninit::uninit();
let mut cfpp: *mut ztcore::ZT_Fingerprint = null_mut();
if controller_fingerprint.is_some() {
let cfp2 = controller_fingerprint.unwrap();
cfpp = cfp.as_mut_ptr();
unsafe {
(*cfpp).address = cfp2.address.0;
(*cfpp).hash = cfp2.hash;
}
}
let network_obj = Box::pin(network_obj);
let rc = unsafe { ztcore::ZT_Node_join(self.intl.capi, clock, ticks, null_mut(), transmute((&*network_obj) as *const N), nwid.0, cfpp) };
if rc == ztcore::ZT_ResultCode_ZT_RESULT_OK {
self.intl.networks_by_id.lock().unwrap().insert(nwid.0, network_obj);
ResultCode::Ok
} else {
ResultCode::from_i32(rc as i32).unwrap_or(ResultCode::ErrorInternalNonFatal)
}
}
/// Leave a network.
pub fn leave(&self, clock: i64, ticks: i64, nwid: NetworkId) -> ResultCode {
self.intl.networks_by_id.lock().unwrap().remove(&nwid.0).map_or_else(|| {
ResultCode::ErrorNetworkNotFound
}, |_| {
unsafe {
ResultCode::from_i32(ztcore::ZT_Node_leave(self.intl.capi, clock, ticks, null_mut(), null_mut(), nwid.0) as i32).unwrap_or(ResultCode::ErrorInternalNonFatal)
}
})
}
/// Access a network's associated network object.
/// This executes the supplied function or closure if we are joined to
/// this network, providing its associated network object as a parameter.
/// This happens while the internal data structure is locked, so do not
/// do anything time consuming while inside this function. The return value
/// (if any) of this function is returned, or None if we are not joined to
/// this network.
#[inline(always)]
pub fn with_network<F: FnOnce(&N) -> R, R>(&self, nwid: NetworkId, f: F) -> Option<R> {
self.intl.networks_by_id.lock().unwrap().get(&nwid.0).map_or(None, |nw| {
Some(f(&*nw))
})
}
/// Get the address of this node.
#[inline(always)]
pub fn address(&self) -> Address {
unsafe { Address(ztcore::ZT_Node_address(self.intl.capi) as u64) }
}
/// Handle a wire packet read from the physical network.
/// This is physical -> virtual.
#[inline(always)]
pub fn process_wire_packet(&self, clock: i64, ticks: i64, local_socket: i64, remote_address: &InetAddress, data: Buffer, next_task_deadline: &mut i64) -> ResultCode {
let intl = &*self.intl;
let rc = unsafe { ResultCode::from_i32(ztcore::ZT_Node_processWirePacket(intl.capi, clock, ticks, null_mut(), local_socket, remote_address.as_capi_ptr(), data.zt_core_buf as *const c_void, data.data_size as u32, 1, (next_task_deadline as *mut i64).cast()) as i32).unwrap_or(ResultCode::ErrorInternalNonFatal) };
std::mem::forget(data); // prevent Buffer from being returned to ZT core twice, see comment in drop() in buffer.rs
rc
}
/// Handle a packet sent via a virtual network interface such as a tun/tap device.
/// This is virtual -> physical.
#[inline(always)]
pub fn process_virtual_network_frame(&self, clock: i64, ticks: i64, nwid: &NetworkId, source_mac: &MAC, dest_mac: &MAC, ethertype: u16, vlan_id: u16, data: Buffer, next_task_deadline: &mut i64) -> ResultCode {
let intl = &*self.intl;
let rc = unsafe { ResultCode::from_i32(ztcore::ZT_Node_processVirtualNetworkFrame(intl.capi, clock, ticks, null_mut(), nwid.0, source_mac.0, dest_mac.0, ethertype as c_uint, vlan_id as c_uint, data.zt_core_buf as *const c_void, data.data_size as u32, 1, (next_task_deadline as *mut i64).cast()) as i32).unwrap_or(ResultCode::ErrorInternalNonFatal) };
std::mem::forget(data); // prevent Buffer from being returned to ZT core twice, see comment in drop() in buffer.rs
rc
}
/// Subscribe to a multicast group on a given network.
#[inline(always)]
pub fn multicast_subscribe(&self, clock: i64, ticks: i64, nwid: &NetworkId, multicast_group: &MAC, multicast_adi: u32) -> ResultCode {
unsafe { ResultCode::from_i32(ztcore::ZT_Node_multicastSubscribe(self.intl.capi, clock, ticks, null_mut(), nwid.0, multicast_group.0, multicast_adi as c_ulong) as i32).unwrap_or(ResultCode::ErrorInternalNonFatal) }
}
/// Unsubscribe from a multicast group on a given network.
#[inline(always)]
pub fn multicast_unsubscribe(&self, clock: i64, ticks: i64, nwid: &NetworkId, multicast_group: &MAC, multicast_adi: u32) -> ResultCode {
unsafe { ResultCode::from_i32(ztcore::ZT_Node_multicastUnsubscribe(self.intl.capi, clock, ticks, null_mut(), nwid.0, multicast_group.0, multicast_adi as c_ulong) as i32).unwrap_or(ResultCode::ErrorInternalNonFatal) }
}
/// Get this node's identity.
#[inline(always)]
pub fn identity(&self) -> &Identity {
let id = self.identity_wrapper.as_ref().unwrap();
unsafe { id.sync_type_and_address_with_capi() };
id
}
/// Get status information for this node.
pub fn status(&self, clock: i64, ticks: i64) -> NodeStatus {
let mut ns: MaybeUninit<ztcore::ZT_NodeStatus> = MaybeUninit::zeroed();
unsafe {
ztcore::ZT_Node_status(self.intl.capi, clock, ticks, null_mut(), ns.as_mut_ptr());
let ns = ns.assume_init();
if ns.identity.is_null() {
panic!("ZT_Node_status() returned null identity");
}
NodeStatus {
address: Address(ns.address),
identity: Identity::new_from_capi(&*ns.identity, false).clone(),
public_identity: cstr_to_string(ns.publicIdentity, -1),
secret_identity: cstr_to_string(ns.secretIdentity, -1),
online: ns.online != 0,
}
}
}
/// Get a list of all peers with which this node is communicating or has very recently communicated.
/// The presence of a peer on this list doesn't mean it has actual access to any network.
pub fn peers(&self, clock: i64, ticks: i64) -> Vec<Peer> {
let mut p: Vec<Peer> = Vec::new();
unsafe {
let pl = ztcore::ZT_Node_peers(self.intl.capi, clock, ticks, null_mut());
if !pl.is_null() {
let peer_count = (*pl).peerCount as usize;
p.reserve(peer_count);
for i in 0..peer_count as isize {
p.push(Peer::new_from_capi(&*(*pl).peers.offset(i)));
}
ztcore::ZT_freeQueryResult(pl as *const c_void);
}
}
p
}
/// Get the networks this node has joined.
pub fn networks(&self) -> Vec<VirtualNetworkConfig> {
let mut n: Vec<VirtualNetworkConfig> = Vec::new();
unsafe {
let nl = ztcore::ZT_Node_networks(self.intl.capi);
if !nl.is_null() {
let net_count = (*nl).networkCount as usize;
n.reserve(net_count);
for i in 0..net_count as isize {
n.push(VirtualNetworkConfig::new_from_capi(&*(*nl).networks.offset(i)));
}
ztcore::ZT_freeQueryResult(nl as *const c_void);
}
}
n
}
/// Get the certificates in this node's trust store and their local trust flags.
pub fn certificates(&self, clock: i64, ticks: i64) -> Vec<(Certificate, u32)> {
let mut c: Vec<(Certificate, u32)> = Vec::new();
unsafe {
let cl = ztcore::ZT_Node_listCertificates(self.intl.capi, clock, ticks, null_mut());
if !cl.is_null() {
let cert_count = (*cl).certCount as usize;
c.reserve(cert_count);
for i in 0..cert_count as isize {
c.push((Certificate::new_from_capi(&**(*cl).certs.offset(i)), *(*cl).localTrust.offset(i)));
}
ztcore::ZT_freeQueryResult(cl as *const c_void);
}
}
c
}
}
impl<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + Clone + 'static, H: NodeEventHandler<N>> Node<T, N, H> {
/// Get a copy of this network's associated object.
/// This is only available if N implements Clone.
pub fn network(&self, nwid: NetworkId) -> Option<N> {
self.intl.networks_by_id.lock().unwrap().get(&nwid.0).map_or(None, |nw| Some((**nw).clone()))
}
}
unsafe impl<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>> Sync for Node<T, N, H> {}
unsafe impl<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>> Send for Node<T, N, H> {}
impl<T: AsRef<H> + Sync + Send + Clone + 'static, N: Sync + Send + 'static, H: NodeEventHandler<N>> Drop for Node<T, N, H> {
fn drop(&mut self) {
unsafe {
ztcore::ZT_Node_delete(self.intl.capi, self.intl.recent_clock.get(), self.intl.recent_ticks.get(), null_mut());
}
}
}

View file

@ -1,41 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use serde::{Deserialize, Serialize};
use crate::Endpoint;
use crate::capi as ztcore;
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq)]
pub struct Path {
pub endpoint: Endpoint,
#[serde(rename = "lastSend")]
pub last_send: i64,
#[serde(rename = "lastReceive")]
pub last_receive: i64,
pub alive: bool,
pub preferred: bool
}
impl Path {
#[inline(always)]
pub(crate) fn new_from_capi(p: &ztcore::ZT_Path) -> Path {
Path{
endpoint: Endpoint::new_from_capi(&p.endpoint),
last_send: p.lastSend,
last_receive: p.lastReceive,
alive: p.alive != 0,
preferred: p.preferred != 0
}
}
}

View file

@ -1,85 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::cmp::Ordering;
use serde::{Deserialize, Serialize};
use crate::*;
use crate::capi as ztcore;
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Peer {
pub address: Address,
pub identity: Identity,
pub fingerprint: Fingerprint,
#[serde(rename = "versionMajor")]
pub version_major: i32,
#[serde(rename = "versionMinor")]
pub version_minor: i32,
#[serde(rename = "versionRev")]
pub version_rev: i32,
#[serde(rename = "versionProto")]
pub version_proto: i32,
pub latency: i32,
pub root: bool,
pub networks: Vec<NetworkId>,
pub paths: Vec<Path>,
pub locator: Option<Locator>
}
impl Peer {
pub(crate) fn new_from_capi(p: &ztcore::ZT_Peer) -> Peer {
unsafe {
let mut networks: Vec<NetworkId> = Vec::new();
for i in 0..(p.networkCount as isize) {
networks.push(NetworkId(*p.networks.offset(i)));
}
let mut paths: Vec<Path> = Vec::new();
for i in 0..(p.pathCount as isize) {
paths.push(Path::new_from_capi(&(*p.paths.offset(i))));
}
return Peer {
address: Address(p.address),
identity: Identity::new_from_capi(p.identity, false).clone(), // clone to get a copy independent of 'p'
fingerprint: Fingerprint::new_from_capi(&(*p.fingerprint)),
version_major: p.versionMajor as i32,
version_minor: p.versionMinor as i32,
version_rev: p.versionRev as i32,
version_proto: p.versionProto as i32,
latency: p.latency as i32,
root: p.root != 0,
networks,
paths,
locator: if p.locator.is_null() { None } else { Some(Locator::new_from_capi(p.locator, false).clone() )}
}
}
}
}
impl PartialOrd for Peer {
#[inline(always)]
fn partial_cmp(&self, p: &Self) -> Option<Ordering> {
Some(self.cmp(&p))
}
}
impl Ord for Peer {
fn cmp(&self, p: &Self) -> Ordering {
let c = self.address.cmp(&p.address);
if c == Ordering::Equal {
self.identity.cmp(&p.identity)
} else {
c
}
}
}

View file

@ -1,85 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
#[cfg(all(target_pointer_width = "32"))]
use std::sync::Mutex;
#[cfg(all(target_pointer_width = "64"))]
use std::sync::atomic::{AtomicI64, Ordering};
// This implements a basic atomic i64 that uses a mutex on 32-bit systems,
// since you can't atomically access something larger than word size in most
// cases.
#[cfg(all(target_pointer_width = "32"))]
pub struct PortableAtomicI64 {
i: Mutex<i64>
}
#[cfg(all(target_pointer_width = "32"))]
impl PortableAtomicI64 {
#[inline(always)]
pub fn new(v: i64) -> PortableAtomicI64 {
PortableAtomicI64{
i: Mutex::new(v)
}
}
#[inline(always)]
pub fn get(&self) -> i64 {
*self.i.lock().unwrap()
}
#[inline(always)]
pub fn set(&self, v: i64) {
*self.i.lock().unwrap() = v;
}
#[inline(always)]
pub fn fetch_add(&self, v: i64) -> i64 {
let i = self.i.lock().unwrap();
let j = *i;
*i += v;
j
}
}
#[cfg(all(target_pointer_width = "64"))]
pub struct PortableAtomicI64 {
i: AtomicI64
}
#[cfg(all(target_pointer_width = "64"))]
impl PortableAtomicI64 {
#[inline(always)]
pub fn new(v: i64) -> PortableAtomicI64 {
PortableAtomicI64{
i: AtomicI64::new(v)
}
}
#[inline(always)]
pub fn get(&self) -> i64 {
self.i.load(Ordering::Relaxed)
}
#[inline(always)]
pub fn set(&self, v: i64) {
self.i.store(v, Ordering::Relaxed)
}
#[inline(always)]
pub fn fetch_add(&self, v: i64) -> i64 {
self.i.fetch_add(v, Ordering::Relaxed)
}
}

View file

@ -1,542 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::str;
use crate::{capi as ztcore, Dictionary, Endpoint, Fingerprint, IpScope, MAC, Address, CredentialType};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
#[derive(FromPrimitive, PartialEq, Eq)]
pub enum TracePacketDropReason {
Unspecified = ztcore::ZT_TracePacketDropReason_ZT_TRACE_PACKET_DROP_REASON_UNSPECIFIED as isize,
PeerTooOld = ztcore::ZT_TracePacketDropReason_ZT_TRACE_PACKET_DROP_REASON_PEER_TOO_OLD as isize,
MalformedPacket = ztcore::ZT_TracePacketDropReason_ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET as isize,
MacFailed = ztcore::ZT_TracePacketDropReason_ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED as isize,
RateLimitExceeded = ztcore::ZT_TracePacketDropReason_ZT_TRACE_PACKET_DROP_REASON_RATE_LIMIT_EXCEEDED as isize,
InvalidObject = ztcore::ZT_TracePacketDropReason_ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT as isize,
InvalidCompressedData = ztcore::ZT_TracePacketDropReason_ZT_TRACE_PACKET_DROP_REASON_INVALID_COMPRESSED_DATA as isize,
UnrecognizedVerb = ztcore::ZT_TracePacketDropReason_ZT_TRACE_PACKET_DROP_REASON_UNRECOGNIZED_VERB as isize,
ReplyNotExpected = ztcore::ZT_TracePacketDropReason_ZT_TRACE_PACKET_DROP_REASON_REPLY_NOT_EXPECTED as isize,
}
impl TracePacketDropReason {
pub fn to_str(&self) -> &'static str {
match *self {
TracePacketDropReason::Unspecified => "Unspecified",
TracePacketDropReason::PeerTooOld => "PeerTooOld",
TracePacketDropReason::MalformedPacket => "MalformedPacket",
TracePacketDropReason::MacFailed => "MacFailed",
TracePacketDropReason::RateLimitExceeded => "RateLimitExceeded",
TracePacketDropReason::InvalidObject => "InvalidObject",
TracePacketDropReason::InvalidCompressedData => "InvalidCompressedData",
TracePacketDropReason::UnrecognizedVerb => "UnrecognizedVerb",
TracePacketDropReason::ReplyNotExpected => "ReplyNotExpected",
}
}
}
#[derive(FromPrimitive, PartialEq, Eq)]
pub enum TraceFrameDropReason {
Unspecified = ztcore::ZT_TraceFrameDropReason_ZT_TRACE_FRAME_DROP_REASON_UNSPECIFIED as isize,
BridgingNotAllowedRemote = ztcore::ZT_TraceFrameDropReason_ZT_TRACE_FRAME_DROP_REASON_BRIDGING_NOT_ALLOWED_REMOTE as isize,
BridgingNotAllowedLocal = ztcore::ZT_TraceFrameDropReason_ZT_TRACE_FRAME_DROP_REASON_BRIDGING_NOT_ALLOWED_LOCAL as isize,
MulticastDisabled = ztcore::ZT_TraceFrameDropReason_ZT_TRACE_FRAME_DROP_REASON_MULTICAST_DISABLED as isize,
BroadcastDisabled = ztcore::ZT_TraceFrameDropReason_ZT_TRACE_FRAME_DROP_REASON_BROADCAST_DISABLED as isize,
FilterBlocked = ztcore::ZT_TraceFrameDropReason_ZT_TRACE_FRAME_DROP_REASON_FILTER_BLOCKED as isize,
FilterBlockedAtBridgeReplication = ztcore::ZT_TraceFrameDropReason_ZT_TRACE_FRAME_DROP_REASON_FILTER_BLOCKED_AT_BRIDGE_REPLICATION as isize,
PermissionDenied = ztcore::ZT_TraceFrameDropReason_ZT_TRACE_FRAME_DROP_REASON_PERMISSION_DENIED as isize,
}
impl TraceFrameDropReason {
pub fn to_str(&self) -> &'static str {
match *self {
TraceFrameDropReason::Unspecified => "Unspecified",
TraceFrameDropReason::BridgingNotAllowedRemote => "BridgingNotAllowedRemote",
TraceFrameDropReason::BridgingNotAllowedLocal => "BridgingNotAllowedLocal",
TraceFrameDropReason::MulticastDisabled => "MulticastDisabled",
TraceFrameDropReason::BroadcastDisabled => "BroadcastDisabled",
TraceFrameDropReason::FilterBlocked => "FilterBlocked",
TraceFrameDropReason::FilterBlockedAtBridgeReplication => "FilterBlockedAtBridgeReplication",
TraceFrameDropReason::PermissionDenied => "PermissionDenied",
}
}
}
#[derive(FromPrimitive, PartialEq, Eq)]
pub enum TraceCredentialRejectionReason {
SignatureVerificationFailed = ztcore::ZT_TraceCredentialRejectionReason_ZT_TRACE_CREDENTIAL_REJECTION_REASON_SIGNATURE_VERIFICATION_FAILED as isize,
Revoked = ztcore::ZT_TraceCredentialRejectionReason_ZT_TRACE_CREDENTIAL_REJECTION_REASON_REVOKED as isize,
OlderThanLatest = ztcore::ZT_TraceCredentialRejectionReason_ZT_TRACE_CREDENTIAL_REJECTION_REASON_OLDER_THAN_LATEST as isize,
Invalid = ztcore::ZT_TraceCredentialRejectionReason_ZT_TRACE_CREDENTIAL_REJECTION_REASON_INVALID as isize,
}
impl TraceCredentialRejectionReason {
pub fn to_str(&self) -> &'static str {
match *self {
TraceCredentialRejectionReason::SignatureVerificationFailed => "SignatureVerificationFailed",
TraceCredentialRejectionReason::Revoked => "Revoked",
TraceCredentialRejectionReason::OlderThanLatest => "OlderThanLatest",
TraceCredentialRejectionReason::Invalid => "Invalid",
}
}
}
#[derive(PartialEq, Eq)]
pub enum TraceFilterResult {
Reject,
Accept,
SuperAccept,
}
impl TraceFilterResult {
pub fn to_str(&self) -> &'static str {
match *self {
TraceFilterResult::Reject => "Reject",
TraceFilterResult::Accept => "Accept",
TraceFilterResult::SuperAccept => "SuperAccept",
}
}
}
#[derive(PartialEq, Eq)]
pub enum TraceEvent {
UnexpectedError {
code_location: u32,
message: String,
},
ResetingPathsInScope {
code_location: u32,
reporter: Option<Fingerprint>,
reporter_endpoint: Option<Endpoint>,
my_old_external: Option<Endpoint>,
my_new_external: Option<Endpoint>,
scope: IpScope,
},
TryingNewPath {
code_location: u32,
trying_peer: Fingerprint,
trying_endpoint: Endpoint,
trigger_peer: Option<Fingerprint>,
trigger_packet_from: Option<Endpoint>,
trigger_packet_id: u64,
trigger_packet_verb: i32,
},
LearnedNewPath {
code_location: u32,
learned_from_packet_id: u64,
peer: Fingerprint,
new_endpoint: Option<Endpoint>,
old_endpoint: Option<Endpoint>,
},
IncomingPacketDropped {
code_location: u32,
packet_id: u64,
network_id: u64,
peer: Option<Fingerprint>,
peer_endpoint: Option<Endpoint>,
hops: i32,
verb: i32,
reason: TracePacketDropReason,
},
OutgoingFrameDropped {
code_location: u32,
network_id: u64,
source_mac: MAC,
dest_mac: MAC,
ethertype: u16,
frame_length: u32,
frame_data: Vec<u8>,
reason: TraceFrameDropReason,
},
IncomingFrameDropped {
code_location: u32,
network_id: u64,
source_mac: MAC,
dest_mac: MAC,
ethertype: u16,
peer: Fingerprint,
peer_endpoint: Option<Endpoint>,
hops: i32,
verb: i32,
frame_length: u32,
frame_data: Vec<u8>,
credential_request_sent: bool,
reason: TraceFrameDropReason,
},
NetworkConfigRequested {
code_location: u32,
network_id: u64,
},
NetworkFilter {
code_location: u32,
network_id: u64,
primary_rule_set_log: Vec<u8>,
matching_capability_rule_set_log: Vec<u8>,
matching_capability_id: u32,
matching_capability_timestamp: i64,
source_address: Address,
dest_address: Address,
source_mac: MAC,
dest_mac: MAC,
frame_length: u32,
frame_data: Vec<u8>,
ethertype: u16,
vlan_id: u16,
flag_notee: bool,
inbound: bool,
result: TraceFilterResult,
},
NetworkCredentialRejected {
code_location: u32,
network_id: u64,
from_peer: Fingerprint,
credential_id: u32,
credential_timestamp: i64,
credential_type: CredentialType,
reason: TraceCredentialRejectionReason,
},
}
fn trace_to_string_optional<T: ToString>(x: &Option<T>) -> String {
x.as_ref().map_or_else(|| { "<unknown>".to_string() }, |xx| { xx.to_string() })
}
fn trace_peer_address_to_string_optional(p: &Option<Fingerprint>) -> String {
p.as_ref().map_or_else(|| { "<unknown>".to_string() }, |pp| { pp.address.to_string() })
}
impl ToString for TraceEvent {
fn to_string(&self) -> String {
match self {
TraceEvent::UnexpectedError { code_location, message } => {
format!("UnexpectedError: {} ({:0>8x})", message, code_location)
}
TraceEvent::ResetingPathsInScope { code_location, reporter, reporter_endpoint, my_old_external, my_new_external, scope } => {
format!(
"VL1 ResettingPathsInScope: resetting scope {} because {}@{} reported that my address changed from {} to {} ({:0>8x})",
scope.to_str(),
trace_peer_address_to_string_optional(reporter),
trace_to_string_optional(reporter_endpoint),
trace_to_string_optional(my_old_external),
trace_to_string_optional(my_new_external),
code_location,
)
}
TraceEvent::TryingNewPath { code_location, trying_peer, trying_endpoint, trigger_peer, trigger_packet_from, trigger_packet_id, .. } => {
format!(
"VL1 TryingNewPath: trying {}@{} triggered by packet {:0>16x} from {}@{} ({:0>8x})",
trying_peer.address.to_string(),
trying_endpoint.to_string(),
trigger_packet_id,
trace_peer_address_to_string_optional(trigger_peer),
trace_to_string_optional(trigger_packet_from),
code_location,
)
}
TraceEvent::LearnedNewPath { code_location, learned_from_packet_id, peer, old_endpoint, new_endpoint } => {
format!(
"VL1 LearnedNewPath: {} is now at {}, was at {}, learned from packet {:0>16x} ({:0>8x})",
peer.address.to_string(),
trace_to_string_optional(new_endpoint),
trace_to_string_optional(old_endpoint),
learned_from_packet_id,
code_location,
)
}
TraceEvent::IncomingPacketDropped { code_location, packet_id, peer, peer_endpoint, hops, verb, reason, .. } => {
format!(
"VL1 IncomingPacketDropped: packet {:0>16x} from {}@{} (hops: {}, verb: {}) dropped: {} ({:0>8x})",
packet_id,
trace_peer_address_to_string_optional(peer),
trace_to_string_optional(peer_endpoint),
hops,
verb,
reason.to_str(),
code_location,
)
}
TraceEvent::OutgoingFrameDropped { code_location, network_id, source_mac, dest_mac, ethertype, frame_length, reason, .. } => {
format!(
"VL2 OutgoingFrameDropped: network {:0>16x} {} -> {} ethertype {:0>4x} length {} dropped: {} ({:0>8x})",
network_id,
source_mac.to_string(),
dest_mac.to_string(),
ethertype,
frame_length,
reason.to_str(),
code_location,
)
}
TraceEvent::IncomingFrameDropped { code_location, network_id, source_mac, dest_mac, ethertype, peer, peer_endpoint, frame_length, reason, .. } => {
format!(
"VL2 IncomingFrameDropped: network {:0>16x} {} -> {} ethertype {:0>4x} length {} from {}@{} dropped: {} ({:0>8x})",
network_id,
source_mac.to_string(),
dest_mac.to_string(),
ethertype,
frame_length,
peer.address.to_string(),
trace_to_string_optional(peer_endpoint),
reason.to_str(),
code_location,
)
}
TraceEvent::NetworkConfigRequested { code_location, network_id } => {
format!(
"VL2 NetworkConfigRequested: {:0>16x} ({:0>8x})", network_id, code_location)
}
TraceEvent::NetworkFilter { code_location, network_id, source_address, dest_address, source_mac, dest_mac, frame_length, ethertype, inbound, result, .. } => {
format!(
"VL2 NetworkFilter: network {:0>16x} {}: {} via {} -> {} via {} length {} ethertype {:0>4x} result {} ({:0>8x})",
network_id,
if *inbound { "IN" } else { "OUT" },
source_mac.to_string(),
source_address.to_string(),
dest_mac.to_string(),
dest_address.to_string(),
frame_length,
ethertype,
result.to_str(),
code_location,
)
}
TraceEvent::NetworkCredentialRejected { code_location, network_id, from_peer, credential_id, credential_timestamp, credential_type, reason } => {
format!(
"VL2 NetworkCredentialRejected: network {:0>16x} from {} id {:0>8x} timestamp {} type {}: {} ({:0>8x})",
network_id,
from_peer.address.to_string(),
credential_id,
credential_timestamp,
credential_type.to_str(),
reason.to_str(),
code_location,
)
}
}
}
}
fn trace_optional_endpoint(bytes: Option<&Vec<u8>>) -> Option<Endpoint> {
bytes.map_or(None, |ep| {
Endpoint::new_from_bytes(ep.as_slice()).map_or(None, |ep| {
Some(ep)
})
})
}
fn trace_optional_fingerprint(bytes: Option<&Vec<u8>>) -> Option<Fingerprint> {
bytes.map_or(None, |fp| {
Fingerprint::new_from_bytes(fp).map_or(None, |fp| {
Some(fp)
})
})
}
/// What layer or log level does this trace event belong to?
pub enum TraceEventLayer {
VL1,
VL2,
VL2Filter,
VL2Multicast,
Other,
}
impl TraceEvent {
#[inline(always)]
pub fn layer(&self) -> TraceEventLayer {
match *self {
TraceEvent::UnexpectedError {..} => TraceEventLayer::Other,
TraceEvent::ResetingPathsInScope {..} => TraceEventLayer::VL1,
TraceEvent::TryingNewPath {..} => TraceEventLayer::VL1,
TraceEvent::LearnedNewPath {..} => TraceEventLayer::VL1,
TraceEvent::IncomingPacketDropped {..} => TraceEventLayer::VL1,
TraceEvent::OutgoingFrameDropped {..} => TraceEventLayer::VL2,
TraceEvent::IncomingFrameDropped {..} => TraceEventLayer::VL2,
TraceEvent::NetworkConfigRequested {..} => TraceEventLayer::VL2,
TraceEvent::NetworkFilter {..} => TraceEventLayer::VL2Filter,
TraceEvent::NetworkCredentialRejected {..} => TraceEventLayer::VL2,
}
}
/// Decode a trace event packaged in a dictionary and return a TraceEvent if it is valid.
pub fn parse_message(msg: &Dictionary) -> Option<TraceEvent> {
msg.get_ui(ztcore::ZT_TRACE_FIELD_TYPE).map_or(None, |mt: u64| -> Option<TraceEvent> {
let cl = msg.get_ui(ztcore::ZT_TRACE_FIELD_CODE_LOCATION).unwrap_or(0) as u32;
match mt as ztcore::ZT_TraceEventType {
ztcore::ZT_TraceEventType_ZT_TRACE_VL1_RESETTING_PATHS_IN_SCOPE => {
Some(TraceEvent::ResetingPathsInScope {
code_location: cl,
reporter: trace_optional_fingerprint(msg.get(ztcore::ZT_TRACE_FIELD_IDENTITY_FINGERPRINT)),
reporter_endpoint: trace_optional_endpoint(msg.get(ztcore::ZT_TRACE_FIELD_TRIGGER_FROM_ENDPOINT)),
my_old_external: trace_optional_endpoint(msg.get(ztcore::ZT_TRACE_FIELD_OLD_ENDPOINT)),
my_new_external: trace_optional_endpoint(msg.get(ztcore::ZT_TRACE_FIELD_NEW_ENDPOINT)),
scope: IpScope::from_i32(msg.get_ui(ztcore::ZT_TRACE_FIELD_RESET_ADDRESS_SCOPE).unwrap_or(0) as i32).unwrap_or(IpScope::None),
})
},
ztcore::ZT_TraceEventType_ZT_TRACE_VL1_TRYING_NEW_PATH => {
let tf = msg.get(ztcore::ZT_TRACE_FIELD_IDENTITY_FINGERPRINT);
let ep = msg.get(ztcore::ZT_TRACE_FIELD_ENDPOINT);
if tf.is_some() && ep.is_some() {
let tf = Fingerprint::new_from_bytes(tf.unwrap().as_slice()).ok();
let ep = Endpoint::new_from_bytes(ep.unwrap().as_slice()).ok();
if tf.is_some() && ep.is_some() {
return Some(TraceEvent::TryingNewPath {
code_location: cl,
trying_peer: tf.unwrap(),
trying_endpoint: ep.unwrap(),
trigger_peer: trace_optional_fingerprint(msg.get(ztcore::ZT_TRACE_FIELD_TRIGGER_FROM_PEER_FINGERPRINT)),
trigger_packet_from: trace_optional_endpoint(msg.get(ztcore::ZT_TRACE_FIELD_TRIGGER_FROM_ENDPOINT)),
trigger_packet_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_TRIGGER_FROM_PACKET_ID).unwrap_or(0),
trigger_packet_verb: msg.get_ui(ztcore::ZT_TRACE_FIELD_TRIGGER_FROM_PACKET_VERB).unwrap_or(0) as i32,
});
}
}
None
},
ztcore::ZT_TraceEventType_ZT_TRACE_VL1_LEARNED_NEW_PATH => {
let fp = msg.get(ztcore::ZT_TRACE_FIELD_IDENTITY_FINGERPRINT);
if fp.is_some() {
let fp = Fingerprint::new_from_bytes(fp.unwrap().as_slice()).ok();
if fp.is_some() {
return Some(TraceEvent::LearnedNewPath {
code_location: cl,
learned_from_packet_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_PACKET_ID).unwrap_or(0),
peer: fp.unwrap(),
new_endpoint: trace_optional_endpoint(msg.get(ztcore::ZT_TRACE_FIELD_ENDPOINT)),
old_endpoint: trace_optional_endpoint(msg.get(ztcore::ZT_TRACE_FIELD_OLD_ENDPOINT)),
});
}
}
None
},
ztcore::ZT_TraceEventType_ZT_TRACE_VL1_INCOMING_PACKET_DROPPED => {
Some(TraceEvent::IncomingPacketDropped {
code_location: cl,
packet_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_PACKET_ID).unwrap_or(0),
network_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_NETWORK_ID).unwrap_or(0),
peer: trace_optional_fingerprint(msg.get(ztcore::ZT_TRACE_FIELD_IDENTITY_FINGERPRINT)),
peer_endpoint: trace_optional_endpoint(msg.get(ztcore::ZT_TRACE_FIELD_ENDPOINT)),
hops: msg.get_ui(ztcore::ZT_TRACE_FIELD_PACKET_HOPS).unwrap_or(0) as i32,
verb: msg.get_ui(ztcore::ZT_TRACE_FIELD_PACKET_VERB).unwrap_or(0) as i32,
reason: TracePacketDropReason::from_i32(msg.get_ui(ztcore::ZT_TRACE_FIELD_REASON).unwrap_or(0) as i32).unwrap_or(TracePacketDropReason::Unspecified),
})
},
ztcore::ZT_TraceEventType_ZT_TRACE_VL2_OUTGOING_FRAME_DROPPED => {
Some(TraceEvent::OutgoingFrameDropped {
code_location: cl,
network_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_NETWORK_ID).unwrap_or(0),
source_mac: MAC(msg.get_ui(ztcore::ZT_TRACE_FIELD_SOURCE_MAC).unwrap_or(0)),
dest_mac: MAC(msg.get_ui(ztcore::ZT_TRACE_FIELD_DEST_MAC).unwrap_or(0)),
ethertype: msg.get_ui(ztcore::ZT_TRACE_FIELD_ETHERTYPE).unwrap_or(0) as u16,
frame_length: msg.get_ui(ztcore::ZT_TRACE_FIELD_FRAME_LENGTH).unwrap_or(0) as u32,
frame_data: msg.get(ztcore::ZT_TRACE_FIELD_FRAME_DATA).map_or_else(|| -> Vec<u8> {
Vec::new()
}, |d: &Vec<u8>| -> Vec<u8> {
d.clone()
}),
reason: TraceFrameDropReason::from_i32(msg.get_ui(ztcore::ZT_TRACE_FIELD_REASON).unwrap_or(0) as i32).unwrap_or(TraceFrameDropReason::Unspecified),
})
},
ztcore::ZT_TraceEventType_ZT_TRACE_VL2_INCOMING_FRAME_DROPPED => {
let fp = msg.get(ztcore::ZT_TRACE_FIELD_IDENTITY_FINGERPRINT);
if fp.is_some() {
let fp = Fingerprint::new_from_bytes(fp.unwrap().as_slice()).ok();
if fp.is_some() {
return Some(TraceEvent::IncomingFrameDropped {
code_location: cl,
network_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_NETWORK_ID).unwrap_or(0),
source_mac: MAC(msg.get_ui(ztcore::ZT_TRACE_FIELD_SOURCE_MAC).unwrap_or(0)),
dest_mac: MAC(msg.get_ui(ztcore::ZT_TRACE_FIELD_DEST_MAC).unwrap_or(0)),
ethertype: msg.get_ui(ztcore::ZT_TRACE_FIELD_ETHERTYPE).unwrap_or(0) as u16,
peer: fp.unwrap(),
peer_endpoint: trace_optional_endpoint(msg.get(ztcore::ZT_TRACE_FIELD_ENDPOINT)),
hops: msg.get_ui(ztcore::ZT_TRACE_FIELD_PACKET_HOPS).unwrap_or(0) as i32,
verb: msg.get_ui(ztcore::ZT_TRACE_FIELD_PACKET_VERB).unwrap_or(0) as i32,
frame_length: msg.get_ui(ztcore::ZT_TRACE_FIELD_FRAME_LENGTH).unwrap_or(0) as u32,
frame_data: msg.get_or_empty(ztcore::ZT_TRACE_FIELD_FRAME_DATA),
credential_request_sent: msg.get_ui(ztcore::ZT_TRACE_FIELD_FLAG_CREDENTIAL_REQUEST_SENT).unwrap_or(0) != 0,
reason: TraceFrameDropReason::from_i32(msg.get_ui(ztcore::ZT_TRACE_FIELD_REASON).unwrap_or(0) as i32).unwrap_or(TraceFrameDropReason::Unspecified),
});
}
}
None
},
ztcore::ZT_TraceEventType_ZT_TRACE_VL2_NETWORK_CONFIG_REQUESTED => {
Some(TraceEvent::NetworkConfigRequested {
code_location: cl,
network_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_NETWORK_ID).unwrap_or(0),
})
},
ztcore::ZT_TraceEventType_ZT_TRACE_VL2_NETWORK_FILTER => {
let verdict_int = msg.get(ztcore::ZT_TRACE_FIELD_RULE_FLAG_ACCEPT).map_or_else(|| -> i32 { 0 as i32 }, |a| -> i32 { i32::from_str_radix(str::from_utf8(a).unwrap_or("0"), 16).unwrap_or(0) });
let mut verdict = TraceFilterResult::Reject;
if verdict_int == 1 {
verdict = TraceFilterResult::Accept;
} else if verdict_int > 1 {
verdict = TraceFilterResult::SuperAccept;
}
Some(TraceEvent::NetworkFilter {
code_location: cl,
network_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_NETWORK_ID).unwrap_or(0),
primary_rule_set_log: msg.get_or_empty(ztcore::ZT_TRACE_FIELD_PRIMARY_RULE_SET_LOG),
matching_capability_rule_set_log: msg.get_or_empty(ztcore::ZT_TRACE_FIELD_MATCHING_CAPABILITY_RULE_SET_LOG),
matching_capability_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_MATCHING_CAPABILITY_ID).unwrap_or(0) as u32,
matching_capability_timestamp: msg.get_ui(ztcore::ZT_TRACE_FIELD_MATCHING_CAPABILITY_TIMESTAMP).unwrap_or(0) as i64,
source_address: Address(msg.get_ui(ztcore::ZT_TRACE_FIELD_SOURCE_ZT_ADDRESS).unwrap_or(0)),
dest_address: Address(msg.get_ui(ztcore::ZT_TRACE_FIELD_DEST_ZT_ADDRESS).unwrap_or(0)),
source_mac: MAC(msg.get_ui(ztcore::ZT_TRACE_FIELD_SOURCE_MAC).unwrap_or(0)),
dest_mac: MAC(msg.get_ui(ztcore::ZT_TRACE_FIELD_DEST_MAC).unwrap_or(0)),
frame_length: msg.get_ui(ztcore::ZT_TRACE_FIELD_FRAME_LENGTH).unwrap_or(0) as u32,
frame_data: msg.get_or_empty(ztcore::ZT_TRACE_FIELD_FRAME_DATA),
ethertype: msg.get_ui(ztcore::ZT_TRACE_FIELD_ETHERTYPE).unwrap_or(0) as u16,
vlan_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_VLAN_ID).unwrap_or(0) as u16,
flag_notee: msg.get_ui(ztcore::ZT_TRACE_FIELD_RULE_FLAG_NOTEE).unwrap_or(0) != 0,
inbound: msg.get_ui(ztcore::ZT_TRACE_FIELD_RULE_FLAG_INBOUND).unwrap_or(0) != 0,
result: verdict,
})
},
ztcore::ZT_TraceEventType_ZT_TRACE_VL2_NETWORK_CREDENTIAL_REJECTED => {
let fp = msg.get(ztcore::ZT_TRACE_FIELD_IDENTITY_FINGERPRINT);
if fp.is_some() {
let fp = Fingerprint::new_from_bytes(fp.unwrap().as_slice()).ok();
if fp.is_some() {
return Some(TraceEvent::NetworkCredentialRejected {
code_location: cl,
network_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_NETWORK_ID).unwrap_or(0),
from_peer: fp.unwrap(),
credential_id: msg.get_ui(ztcore::ZT_TRACE_FIELD_CREDENTIAL_ID).unwrap_or(0) as u32,
credential_timestamp: msg.get_ui(ztcore::ZT_TRACE_FIELD_CREDENTIAL_TIMESTAMP).unwrap_or(0) as i64,
credential_type: CredentialType::from_i32(msg.get_ui(ztcore::ZT_TRACE_FIELD_CREDENTIAL_TYPE).unwrap_or(0) as i32).unwrap_or(CredentialType::Null),
reason: TraceCredentialRejectionReason::from_i32(msg.get_ui(ztcore::ZT_TRACE_FIELD_REASON).unwrap_or(0) as i32).unwrap_or(TraceCredentialRejectionReason::Invalid),
});
}
}
None
},
_ => { // ztcore::ZT_TraceEventType_ZT_TRACE_UNEXPECTED_ERROR
Some(TraceEvent::UnexpectedError {
code_location: cl,
message: msg.get_str(ztcore::ZT_TRACE_FIELD_MESSAGE).map_or_else(|| {
format!("WARNING: unknown trace message type {}, this version may be too old!", mt)
}, |m| {
m.to_string()
}),
})
},
}
})
}
}

View file

@ -1,267 +0,0 @@
/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use serde::{Deserialize, Serialize};
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use crate::*;
use crate::capi as ztcore;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(FromPrimitive,ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum VirtualNetworkType {
Private = ztcore::ZT_VirtualNetworkType_ZT_NETWORK_TYPE_PRIVATE as isize,
Public = ztcore::ZT_VirtualNetworkType_ZT_NETWORK_TYPE_PUBLIC as isize
}
impl VirtualNetworkType {
pub fn to_str(&self) -> &str {
match *self {
//VirtualNetworkType::Private => "PRIVATE",
VirtualNetworkType::Public => "PUBLIC",
_ => "PRIVATE"
}
}
}
impl From<&str> for VirtualNetworkType {
fn from(s: &str) -> VirtualNetworkType {
match s.to_ascii_lowercase().as_str() {
//"requesting_configuration" | "requestingconfiguration" => VirtualNetworkStatus::RequestingConfiguration,
"public" => VirtualNetworkType::Public,
_ => VirtualNetworkType::Private
}
}
}
impl ToString for VirtualNetworkType {
#[inline(always)]
fn to_string(&self) -> String {
self.to_str().to_owned()
}
}
impl serde::Serialize for VirtualNetworkType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
serializer.serialize_str(self.to_str())
}
}
struct VirtualNetworkTypeVisitor;
impl<'de> serde::de::Visitor<'de> for VirtualNetworkTypeVisitor {
type Value = VirtualNetworkType;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("VirtualNetworkType value in string form")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error {
Ok(VirtualNetworkType::from(s))
}
}
impl<'de> serde::Deserialize<'de> for VirtualNetworkType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
deserializer.deserialize_str(VirtualNetworkTypeVisitor)
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(FromPrimitive,ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum VirtualNetworkRuleType {
ActionDrop = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_ACTION_DROP as isize,
ActionAccept = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_ACTION_ACCEPT as isize,
ActionTee = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_ACTION_TEE as isize,
ActionWatch = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_ACTION_WATCH as isize,
ActionRedirect = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_ACTION_REDIRECT as isize,
ActionBreak = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_ACTION_BREAK as isize,
ActionPriority = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_ACTION_PRIORITY as isize,
MatchSourceZeroTierAddress = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS as isize,
MatchDestinationZeroTierAddress = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS as isize,
MatchVlanId = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_VLAN_ID as isize,
MatchVlanPcp = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_VLAN_PCP as isize,
MatchVlanDei = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_VLAN_DEI as isize,
MatchMacSource = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_MAC_SOURCE as isize,
MatchMacDestination = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_MAC_DEST as isize,
MatchIpv4Source = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_IPV4_SOURCE as isize,
MatchIpv4Destination = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_IPV4_DEST as isize,
MatchIpv6Source = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_IPV6_SOURCE as isize,
MatchIpv6Destination = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_IPV6_DEST as isize,
MatchIpTos = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_IP_TOS as isize,
MatchIpProtocol = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_IP_PROTOCOL as isize,
MatchEtherType = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_ETHERTYPE as isize,
MatchIcmp = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_ICMP as isize,
MatchIpSourcePortRange = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE as isize,
MatchIpDestinationSourceRange = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE as isize,
MatchCharacteristics = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_CHARACTERISTICS as isize,
MatchFrameSizeRange = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE as isize,
MatchRandom = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_RANDOM as isize,
MatchTagsDifference = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE as isize,
MatchTagsBitwiseAnd = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND as isize,
MatchTagsBitwiseOr = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR as isize,
MatchTagsBitwiseXor = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR as isize,
MatchTagsEqual = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_TAGS_EQUAL as isize,
MatchTagSender = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_TAG_SENDER as isize,
MatchTagReceiver = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_TAG_RECEIVER as isize,
MatchIntegerRange = ztcore::ZT_VirtualNetworkRuleType_ZT_NETWORK_RULE_MATCH_INTEGER_RANGE as isize
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(FromPrimitive,ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum VirtualNetworkConfigOperation {
Up = ztcore::ZT_VirtualNetworkConfigOperation_ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP as isize,
ConfigUpdate = ztcore::ZT_VirtualNetworkConfigOperation_ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE as isize,
Down = ztcore::ZT_VirtualNetworkConfigOperation_ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN as isize,
Destroy = ztcore::ZT_VirtualNetworkConfigOperation_ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY as isize
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(FromPrimitive,ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum VirtualNetworkStatus {
RequestingConfiguration = ztcore::ZT_VirtualNetworkStatus_ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION as isize,
Ok = ztcore::ZT_VirtualNetworkStatus_ZT_NETWORK_STATUS_OK as isize,
AccessDenied = ztcore::ZT_VirtualNetworkStatus_ZT_NETWORK_STATUS_ACCESS_DENIED as isize,
NotFound = ztcore::ZT_VirtualNetworkStatus_ZT_NETWORK_STATUS_NOT_FOUND as isize
}
impl VirtualNetworkStatus {
pub fn to_str(&self) -> &str {
match *self {
VirtualNetworkStatus::RequestingConfiguration => "REQUESTING_CONFIGURATION",
VirtualNetworkStatus::Ok => "OK",
VirtualNetworkStatus::AccessDenied => "ACCESS_DENIED",
VirtualNetworkStatus::NotFound => "NOT_FOUND"
}
}
}
impl From<&str> for VirtualNetworkStatus {
fn from(s: &str) -> VirtualNetworkStatus {
match s.to_ascii_lowercase().as_str() {
//"requesting_configuration" | "requestingconfiguration" => VirtualNetworkStatus::RequestingConfiguration,
"ok" => VirtualNetworkStatus::Ok,
"access_denied" | "accessdenied" => VirtualNetworkStatus::AccessDenied,
"not_found" | "notfound" => VirtualNetworkStatus::NotFound,
_ => VirtualNetworkStatus::RequestingConfiguration
}
}
}
impl ToString for VirtualNetworkStatus {
#[inline(always)]
fn to_string(&self) -> String {
self.to_str().to_owned()
}
}
impl serde::Serialize for VirtualNetworkStatus {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
serializer.serialize_str(self.to_str())
}
}
struct VirtualNetworkStatusVisitor;
impl<'de> serde::de::Visitor<'de> for VirtualNetworkStatusVisitor {
type Value = VirtualNetworkStatus;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("VirtualNetworkStatus value in string form")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error {
Ok(VirtualNetworkStatus::from(s))
}
}
impl<'de> serde::Deserialize<'de> for VirtualNetworkStatus {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
deserializer.deserialize_str(VirtualNetworkStatusVisitor)
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct VirtualNetworkRoute {
pub target: Option<InetAddress>,
pub via: Option<InetAddress>,
pub flags: u16,
pub metric: u16
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct VirtualNetworkConfig {
pub nwid: NetworkId,
pub mac: MAC,
pub name: String,
pub status: VirtualNetworkStatus,
#[serde(rename = "type")]
pub type_: VirtualNetworkType,
pub mtu: u32,
pub bridge: bool,
#[serde(rename = "broadcastEnabled")]
pub broadcast_enabled: bool,
#[serde(rename = "netconfRevision")]
pub netconf_revision: u64,
#[serde(rename = "assignedAddresses")]
pub assigned_addresses: Vec<InetAddress>,
pub routes: Vec<VirtualNetworkRoute>
}
impl VirtualNetworkConfig {
pub(crate) fn new_from_capi(vnc: &ztcore::ZT_VirtualNetworkConfig) -> VirtualNetworkConfig {
let mut aa: Vec<InetAddress> = Vec::new();
let saptr = vnc.assignedAddresses.as_ptr();
for i in 0..vnc.assignedAddressCount as isize {
let a = InetAddress::new_from_capi(unsafe { &*saptr.offset(i) });
if a.is_some() {
aa.push(a.unwrap());
}
}
let mut rts: Vec<VirtualNetworkRoute> = Vec::new();
let rtptr = vnc.routes.as_ptr();
for i in 0..vnc.routeCount as isize {
let r = unsafe { *rtptr.offset(i) };
rts.push(VirtualNetworkRoute{
target: InetAddress::new_from_capi(&r.target),
via: InetAddress::new_from_capi(&r.via),
flags: r.flags,
metric: r.metric
})
}
return VirtualNetworkConfig{
nwid: NetworkId(vnc.nwid),
mac: MAC(vnc.mac),
name: unsafe { cstr_to_string(vnc.name.as_ptr(), vnc.name.len() as isize) },
status: FromPrimitive::from_i32(vnc.status as i32).unwrap_or(VirtualNetworkStatus::RequestingConfiguration),
type_: FromPrimitive::from_i32(vnc.type_ as i32).unwrap_or(VirtualNetworkType::Private),
mtu: vnc.mtu as u32,
bridge: vnc.bridge != 0,
broadcast_enabled: vnc.broadcastEnabled != 0,
netconf_revision: vnc.netconfRevision as u64,
assigned_addresses: aa,
routes: rts
}
}
}