use derive_more::Display;
use n0_error::{e, ensure, stack_error};
use num_enum::{IntoPrimitive, TryFromPrimitive, TryFromPrimitiveError};
use super::{Opcode, Version, opcode_data::OpcodeData};
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum SuccessCode {
Success = 0,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive, Display)]
#[repr(u8)]
pub enum ErrorCode {
#[display("sent version is not supported")]
UnsuppVersion = 1,
#[display("operation not authorized")]
NotAuthorized = 2,
#[display("could not parse the request")]
MalformedRequest = 3,
#[display("opcode is not supported")]
UnsuppOpcode = 4,
#[display("option is not supported")]
UnsuppOption = 5,
#[display("option could not be parsed")]
MalformedOption = 6,
#[display("spurious network failure")]
NetworkFailure = 7,
#[display("not enough resources for this request")]
NoResources = 8,
#[display("unsupported protocol")]
UnsuppProtocol = 9,
#[display("quota exceeded")]
UserExQuota = 10,
#[display("requested external address cannot be provided")]
CannotProvideExternal = 11,
#[display("sender and declared ip do not match")]
AddressMismatch = 12,
#[display("excessive reporte peers in filter option")]
ExcessiveRemotePeers = 13,
}
impl std::error::Error for ErrorCode {}
#[derive(Debug)]
pub enum ResultCode {
Success,
Error(ErrorCode),
}
impl TryFrom<u8> for ResultCode {
type Error = TryFromPrimitiveError<ErrorCode>;
fn try_from(value: u8) -> Result<Self, TryFromPrimitiveError<ErrorCode>> {
if let Ok(SuccessCode::Success) = SuccessCode::try_from(value) {
Ok(ResultCode::Success)
} else {
ErrorCode::try_from(value).map(ResultCode::Error)
}
}
}
impl From<ResultCode> for u8 {
fn from(value: ResultCode) -> Self {
match value {
ResultCode::Success => SuccessCode::Success.into(),
ResultCode::Error(e) => e.into(),
}
}
}
#[allow(unused)]
#[derive(Debug, PartialEq, Eq)]
pub struct Response {
pub lifetime_seconds: u32,
pub epoch_time: u32,
pub data: OpcodeData,
}
#[allow(missing_docs)]
#[non_exhaustive]
#[stack_error(derive, add_meta)]
pub enum DecodeError {
#[error("Response is malformed")]
Malformed {},
#[error("Packet does not appear to be a response")]
NotAResponse {},
#[error("Invalid Opcode received")]
InvalidOpcode {},
#[error("Invalid version received")]
InvalidVersion {},
#[error("Invalid result code received")]
InvalidResultCode {},
#[error("Invalid opcode data received")]
InvalidOpcodeData {},
}
#[stack_error(derive, add_meta, from_sources)]
pub enum Error {
#[error(transparent)]
DecodeError { source: DecodeError },
#[error("PCP error code")]
ErrorCode {
#[error(std_err)]
source: ErrorCode,
},
}
impl Response {
pub const MAX_SIZE: usize = 1100;
pub const MIN_SIZE: usize = 1 + 1 + 1 + 1 + 4 + 4 + 12;
pub const RESPONSE_INDICATOR: u8 = 1u8 << 7;
pub fn decode(buf: &[u8]) -> Result<Self, Error> {
ensure!(
Self::MIN_SIZE <= buf.len() && buf.len() <= Self::MAX_SIZE,
DecodeError::Malformed
);
let _version: Version = buf[0]
.try_into()
.map_err(|_| e!(DecodeError::InvalidVersion))?;
let opcode = buf[1];
ensure!(
opcode & Self::RESPONSE_INDICATOR == Self::RESPONSE_INDICATOR,
DecodeError::NotAResponse
);
let opcode: Opcode = (opcode & !Self::RESPONSE_INDICATOR)
.try_into()
.map_err(|_| e!(DecodeError::InvalidOpcode))?;
let result_code: ResultCode = buf[3]
.try_into()
.map_err(|_| e!(DecodeError::InvalidResultCode))?;
match result_code {
ResultCode::Success => {}
ResultCode::Error(error_code) => return Err(error_code.into()),
}
let lifetime_bytes = buf[4..8].try_into().expect("slice has the right len");
let lifetime_seconds = u32::from_be_bytes(lifetime_bytes);
let epoch_bytes = buf[8..12].try_into().expect("slice has the right len");
let epoch_time = u32::from_be_bytes(epoch_bytes);
let data = OpcodeData::decode(opcode, &buf[24..])
.map_err(|_| e!(DecodeError::InvalidOpcodeData))?;
Ok(Response {
lifetime_seconds,
epoch_time,
data,
})
}
#[cfg(test)]
fn random<R: rand::Rng>(opcode: Opcode, rng: &mut R) -> Self {
use rand::RngExt;
let data = OpcodeData::random(opcode, rng);
Self {
lifetime_seconds: rng.random(),
epoch_time: rng.random(),
data,
}
}
#[cfg(test)]
fn encode(&self) -> Vec<u8> {
let Response {
lifetime_seconds,
epoch_time,
data,
} = self;
let mut buf = Vec::with_capacity(Self::MIN_SIZE);
buf.push(Version::Pcp.into());
let opcode: u8 = data.opcode().into();
buf.push(Response::RESPONSE_INDICATOR | opcode);
buf.push(0);
buf.push(ResultCode::Success.into());
for b in lifetime_seconds.to_be_bytes() {
buf.push(b);
}
for b in epoch_time.to_be_bytes() {
buf.push(b);
}
for _ in 12..Response::MIN_SIZE {
buf.push(0)
}
data.encode_into(&mut buf);
buf
}
}
#[cfg(test)]
mod tests {
use rand::SeedableRng;
use super::*;
#[test]
fn test_decode_external_addr_response() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42);
let response = Response::random(Opcode::Announce, &mut rng);
let encoded = response.encode();
assert_eq!(response, Response::decode(&encoded).unwrap());
}
#[test]
fn test_decode_known_response_vector() {
let encoded = [
2, 129, 0, 0, 0, 0, 28, 32, 0, 2, 155, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129,
112, 9, 24, 241, 208, 251, 45, 157, 76, 10, 188, 17, 0, 0, 0, 4, 210, 4, 210, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 255, 255, 135, 180, 175, 246,
];
let response = Response::decode(&encoded).unwrap();
assert_eq!(&response.encode(), &encoded);
}
#[test]
fn test_encode_decode_map_response() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42);
let response = Response::random(Opcode::Map, &mut rng);
let encoded = response.encode();
assert_eq!(response, Response::decode(&encoded).unwrap());
}
}