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_DELETE_PAYLOAD_NAME: &str = "IkeDeletePayload";
pub const DELETE_FIXED_LEN: usize = 4;
pub const DELETE_PROTOCOL_IKE: u8 = 1;
pub const DELETE_PROTOCOL_AH: u8 = 2;
pub const DELETE_PROTOCOL_ESP: u8 = 3;
#[derive(Debug, Clone)]
pub struct IkeDeletePayload {
protocol_id: Field<u8>,
spi_size: Field<u8>,
num_spis: Field<u16>,
spis: Vec<Vec<u8>>,
header: PayloadHeaderFields,
}
impl IkeDeletePayload {
pub fn new(protocol_id: u8) -> Self {
Self {
protocol_id: Field::user(protocol_id),
spi_size: Field::unset(),
num_spis: Field::unset(),
spis: Vec::new(),
header: PayloadHeaderFields::new(),
}
}
pub fn protocol_id(mut self, protocol_id: u8) -> Self {
self.protocol_id.set_user(protocol_id);
self
}
pub fn spi(mut self, spi: impl Into<Vec<u8>>) -> Self {
self.spis.push(spi.into());
self
}
pub fn spis(mut self, spis: impl IntoIterator<Item = Vec<u8>>) -> Self {
self.spis = spis.into_iter().collect();
self
}
pub fn spi_size(mut self, spi_size: u8) -> Self {
self.spi_size.set_user(spi_size);
self
}
pub fn num_spis(mut self, num_spis: u16) -> Self {
self.num_spis.set_user(num_spis);
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 protocol_id_value(&self) -> u8 {
self.protocol_id.value().copied().unwrap_or(0)
}
pub fn effective_spi_size(&self) -> u8 {
self.spi_size
.value()
.copied()
.unwrap_or_else(|| self.spis.first().map(|spi| spi.len() as u8).unwrap_or(0))
}
pub fn effective_num_spis(&self) -> u16 {
self.num_spis
.value()
.copied()
.unwrap_or(self.spis.len() as u16)
}
pub fn spis_list(&self) -> &[Vec<u8>] {
&self.spis
}
fn delete_body(&self) -> Vec<u8> {
let spis_len: usize = self.spis.iter().map(|spi| spi.len()).sum();
let mut out = Vec::with_capacity(DELETE_FIXED_LEN + spis_len);
out.push(self.protocol_id_value());
out.push(self.effective_spi_size());
out.extend_from_slice(&self.effective_num_spis().to_be_bytes());
for spi in &self.spis {
out.extend_from_slice(spi);
}
out
}
}
impl IkePayload for IkeDeletePayload {
fn payload_type(&self) -> PayloadType {
PayloadType::Delete
}
fn payload_body(&self, _ctx: &LayerContext<'_>) -> Result<Vec<u8>> {
Ok(self.delete_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 IkeDeletePayload {
fn name(&self) -> &'static str {
IKE_DELETE_PAYLOAD_NAME
}
fn summary(&self) -> String {
format!(
"IkeDeletePayload(protocol_id={}, spi_size={}, num_spis={})",
self.protocol_id_value(),
self.effective_spi_size(),
self.effective_num_spis()
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
vec![
("protocol_id", self.protocol_id_value().to_string()),
("spi_size", self.effective_spi_size().to_string()),
("num_spis", self.effective_num_spis().to_string()),
]
}
fn encoded_len(&self) -> usize {
let spis_len: usize = self.spis.iter().map(|spi| spi.len()).sum();
super::GENERIC_PAYLOAD_HEADER_LEN + DELETE_FIXED_LEN + spis_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!(IkeDeletePayload);
}
impl_layer_div!(IkeDeletePayload);
pub(crate) fn parse_delete_payload_body(bytes: &[u8]) -> Result<IkeDeletePayload> {
if bytes.len() < DELETE_FIXED_LEN {
return Err(CrafterError::buffer_too_short(
"ikev2.delete",
DELETE_FIXED_LEN,
bytes.len(),
));
}
let protocol_id = bytes[0];
let spi_size = bytes[1] as usize;
let num_spis = u16::from_be_bytes([bytes[2], bytes[3]]) as usize;
let spis_end = DELETE_FIXED_LEN + spi_size * num_spis;
if bytes.len() < spis_end {
return Err(CrafterError::buffer_too_short(
"ikev2.delete.spis",
spis_end,
bytes.len(),
));
}
let mut spis = Vec::with_capacity(num_spis);
let mut offset = DELETE_FIXED_LEN;
for _ in 0..num_spis {
spis.push(bytes[offset..offset + spi_size].to_vec());
offset += spi_size;
}
Ok(IkeDeletePayload::new(protocol_id)
.spis(spis)
.spi_size(bytes[1])
.num_spis(u16::from_be_bytes([bytes[2], bytes[3]])))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::{LayerContext, Packet, Raw};
use crate::protocols::ipsec::ikev2::payload::GENERIC_PAYLOAD_HEADER_LEN;
fn compile_payload(payload: IkeDeletePayload) -> 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 esp_delete_payload() -> IkeDeletePayload {
IkeDeletePayload::new(DELETE_PROTOCOL_ESP)
.spi(vec![0x11u8, 0x22, 0x33, 0x44])
.spi(vec![0xAAu8, 0xBB, 0xCC, 0xDD])
}
#[test]
fn delete_constants_match_rfc() {
assert_eq!(DELETE_FIXED_LEN, 4);
assert_eq!(DELETE_PROTOCOL_IKE, 1);
assert_eq!(DELETE_PROTOCOL_AH, 2);
assert_eq!(DELETE_PROTOCOL_ESP, 3);
}
#[test]
fn payload_type_is_delete() {
let payload = esp_delete_payload();
assert_eq!(payload.payload_type(), PayloadType::Delete);
assert_eq!(payload.name(), IKE_DELETE_PAYLOAD_NAME);
}
#[test]
fn ike_delete_has_no_spis() {
let payload = IkeDeletePayload::new(DELETE_PROTOCOL_IKE);
assert_eq!(payload.effective_spi_size(), 0);
assert_eq!(payload.effective_num_spis(), 0);
let body = payload.delete_body();
assert_eq!(body[0], DELETE_PROTOCOL_IKE);
assert_eq!(body[1], 0); assert_eq!(u16::from_be_bytes([body[2], body[3]]), 0); assert_eq!(body.len(), DELETE_FIXED_LEN);
}
#[test]
fn body_lays_out_fixed_header_then_spis() {
let payload = esp_delete_payload();
let body = payload.delete_body();
assert_eq!(body[0], DELETE_PROTOCOL_ESP); assert_eq!(body[1], 4); assert_eq!(u16::from_be_bytes([body[2], body[3]]), 2); assert_eq!(
&body[DELETE_FIXED_LEN..DELETE_FIXED_LEN + 4],
&[0x11, 0x22, 0x33, 0x44]
);
assert_eq!(&body[DELETE_FIXED_LEN + 4..], &[0xAA, 0xBB, 0xCC, 0xDD]);
assert_eq!(body.len(), DELETE_FIXED_LEN + 8);
}
#[test]
fn spi_size_and_num_spis_auto_fill() {
let payload = esp_delete_payload();
assert_eq!(payload.effective_spi_size(), 4);
assert_eq!(payload.effective_num_spis(), 2);
}
#[test]
fn spi_size_override_is_honored() {
let payload = esp_delete_payload().spi_size(99);
assert_eq!(payload.effective_spi_size(), 99);
assert_eq!(payload.delete_body()[1], 99);
}
#[test]
fn num_spis_override_is_honored() {
let payload = esp_delete_payload().num_spis(7);
assert_eq!(payload.effective_num_spis(), 7);
let body = payload.delete_body();
assert_eq!(u16::from_be_bytes([body[2], body[3]]), 7);
}
#[test]
fn payload_compiles_generic_header_then_body() {
let payload = esp_delete_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.delete_body()[..]
);
}
#[test]
fn payload_honors_generic_header_overrides() {
let payload = esp_delete_payload()
.next_payload(42)
.critical(true)
.payload_length(0xBEEF);
let bytes = compile_payload(payload);
assert_eq!(bytes[0], 42);
assert_eq!(bytes[1], 0x80); assert_eq!(u16::from_be_bytes([bytes[2], bytes[3]]), 0xBEEF);
}
#[test]
fn payload_chain_next_payload_points_at_delete() {
use crate::protocols::ipsec::ikev2::payload::{
following_next_payload, payload_type_for_layer_name, PAYLOAD_DELETE,
};
assert_eq!(
payload_type_for_layer_name(IKE_DELETE_PAYLOAD_NAME),
Some(PayloadType::Delete)
);
let packet: Packet = Packet::from_layer(Raw::from_bytes([0u8; 0])) / esp_delete_payload();
let ctx = LayerContext::new(&packet, 0);
assert_eq!(following_next_payload(&ctx), PAYLOAD_DELETE);
}
#[test]
fn round_trip_preserves_spis_and_counts() {
let payload = esp_delete_payload();
let bytes = compile_payload(payload.clone());
let parsed = parse_delete_payload_body(&bytes[GENERIC_PAYLOAD_HEADER_LEN..]).unwrap();
assert_eq!(parsed.protocol_id_value(), DELETE_PROTOCOL_ESP);
assert_eq!(parsed.effective_spi_size(), 4);
assert_eq!(parsed.effective_num_spis(), 2);
assert_eq!(
parsed.spis_list(),
&[
vec![0x11u8, 0x22, 0x33, 0x44],
vec![0xAAu8, 0xBB, 0xCC, 0xDD]
]
);
}
#[test]
fn round_trip_recompiles_byte_for_byte() {
let payload = esp_delete_payload();
let bytes = compile_payload(payload);
let parsed = parse_delete_payload_body(&bytes[GENERIC_PAYLOAD_HEADER_LEN..]).unwrap();
let recompiled = compile_payload(parsed);
assert_eq!(recompiled, bytes);
}
#[test]
fn round_trip_ike_delete_recompiles_byte_for_byte() {
let payload = IkeDeletePayload::new(DELETE_PROTOCOL_IKE);
let bytes = compile_payload(payload);
let parsed = parse_delete_payload_body(&bytes[GENERIC_PAYLOAD_HEADER_LEN..]).unwrap();
let recompiled = compile_payload(parsed);
assert_eq!(recompiled, bytes);
}
#[test]
fn parse_rejects_truncated_fixed_header() {
let err = parse_delete_payload_body(&[0u8, 0, 0]).unwrap_err();
assert!(matches!(err, CrafterError::BufferTooShort { .. }));
}
#[test]
fn parse_rejects_spis_past_end() {
let body = [3u8, 4, 0x00, 0x02, 0x11, 0x22, 0x33, 0x44];
let err = parse_delete_payload_body(&body).unwrap_err();
assert!(matches!(err, CrafterError::BufferTooShort { .. }));
}
}