mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-05-08 14:43:44 +02:00
OMG Rust links! On MacOS only so far.
This commit is contained in:
parent
f677a7408e
commit
36f9dd54d9
7 changed files with 114 additions and 35 deletions
|
@ -3,6 +3,7 @@ name = "rust-zerotier-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Adam Ierymenko <adam.ierymenko@zerotier.com>"]
|
authors = ["Adam Ierymenko <adam.ierymenko@zerotier.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
num-traits = "0.2.14"
|
num-traits = "0.2.14"
|
||||||
|
|
6
rust-zerotier-core/build.rs
Normal file
6
rust-zerotier-core/build.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
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");
|
||||||
|
println!("cargo:rustc-link-lib=c++");
|
||||||
|
}
|
|
@ -1,16 +1,21 @@
|
||||||
pub struct Address(pub u64);
|
pub struct Address(pub u64);
|
||||||
|
|
||||||
impl Address {
|
impl ToString for Address {
|
||||||
#[inline]
|
fn to_string(&self) -> String {
|
||||||
pub fn new_from_string(s: &str) -> Address {
|
format!("{:0>10x}", self.0)
|
||||||
return Address(u64::from_str_radix(s, 16).unwrap_or(0));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for Address {
|
impl From<u64> for Address {
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
fn to_string(&self) -> String {
|
fn from(i: u64) -> Address {
|
||||||
format!("{:0>10x}", self.0)
|
Address(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Address {
|
||||||
|
fn from(s: &str) -> Address {
|
||||||
|
Address(u64::from_str_radix(s, 16).unwrap_or(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +35,7 @@ impl<'de> serde::de::Visitor<'de> for AddressVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error {
|
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error {
|
||||||
Ok(Address::new_from_string(s))
|
Ok(Address::from(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
use std::os::raw::c_void;
|
use std::os::raw::c_void;
|
||||||
use std::ptr::null_mut;
|
use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut};
|
||||||
use std::slice::{from_raw_parts, from_raw_parts_mut};
|
|
||||||
|
|
||||||
use crate::bindings::capi as ztcore;
|
use crate::bindings::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.
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
pub(crate) zt_core_buf: *mut u8,
|
pub(crate) zt_core_buf: *mut u8,
|
||||||
pub(crate) data_size: u32
|
pub(crate) data_size: u32
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buffer {
|
impl Buffer {
|
||||||
pub const CAPACITY: u32 = ztcore::ZT_BUF_SIZE;
|
/// Maximum capacity of a ZeroTier reusable buffer.
|
||||||
|
pub const CAPACITY: u32 = ztcore::ZT_BUF_SIZE as u32;
|
||||||
|
|
||||||
|
/// 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)]
|
#[inline(always)]
|
||||||
pub fn new() -> Buffer {
|
pub fn new() -> Buffer {
|
||||||
let b = unsafe { ztcore::ZT_getBuffer() as *mut u8 };
|
let b = unsafe { ztcore::ZT_getBuffer() as *mut u8 };
|
||||||
|
@ -24,6 +31,8 @@ impl Buffer {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current size of the data held by this buffer. Initially this is
|
||||||
|
/// equal to CAPACITY.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn len(&self) -> u32 {
|
pub fn len(&self) -> u32 {
|
||||||
self.data_size
|
self.data_size
|
||||||
|
@ -36,10 +45,29 @@ impl Buffer {
|
||||||
pub unsafe fn set_len(&mut self, s: u32) {
|
pub unsafe fn set_len(&mut self, s: u32) {
|
||||||
self.data_size = s;
|
self.data_size = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a slice that points to this buffer's data. This is unsafe because
|
||||||
|
/// the returned slice will be invalid if set_len() has been called with a
|
||||||
|
/// value higher than CAPACITY or if this has been consumed by the ZeroTier
|
||||||
|
/// core. The latter case is handled automatically in node.rs though, so it
|
||||||
|
/// is not something you generally have to worry about.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
|
||||||
|
return &mut *slice_from_raw_parts_mut(self.zt_core_buf, self.data_size as usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a slice that points to this buffer's data. This is unsafe because
|
||||||
|
/// the returned slice will be invalid if set_len() has been called with a
|
||||||
|
/// value higher than CAPACITY or if this has been consumed by the ZeroTier
|
||||||
|
/// core. The latter case is handled automatically in node.rs though, so it
|
||||||
|
/// is not something you generally have to worry about.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn as_slice(&mut self) -> &[u8] {
|
||||||
|
return &*slice_from_raw_parts(self.zt_core_buf, self.data_size as usize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Buffer {
|
impl Drop for Buffer {
|
||||||
#[inline(always)]
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// NOTE: in node.rs std::mem::forget() is used to prevent this from
|
// NOTE: in node.rs std::mem::forget() is used to prevent this from
|
||||||
// being called on buffers that have been returned via one of the
|
// being called on buffers that have been returned via one of the
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::os::raw::{c_char, c_uint, c_void};
|
||||||
use std::ptr::{copy_nonoverlapping, null, null_mut};
|
use std::ptr::{copy_nonoverlapping, null, null_mut};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -173,7 +174,7 @@ pub struct CertificateSubjectUniqueIdSecret {
|
||||||
pub public: Vec<u8>,
|
pub public: Vec<u8>,
|
||||||
pub private: Vec<u8>,
|
pub private: Vec<u8>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub type_: CertificateUniqueIdType
|
pub type_: CertificateUniqueIdType,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CERTIFICATE_UNIQUE_ID_CREATE_BUF_SIZE: usize = 128;
|
const CERTIFICATE_UNIQUE_ID_CREATE_BUF_SIZE: usize = 128;
|
||||||
|
@ -236,6 +237,49 @@ impl ToString for CertificateError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&str> for CertificateError {
|
||||||
|
fn from(s: &str) -> CertificateError {
|
||||||
|
match s.to_ascii_lowercase().as_str() {
|
||||||
|
"havenewercert" => CertificateError::HaveNewerCert,
|
||||||
|
"invalidformat" => CertificateError::InvalidFormat,
|
||||||
|
"invalididentity" => CertificateError::InvalidIdentity,
|
||||||
|
"invalidprimarysignature" => CertificateError::InvalidPrimarySignature,
|
||||||
|
"invalidchain" => CertificateError::InvalidChain,
|
||||||
|
"invalidcomponentsignature" => CertificateError::InvalidComponentSignature,
|
||||||
|
"invaliduniqueidproof" => CertificateError::InvalidUniqueIdProof,
|
||||||
|
"missingrequiredfields" => CertificateError::MissingRequiredFields,
|
||||||
|
"outofvalidtimewindow" => CertificateError::OutOfValidTimeWindow,
|
||||||
|
_ => CertificateError::None // also "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("CertificateError value in string form")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -272,7 +316,7 @@ impl CertificateName {
|
||||||
postal_code: String::new(),
|
postal_code: String::new(),
|
||||||
email: String::new(),
|
email: String::new(),
|
||||||
url: String::new(),
|
url: String::new(),
|
||||||
host: String::new()
|
host: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +333,7 @@ impl CertificateName {
|
||||||
postal_code: cstr_to_string(cn.postalCode.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),
|
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),
|
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)
|
host: cstr_to_string(cn.host.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,7 +469,7 @@ impl CertificateSubject {
|
||||||
update_urls: Vec::new(),
|
update_urls: Vec::new(),
|
||||||
name: CertificateName::new(),
|
name: CertificateName::new(),
|
||||||
unique_id: Vec::new(),
|
unique_id: Vec::new(),
|
||||||
unique_id_proof_signature: Vec::new()
|
unique_id_proof_signature: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,7 +626,7 @@ pub struct Certificate {
|
||||||
pub extended_attributes: Vec<u8>,
|
pub extended_attributes: Vec<u8>,
|
||||||
#[serde(rename = "maxPathLength")]
|
#[serde(rename = "maxPathLength")]
|
||||||
pub max_path_length: u32,
|
pub max_path_length: u32,
|
||||||
pub signature: Vec<u8>
|
pub signature: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct CertificateCAPIContainer {
|
pub(crate) struct CertificateCAPIContainer {
|
||||||
|
@ -602,7 +646,7 @@ impl Certificate {
|
||||||
issuer_name: CertificateName::new(),
|
issuer_name: CertificateName::new(),
|
||||||
extended_attributes: Vec::new(),
|
extended_attributes: Vec::new(),
|
||||||
max_path_length: 0,
|
max_path_length: 0,
|
||||||
signature: Vec::new()
|
signature: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -712,4 +756,11 @@ mod tests {
|
||||||
let sn = CertificateSerialNo::from(&test[0..48]);
|
let sn = CertificateSerialNo::from(&test[0..48]);
|
||||||
assert!(test.eq(&sn.0));
|
assert!(test.eq(&sn.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_certificate_unique_id() {
|
||||||
|
let uid = CertificateSubjectUniqueIdSecret::new(CertificateUniqueIdType::NistP384);
|
||||||
|
println!("certificate unique ID public: {}", hex::encode(uid.public).as_str());
|
||||||
|
println!("certificate unique ID private: {}", hex::encode(uid.private).as_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#[link(name = ":../../build/core/libzt_core.a")]
|
|
||||||
|
|
||||||
use std::os::raw::{c_char, c_int};
|
use std::os::raw::{c_char, c_int};
|
||||||
use num_derive::{FromPrimitive, ToPrimitive};
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
|
|
||||||
|
@ -49,7 +47,7 @@ pub const DEFAULT_UDP_MTU: u32 = ztcore::ZT_DEFAULT_UDP_MTU;
|
||||||
pub const MAX_UDP_MTU: u32 = ztcore::ZT_MAX_UDP_MTU;
|
pub const MAX_UDP_MTU: u32 = ztcore::ZT_MAX_UDP_MTU;
|
||||||
|
|
||||||
#[allow(non_snake_case,non_upper_case_globals)]
|
#[allow(non_snake_case,non_upper_case_globals)]
|
||||||
pub mod RulePacketCharacteristics {
|
pub mod RulePacketCharacteristicFlags {
|
||||||
pub const Inbound: u64 = crate::bindings::capi::ZT_RULE_PACKET_CHARACTERISTICS_INBOUND as u64;
|
pub const Inbound: u64 = crate::bindings::capi::ZT_RULE_PACKET_CHARACTERISTICS_INBOUND as u64;
|
||||||
pub const Multicast: u64 = crate::bindings::capi::ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST as u64;
|
pub const Multicast: u64 = crate::bindings::capi::ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST as u64;
|
||||||
pub const Broadcast: u64 = crate::bindings::capi::ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST as u64;
|
pub const Broadcast: u64 = crate::bindings::capi::ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST as u64;
|
||||||
|
@ -183,7 +181,7 @@ pub unsafe fn cstr_to_string(cstr: *const c_char, max_len: isize) -> String {
|
||||||
if !cstr.is_null() {
|
if !cstr.is_null() {
|
||||||
let mut cstr_len: isize = 0;
|
let mut cstr_len: isize = 0;
|
||||||
while max_len < 0 || cstr_len < max_len {
|
while max_len < 0 || cstr_len < max_len {
|
||||||
if (*cstr.offset(cstr_len) == 0) {
|
if *cstr.offset(cstr_len) == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cstr_len += 1;
|
cstr_len += 1;
|
||||||
|
|
|
@ -56,7 +56,6 @@ macro_rules! node_from_raw_ptr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
extern "C" fn zt_virtual_network_config_function<T: NodeEventHandler + 'static>(
|
extern "C" fn zt_virtual_network_config_function<T: NodeEventHandler + 'static>(
|
||||||
capi: *mut ztcore::ZT_Node,
|
capi: *mut ztcore::ZT_Node,
|
||||||
uptr: *mut c_void,
|
uptr: *mut c_void,
|
||||||
|
@ -70,7 +69,6 @@ extern "C" fn zt_virtual_network_config_function<T: NodeEventHandler + 'static>(
|
||||||
n.event_handler.virtual_network_config();
|
n.event_handler.virtual_network_config();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
extern "C" fn zt_virtual_network_frame_function<T: NodeEventHandler + 'static>(
|
extern "C" fn zt_virtual_network_frame_function<T: NodeEventHandler + 'static>(
|
||||||
capi: *mut ztcore::ZT_Node,
|
capi: *mut ztcore::ZT_Node,
|
||||||
uptr: *mut c_void,
|
uptr: *mut c_void,
|
||||||
|
@ -88,7 +86,6 @@ extern "C" fn zt_virtual_network_frame_function<T: NodeEventHandler + 'static>(
|
||||||
n.event_handler.virtual_network_frame();
|
n.event_handler.virtual_network_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
extern "C" fn zt_event_callback<T: NodeEventHandler + 'static>(
|
extern "C" fn zt_event_callback<T: NodeEventHandler + 'static>(
|
||||||
capi: *mut ztcore::ZT_Node,
|
capi: *mut ztcore::ZT_Node,
|
||||||
uptr: *mut c_void,
|
uptr: *mut c_void,
|
||||||
|
@ -100,7 +97,6 @@ extern "C" fn zt_event_callback<T: NodeEventHandler + 'static>(
|
||||||
n.event_handler.event();
|
n.event_handler.event();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
extern "C" fn zt_state_put_function<T: NodeEventHandler + 'static>(
|
extern "C" fn zt_state_put_function<T: NodeEventHandler + 'static>(
|
||||||
capi: *mut ztcore::ZT_Node,
|
capi: *mut ztcore::ZT_Node,
|
||||||
uptr: *mut c_void,
|
uptr: *mut c_void,
|
||||||
|
@ -114,7 +110,6 @@ extern "C" fn zt_state_put_function<T: NodeEventHandler + 'static>(
|
||||||
n.event_handler.state_put();
|
n.event_handler.state_put();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
extern "C" fn zt_state_get_function<T: NodeEventHandler + 'static>(
|
extern "C" fn zt_state_get_function<T: NodeEventHandler + 'static>(
|
||||||
capi: *mut ztcore::ZT_Node,
|
capi: *mut ztcore::ZT_Node,
|
||||||
uptr: *mut c_void,
|
uptr: *mut c_void,
|
||||||
|
@ -128,7 +123,6 @@ extern "C" fn zt_state_get_function<T: NodeEventHandler + 'static>(
|
||||||
n.event_handler.state_get();
|
n.event_handler.state_get();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
extern "C" fn zt_wire_packet_send_function<T: NodeEventHandler + 'static>(
|
extern "C" fn zt_wire_packet_send_function<T: NodeEventHandler + 'static>(
|
||||||
capi: *mut ztcore::ZT_Node,
|
capi: *mut ztcore::ZT_Node,
|
||||||
uptr: *mut c_void,
|
uptr: *mut c_void,
|
||||||
|
@ -143,7 +137,6 @@ extern "C" fn zt_wire_packet_send_function<T: NodeEventHandler + 'static>(
|
||||||
n.event_handler.wire_packet_send();
|
n.event_handler.wire_packet_send();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
extern "C" fn zt_path_check_function<T: NodeEventHandler + 'static>(
|
extern "C" fn zt_path_check_function<T: NodeEventHandler + 'static>(
|
||||||
capi: *mut ztcore::ZT_Node,
|
capi: *mut ztcore::ZT_Node,
|
||||||
uptr: *mut c_void,
|
uptr: *mut c_void,
|
||||||
|
@ -157,7 +150,6 @@ extern "C" fn zt_path_check_function<T: NodeEventHandler + 'static>(
|
||||||
n.event_handler.path_check();
|
n.event_handler.path_check();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
extern "C" fn zt_path_lookup_function<T: NodeEventHandler + 'static>(
|
extern "C" fn zt_path_lookup_function<T: NodeEventHandler + 'static>(
|
||||||
capi: *mut ztcore::ZT_Node,
|
capi: *mut ztcore::ZT_Node,
|
||||||
uptr: *mut c_void,
|
uptr: *mut c_void,
|
||||||
|
@ -309,11 +301,9 @@ impl<T: NodeEventHandler + 'static> Node<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn identity(&self) -> Identity {
|
pub fn identity(&self) -> Identity {
|
||||||
unsafe {
|
let id = unsafe { ztcore::ZT_Node_identity(self.capi.get()) };
|
||||||
let mut id = ztcore::ZT_Node_identity(self.capi.get());
|
|
||||||
return Identity::new_from_capi(id, false).clone();
|
return Identity::new_from_capi(id, false).clone();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn status(&self) -> NodeStatus {
|
pub fn status(&self) -> NodeStatus {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
Loading…
Add table
Reference in a new issue