broke apart files

This commit is contained in:
Monica Moniot
2024-02-26 12:08:45 -05:00
parent ac23eed2b4
commit 83778c3ebc
7 changed files with 896 additions and 865 deletions
+1
View File
@@ -13,3 +13,4 @@
/.idea
/.nova
*.secret
.vscode
-865
View File
File diff suppressed because it is too large Load Diff
+218
View File
@@ -0,0 +1,218 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::fmt::Debug;
use std::hash::Hash;
use std::mem::{size_of, transmute};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::p384::*;
use zerotier_common_utils::blob::Blob;
use zerotier_common_utils::error::InvalidParameterError;
use zerotier_common_utils::tofrombytes::ToFromBytes;
use crate::base62;
use crate::ADDRESS_ERR;
// Implementation note: the addresses use u64 arrays that are actually treated as flat byte
// array memory arenas in order to optimize for fast lookup when these are used as map keys.
// This reduces the number of instructions required to perform equality comparisons and
// simplifies the implementation of Hash. The effect is small but might matter at scale.
/// 384-bit ZeroTier address.
/// An address is the SHA384(public master signing key) of an identity.
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Address(pub(crate) [u64; 6]); // treated as [u8; 48]
impl Address {
/// The first byte of a valid address must be 0xfc.
///
/// This allows the 128-bit prefix of every address to also be a valid private IPv6 address,
/// which is useful for a number of purposes. It also imposes a small extra computational cost
/// on the generation of new identities with a given short address, making it slightly harder
/// to brute force the short address space. (A 128-bit space is already impractical to brute
/// force, but untargeted birthday type collisions are possible with sufficient storage.)
pub const REQUIRED_PREFIX: u8 = 0xfc;
/// Length of a full address in string format.
pub const STRING_SIZE: usize = 76;
/// Get this address as a raw byte array.
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; 48] {
debug_assert_eq!(size_of::<[u8; 48]>(), size_of::<Self>());
unsafe { &*self.0.as_ptr().cast::<[u8; 48]>() }
}
/// Get this address's 128-bit short prefix.
#[inline(always)]
pub fn prefix(&self) -> &ShortAddress {
unsafe { transmute(&self.0) }
}
/// Get mutable bytes.
/// This is private because it should be impossible for external code to create an invalid address.
#[inline(always)]
pub(crate) fn as_mut_bytes(&mut self) -> &mut [u8; 48] {
debug_assert_eq!(size_of::<[u8; 48]>(), size_of::<Self>());
unsafe { &mut *self.0.as_mut_ptr().cast::<[u8; 48]>() }
}
/// Check address validity, used in deserialization code.
#[inline(always)]
pub(crate) fn is_valid(&self) -> bool {
self.as_bytes()[0] == Self::REQUIRED_PREFIX
}
}
impl TryFrom<[u8; 48]> for Address {
type Error = InvalidParameterError;
#[inline]
fn try_from(value: [u8; 48]) -> Result<Self, Self::Error> {
let a = Self(unsafe { transmute(value) });
if a.is_valid() {
Ok(a)
} else {
Err(ADDRESS_ERR)
}
}
}
impl From<Address> for [u8; 48] {
#[inline(always)]
fn from(value: Address) -> Self {
unsafe { transmute(value) }
}
}
impl ToFromBytes for Address {
#[inline]
fn read_bytes<R: std::io::Read>(r: &mut R) -> std::io::Result<Self> {
let mut tmp = Self([0; 6]);
r.read_exact(tmp.as_mut_bytes())?;
if tmp.is_valid() {
Ok(tmp)
} else {
Err(std::io::Error::new(std::io::ErrorKind::Other, ADDRESS_ERR.0))
}
}
#[inline(always)]
fn write_bytes<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
w.write_all(self.as_bytes())
}
}
impl ToString for Address {
fn to_string(&self) -> String {
let mut s = String::with_capacity(Self::STRING_SIZE);
first_128_to_string(&self.as_bytes()[..16], &mut s);
s.push('.');
base62::encode_8to11(u64::from_be(self.0[2]), &mut s);
base62::encode_8to11(u64::from_be(self.0[3]), &mut s);
base62::encode_8to11(u64::from_be(self.0[4]), &mut s);
base62::encode_8to11(u64::from_be(self.0[5]), &mut s);
s
}
}
impl Debug for Address {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.to_string().as_str())
}
}
impl FromStr for Address {
type Err = InvalidParameterError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let sb = s.as_bytes();
if s.len() == sb.len() && sb.len() == Self::STRING_SIZE && sb[31] == b'.' {
let prefix = ShortAddress::from_str(&s[..31])?;
Ok(Address([
prefix.0[0],
prefix.0[1],
base62::decode_11to8(&sb[32..43])?.to_be(),
base62::decode_11to8(&sb[43..54])?.to_be(),
base62::decode_11to8(&sb[54..65])?.to_be(),
base62::decode_11to8(&sb[65..76])?.to_be(),
]))
} else {
Err(ADDRESS_ERR)
}
}
}
impl AsRef<[u8]> for Address {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Hash for Address {
#[inline(always)]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_usize(self.0[0] as usize)
}
}
impl PartialOrd for Address {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Address {
#[inline(always)]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0
.iter()
.map(|i| u64::from_be(*i))
.cmp(other.0.iter().map(|i| u64::from_be(*i)))
}
}
impl Serialize for Address {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
self.to_string().serialize(serializer)
} else {
<&Blob<48>>::from(self.as_bytes()).serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for Address {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
Address::from_str(<&str>::deserialize(deserializer)?).map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0))
} else {
Address::try_from(<[u8; 48]>::from(Blob::<48>::deserialize(deserializer)?))
.map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0))
}
}
}
impl crate::Address for Address {
const SIZE: usize = 48;
}
+223
View File
@@ -0,0 +1,223 @@
use crate::p384::*;
const TIMESTAMP_START: usize = P384_PUBLIC_KEY_SIZE;
const SUBKEY_ECDH_START: usize = TIMESTAMP_START + 8;
const SUBKEY_ECDSA_START: usize = SUBKEY_ECDH_START + P384_PUBLIC_KEY_SIZE;
const MASTER_SIG_START: usize = SUBKEY_ECDSA_START + P384_PUBLIC_KEY_SIZE;
const SUBKEY_SIG_START: usize = MASTER_SIG_START + P384_ECDSA_SIGNATURE_SIZE;
const P384_IDENTITY_SIZE: usize = SUBKEY_SIG_START + P384_ECDSA_SIGNATURE_SIZE;
/// NIST P-384 based new format identity with key upgrade capability.
#[derive(Clone)]
pub struct Identity {
pub address: Address,
pub master_signing_key: P384PublicKey,
pub timestamp: u64,
pub ecdh: P384PublicKey,
pub ecdsa: P384PublicKey,
pub master_signature: [u8; P384_ECDSA_SIGNATURE_SIZE],
pub ecdsa_signature: [u8; P384_ECDSA_SIGNATURE_SIZE],
}
impl Identity {
pub(crate) fn locally_validate(&self) -> bool {
let to_sign: &[&[u8]] = &[
self.master_signing_key.as_bytes(),
&self.timestamp.to_be_bytes(),
self.ecdh.as_bytes(),
self.ecdsa.as_bytes(),
];
self.address.is_valid()
&& self
.master_signing_key
.verify_all(DOMAIN_MASTER_SIG, to_sign, &self.master_signature)
&& self.ecdsa.verify_all(DOMAIN_SUBKEY_SIG, to_sign, &self.ecdsa_signature)
}
/// Returns true if this identity should replace the other.
/// This just returns true if the timestamp is newer and the address (master signing key hash) is the same.
#[inline(always)]
pub fn replaces(&self, other: &Identity) -> bool {
self.address == other.address && self.timestamp > other.timestamp
}
}
impl ToString for Identity {
fn to_string(&self) -> String {
let mut tmp = String::with_capacity(1024);
tmp.push_str(self.address.to_string().as_str());
tmp.push_str(":1:");
tmp.push_str(base64::to_string(self.to_bytes_on_stack::<1024>().as_bytes()).as_str());
tmp
}
}
impl Debug for Identity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("x25519::Identity")
.field("address", &self.address)
.field("master_signing_key", self.master_signing_key.as_bytes())
.field("timestamp", &self.timestamp)
.field("ecdh", self.ecdh.as_bytes())
.field("ecdsa", self.ecdsa.as_bytes())
.field("master_signature", &self.master_signature)
.field("ecdsa_signature", &self.ecdsa_signature)
.finish()
}
}
impl FromStr for Identity {
type Err = InvalidParameterError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(div_idx) = s.rfind(':') {
if div_idx > 0 && div_idx < s.len() {
if let Some(bytes) = base64::from_string(s[div_idx + 1..].as_bytes()) {
return Self::from_bytes(bytes.as_slice()).map_err(|_| IDENTITY_ERR);
}
}
}
return Err(IDENTITY_ERR);
}
}
impl ToFromBytes for Identity {
fn read_bytes<R: std::io::Read>(r: &mut R) -> std::io::Result<Self> {
let mut tmp = [0u8; P384_IDENTITY_SIZE];
r.read_exact(&mut tmp)?;
if let (Some(master_signing_key), Some(ecdh), Some(ecdsa)) = (
P384PublicKey::from_bytes(&tmp[..TIMESTAMP_START]),
P384PublicKey::from_bytes(&tmp[SUBKEY_ECDH_START..SUBKEY_ECDSA_START]),
P384PublicKey::from_bytes(&tmp[SUBKEY_ECDSA_START..MASTER_SIG_START]),
) {
let id = Self {
address: Address(unsafe { transmute(SHA384::hash(master_signing_key.as_bytes())) }),
master_signing_key,
timestamp: u64::from_be_bytes(tmp[TIMESTAMP_START..SUBKEY_ECDH_START].try_into().unwrap()),
ecdh,
ecdsa,
master_signature: tmp[MASTER_SIG_START..SUBKEY_SIG_START].try_into().unwrap(),
ecdsa_signature: tmp[SUBKEY_SIG_START..P384_IDENTITY_SIZE].try_into().unwrap(),
};
if id.locally_validate() {
return Ok(id);
}
}
return Err(std::io::Error::new(std::io::ErrorKind::Other, IDENTITY_ERR.0));
}
/// This function cannot rollback changes to `w` if an error occurs.
fn write_bytes<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
// The address is SHA384(master_signing_key) so we do not need to output it. We will want
// to recalculate it to check it anyway.
w.write_all(self.master_signing_key.as_bytes())?;
w.write_all(&self.timestamp.to_be_bytes())?;
w.write_all(self.ecdh.as_bytes())?;
w.write_all(self.ecdsa.as_bytes())?;
w.write_all(&self.master_signature)?;
w.write_all(&self.ecdsa_signature)
}
}
impl Serialize for Identity {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
self.to_string().serialize(serializer)
} else {
serializer.serialize_bytes(self.to_bytes_on_stack::<P384_IDENTITY_SIZE>().as_ref())
}
}
}
impl<'de> Deserialize<'de> for Identity {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
Identity::from_str(<&str>::deserialize(deserializer)?).map_err(|_| serde::de::Error::custom(IDENTITY_ERR.0))
} else {
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = Identity;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a pair of ratchet states")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Identity::from_bytes(v).map_err(|_| serde::de::Error::custom(IDENTITY_ERR.0))
}
}
deserializer.deserialize_bytes(Visitor)
}
}
}
impl PartialEq for Identity {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
// Two identities are equal if their addresses, which are SHA384(master signing key), match and
// if their signatures match. The latter is because differing signatures would indicate different
// revisions of the working keys within an identity.
self.address.eq(&other.address) && self.master_signature.eq(&other.master_signature)
}
}
impl Eq for Identity {}
impl PartialOrd for Identity {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.address.cmp(&other.address))
}
}
impl Ord for Identity {
#[inline(always)]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.address.cmp(&other.address)
}
}
impl Hash for Identity {
#[inline(always)]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.address.hash(state);
}
}
impl crate::Identity for Identity {
const SIZE: usize = P384_IDENTITY_SIZE;
const SIGNATURE_SIZE: usize = P384_ECDSA_SIGNATURE_SIZE;
type Secret = IdentitySecret;
#[inline(always)]
fn verify_signature(&self, data: &[u8], signature: &[u8]) -> bool {
if let Ok(sig) = signature.try_into() {
self.ecdsa.verify_raw(data, sig)
} else {
false
}
}
#[inline(always)]
fn verify_domain_restricted_signature(&self, domain: &[u8], data: &[u8], signature: &[u8]) -> bool {
if let Ok(sig) = signature.try_into() {
self.ecdsa.verify(domain, data, sig)
} else {
false
}
}
}
+187
View File
@@ -0,0 +1,187 @@
use crate::p384::*;
/// Secret NIST P-384 identity (also contains public).
///
/// The master signing key is optional to allow it to be removed and placed in cold storage.
/// It's only needed if the identity is to have its regular working keys upgraded.
pub struct IdentitySecret {
pub public: Identity,
pub master_signing_key: Option<P384KeyPair>,
pub ecdh: P384KeyPair,
pub ecdsa: P384KeyPair,
}
impl PartialEq for IdentitySecret {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.public == other.public
}
}
impl Eq for IdentitySecret {}
impl Clone for IdentitySecret {
fn clone(&self) -> Self {
Self::from_bytes(self.to_bytes_on_stack::<2048>().as_bytes()).unwrap()
}
}
#[derive(Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
struct IdentitySecretSerialized {
#[zeroize(skip)]
a: Address,
#[zeroize(skip)]
pm: Blob<P384_PUBLIC_KEY_SIZE>,
sm: Option<Blob<P384_SECRET_KEY_SIZE>>,
#[zeroize(skip)]
ts: u64,
#[zeroize(skip)]
p0: Blob<P384_PUBLIC_KEY_SIZE>,
s0: Blob<P384_SECRET_KEY_SIZE>,
#[zeroize(skip)]
p1: Blob<P384_PUBLIC_KEY_SIZE>,
s1: Blob<P384_SECRET_KEY_SIZE>,
#[zeroize(skip)]
ms: Blob<P384_ECDSA_SIGNATURE_SIZE>,
#[zeroize(skip)]
ss: Blob<P384_ECDSA_SIGNATURE_SIZE>,
}
impl Serialize for IdentitySecret {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut tmp = IdentitySecretSerialized {
a: self.public.address,
pm: (*self.public.master_signing_key.as_bytes()).into(),
sm: None,
ts: self.public.timestamp,
p0: (*self.public.ecdh.as_bytes()).into(),
s0: Blob::default(),
p1: (*self.public.ecdsa.as_bytes()).into(),
s1: Blob::default(),
ms: self.public.master_signature.into(),
ss: self.public.ecdsa_signature.into(),
};
self.ecdh.secret_key_bytes(&mut tmp.s0);
self.ecdsa.secret_key_bytes(&mut tmp.s1);
if let Some(ecdsa) = self.master_signing_key.as_ref() {
tmp.sm = Some(Blob::default());
ecdsa.secret_key_bytes(tmp.sm.as_mut().unwrap());
}
tmp.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for IdentitySecret {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let d = <IdentitySecretSerialized>::deserialize(deserializer)?;
if let (Some(pm), Some(ecdh), Some(ecdsa)) = (
P384PublicKey::from_bytes(d.pm.as_bytes()),
P384KeyPair::from_bytes(d.p0.as_bytes(), d.s0.as_bytes()),
P384KeyPair::from_bytes(d.p1.as_bytes(), d.s1.as_bytes()),
) {
let mut master_signing_key_sec = None;
if let Some(sm) = d.sm.as_ref() {
if let Some(sm) = P384KeyPair::from_bytes(pm.as_bytes(), sm.as_bytes()) {
master_signing_key_sec = Some(sm);
} else {
return Err(serde::de::Error::custom(IDENTITY_ERR.0));
}
}
if let Ok(address) = Address::try_from(SHA384::hash(pm.as_bytes())) {
if address.eq(&d.a) {
let id = Self {
public: Identity {
address,
master_signing_key: pm,
timestamp: d.ts,
ecdh: ecdh.to_public_key(),
ecdsa: ecdsa.to_public_key(),
master_signature: *d.ms.as_bytes(),
ecdsa_signature: *d.ss.as_bytes(),
},
master_signing_key: master_signing_key_sec,
ecdh,
ecdsa,
};
if id.public.locally_validate() {
return Ok(id);
}
}
}
}
return Err(serde::de::Error::custom(IDENTITY_ERR.0));
}
}
impl crate::IdentitySecret for IdentitySecret {
type Public = Identity;
type Signature = [u8; 96];
fn generate(timestamp: u64) -> Self {
let mut address = Address([0; 6]);
let mut master_signing_key;
loop {
master_signing_key = P384KeyPair::generate();
*address.as_mut_bytes() = SHA384::hash(master_signing_key.public_key_bytes());
if address.is_valid() {
break;
}
}
let ecdh = P384KeyPair::generate();
let ecdsa = P384KeyPair::generate();
let to_sign: &[&[u8]] = &[
master_signing_key.public_key_bytes(),
&timestamp.to_be_bytes(),
ecdh.public_key_bytes(),
ecdsa.public_key_bytes(),
];
Self {
public: Identity {
address,
master_signing_key: master_signing_key.to_public_key(),
timestamp,
ecdh: ecdh.to_public_key(),
ecdsa: ecdsa.to_public_key(),
master_signature: master_signing_key.sign_all(DOMAIN_MASTER_SIG, to_sign),
ecdsa_signature: ecdsa.sign_all(DOMAIN_SUBKEY_SIG, to_sign),
},
master_signing_key: Some(master_signing_key),
ecdh,
ecdsa,
}
}
#[inline(always)]
fn public(&self) -> &Self::Public {
&self.public
}
#[inline(always)]
fn sign(&self, data: &[u8]) -> Self::Signature {
self.ecdsa.sign_raw(data)
}
#[inline(always)]
fn sign_domain_restricted(&self, domain: &[u8], data: &[u8]) -> Self::Signature {
self.ecdsa.sign(domain, data)
}
}
impl ToFromBytes for IdentitySecret {
fn read_bytes<R: std::io::Read>(r: &mut R) -> std::io::Result<Self> {
serde_cbor::from_reader(r).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
}
fn write_bytes<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
serde_cbor::to_writer(w, self).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
}
}
+87
View File
@@ -0,0 +1,87 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::fmt::Debug;
use std::hash::Hash;
use std::io::Write;
use std::mem::{size_of, transmute};
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::zeroize::{Zeroize, ZeroizeOnDrop};
use zerotier_common_utils::{base64, blob::Blob, error::InvalidParameterError, tofrombytes::ToFromBytes};
use zerotier_crypto_glue::{hash::SHA384, p384::*};
use crate::base24;
use crate::{ADDRESS_ERR, IDENTITY_ERR};
mod address;
mod identity;
mod identity_secret;
mod short_address;
pub use address::*;
pub use identity::*;
pub use identity_secret::*;
pub use short_address::*;
pub(crate) const DOMAIN_MASTER_SIG: &[u8] = b"ZTID_MASTERSIG_P384";
pub(crate) const DOMAIN_SUBKEY_SIG: &[u8] = b"ZTID_SUBKEYSIG_P384";
fn first_128_to_string(b: &[u8], s: &mut String) {
base24::encode_4to7(&b[0..4], s);
s.push('.');
base24::encode_4to7(&b[4..8], s);
s.push('.');
base24::encode_4to7(&b[8..12], s);
s.push('.');
base24::encode_4to7(&b[12..16], s);
}
#[cfg(test)]
mod tests {
use crate::*;
use zerotier_common_utils::ms_monotonic;
#[test]
fn generate() {
let start = ms_monotonic();
for _ in 0..3 {
let secret = p384::IdentitySecret::generate(1);
println!("P: {}", secret.public.to_string());
}
let end = ms_monotonic();
println!("p384 generation time: {} ms/identity", ((end - start) as f64) / 3.0);
}
#[test]
fn tostring_fromstring() {
let secret = p384::IdentitySecret::generate(0);
assert!(p384::Address::from_str(secret.public.address.to_string().as_str())
.unwrap()
.eq(&secret.public.address));
assert!(p384::Identity::from_str(secret.public.to_string().as_str())
.unwrap()
.eq(&secret.public));
}
#[test]
fn tobytes_frombytes() {
let secret = p384::IdentitySecret::generate(0);
assert!(p384::Address::from_bytes(secret.public.address.to_bytes().as_slice())
.unwrap()
.eq(&secret.public.address));
assert!(p384::Identity::from_bytes(secret.public.to_bytes().as_slice())
.unwrap()
.eq(&secret.public));
assert!(p384::IdentitySecret::from_bytes(secret.to_bytes().as_slice())
.unwrap()
.eq(&secret));
}
}
+180
View File
@@ -0,0 +1,180 @@
use crate::p384::*;
/// 128-bit short address prefix.
///
/// Short addresses are primarily for cases where humans need to type addresses or where
/// they need to be mapped onto an IPv6 address. The fully qualified 384-bit address
/// should be preferred if address transfer is automated or via cut/paste.
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ShortAddress(pub(crate) [u64; 2]); // treated as [u8; 16]
impl ShortAddress {
pub const SIZE: usize = 16;
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; Self::SIZE] {
debug_assert_eq!(size_of::<[u8; Self::SIZE]>(), size_of::<Self>());
unsafe { &*(&self.0 as *const [u64; 2]).cast() }
}
#[inline(always)]
fn as_mut_bytes(&mut self) -> &mut [u8; Self::SIZE] {
debug_assert_eq!(size_of::<[u8; Self::SIZE]>(), size_of::<Self>());
unsafe { &mut *(&mut self.0 as *mut [u64; 2]).cast() }
}
#[inline(always)]
fn is_valid(&self) -> bool {
self.as_bytes()[0] == Address::REQUIRED_PREFIX
}
}
impl TryFrom<[u8; 16]> for ShortAddress {
type Error = InvalidParameterError;
#[inline]
fn try_from(value: [u8; 16]) -> Result<Self, Self::Error> {
let a = Self(unsafe { transmute(value) });
if a.is_valid() {
Ok(a)
} else {
Err(ADDRESS_ERR)
}
}
}
impl From<ShortAddress> for [u8; 16] {
#[inline(always)]
fn from(value: ShortAddress) -> Self {
unsafe { transmute(value) }
}
}
impl ToFromBytes for ShortAddress {
#[inline]
fn read_bytes<R: std::io::Read>(r: &mut R) -> std::io::Result<Self> {
let mut tmp = Self([0; 2]);
r.read_exact(tmp.as_mut_bytes())?;
if tmp.is_valid() {
Ok(tmp)
} else {
Err(std::io::Error::new(std::io::ErrorKind::Other, ADDRESS_ERR.0))
}
}
#[inline(always)]
fn write_bytes<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
w.write_all(self.as_bytes())
}
}
impl ToString for ShortAddress {
fn to_string(&self) -> String {
let mut s = String::with_capacity(32);
first_128_to_string(self.as_bytes(), &mut s);
s
}
}
impl Debug for ShortAddress {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.to_string().as_str())
}
}
impl FromStr for ShortAddress {
type Err = InvalidParameterError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
if s.len() == 31 {
let mut tmp = [0u8; 16];
let mut w = &mut tmp[..];
for ss in s.split('.') {
if ss.len() == 7 {
let _ = w.write_all(&base24::decode_7to4(ss.as_bytes())?);
} else {
return Err(ADDRESS_ERR);
}
if w.is_empty() {
return Self::try_from(tmp);
}
}
}
return Err(ADDRESS_ERR);
}
}
impl AsRef<[u8]> for ShortAddress {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Hash for ShortAddress {
#[inline(always)]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_usize(self.0[0] as usize)
}
}
impl PartialOrd for ShortAddress {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ShortAddress {
#[inline(always)]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0
.iter()
.map(|i| u64::from_be(*i))
.cmp(other.0.iter().map(|i| u64::from_be(*i)))
}
}
impl Serialize for ShortAddress {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
self.to_string().serialize(serializer)
} else {
<&Blob<16>>::from(self.as_bytes()).serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for ShortAddress {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
ShortAddress::from_str(<&str>::deserialize(deserializer)?)
.map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0))
} else {
ShortAddress::try_from(<[u8; 16]>::from(Blob::<16>::deserialize(deserializer)?))
.map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0))
}
}
}
impl From<ShortAddress> for std::net::Ipv6Addr {
fn from(value: ShortAddress) -> Self {
std::net::Ipv6Addr::from(*value.as_bytes())
}
}
impl From<&ShortAddress> for std::net::Ipv6Addr {
fn from(value: &ShortAddress) -> Self {
std::net::Ipv6Addr::from(*value.as_bytes())
}
}