//! A PCP response encoding and decoding.
use derive_more::Display;
use n0_error::{e, ensure, stack_error};
use num_enum::{IntoPrimitive, TryFromPrimitive, TryFromPrimitiveError};
use super::{Opcode, Version, opcode_data::OpcodeData};
/// ResultCode in a [`Response`] when it's successful.
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum SuccessCode {
/// Result code indicating a successful response.
Success = 0,
}
/// ResultCode in a [`Response`], when said code is an error.
/// [`SuccessCode`] handles the success case.
///
/// Refer to [RFC 6887 Result Codes](https://datatracker.ietf.org/doc/html/rfc6887#section-7.4)
// NOTE: docs for each variant are largely adapted from the RFC's description of each code.
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive, Display)]
#[repr(u8)]
pub enum ErrorCode {
/// The version number at the start of the PCP Request header is not recognized by the PCP
/// server.
#[display("sent version is not supported")]
UnsuppVersion = 1,
/// The requested operation is disabled for this PCP client, or the PCP client requested an
/// operation that cannot be fulfilled by the PCP server's security policy.
#[display("operation not authorized")]
NotAuthorized = 2,
/// The request could not be successfully parsed.
#[display("could not parse the request")]
MalformedRequest = 3,
/// Unsupported Opcode.
#[display("opcode is not supported")]
UnsuppOpcode = 4,
/// Unsupported option. This error only occurs if the option is in the mandatory-to-process
/// range.
#[display("option is not supported")]
UnsuppOption = 5,
/// Malformed option (e.g., appears too many times, invalid length).
#[display("option could not be parsed")]
MalformedOption = 6,
/// The PCP server or the device it controls is experiencing a network failure of some sort
/// (e.g., has not yet obtained an external IP address). This is a short lifetime error.
#[display("spurious network failure")]
NetworkFailure = 7,
/// Request is well-formed and valid, but the server has insufficient resources to complete the
/// requested operation at this time. This is a short lifetime error.
#[display("not enough resources for this request")]
NoResources = 8,
/// Unsupported transport protocol, e.g., SCTP in a NAT that handles only UDP and TCP. This is
/// a long lifetime error.
#[display("unsupported protocol")]
UnsuppProtocol = 9,
/// This attempt to create a new mapping would exceed this subscriber's port quota. This is a
/// short lifetime error.
#[display("quota exceeded")]
UserExQuota = 10,
/// The suggested external port and/or external address cannot be provided.
#[display("requested external address cannot be provided")]
CannotProvideExternal = 11,
/// The source IP address of the request packet does not match the contents of the PCP Client's
/// IP Address field.
#[display("sender and declared ip do not match")]
AddressMismatch = 12,
/// The PCP server was not able to create the filters in this request.
#[display("excessive reporte peers in filter option")]
ExcessiveRemotePeers = 13,
}
impl std::error::Error for ErrorCode {}
/// Result code of a PCP response.
#[derive(Debug)]
pub enum ResultCode {
/// A success result code. See [`SuccessCode`].
Success,
/// An error code. See [`ErrorCode`].
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(),
}
}
}
/// A PCP successful Response/Notification.
///
/// See [RFC 6887 Response Header](https://datatracker.ietf.org/doc/html/rfc6887#section-7.2)
// NOTE: first two fields are *currently* not used, but are useful for debug purposes
#[allow(unused)]
#[derive(Debug, PartialEq, Eq)]
pub struct Response {
/// Lifetime in seconds that can be assumed by this response.
///
/// For map requests, this lifetime is how long to assume a mapping will last.
pub lifetime_seconds: u32,
/// Epoch time of the server.
pub epoch_time: u32,
/// Data of the resoponse.
pub data: OpcodeData,
}
/// Errors that can occur when decoding a [`Response`] from a server.
#[allow(missing_docs)]
#[non_exhaustive]
#[stack_error(derive, add_meta)]
pub enum DecodeError {
/// Request is too short or is otherwise malformed.
#[error("Response is malformed")]
Malformed {},
/// The [`Response::RESPONSE_INDICATOR`] is not present.
#[error("Packet does not appear to be a response")]
NotAResponse {},
/// The received opcode is not recognized.
#[error("Invalid Opcode received")]
InvalidOpcode {},
/// The received version is not recognized.
#[error("Invalid version received")]
InvalidVersion {},
/// The received result code is not recognized.
#[error("Invalid result code received")]
InvalidResultCode {},
/// The received opcode data could not be decoded.
#[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 {
/// Max size of a PCP packet as indicated in
/// [RFC 6887 Common Request and Response Header Format](https://datatracker.ietf.org/doc/html/rfc6887#section-7)
pub const MAX_SIZE: usize = 1100;
/// Minimum size of an encoded [`Response`] sent by a server to this client.
pub const MIN_SIZE: usize = // parts
1 + // version
1 + // opcode ORd with [`Response::RESPONSE_INDICATOR`]
1 + // reserved
1 + // result code
4 + // lifetime
4 + // epoch time
12; // reserved
/// Indicator ORd into the [`Opcode`] to indicate a response packet.
pub const RESPONSE_INDICATOR: u8 = 1u8 << 7;
/// Decode a response.
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))?;
// buf[2] reserved
// return early if the result code is an error
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);
// buf[12..24] reserved
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 {
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);
// version
buf.push(Version::Pcp.into());
// response indicator and opcode
let opcode: u8 = data.opcode().into();
buf.push(Response::RESPONSE_INDICATOR | opcode);
// reserved
buf.push(0);
// result code
buf.push(ResultCode::Success.into());
// lifetime
for b in lifetime_seconds.to_be_bytes() {
buf.push(b);
}
// epoch
for b in epoch_time.to_be_bytes() {
buf.push(b);
}
// reserved
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() {
// only test vector in the tailscale impl: a map response
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());
}
}
//! A PCP response encoding and decoding.
use Display;
use ;
use ;
use ;
/// ResultCode in a [`Response`] when it's successful.
/// ResultCode in a [`Response`], when said code is an error.
/// [`SuccessCode`] handles the success case.
///
/// Refer to [RFC 6887 Result Codes](https://datatracker.ietf.org/doc/html/rfc6887#section-7.4)
// NOTE: docs for each variant are largely adapted from the RFC's description of each code.
/// Result code of a PCP response.
/// A PCP successful Response/Notification.
///
/// See [RFC 6887 Response Header](https://datatracker.ietf.org/doc/html/rfc6887#section-7.2)
// NOTE: first two fields are *currently* not used, but are useful for debug purposes
/// Errors that can occur when decoding a [`Response`] from a server.