use std::net::{Ipv4Addr, Ipv6Addr};
use super::{
Version,
opcode_data::{MapData, MapProtocol, OpcodeData},
};
#[derive(Debug, PartialEq, Eq)]
pub struct Request {
pub(super) version: Version,
pub(super) lifetime_seconds: u32,
pub(super) client_addr: Ipv6Addr,
pub(super) opcode_data: OpcodeData,
}
impl Request {
pub const MIN_SIZE: usize = 1 + 1 + 2 + 4 + 16;
pub fn encode(&self) -> Vec<u8> {
let Request {
version,
lifetime_seconds,
client_addr,
opcode_data,
} = self;
let mut buf = Vec::with_capacity(Self::MIN_SIZE + opcode_data.encoded_size());
buf.push((*version).into());
buf.push(opcode_data.opcode().into());
buf.push(0);
buf.push(0);
buf.extend_from_slice(&lifetime_seconds.to_be_bytes());
buf.extend_from_slice(&client_addr.octets());
opcode_data.encode_into(&mut buf);
buf
}
pub fn announce(client_addr: Ipv6Addr) -> Request {
Request {
version: Version::Pcp,
lifetime_seconds: 0,
client_addr,
opcode_data: OpcodeData::Announce,
}
}
pub fn mapping(
nonce: [u8; 12],
protocol: MapProtocol,
local_port: u16,
local_ip: Ipv4Addr,
preferred_external_port: Option<u16>,
preferred_external_address: Option<Ipv4Addr>,
lifetime_seconds: u32,
) -> Request {
Request {
version: Version::Pcp,
lifetime_seconds,
client_addr: local_ip.to_ipv6_mapped(),
opcode_data: OpcodeData::MapData(MapData {
nonce,
protocol,
local_port,
external_port: preferred_external_port.unwrap_or_default(),
external_address: preferred_external_address
.unwrap_or(Ipv4Addr::UNSPECIFIED)
.to_ipv6_mapped(),
}),
}
}
#[cfg(test)]
fn random<R: rand::Rng>(opcode: super::Opcode, rng: &mut R) -> Self {
use rand::RngExt;
let opcode_data = OpcodeData::random(opcode, rng);
let addr_octets: [u8; 16] = rng.random();
Request {
version: Version::Pcp,
lifetime_seconds: rng.random(),
client_addr: Ipv6Addr::from(addr_octets),
opcode_data,
}
}
#[cfg(test)]
#[track_caller]
fn decode(buf: &[u8]) -> Self {
let version: Version = buf[0].try_into().unwrap();
let opcode: super::Opcode = buf[1].try_into().unwrap();
let lifetime_bytes: [u8; 4] = buf[4..8].try_into().unwrap();
let lifetime_seconds = u32::from_be_bytes(lifetime_bytes);
let local_ip_bytes: [u8; 16] = buf[8..24].try_into().unwrap();
let client_addr: Ipv6Addr = local_ip_bytes.into();
let opcode_data = OpcodeData::decode(opcode, &buf[24..]).unwrap();
Self {
version,
lifetime_seconds,
client_addr,
opcode_data,
}
}
}
#[cfg(test)]
mod tests {
use rand::SeedableRng;
use super::*;
#[test]
fn test_encode_decode_addr_request() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42);
let request = Request::random(super::super::Opcode::Announce, &mut rng);
let encoded = request.encode();
assert_eq!(request, Request::decode(&encoded));
}
#[test]
fn test_encode_decode_map_request() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42);
let request = Request::random(super::super::Opcode::Map, &mut rng);
let encoded = request.encode();
assert_eq!(request, Request::decode(&encoded));
}
}