use std::{
io::Read,
net::{IpAddr, SocketAddr},
};
use byteorder::{BigEndian, ReadBytesExt};
use thiserror::Error;
use zebra_chain::serialization::{
zcash_deserialize_bytes_external_count, CompactSize64, CompactSizeMessage, DateTime32,
SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashDeserializeInto,
};
use crate::{meta_addr::MetaAddr, protocol::external::types::PeerServices, PeerSocketAddr};
use super::canonical_peer_addr;
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
#[cfg(test)]
use byteorder::WriteBytesExt;
#[cfg(test)]
use std::io::Write;
#[cfg(test)]
use zebra_chain::serialization::{zcash_serialize_bytes, ZcashSerialize};
pub const MAX_ADDR_V2_ADDR_SIZE: usize = 512;
pub const ADDR_V2_IPV4_NETWORK_ID: u8 = 0x01;
pub const ADDR_V2_IPV4_ADDR_SIZE: usize = 4;
pub const ADDR_V2_IPV6_NETWORK_ID: u8 = 0x02;
pub const ADDR_V2_IPV6_ADDR_SIZE: usize = 16;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub(in super::super) enum AddrV2 {
IpAddr {
untrusted_last_seen: DateTime32,
untrusted_services: PeerServices,
addr: PeerSocketAddr,
},
Unsupported,
}
#[cfg(test)]
impl From<MetaAddr> for AddrV2 {
fn from(meta_addr: MetaAddr) -> Self {
let untrusted_services = meta_addr.services.expect(
"unexpected MetaAddr with missing peer services: \
MetaAddrs should be sanitized before serialization",
);
let untrusted_last_seen = meta_addr.last_seen().expect(
"unexpected MetaAddr with missing last seen time: \
MetaAddrs should be sanitized before serialization",
);
AddrV2::IpAddr {
untrusted_last_seen,
untrusted_services,
addr: canonical_peer_addr(meta_addr.addr()),
}
}
}
#[derive(Error, Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
#[error("can not parse this addrv2 variant: unimplemented or unrecognised AddrV2 network ID")]
pub struct UnsupportedAddrV2NetworkIdError;
impl TryFrom<AddrV2> for MetaAddr {
type Error = UnsupportedAddrV2NetworkIdError;
fn try_from(addr: AddrV2) -> Result<MetaAddr, UnsupportedAddrV2NetworkIdError> {
if let AddrV2::IpAddr {
untrusted_last_seen,
untrusted_services,
addr,
} = addr
{
Ok(MetaAddr::new_gossiped_meta_addr(
addr,
untrusted_services,
untrusted_last_seen,
))
} else {
Err(UnsupportedAddrV2NetworkIdError)
}
}
}
impl AddrV2 {
#[allow(clippy::unwrap_in_result)]
fn ip_addr_from_bytes<const IP_ADDR_SIZE: usize>(
addr_bytes: Vec<u8>,
) -> Result<IpAddr, SerializationError>
where
IpAddr: From<[u8; IP_ADDR_SIZE]>,
{
if addr_bytes.len() != IP_ADDR_SIZE {
let error_msg = if IP_ADDR_SIZE == ADDR_V2_IPV4_ADDR_SIZE {
"IP address field length did not match expected IPv4 address size in addrv2 message"
} else if IP_ADDR_SIZE == ADDR_V2_IPV6_ADDR_SIZE {
"IP address field length did not match expected IPv6 address size in addrv2 message"
} else {
unreachable!("unexpected IP address size when converting from bytes");
};
return Err(SerializationError::Parse(error_msg));
};
let ip: [u8; IP_ADDR_SIZE] = addr_bytes.try_into().expect("just checked length");
Ok(IpAddr::from(ip))
}
}
#[cfg(test)]
impl ZcashSerialize for AddrV2 {
fn zcash_serialize<W: Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
if let AddrV2::IpAddr {
untrusted_last_seen,
untrusted_services,
addr,
} = self
{
untrusted_last_seen.zcash_serialize(&mut writer)?;
let untrusted_services: CompactSize64 = untrusted_services.bits().into();
untrusted_services.zcash_serialize(&mut writer)?;
match addr.ip() {
IpAddr::V4(ip) => {
writer.write_u8(ADDR_V2_IPV4_NETWORK_ID)?;
let ip: [u8; ADDR_V2_IPV4_ADDR_SIZE] = ip.octets();
zcash_serialize_bytes(&ip.to_vec(), &mut writer)?;
writer.write_u16::<BigEndian>(addr.port())?;
}
IpAddr::V6(ip) => {
writer.write_u8(ADDR_V2_IPV6_NETWORK_ID)?;
let ip: [u8; ADDR_V2_IPV6_ADDR_SIZE] = ip.octets();
zcash_serialize_bytes(&ip.to_vec(), &mut writer)?;
writer.write_u16::<BigEndian>(addr.port())?;
}
}
} else {
unreachable!("unexpected AddrV2 variant: {:?}", self);
}
Ok(())
}
}
impl ZcashDeserialize for AddrV2 {
fn zcash_deserialize<R: Read>(mut reader: R) -> Result<Self, SerializationError> {
let untrusted_last_seen = (&mut reader).zcash_deserialize_into()?;
let untrusted_services: CompactSize64 = (&mut reader).zcash_deserialize_into()?;
let untrusted_services = PeerServices::from_bits_truncate(untrusted_services.into());
let network_id = reader.read_u8()?;
let addr_len: CompactSizeMessage = (&mut reader).zcash_deserialize_into()?;
let addr_len: usize = addr_len.into();
if addr_len > MAX_ADDR_V2_ADDR_SIZE {
return Err(SerializationError::Parse(
"addr field longer than MAX_ADDR_V2_ADDR_SIZE in addrv2 message",
));
}
let addr: Vec<u8> = zcash_deserialize_bytes_external_count(addr_len, &mut reader)?;
let port = reader.read_u16::<BigEndian>()?;
let ip = if network_id == ADDR_V2_IPV4_NETWORK_ID {
AddrV2::ip_addr_from_bytes::<ADDR_V2_IPV4_ADDR_SIZE>(addr)?
} else if network_id == ADDR_V2_IPV6_NETWORK_ID {
AddrV2::ip_addr_from_bytes::<ADDR_V2_IPV6_ADDR_SIZE>(addr)?
} else {
return Ok(AddrV2::Unsupported);
};
Ok(AddrV2::IpAddr {
untrusted_last_seen,
untrusted_services,
addr: canonical_peer_addr(SocketAddr::new(ip, port)),
})
}
}
impl TrustedPreallocate for AddrV2 {
fn max_allocation() -> u64 {
crate::constants::MAX_ADDRS_IN_MESSAGE as u64
}
}