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_NOTIFY_PAYLOAD_NAME: &str = "IkeNotifyPayload";
pub const NOTIFY_FIXED_LEN: usize = 4;
pub const NOTIFY_PROTOCOL_NONE: u8 = 0;
pub const NOTIFY_PROTOCOL_IKE: u8 = 1;
pub const NOTIFY_PROTOCOL_AH: u8 = 2;
pub const NOTIFY_PROTOCOL_ESP: u8 = 3;
pub const NOTIFY_UNSUPPORTED_CRITICAL_PAYLOAD: u16 = 1;
pub const NOTIFY_INVALID_SYNTAX: u16 = 7;
pub const NOTIFY_NO_PROPOSAL_CHOSEN: u16 = 14;
pub const NOTIFY_INVALID_KE_PAYLOAD: u16 = 17;
pub const NOTIFY_AUTHENTICATION_FAILED: u16 = 24;
pub const NOTIFY_INITIAL_CONTACT: u16 = 16384;
pub const NOTIFY_SET_WINDOW_SIZE: u16 = 16385;
pub const NOTIFY_ADDITIONAL_TS_POSSIBLE: u16 = 16386;
pub const NOTIFY_IPCOMP_SUPPORTED: u16 = 16387;
pub const NOTIFY_NAT_DETECTION_SOURCE_IP: u16 = 16388;
pub const NOTIFY_NAT_DETECTION_DESTINATION_IP: u16 = 16389;
pub const NOTIFY_COOKIE: u16 = 16390;
pub const NOTIFY_USE_TRANSPORT_MODE: u16 = 16391;
pub const NOTIFY_REKEY_SA: u16 = 16393;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NotifyType {
UnsupportedCriticalPayload,
InvalidSyntax,
NoProposalChosen,
InvalidKePayload,
AuthenticationFailed,
InitialContact,
SetWindowSize,
AdditionalTsPossible,
IpcompSupported,
NatDetectionSourceIp,
NatDetectionDestinationIp,
Cookie,
UseTransportMode,
RekeySa,
Unknown(u16),
}
impl NotifyType {
pub fn codepoint(self) -> u16 {
match self {
Self::UnsupportedCriticalPayload => NOTIFY_UNSUPPORTED_CRITICAL_PAYLOAD,
Self::InvalidSyntax => NOTIFY_INVALID_SYNTAX,
Self::NoProposalChosen => NOTIFY_NO_PROPOSAL_CHOSEN,
Self::InvalidKePayload => NOTIFY_INVALID_KE_PAYLOAD,
Self::AuthenticationFailed => NOTIFY_AUTHENTICATION_FAILED,
Self::InitialContact => NOTIFY_INITIAL_CONTACT,
Self::SetWindowSize => NOTIFY_SET_WINDOW_SIZE,
Self::AdditionalTsPossible => NOTIFY_ADDITIONAL_TS_POSSIBLE,
Self::IpcompSupported => NOTIFY_IPCOMP_SUPPORTED,
Self::NatDetectionSourceIp => NOTIFY_NAT_DETECTION_SOURCE_IP,
Self::NatDetectionDestinationIp => NOTIFY_NAT_DETECTION_DESTINATION_IP,
Self::Cookie => NOTIFY_COOKIE,
Self::UseTransportMode => NOTIFY_USE_TRANSPORT_MODE,
Self::RekeySa => NOTIFY_REKEY_SA,
Self::Unknown(value) => value,
}
}
pub fn is_error(self) -> bool {
let value = self.codepoint();
(1..=16383).contains(&value)
}
pub fn is_status(self) -> bool {
self.codepoint() >= 16384
}
}
impl From<u16> for NotifyType {
fn from(value: u16) -> Self {
match value {
NOTIFY_UNSUPPORTED_CRITICAL_PAYLOAD => Self::UnsupportedCriticalPayload,
NOTIFY_INVALID_SYNTAX => Self::InvalidSyntax,
NOTIFY_NO_PROPOSAL_CHOSEN => Self::NoProposalChosen,
NOTIFY_INVALID_KE_PAYLOAD => Self::InvalidKePayload,
NOTIFY_AUTHENTICATION_FAILED => Self::AuthenticationFailed,
NOTIFY_INITIAL_CONTACT => Self::InitialContact,
NOTIFY_SET_WINDOW_SIZE => Self::SetWindowSize,
NOTIFY_ADDITIONAL_TS_POSSIBLE => Self::AdditionalTsPossible,
NOTIFY_IPCOMP_SUPPORTED => Self::IpcompSupported,
NOTIFY_NAT_DETECTION_SOURCE_IP => Self::NatDetectionSourceIp,
NOTIFY_NAT_DETECTION_DESTINATION_IP => Self::NatDetectionDestinationIp,
NOTIFY_COOKIE => Self::Cookie,
NOTIFY_USE_TRANSPORT_MODE => Self::UseTransportMode,
NOTIFY_REKEY_SA => Self::RekeySa,
other => Self::Unknown(other),
}
}
}
impl From<NotifyType> for u16 {
fn from(notify_type: NotifyType) -> Self {
notify_type.codepoint()
}
}
#[derive(Debug, Clone)]
pub struct IkeNotifyPayload {
protocol_id: Field<u8>,
spi_size: Field<u8>,
notify_type: Field<u16>,
spi: Vec<u8>,
data: Vec<u8>,
header: PayloadHeaderFields,
}
impl IkeNotifyPayload {
pub fn new(
protocol_id: u8,
notify_type: impl Into<NotifyType>,
data: impl Into<Vec<u8>>,
) -> Self {
Self {
protocol_id: Field::user(protocol_id),
spi_size: Field::unset(),
notify_type: Field::user(notify_type.into().codepoint()),
spi: Vec::new(),
data: data.into(),
header: PayloadHeaderFields::new(),
}
}
pub fn protocol_id(mut self, protocol_id: u8) -> Self {
self.protocol_id.set_user(protocol_id);
self
}
pub fn notify_type(mut self, notify_type: impl Into<NotifyType>) -> Self {
self.notify_type.set_user(notify_type.into().codepoint());
self
}
pub fn spi(mut self, spi: impl Into<Vec<u8>>) -> Self {
self.spi = spi.into();
self
}
pub fn spi_size(mut self, spi_size: u8) -> Self {
self.spi_size.set_user(spi_size);
self
}
pub fn data(mut self, data: impl Into<Vec<u8>>) -> Self {
self.data = data.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 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(self.spi.len() as u8)
}
pub fn notify_message_type(&self) -> NotifyType {
NotifyType::from(self.notify_type.value().copied().unwrap_or(0))
}
pub fn notify_message_type_value(&self) -> u16 {
self.notify_type.value().copied().unwrap_or(0)
}
pub fn spi_bytes(&self) -> &[u8] {
&self.spi
}
pub fn data_bytes(&self) -> &[u8] {
&self.data
}
fn notify_body(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(NOTIFY_FIXED_LEN + self.spi.len() + self.data.len());
out.push(self.protocol_id_value());
out.push(self.effective_spi_size());
out.extend_from_slice(&self.notify_message_type_value().to_be_bytes());
out.extend_from_slice(&self.spi);
out.extend_from_slice(&self.data);
out
}
}
impl IkePayload for IkeNotifyPayload {
fn payload_type(&self) -> PayloadType {
PayloadType::Notify
}
fn payload_body(&self, _ctx: &LayerContext<'_>) -> Result<Vec<u8>> {
Ok(self.notify_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 IkeNotifyPayload {
fn name(&self) -> &'static str {
IKE_NOTIFY_PAYLOAD_NAME
}
fn summary(&self) -> String {
format!(
"IkeNotifyPayload(protocol_id={}, notify_type={}, spi_len={}, data_len={})",
self.protocol_id_value(),
self.notify_message_type_value(),
self.spi.len(),
self.data.len()
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
vec![
("protocol_id", self.protocol_id_value().to_string()),
("notify_type", self.notify_message_type_value().to_string()),
("spi_size", self.effective_spi_size().to_string()),
("spi_len", self.spi.len().to_string()),
("data_len", self.data.len().to_string()),
]
}
fn encoded_len(&self) -> usize {
super::GENERIC_PAYLOAD_HEADER_LEN + NOTIFY_FIXED_LEN + self.spi.len() + self.data.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!(IkeNotifyPayload);
}
impl_layer_div!(IkeNotifyPayload);
pub(crate) fn parse_notify_payload_body(bytes: &[u8]) -> Result<IkeNotifyPayload> {
if bytes.len() < NOTIFY_FIXED_LEN {
return Err(CrafterError::buffer_too_short(
"ikev2.notify",
NOTIFY_FIXED_LEN,
bytes.len(),
));
}
let protocol_id = bytes[0];
let spi_size = bytes[1] as usize;
let notify_type = u16::from_be_bytes([bytes[2], bytes[3]]);
let spi_end = NOTIFY_FIXED_LEN + spi_size;
if bytes.len() < spi_end {
return Err(CrafterError::buffer_too_short(
"ikev2.notify.spi",
spi_end,
bytes.len(),
));
}
let spi = bytes[NOTIFY_FIXED_LEN..spi_end].to_vec();
let data = bytes[spi_end..].to_vec();
Ok(IkeNotifyPayload::new(protocol_id, notify_type, data)
.spi(spi)
.spi_size(bytes[1]))
}
#[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: IkeNotifyPayload) -> 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 nat_detection_source_payload() -> IkeNotifyPayload {
IkeNotifyPayload::new(
NOTIFY_PROTOCOL_NONE,
NotifyType::NatDetectionSourceIp,
(0u8..20).collect::<Vec<u8>>(),
)
}
#[test]
fn notify_constants_match_manifest() {
assert_eq!(NOTIFY_INVALID_SYNTAX, 7);
assert_eq!(NOTIFY_NO_PROPOSAL_CHOSEN, 14);
assert_eq!(NOTIFY_INVALID_KE_PAYLOAD, 17);
assert_eq!(NOTIFY_AUTHENTICATION_FAILED, 24);
assert_eq!(NOTIFY_FIXED_LEN, 4);
assert_eq!(NOTIFY_PROTOCOL_IKE, 1);
assert_eq!(NOTIFY_PROTOCOL_AH, 2);
assert_eq!(NOTIFY_PROTOCOL_ESP, 3);
}
#[test]
fn notify_status_codepoints_match_iana() {
assert_eq!(NOTIFY_INITIAL_CONTACT, 16384);
assert_eq!(NOTIFY_NAT_DETECTION_SOURCE_IP, 16388);
assert_eq!(NOTIFY_NAT_DETECTION_DESTINATION_IP, 16389);
assert_eq!(NOTIFY_USE_TRANSPORT_MODE, 16391);
assert_eq!(NOTIFY_REKEY_SA, 16393);
}
#[test]
fn notify_type_round_trips_through_u16() {
for &value in &[
NOTIFY_UNSUPPORTED_CRITICAL_PAYLOAD,
NOTIFY_INVALID_SYNTAX,
NOTIFY_NO_PROPOSAL_CHOSEN,
NOTIFY_INVALID_KE_PAYLOAD,
NOTIFY_AUTHENTICATION_FAILED,
NOTIFY_INITIAL_CONTACT,
NOTIFY_SET_WINDOW_SIZE,
NOTIFY_ADDITIONAL_TS_POSSIBLE,
NOTIFY_IPCOMP_SUPPORTED,
NOTIFY_NAT_DETECTION_SOURCE_IP,
NOTIFY_NAT_DETECTION_DESTINATION_IP,
NOTIFY_COOKIE,
NOTIFY_USE_TRANSPORT_MODE,
NOTIFY_REKEY_SA,
0,
9999,
40000,
65535,
] {
let notify_type = NotifyType::from(value);
assert_eq!(notify_type.codepoint(), value);
assert_eq!(u16::from(notify_type), value);
}
}
#[test]
fn unknown_notify_type_is_preserved() {
let unassigned = 16450u16;
assert_eq!(
NotifyType::from(unassigned),
NotifyType::Unknown(unassigned)
);
assert_eq!(NotifyType::Unknown(unassigned).codepoint(), unassigned);
}
#[test]
fn notify_type_error_status_classification() {
assert!(NotifyType::InvalidSyntax.is_error());
assert!(!NotifyType::InvalidSyntax.is_status());
assert!(NotifyType::NatDetectionSourceIp.is_status());
assert!(!NotifyType::NatDetectionSourceIp.is_error());
assert!(NotifyType::from(40000).is_status());
assert!(NotifyType::from(100).is_error());
}
#[test]
fn payload_type_is_notify() {
let payload = nat_detection_source_payload();
assert_eq!(payload.payload_type(), PayloadType::Notify);
assert_eq!(payload.name(), IKE_NOTIFY_PAYLOAD_NAME);
}
#[test]
fn body_lays_out_fixed_header_then_spi_and_data() {
let payload = nat_detection_source_payload();
let body = payload.notify_body();
assert_eq!(body[0], NOTIFY_PROTOCOL_NONE); assert_eq!(body[1], 0); assert_eq!(
u16::from_be_bytes([body[2], body[3]]),
NOTIFY_NAT_DETECTION_SOURCE_IP
);
assert_eq!(
&body[NOTIFY_FIXED_LEN..],
&(0u8..20).collect::<Vec<u8>>()[..]
);
assert_eq!(body.len(), NOTIFY_FIXED_LEN + 20);
}
#[test]
fn spi_size_auto_fills_from_spi_length() {
let payload = IkeNotifyPayload::new(NOTIFY_PROTOCOL_ESP, NotifyType::RekeySa, vec![0xAAu8])
.spi(vec![0x01u8, 0x02, 0x03, 0x04]);
assert_eq!(payload.effective_spi_size(), 4);
let body = payload.notify_body();
assert_eq!(body[0], NOTIFY_PROTOCOL_ESP);
assert_eq!(body[1], 4); assert_eq!(u16::from_be_bytes([body[2], body[3]]), NOTIFY_REKEY_SA);
assert_eq!(&body[NOTIFY_FIXED_LEN..NOTIFY_FIXED_LEN + 4], &[1, 2, 3, 4]);
assert_eq!(&body[NOTIFY_FIXED_LEN + 4..], &[0xAA]);
}
#[test]
fn spi_size_override_is_honored() {
let payload =
IkeNotifyPayload::new(NOTIFY_PROTOCOL_AH, NotifyType::Cookie, Vec::<u8>::new())
.spi(vec![0x01u8, 0x02, 0x03, 0x04])
.spi_size(99);
assert_eq!(payload.effective_spi_size(), 99);
assert_eq!(payload.notify_body()[1], 99);
}
#[test]
fn payload_compiles_generic_header_then_body() {
let payload = nat_detection_source_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.notify_body()[..]
);
}
#[test]
fn payload_honors_generic_header_overrides() {
let payload = nat_detection_source_payload()
.next_payload(41)
.critical(true)
.payload_length(0xBEEF);
let bytes = compile_payload(payload);
assert_eq!(bytes[0], 41);
assert_eq!(bytes[1], 0x80); assert_eq!(u16::from_be_bytes([bytes[2], bytes[3]]), 0xBEEF);
}
#[test]
fn payload_chain_next_payload_points_at_notify() {
use crate::protocols::ipsec::ikev2::payload::{
following_next_payload, payload_type_for_layer_name, PAYLOAD_NOTIFY,
};
assert_eq!(
payload_type_for_layer_name(IKE_NOTIFY_PAYLOAD_NAME),
Some(PayloadType::Notify)
);
let packet: Packet =
Packet::from_layer(Raw::from_bytes([0u8; 0])) / nat_detection_source_payload();
let ctx = LayerContext::new(&packet, 0);
assert_eq!(following_next_payload(&ctx), PAYLOAD_NOTIFY);
}
#[test]
fn round_trip_preserves_all_fields() {
let payload =
IkeNotifyPayload::new(NOTIFY_PROTOCOL_ESP, NotifyType::RekeySa, vec![0xDE, 0xAD])
.spi(vec![0x10u8, 0x20, 0x30, 0x40]);
let bytes = compile_payload(payload.clone());
let parsed = parse_notify_payload_body(&bytes[GENERIC_PAYLOAD_HEADER_LEN..]).unwrap();
assert_eq!(parsed.protocol_id_value(), NOTIFY_PROTOCOL_ESP);
assert_eq!(parsed.effective_spi_size(), 4);
assert_eq!(parsed.notify_message_type(), NotifyType::RekeySa);
assert_eq!(parsed.spi_bytes(), &[0x10, 0x20, 0x30, 0x40]);
assert_eq!(parsed.data_bytes(), &[0xDE, 0xAD]);
}
#[test]
fn round_trip_nat_detection_recompiles_byte_for_byte() {
let payload = nat_detection_source_payload();
let bytes = compile_payload(payload);
let parsed = parse_notify_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_notify_payload_body(&[0u8, 0, 0]).unwrap_err();
assert!(matches!(err, CrafterError::BufferTooShort { .. }));
}
#[test]
fn parse_rejects_spi_size_past_end() {
let body = [3u8, 8, 0x40, 0x09, 0xAA, 0xBB];
let err = parse_notify_payload_body(&body).unwrap_err();
assert!(matches!(err, CrafterError::BufferTooShort { .. }));
}
}