use derive_more::Display;
use num_enum::{IntoPrimitive, TryFromPrimitive, TryFromPrimitiveError};
use super::{opcode_data::OpcodeData, Opcode, Version};
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum SuccessCode {
Success = 0,
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive, Display, thiserror::Error,
)]
#[repr(u8)]
pub enum ErrorCode {
#[display(fmt = "sent version is not supported")]
UnsuppVersion = 1,
#[display(fmt = "operation not authorized")]
NotAuthorized = 2,
#[display(fmt = "could not parse the request")]
MalformedRequest = 3,
#[display(fmt = "opcode is not supported")]
UnsuppOpcode = 4,
#[display(fmt = "option is not supported")]
UnsuppOption = 5,
#[display(fmt = "option could not be parsed")]
MalformedOption = 6,
#[display(fmt = "spurious network failure")]
NetworkFailure = 7,
#[display(fmt = "not enough resources for this request")]
NoResources = 8,
#[display(fmt = "unsupported protocol")]
UnsuppProtocol = 9,
#[display(fmt = "quota exceeded")]
UserExQuota = 10,
#[display(fmt = "requested external address cannot be provided")]
CannotProvideExternal = 11,
#[display(fmt = "sender and declared ip do not match")]
AddressMismatch = 12,
#[display(fmt = "excessive reporte peers in filter option")]
ExcessiveRemotePeers = 13,
}
#[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,
}
#[derive(Debug, derive_more::Display, thiserror::Error, PartialEq, Eq)]
pub enum DecodeError {
#[display(fmt = "Response is malformed")]
Malformed,
#[display(fmt = "Packet does not appear to be a response")]
NotAResponse,
#[display(fmt = "Invalid Opcode received")]
InvalidOpcode,
#[display(fmt = "Invalid version received")]
InvalidVersion,
#[display(fmt = "Invalid result code received")]
InvalidResultCode,
#[display(fmt = "Invalid opcode data received")]
InvalidOpcodeData,
}
#[derive(Debug, derive_more::Display, thiserror::Error, PartialEq, Eq)]
pub enum Error {
DecodeError(DecodeError),
ErrorCode(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> {
if buf.len() < Self::MIN_SIZE || buf.len() > Self::MAX_SIZE {
return Err(Error::DecodeError(DecodeError::Malformed));
}
let _version: Version = buf[0]
.try_into()
.map_err(|_| Error::DecodeError(DecodeError::InvalidVersion))?;
let opcode = buf[1];
if opcode & Self::RESPONSE_INDICATOR != Self::RESPONSE_INDICATOR {
return Err(Error::DecodeError(DecodeError::NotAResponse));
}
let opcode: Opcode = (opcode & !Self::RESPONSE_INDICATOR)
.try_into()
.map_err(|_| Error::DecodeError(DecodeError::InvalidOpcode))?;
let result_code: ResultCode = buf[3]
.try_into()
.map_err(|_| Error::DecodeError(DecodeError::InvalidResultCode))?;
match result_code {
ResultCode::Success => {}
ResultCode::Error(error_code) => return Err(Error::ErrorCode(error_code)),
}
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(|_| Error::DecodeError(DecodeError::InvalidOpcodeData))?;
Ok(Response {
lifetime_seconds,
epoch_time,
data,
})
}
#[cfg(test)]
fn random<R: rand::Rng>(opcode: Opcode, rng: &mut R) -> Self {
let data = OpcodeData::random(opcode, rng);
Self {
lifetime_seconds: rng.gen(),
epoch_time: rng.gen(),
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 super::*;
use rand::SeedableRng;
#[test]
fn test_decode_external_addr_response() {
let mut gen = rand_chacha::ChaCha8Rng::seed_from_u64(42);
let response = Response::random(Opcode::Announce, &mut gen);
let encoded = response.encode();
assert_eq!(Ok(response), Response::decode(&encoded));
}
#[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!(Ok(response), Response::decode(&encoded));
}
}