use crate::field::Field;
use crate::packet::{Layer, LayerContext};
use crate::protocols::ipsec::ikev2::payload::{
write_generic_payload_header, IkePayload, PayloadHeaderFields, PayloadType,
};
use crate::protocols::transport::common::{impl_layer_div, impl_layer_object};
use crate::CrafterError;
use crate::Result;
pub const IKE_CONFIG_PAYLOAD_NAME: &str = "IkeConfigPayload";
pub const CONFIG_FIXED_LEN: usize = 4;
pub const CONFIG_ATTRIBUTE_FIXED_LEN: usize = 4;
pub const CFG_REQUEST: u8 = 1;
pub const CFG_REPLY: u8 = 2;
pub const CFG_SET: u8 = 3;
pub const CFG_ACK: u8 = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CfgType {
Request,
Reply,
Set,
Ack,
Unknown(u8),
}
impl CfgType {
pub fn codepoint(self) -> u8 {
match self {
Self::Request => CFG_REQUEST,
Self::Reply => CFG_REPLY,
Self::Set => CFG_SET,
Self::Ack => CFG_ACK,
Self::Unknown(value) => value,
}
}
}
impl From<u8> for CfgType {
fn from(value: u8) -> Self {
match value {
CFG_REQUEST => Self::Request,
CFG_REPLY => Self::Reply,
CFG_SET => Self::Set,
CFG_ACK => Self::Ack,
other => Self::Unknown(other),
}
}
}
impl From<CfgType> for u8 {
fn from(cfg_type: CfgType) -> Self {
cfg_type.codepoint()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConfigAttribute {
attribute_type: u16,
value: Vec<u8>,
}
impl ConfigAttribute {
pub fn new(attribute_type: u16, value: impl Into<Vec<u8>>) -> Self {
Self {
attribute_type: attribute_type & 0x7FFF,
value: value.into(),
}
}
pub fn attribute_type(&self) -> u16 {
self.attribute_type
}
pub fn value(&self) -> &[u8] {
&self.value
}
pub fn encoded_len(&self) -> usize {
CONFIG_ATTRIBUTE_FIXED_LEN + self.value.len()
}
fn write(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&(self.attribute_type & 0x7FFF).to_be_bytes());
out.extend_from_slice(&(self.value.len() as u16).to_be_bytes());
out.extend_from_slice(&self.value);
}
}
#[derive(Debug, Clone)]
pub struct IkeConfigPayload {
cfg_type: Field<u8>,
attributes: Vec<ConfigAttribute>,
header: PayloadHeaderFields,
}
impl IkeConfigPayload {
pub fn new(cfg_type: impl Into<CfgType>) -> Self {
Self {
cfg_type: Field::user(cfg_type.into().codepoint()),
attributes: Vec::new(),
header: PayloadHeaderFields::new(),
}
}
pub fn request() -> Self {
Self::new(CfgType::Request)
}
pub fn reply() -> Self {
Self::new(CfgType::Reply)
}
pub fn cfg_type(mut self, cfg_type: impl Into<CfgType>) -> Self {
self.cfg_type.set_user(cfg_type.into().codepoint());
self
}
pub fn attribute(mut self, attribute: ConfigAttribute) -> Self {
self.attributes.push(attribute);
self
}
pub fn attributes(mut self, attributes: impl Into<Vec<ConfigAttribute>>) -> Self {
self.attributes = attributes.into();
self
}
pub fn next_payload(mut self, next_payload: u8) -> Self {
self.header.set_next_payload(next_payload);
self
}
pub fn payload_length(mut self, length: u16) -> Self {
self.header.set_length(length);
self
}
pub fn critical(mut self, critical: bool) -> Self {
self.header.set_critical(critical);
self
}
pub fn cfg_type_value(&self) -> u8 {
self.cfg_type.value().copied().unwrap_or(0)
}
pub fn cfg_type_kind(&self) -> CfgType {
CfgType::from(self.cfg_type_value())
}
pub fn config_attributes(&self) -> &[ConfigAttribute] {
&self.attributes
}
fn config_body(&self) -> Vec<u8> {
let attributes_len: usize = self
.attributes
.iter()
.map(ConfigAttribute::encoded_len)
.sum();
let mut out = Vec::with_capacity(CONFIG_FIXED_LEN + attributes_len);
out.push(self.cfg_type_value());
out.extend_from_slice(&[0u8, 0u8, 0u8]); for attribute in &self.attributes {
attribute.write(&mut out);
}
out
}
}
impl IkePayload for IkeConfigPayload {
fn payload_type(&self) -> PayloadType {
PayloadType::Configuration
}
fn payload_body(&self, _ctx: &LayerContext<'_>) -> Result<Vec<u8>> {
Ok(self.config_body())
}
fn next_payload_override(&self) -> Option<u8> {
self.header.next_payload_override()
}
fn payload_length_override(&self) -> Option<u16> {
self.header.payload_length_override()
}
fn critical(&self) -> bool {
self.header.critical()
}
}
impl Layer for IkeConfigPayload {
fn name(&self) -> &'static str {
IKE_CONFIG_PAYLOAD_NAME
}
fn summary(&self) -> String {
format!(
"IkeConfigPayload(cfg_type={}, attributes={})",
self.cfg_type_value(),
self.attributes.len()
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
vec![
("cfg_type", self.cfg_type_value().to_string()),
("attributes", self.attributes.len().to_string()),
]
}
fn encoded_len(&self) -> usize {
let attributes_len: usize = self
.attributes
.iter()
.map(ConfigAttribute::encoded_len)
.sum();
super::GENERIC_PAYLOAD_HEADER_LEN + CONFIG_FIXED_LEN + attributes_len
}
fn compile(&self, ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
let body = self.payload_body(ctx)?;
write_generic_payload_header(
out,
ctx,
self.next_payload_override(),
self.critical(),
self.payload_length_override(),
body.len(),
)?;
out.extend_from_slice(&body);
Ok(())
}
impl_layer_object!(IkeConfigPayload);
}
impl_layer_div!(IkeConfigPayload);
pub(crate) fn parse_config_payload_body(bytes: &[u8]) -> Result<IkeConfigPayload> {
if bytes.len() < CONFIG_FIXED_LEN {
return Err(CrafterError::buffer_too_short(
"ikev2.config",
CONFIG_FIXED_LEN,
bytes.len(),
));
}
let cfg_type = bytes[0];
let mut attributes = Vec::new();
let mut offset = CONFIG_FIXED_LEN;
while offset < bytes.len() {
let header_end = offset + CONFIG_ATTRIBUTE_FIXED_LEN;
if bytes.len() < header_end {
return Err(CrafterError::buffer_too_short(
"ikev2.config.attribute",
header_end,
bytes.len(),
));
}
let attribute_type = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) & 0x7FFF;
let length = u16::from_be_bytes([bytes[offset + 2], bytes[offset + 3]]) as usize;
let value_end = header_end + length;
if bytes.len() < value_end {
return Err(CrafterError::buffer_too_short(
"ikev2.config.attribute.value",
value_end,
bytes.len(),
));
}
let value = bytes[header_end..value_end].to_vec();
attributes.push(ConfigAttribute::new(attribute_type, value));
offset = value_end;
}
Ok(IkeConfigPayload::new(cfg_type).attributes(attributes))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::{LayerContext, Packet, Raw};
use crate::protocols::ipsec::ikev2::payload::GENERIC_PAYLOAD_HEADER_LEN;
const ATTR_INTERNAL_IP4_ADDRESS: u16 = 1;
const ATTR_INTERNAL_IP4_DNS: u16 = 3;
fn compile_payload(payload: IkeConfigPayload) -> Vec<u8> {
let packet = Packet::from_layer(payload);
let ctx = LayerContext::new(&packet, 0);
let mut out = Vec::new();
packet.get(0).unwrap().compile(&ctx, &mut out).unwrap();
out
}
fn cfg_reply_payload() -> IkeConfigPayload {
IkeConfigPayload::reply()
.attribute(ConfigAttribute::new(
ATTR_INTERNAL_IP4_ADDRESS,
vec![192u8, 0, 2, 10],
))
.attribute(ConfigAttribute::new(
ATTR_INTERNAL_IP4_DNS,
vec![192u8, 0, 2, 53],
))
}
#[test]
fn config_constants_match_manifest() {
assert_eq!(CONFIG_FIXED_LEN, 4);
assert_eq!(CONFIG_ATTRIBUTE_FIXED_LEN, 4);
assert_eq!(CFG_REQUEST, 1);
assert_eq!(CFG_REPLY, 2);
assert_eq!(CFG_SET, 3);
assert_eq!(CFG_ACK, 4);
assert_eq!(PayloadType::Configuration.codepoint(), 47);
}
#[test]
fn cfg_type_round_trips_through_u8() {
for value in 0u8..=255 {
let cfg_type = CfgType::from(value);
assert_eq!(cfg_type.codepoint(), value);
assert_eq!(u8::from(cfg_type), value);
}
}
#[test]
fn named_cfg_types_map_to_codepoints() {
assert_eq!(CfgType::from(CFG_REQUEST), CfgType::Request);
assert_eq!(CfgType::from(CFG_REPLY), CfgType::Reply);
assert_eq!(CfgType::from(CFG_SET), CfgType::Set);
assert_eq!(CfgType::from(CFG_ACK), CfgType::Ack);
}
#[test]
fn unknown_cfg_type_is_preserved() {
let unassigned = 200u8;
assert_eq!(CfgType::from(unassigned), CfgType::Unknown(unassigned));
assert_eq!(CfgType::Unknown(unassigned).codepoint(), unassigned);
}
#[test]
fn payload_type_and_name() {
let payload = cfg_reply_payload();
assert_eq!(payload.payload_type(), PayloadType::Configuration);
assert_eq!(payload.name(), IKE_CONFIG_PAYLOAD_NAME);
}
#[test]
fn body_lays_out_cfg_type_reserved_then_attributes() {
let payload = cfg_reply_payload();
let body = payload.config_body();
assert_eq!(body[0], CFG_REPLY);
assert_eq!(&body[1..4], &[0, 0, 0]);
assert_eq!(
u16::from_be_bytes([body[4], body[5]]),
ATTR_INTERNAL_IP4_ADDRESS
);
assert_eq!(u16::from_be_bytes([body[6], body[7]]), 4);
assert_eq!(&body[8..12], &[192, 0, 2, 10]);
assert_eq!(
u16::from_be_bytes([body[12], body[13]]),
ATTR_INTERNAL_IP4_DNS
);
assert_eq!(u16::from_be_bytes([body[14], body[15]]), 4);
assert_eq!(&body[16..20], &[192, 0, 2, 53]);
assert_eq!(body.len(), CONFIG_FIXED_LEN + 8 + 8);
}
#[test]
fn attribute_reserved_high_bit_is_masked() {
let attribute = ConfigAttribute::new(0x8000 | ATTR_INTERNAL_IP4_ADDRESS, vec![0u8; 0]);
assert_eq!(attribute.attribute_type(), ATTR_INTERNAL_IP4_ADDRESS);
let mut out = Vec::new();
attribute.write(&mut out);
assert_eq!(
u16::from_be_bytes([out[0], out[1]]),
ATTR_INTERNAL_IP4_ADDRESS
);
assert_eq!(u16::from_be_bytes([out[2], out[3]]), 0); }
#[test]
fn empty_value_attribute_round_trips() {
let payload = IkeConfigPayload::request().attribute(ConfigAttribute::new(
ATTR_INTERNAL_IP4_ADDRESS,
Vec::<u8>::new(),
));
let body = payload.config_body();
assert_eq!(body[0], CFG_REQUEST);
assert_eq!(u16::from_be_bytes([body[6], body[7]]), 0); assert_eq!(body.len(), CONFIG_FIXED_LEN + CONFIG_ATTRIBUTE_FIXED_LEN);
}
#[test]
fn payload_compiles_generic_header_then_body() {
let payload = cfg_reply_payload();
let bytes = compile_payload(payload.clone());
assert_eq!(bytes[0], 0); assert_eq!(bytes[1], 0); let payload_len = u16::from_be_bytes([bytes[2], bytes[3]]) as usize;
assert_eq!(payload_len, bytes.len());
assert_eq!(payload_len, payload.encoded_len());
assert_eq!(
&bytes[GENERIC_PAYLOAD_HEADER_LEN..],
&payload.config_body()[..]
);
}
#[test]
fn payload_honors_generic_header_overrides() {
let payload = cfg_reply_payload()
.next_payload(48)
.critical(true)
.payload_length(0xBEEF);
let bytes = compile_payload(payload);
assert_eq!(bytes[0], 48);
assert_eq!(bytes[1], 0x80); assert_eq!(u16::from_be_bytes([bytes[2], bytes[3]]), 0xBEEF);
}
#[test]
fn chain_next_payload_points_at_config() {
use crate::protocols::ipsec::ikev2::payload::{
following_next_payload, payload_type_for_layer_name, PAYLOAD_CP,
};
assert_eq!(
payload_type_for_layer_name(IKE_CONFIG_PAYLOAD_NAME),
Some(PayloadType::Configuration)
);
let packet: Packet = Packet::from_layer(Raw::from_bytes([0u8; 0])) / cfg_reply_payload();
let ctx = LayerContext::new(&packet, 0);
assert_eq!(following_next_payload(&ctx), PAYLOAD_CP);
}
#[test]
fn round_trip_preserves_cfg_type_and_attributes() {
let payload = cfg_reply_payload();
let bytes = compile_payload(payload.clone());
let parsed = parse_config_payload_body(&bytes[GENERIC_PAYLOAD_HEADER_LEN..]).unwrap();
assert_eq!(parsed.cfg_type_value(), CFG_REPLY);
assert_eq!(parsed.cfg_type_kind(), CfgType::Reply);
let attributes = parsed.config_attributes();
assert_eq!(attributes.len(), 2);
assert_eq!(attributes[0].attribute_type(), ATTR_INTERNAL_IP4_ADDRESS);
assert_eq!(attributes[0].value(), &[192, 0, 2, 10]);
assert_eq!(attributes[1].attribute_type(), ATTR_INTERNAL_IP4_DNS);
assert_eq!(attributes[1].value(), &[192, 0, 2, 53]);
let recompiled = compile_payload(parsed);
assert_eq!(recompiled, bytes);
}
#[test]
fn round_trip_empty_attribute_list() {
let payload = IkeConfigPayload::request();
let bytes = compile_payload(payload.clone());
let parsed = parse_config_payload_body(&bytes[GENERIC_PAYLOAD_HEADER_LEN..]).unwrap();
assert_eq!(parsed.cfg_type_value(), CFG_REQUEST);
assert!(parsed.config_attributes().is_empty());
assert_eq!(compile_payload(parsed), bytes);
}
#[test]
fn parse_rejects_truncated_fixed_header() {
let err = parse_config_payload_body(&[1u8, 0, 0]).unwrap_err();
assert!(matches!(err, CrafterError::BufferTooShort { .. }));
}
#[test]
fn parse_rejects_attribute_length_past_end() {
let body = [1u8, 0, 0, 0, 0x00, 0x01, 0x00, 0x08, 0xAA, 0xBB];
let err = parse_config_payload_body(&body).unwrap_err();
assert!(matches!(err, CrafterError::BufferTooShort { .. }));
}
}