mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-05-12 16:43:43 +02:00
build fixes
This commit is contained in:
parent
614b84ef40
commit
3770fcdc83
9 changed files with 102 additions and 38 deletions
|
@ -5,6 +5,8 @@ use std::mem::{size_of, MaybeUninit};
|
|||
|
||||
use crate::util::pool::PoolFactory;
|
||||
|
||||
use zerotier_utils::varint;
|
||||
|
||||
/// An I/O buffer with extensions for efficiently reading and writing various objects.
|
||||
///
|
||||
/// WARNING: Structures can only be handled through raw read/write here if they are
|
||||
|
@ -280,7 +282,7 @@ impl<const L: usize> Buffer<L> {
|
|||
|
||||
#[inline(always)]
|
||||
pub fn append_varint(&mut self, i: u64) -> std::io::Result<()> {
|
||||
crate::util::varint::write(self, i)
|
||||
varint::write(self, i)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -469,7 +471,7 @@ impl<const L: usize> Buffer<L> {
|
|||
let c = *cursor;
|
||||
if c < self.0 {
|
||||
let mut a = &self.1[c..];
|
||||
crate::util::varint::read(&mut a).map(|r| {
|
||||
varint::read(&mut a).map(|r| {
|
||||
*cursor = c + r.1;
|
||||
debug_assert!(*cursor <= self.0);
|
||||
r.0
|
||||
|
|
|
@ -6,9 +6,6 @@ pub(crate) mod gate;
|
|||
pub mod marshalable;
|
||||
pub(crate) mod pool;
|
||||
|
||||
pub use zerotier_core_crypto::hex;
|
||||
pub use zerotier_core_crypto::varint;
|
||||
|
||||
/// A value for ticks that indicates that something never happened, and is thus very long before zero ticks.
|
||||
pub(crate) const NEVER_HAPPENED_TICKS: i64 = -2147483648;
|
||||
|
||||
|
@ -55,8 +52,3 @@ pub(crate) fn bytes_as_flat_object<T: Copy + AlignmentNeutral>(b: &[u8]) -> &T {
|
|||
assert!(b.len() >= std::mem::size_of::<T>());
|
||||
unsafe { &*b.as_ptr().cast() }
|
||||
}
|
||||
|
||||
/// Include this in a branch to hint the compiler that it's unlikely.
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
pub(crate) extern "C" fn unlikely_branch() {}
|
||||
|
|
|
@ -8,10 +8,12 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||
|
||||
use crate::error::InvalidFormatError;
|
||||
use crate::util::buffer::Buffer;
|
||||
use crate::util::hex::HEX_CHARS;
|
||||
use crate::util::marshalable::Marshalable;
|
||||
use crate::vl1::protocol::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE};
|
||||
|
||||
use zerotier_utils::hex;
|
||||
use zerotier_utils::hex::HEX_CHARS;
|
||||
|
||||
/// A unique address on the global ZeroTier VL1 network.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
|
@ -96,7 +98,7 @@ impl FromStr for Address {
|
|||
type Err = InvalidFormatError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Address::from_bytes(crate::util::hex::from_string(s).as_slice()).map_or_else(|| Err(InvalidFormatError), |a| Ok(a))
|
||||
Address::from_bytes(hex::from_string(s).as_slice()).map_or_else(|| Err(InvalidFormatError), |a| Ok(a))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::io::{Read, Write};
|
|||
use crate::vl1::identity::Identity;
|
||||
use crate::vl1::protocol::IDENTITY_FINGERPRINT_SIZE;
|
||||
|
||||
use zerotier_core_crypto::varint;
|
||||
use zerotier_utils::varint;
|
||||
|
||||
/// A signed bundle of identity fingerprints of nodes through which a node might be reached (e.g. roots).
|
||||
///
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::util::hex::HEX_CHARS;
|
||||
use zerotier_utils::hex;
|
||||
use zerotier_utils::hex::HEX_CHARS;
|
||||
|
||||
const BOOL_TRUTH: &str = "1tTyY";
|
||||
|
||||
|
@ -104,7 +105,7 @@ impl Dictionary {
|
|||
}
|
||||
|
||||
pub fn set_u64(&mut self, k: &str, v: u64) {
|
||||
let _ = self.0.insert(String::from(k), crate::util::hex::to_vec_u64(v, true));
|
||||
let _ = self.0.insert(String::from(k), hex::to_vec_u64(v, true));
|
||||
}
|
||||
|
||||
pub fn set_bytes(&mut self, k: &str, v: Vec<u8>) {
|
||||
|
@ -112,7 +113,14 @@ impl Dictionary {
|
|||
}
|
||||
|
||||
pub fn set_bool(&mut self, k: &str, v: bool) {
|
||||
let _ = self.0.insert(String::from(k), vec![if v { b'1' } else { b'0' }]);
|
||||
let _ = self.0.insert(
|
||||
String::from(k),
|
||||
vec![if v {
|
||||
b'1'
|
||||
} else {
|
||||
b'0'
|
||||
}],
|
||||
);
|
||||
}
|
||||
|
||||
/// Write a dictionary in transport format to a writer.
|
||||
|
|
|
@ -9,12 +9,13 @@ use std::str::FromStr;
|
|||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use zerotier_core_crypto::hash::*;
|
||||
use zerotier_core_crypto::hex;
|
||||
use zerotier_core_crypto::p384::*;
|
||||
use zerotier_core_crypto::salsa::Salsa;
|
||||
use zerotier_core_crypto::secret::Secret;
|
||||
use zerotier_core_crypto::x25519::*;
|
||||
|
||||
use zerotier_utils::hex;
|
||||
|
||||
use crate::error::{InvalidFormatError, InvalidParameterError};
|
||||
use crate::util::{bytes_as_flat_object, flat_object_as_bytes, AlignmentNeutral};
|
||||
use crate::vl1::protocol::{ADDRESS_SIZE, ADDRESS_SIZE_STRING, IDENTITY_FINGERPRINT_SIZE, IDENTITY_POW_THRESHOLD};
|
||||
|
@ -208,7 +209,8 @@ impl Identity {
|
|||
let p384_ecdh = P384KeyPair::generate();
|
||||
let p384_ecdsa = P384KeyPair::generate();
|
||||
|
||||
let mut self_sign_buf: Vec<u8> = Vec::with_capacity(ADDRESS_SIZE + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + 4);
|
||||
let mut self_sign_buf: Vec<u8> =
|
||||
Vec::with_capacity(ADDRESS_SIZE + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + 4);
|
||||
let _ = self_sign_buf.write_all(&self.address.to_bytes());
|
||||
let _ = self_sign_buf.write_all(&self.x25519);
|
||||
let _ = self_sign_buf.write_all(&self.ed25519);
|
||||
|
@ -302,7 +304,13 @@ impl Identity {
|
|||
// for the final result to be technically FIPS compliant. Non-FIPS algorithm secrets are considered
|
||||
// a salt in the HMAC(salt, key) HKDF construction.
|
||||
if secret.p384.is_some() && other.p384.is_some() {
|
||||
secret.p384.as_ref().unwrap().ecdh.agree(&other.p384.as_ref().unwrap().ecdh).map(|p384_secret| Secret(hmac_sha512(&c25519_secret.0, &p384_secret.0)))
|
||||
secret
|
||||
.p384
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.ecdh
|
||||
.agree(&other.p384.as_ref().unwrap().ecdh)
|
||||
.map(|p384_secret| Secret(hmac_sha512(&c25519_secret.0, &p384_secret.0)))
|
||||
} else {
|
||||
Some(c25519_secret)
|
||||
}
|
||||
|
@ -493,7 +501,12 @@ impl Identity {
|
|||
}
|
||||
IdentityBytes::X25519P384Public(b) => {
|
||||
let b: &packed::V1 = bytes_as_flat_object(b);
|
||||
if b.v0.key_type == 0 && b.v0.secret_length == 0 && b.v0.reserved == 0x03 && u16::from_be_bytes(b.v0.ext_len) == (Self::BYTE_LENGTH_X25519P384_PUBLIC - Self::BYTE_LENGTH_X25519_PUBLIC) as u16 && b.key_type_flags == Self::ALGORITHM_EC_NIST_P384 {
|
||||
if b.v0.key_type == 0
|
||||
&& b.v0.secret_length == 0
|
||||
&& b.v0.reserved == 0x03
|
||||
&& u16::from_be_bytes(b.v0.ext_len) == (Self::BYTE_LENGTH_X25519P384_PUBLIC - Self::BYTE_LENGTH_X25519_PUBLIC) as u16
|
||||
&& b.key_type_flags == Self::ALGORITHM_EC_NIST_P384
|
||||
{
|
||||
Some(Self {
|
||||
address: Address::from_bytes_fixed(&b.v0.address)?,
|
||||
x25519: b.v0.x25519,
|
||||
|
@ -608,13 +621,15 @@ impl Identity {
|
|||
s.push(':');
|
||||
}
|
||||
s.push_str(":2:"); // 2 == IDENTITY_ALGORITHM_EC_NIST_P384
|
||||
let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] = concat_arrays_4(p384.ecdh.as_bytes(), p384.ecdsa.as_bytes(), &p384.ecdsa_self_signature, &p384.ed25519_self_signature);
|
||||
let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] =
|
||||
concat_arrays_4(p384.ecdh.as_bytes(), p384.ecdsa.as_bytes(), &p384.ecdsa_self_signature, &p384.ed25519_self_signature);
|
||||
s.push_str(base64::encode_config(p384_joined, base64::URL_SAFE_NO_PAD).as_str());
|
||||
if self.secret.is_some() && include_private {
|
||||
let secret = self.secret.as_ref().unwrap();
|
||||
if secret.p384.is_some() {
|
||||
let p384_secret = secret.p384.as_ref().unwrap();
|
||||
let p384_secret_joined: [u8; P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE] = concat_arrays_2(p384_secret.ecdh.secret_key_bytes().as_bytes(), p384_secret.ecdsa.secret_key_bytes().as_bytes());
|
||||
let p384_secret_joined: [u8; P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE] =
|
||||
concat_arrays_2(p384_secret.ecdh.secret_key_bytes().as_bytes(), p384_secret.ecdsa.secret_key_bytes().as_bytes());
|
||||
s.push(':');
|
||||
s.push_str(base64::encode_config(p384_secret_joined, base64::URL_SAFE_NO_PAD).as_str());
|
||||
}
|
||||
|
@ -723,7 +738,9 @@ impl FromStr for Identity {
|
|||
Some(IdentityP384Public {
|
||||
ecdh: ecdh.unwrap(),
|
||||
ecdsa: ecdsa.unwrap(),
|
||||
ecdsa_self_signature: keys[2].as_slice()[(P384_PUBLIC_KEY_SIZE * 2)..((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE)].try_into().unwrap(),
|
||||
ecdsa_self_signature: keys[2].as_slice()[(P384_PUBLIC_KEY_SIZE * 2)..((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE)]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
ed25519_self_signature: keys[2].as_slice()[((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE)..].try_into().unwrap(),
|
||||
})
|
||||
},
|
||||
|
@ -937,7 +954,10 @@ impl<'de> serde::de::Visitor<'de> for IdentityVisitor {
|
|||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
IdentityBytes::try_from(v).map_or_else(|e| Err(E::custom(e.to_string())), |b| Identity::from_bytes(&b).map_or_else(|| Err(E::custom("invalid identity")), |id| Ok(id)))
|
||||
IdentityBytes::try_from(v).map_or_else(
|
||||
|e| Err(E::custom(e.to_string())),
|
||||
|b| Identity::from_bytes(&b).map_or_else(|| Err(E::custom("invalid identity")), |id| Ok(id)),
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
|
@ -965,7 +985,7 @@ impl<'de> Deserialize<'de> for Identity {
|
|||
mod tests {
|
||||
use crate::vl1::identity::*;
|
||||
use std::str::FromStr;
|
||||
use zerotier_core_crypto::hex;
|
||||
use zerotier_utils::hex;
|
||||
|
||||
#[test]
|
||||
fn v0_identity() {
|
||||
|
|
|
@ -11,6 +11,8 @@ use crate::error::InvalidFormatError;
|
|||
use crate::util::buffer::Buffer;
|
||||
use crate::util::marshalable::Marshalable;
|
||||
|
||||
use zerotier_utils::hex;
|
||||
|
||||
/// An Ethernet MAC address.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
|
@ -88,7 +90,7 @@ impl FromStr for MAC {
|
|||
type Err = InvalidFormatError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
MAC::from_bytes(crate::util::hex::from_string(s).as_slice()).map_or_else(|| Err(InvalidFormatError), |m| Ok(m))
|
||||
MAC::from_bytes(hex::from_string(s).as_slice()).map_or_else(|| Err(InvalidFormatError), |m| Ok(m))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ use crate::vl1::whoisqueue::{QueuedPacket, WhoisQueue};
|
|||
use crate::vl1::{Address, Endpoint, Identity, RootSet};
|
||||
use crate::Event;
|
||||
|
||||
use zerotier_utils::hex;
|
||||
|
||||
/// Trait implemented by external code to handle events and provide an interface to the system or application.
|
||||
///
|
||||
/// These methods are basically callbacks that the core calls to request or transmit things. They are called
|
||||
|
@ -97,7 +99,16 @@ pub trait InnerProtocolInterface: Sync + Send + 'static {
|
|||
async fn handle_packet<SI: SystemInterface>(&self, source: &Peer<SI>, source_path: &Path<SI>, verb: u8, payload: &PacketBuffer) -> bool;
|
||||
|
||||
/// Handle errors, returning true if the error was recognized.
|
||||
async fn handle_error<SI: SystemInterface>(&self, source: &Peer<SI>, source_path: &Path<SI>, in_re_verb: u8, in_re_message_id: u64, error_code: u8, payload: &PacketBuffer, cursor: &mut usize) -> bool;
|
||||
async fn handle_error<SI: SystemInterface>(
|
||||
&self,
|
||||
source: &Peer<SI>,
|
||||
source_path: &Path<SI>,
|
||||
in_re_verb: u8,
|
||||
in_re_message_id: u64,
|
||||
error_code: u8,
|
||||
payload: &PacketBuffer,
|
||||
cursor: &mut usize,
|
||||
) -> bool;
|
||||
|
||||
/// Handle an OK, returing true if the OK was recognized.
|
||||
async fn handle_ok<SI: SystemInterface>(&self, source: &Peer<SI>, source_path: &Path<SI>, in_re_verb: u8, in_re_message_id: u64, payload: &PacketBuffer, cursor: &mut usize) -> bool;
|
||||
|
@ -400,7 +411,9 @@ impl<SI: SystemInterface> Node<SI> {
|
|||
for m in rs.members.iter() {
|
||||
if m.identity.eq(&self.identity) {
|
||||
let _ = my_root_sets.get_or_insert_with(|| Vec::new()).write_all(rs.to_bytes().as_slice());
|
||||
} else if self.peers.read().get(&m.identity.address).map_or(false, |p| !p.identity.eq(&m.identity)) || address_collision_check.insert(m.identity.address, &m.identity).map_or(false, |old_id| !old_id.eq(&m.identity)) {
|
||||
} else if self.peers.read().get(&m.identity.address).map_or(false, |p| !p.identity.eq(&m.identity))
|
||||
|| address_collision_check.insert(m.identity.address, &m.identity).map_or(false, |old_id| !old_id.eq(&m.identity))
|
||||
{
|
||||
address_collisions.push(m.identity.address);
|
||||
}
|
||||
}
|
||||
|
@ -410,14 +423,22 @@ impl<SI: SystemInterface> Node<SI> {
|
|||
for (_, rs) in roots.sets.iter() {
|
||||
for m in rs.members.iter() {
|
||||
if m.endpoints.is_some() && !address_collisions.contains(&m.identity.address) && !m.identity.eq(&self.identity) {
|
||||
debug_event!(si, "[vl1] examining root {} with {} endpoints", m.identity.address.to_string(), m.endpoints.as_ref().map_or(0, |e| e.len()));
|
||||
debug_event!(
|
||||
si,
|
||||
"[vl1] examining root {} with {} endpoints",
|
||||
m.identity.address.to_string(),
|
||||
m.endpoints.as_ref().map_or(0, |e| e.len())
|
||||
);
|
||||
let peers = self.peers.upgradable_read();
|
||||
if let Some(peer) = peers.get(&m.identity.address) {
|
||||
new_roots.insert(peer.clone(), m.endpoints.as_ref().unwrap().iter().cloned().collect());
|
||||
} else {
|
||||
if let Some(peer) = Peer::<SI>::new(&self.identity, m.identity.clone(), tt) {
|
||||
new_roots.insert(
|
||||
parking_lot::RwLockUpgradableReadGuard::upgrade(peers).entry(m.identity.address).or_insert_with(|| Arc::new(peer)).clone(),
|
||||
parking_lot::RwLockUpgradableReadGuard::upgrade(peers)
|
||||
.entry(m.identity.address)
|
||||
.or_insert_with(|| Arc::new(peer))
|
||||
.clone(),
|
||||
m.endpoints.as_ref().unwrap().iter().cloned().collect(),
|
||||
);
|
||||
} else {
|
||||
|
@ -542,14 +563,22 @@ impl<SI: SystemInterface> Node<SI> {
|
|||
Duration::from_millis(1000)
|
||||
}
|
||||
|
||||
pub async fn handle_incoming_physical_packet<PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, source_endpoint: &Endpoint, source_local_socket: &SI::LocalSocket, source_local_interface: &SI::LocalInterface, mut data: PooledPacketBuffer) {
|
||||
pub async fn handle_incoming_physical_packet<PH: InnerProtocolInterface>(
|
||||
&self,
|
||||
si: &SI,
|
||||
ph: &PH,
|
||||
source_endpoint: &Endpoint,
|
||||
source_local_socket: &SI::LocalSocket,
|
||||
source_local_interface: &SI::LocalInterface,
|
||||
mut data: PooledPacketBuffer,
|
||||
) {
|
||||
debug_event!(
|
||||
si,
|
||||
"[vl1] {} -> #{} {}->{} length {} (on socket {}@{})",
|
||||
source_endpoint.to_string(),
|
||||
data.bytes_fixed_at::<8>(0).map_or("????????????????".into(), |pid| zerotier_core_crypto::hex::to_string(pid)),
|
||||
data.bytes_fixed_at::<5>(13).map_or("??????????".into(), |src| zerotier_core_crypto::hex::to_string(src)),
|
||||
data.bytes_fixed_at::<5>(8).map_or("??????????".into(), |dest| zerotier_core_crypto::hex::to_string(dest)),
|
||||
data.bytes_fixed_at::<8>(0).map_or("????????????????".into(), |pid| hex::to_string(pid)),
|
||||
data.bytes_fixed_at::<5>(13).map_or("??????????".into(), |src| hex::to_string(src)),
|
||||
data.bytes_fixed_at::<5>(8).map_or("??????????".into(), |dest| hex::to_string(dest)),
|
||||
data.len(),
|
||||
source_local_socket.to_string(),
|
||||
source_local_interface.to_string()
|
||||
|
@ -565,7 +594,13 @@ impl<SI: SystemInterface> Node<SI> {
|
|||
if fragment_header.is_fragment() {
|
||||
#[cfg(debug_assertions)]
|
||||
let fragment_header_id = u64::from_be_bytes(fragment_header.id);
|
||||
debug_event!(si, "[vl1] #{:0>16x} fragment {} of {} received", u64::from_be_bytes(fragment_header.id), fragment_header.fragment_no(), fragment_header.total_fragments());
|
||||
debug_event!(
|
||||
si,
|
||||
"[vl1] #{:0>16x} fragment {} of {} received",
|
||||
u64::from_be_bytes(fragment_header.id),
|
||||
fragment_header.fragment_no(),
|
||||
fragment_header.total_fragments()
|
||||
);
|
||||
|
||||
if let Some(assembled_packet) = path.receive_fragment(fragment_header.packet_id(), fragment_header.fragment_no(), fragment_header.total_fragments(), data, time_ticks) {
|
||||
if let Some(frag0) = assembled_packet.frags[0].as_ref() {
|
||||
|
@ -575,7 +610,8 @@ impl<SI: SystemInterface> Node<SI> {
|
|||
if let Ok(packet_header) = frag0.struct_at::<PacketHeader>(0) {
|
||||
if let Some(source) = Address::from_bytes(&packet_header.src) {
|
||||
if let Some(peer) = self.peer(source) {
|
||||
peer.receive(self, si, ph, time_ticks, &path, &packet_header, frag0, &assembled_packet.frags[1..(assembled_packet.have as usize)]).await;
|
||||
peer.receive(self, si, ph, time_ticks, &path, &packet_header, frag0, &assembled_packet.frags[1..(assembled_packet.have as usize)])
|
||||
.await;
|
||||
} else {
|
||||
self.whois.query(self, si, source, Some(QueuedPacket::Fragmented(assembled_packet)));
|
||||
}
|
||||
|
|
|
@ -8,9 +8,11 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||
|
||||
use crate::error::InvalidFormatError;
|
||||
use crate::util::buffer::Buffer;
|
||||
use crate::util::hex::HEX_CHARS;
|
||||
use crate::util::marshalable::Marshalable;
|
||||
|
||||
use zerotier_utils::hex;
|
||||
use zerotier_utils::hex::HEX_CHARS;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct NetworkId(NonZeroU64);
|
||||
|
@ -85,7 +87,7 @@ impl FromStr for NetworkId {
|
|||
type Err = InvalidFormatError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
NetworkId::from_bytes(crate::util::hex::from_string(s).as_slice()).map_or_else(|| Err(InvalidFormatError), |a| Ok(a))
|
||||
NetworkId::from_bytes(hex::from_string(s).as_slice()).map_or_else(|| Err(InvalidFormatError), |a| Ok(a))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue