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_SA_PAYLOAD_NAME: &str = "IkeSaPayload";
pub const PROPOSAL_FIXED_LEN: usize = 8;
pub const TRANSFORM_FIXED_LEN: usize = 8;
pub const SUBSTRUC_LAST: u8 = 0;
pub const SUBSTRUC_MORE_PROPOSAL: u8 = 2;
pub const SUBSTRUC_MORE_TRANSFORM: u8 = 3;
pub const PROTOCOL_ID_IKE: u8 = 1;
pub const PROTOCOL_ID_AH: u8 = 2;
pub const PROTOCOL_ID_ESP: u8 = 3;
pub const TRANSFORM_TYPE_ENCR: u8 = 1;
pub const TRANSFORM_TYPE_PRF: u8 = 2;
pub const TRANSFORM_TYPE_INTEG: u8 = 3;
pub const TRANSFORM_TYPE_DH: u8 = 4;
pub const TRANSFORM_TYPE_ESN: u8 = 5;
pub const ATTRIBUTE_FORMAT_TV: u16 = 0x8000;
pub const ATTRIBUTE_TYPE_KEY_LENGTH: u16 = 14;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransformAttribute {
attribute_type: u16,
tv_value: Option<u16>,
tlv_value: Option<Vec<u8>>,
}
impl TransformAttribute {
pub fn tv(attribute_type: u16, value: u16) -> Self {
Self {
attribute_type: attribute_type & !ATTRIBUTE_FORMAT_TV,
tv_value: Some(value),
tlv_value: None,
}
}
pub fn tlv(attribute_type: u16, value: impl Into<Vec<u8>>) -> Self {
Self {
attribute_type: attribute_type & !ATTRIBUTE_FORMAT_TV,
tv_value: None,
tlv_value: Some(value.into()),
}
}
pub fn key_length(bits: u16) -> Self {
Self::tv(ATTRIBUTE_TYPE_KEY_LENGTH, bits)
}
pub fn attribute_type(&self) -> u16 {
self.attribute_type
}
pub fn is_tv(&self) -> bool {
self.tv_value.is_some()
}
pub fn tv_value(&self) -> Option<u16> {
self.tv_value
}
pub fn tlv_value(&self) -> Option<&[u8]> {
self.tlv_value.as_deref()
}
pub fn encoded_len(&self) -> usize {
match (&self.tv_value, &self.tlv_value) {
(Some(_), _) => 4,
(None, Some(value)) => 4 + value.len(),
(None, None) => 4,
}
}
fn write(&self, out: &mut Vec<u8>) {
match (&self.tv_value, &self.tlv_value) {
(Some(value), _) => {
let type_word = ATTRIBUTE_FORMAT_TV | (self.attribute_type & !ATTRIBUTE_FORMAT_TV);
out.extend_from_slice(&type_word.to_be_bytes());
out.extend_from_slice(&value.to_be_bytes());
}
(None, Some(value)) => {
let type_word = self.attribute_type & !ATTRIBUTE_FORMAT_TV;
out.extend_from_slice(&type_word.to_be_bytes());
out.extend_from_slice(&(value.len() as u16).to_be_bytes());
out.extend_from_slice(value);
}
(None, None) => {
let type_word = self.attribute_type & !ATTRIBUTE_FORMAT_TV;
out.extend_from_slice(&type_word.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes());
}
}
}
}
#[derive(Debug, Clone)]
pub struct Transform {
transform_type: u8,
transform_id: u16,
attributes: Vec<TransformAttribute>,
last_substruc: Field<u8>,
length: Field<u16>,
}
impl Transform {
pub fn new(transform_type: u8, transform_id: u16) -> Self {
Self {
transform_type,
transform_id,
attributes: Vec::new(),
last_substruc: Field::unset(),
length: Field::unset(),
}
}
pub fn encryption(transform_id: u16) -> Self {
Self::new(TRANSFORM_TYPE_ENCR, transform_id)
}
pub fn prf(transform_id: u16) -> Self {
Self::new(TRANSFORM_TYPE_PRF, transform_id)
}
pub fn integrity(transform_id: u16) -> Self {
Self::new(TRANSFORM_TYPE_INTEG, transform_id)
}
pub fn key_exchange(transform_id: u16) -> Self {
Self::new(TRANSFORM_TYPE_DH, transform_id)
}
pub fn extended_sequence_numbers(transform_id: u16) -> Self {
Self::new(TRANSFORM_TYPE_ESN, transform_id)
}
pub fn with_attribute(mut self, attribute: TransformAttribute) -> Self {
self.attributes.push(attribute);
self
}
pub fn push_attribute(&mut self, attribute: TransformAttribute) {
self.attributes.push(attribute);
}
pub fn last_substruc(mut self, last_substruc: u8) -> Self {
self.last_substruc.set_user(last_substruc);
self
}
pub fn length(mut self, length: u16) -> Self {
self.length.set_user(length);
self
}
pub fn transform_type(&self) -> u8 {
self.transform_type
}
pub fn transform_id(&self) -> u16 {
self.transform_id
}
pub fn attributes(&self) -> &[TransformAttribute] {
&self.attributes
}
pub fn encoded_len(&self) -> usize {
let attrs: usize = self.attributes.iter().map(|a| a.encoded_len()).sum();
TRANSFORM_FIXED_LEN + attrs
}
fn write(&self, out: &mut Vec<u8>, is_last: bool) {
let last_substruc = self.last_substruc.value().copied().unwrap_or(if is_last {
SUBSTRUC_LAST
} else {
SUBSTRUC_MORE_TRANSFORM
});
let length = self
.length
.value()
.copied()
.unwrap_or(self.encoded_len() as u16);
out.push(last_substruc);
out.push(0); out.extend_from_slice(&length.to_be_bytes());
out.push(self.transform_type);
out.push(0); out.extend_from_slice(&self.transform_id.to_be_bytes());
for attribute in &self.attributes {
attribute.write(out);
}
}
}
#[derive(Debug, Clone)]
pub struct Proposal {
num: u8,
protocol_id: u8,
spi: Vec<u8>,
transforms: Vec<Transform>,
last_substruc: Field<u8>,
length: Field<u16>,
spi_size: Field<u8>,
num_transforms: Field<u8>,
}
impl Proposal {
pub fn new(num: u8, protocol_id: u8) -> Self {
Self {
num,
protocol_id,
spi: Vec::new(),
transforms: Vec::new(),
last_substruc: Field::unset(),
length: Field::unset(),
spi_size: Field::unset(),
num_transforms: Field::unset(),
}
}
pub fn spi(mut self, spi: impl Into<Vec<u8>>) -> Self {
self.spi = spi.into();
self
}
pub fn with_transform(mut self, transform: Transform) -> Self {
self.transforms.push(transform);
self
}
pub fn push_transform(&mut self, transform: Transform) {
self.transforms.push(transform);
}
pub fn last_substruc(mut self, last_substruc: u8) -> Self {
self.last_substruc.set_user(last_substruc);
self
}
pub fn length(mut self, length: u16) -> Self {
self.length.set_user(length);
self
}
pub fn spi_size(mut self, spi_size: u8) -> Self {
self.spi_size.set_user(spi_size);
self
}
pub fn num_transforms(mut self, num_transforms: u8) -> Self {
self.num_transforms.set_user(num_transforms);
self
}
pub fn proposal_num(&self) -> u8 {
self.num
}
pub fn protocol_id(&self) -> u8 {
self.protocol_id
}
pub fn spi_bytes(&self) -> &[u8] {
&self.spi
}
pub fn transforms(&self) -> &[Transform] {
&self.transforms
}
pub fn encoded_len(&self) -> usize {
let transforms: usize = self.transforms.iter().map(|t| t.encoded_len()).sum();
PROPOSAL_FIXED_LEN + self.spi.len() + transforms
}
fn write(&self, out: &mut Vec<u8>, is_last: bool) {
let last_substruc = self.last_substruc.value().copied().unwrap_or(if is_last {
SUBSTRUC_LAST
} else {
SUBSTRUC_MORE_PROPOSAL
});
let length = self
.length
.value()
.copied()
.unwrap_or(self.encoded_len() as u16);
let spi_size = self
.spi_size
.value()
.copied()
.unwrap_or(self.spi.len() as u8);
let num_transforms = self
.num_transforms
.value()
.copied()
.unwrap_or(self.transforms.len() as u8);
out.push(last_substruc);
out.push(0); out.extend_from_slice(&length.to_be_bytes());
out.push(self.num);
out.push(self.protocol_id);
out.push(spi_size);
out.push(num_transforms);
out.extend_from_slice(&self.spi);
let last_index = self.transforms.len().saturating_sub(1);
for (index, transform) in self.transforms.iter().enumerate() {
transform.write(out, index == last_index);
}
}
}
#[derive(Debug, Clone)]
pub struct IkeSaPayload {
proposals: Vec<Proposal>,
header: PayloadHeaderFields,
}
impl IkeSaPayload {
pub fn new() -> Self {
Self {
proposals: Vec::new(),
header: PayloadHeaderFields::new(),
}
}
pub fn with_proposal(mut self, proposal: Proposal) -> Self {
self.proposals.push(proposal);
self
}
pub fn push_proposal(&mut self, proposal: Proposal) {
self.proposals.push(proposal);
}
pub fn proposals(&self) -> &[Proposal] {
&self.proposals
}
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
}
fn proposals_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
let last_index = self.proposals.len().saturating_sub(1);
for (index, proposal) in self.proposals.iter().enumerate() {
proposal.write(&mut out, index == last_index);
}
out
}
}
impl Default for IkeSaPayload {
fn default() -> Self {
Self::new()
}
}
impl IkePayload for IkeSaPayload {
fn payload_type(&self) -> PayloadType {
PayloadType::SecurityAssociation
}
fn payload_body(&self, _ctx: &LayerContext<'_>) -> Result<Vec<u8>> {
Ok(self.proposals_bytes())
}
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 IkeSaPayload {
fn name(&self) -> &'static str {
IKE_SA_PAYLOAD_NAME
}
fn summary(&self) -> String {
let transforms: usize = self.proposals.iter().map(|p| p.transforms.len()).sum();
format!(
"IkeSaPayload(proposals={}, transforms={})",
self.proposals.len(),
transforms
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = vec![("proposals", self.proposals.len().to_string())];
for proposal in &self.proposals {
let transforms = proposal
.transforms
.iter()
.map(|t| format!("{}:{}", t.transform_type, t.transform_id))
.collect::<Vec<_>>()
.join(",");
fields.push((
"proposal",
format!(
"num={} protocol={} spi_len={} transforms=[{}]",
proposal.num,
proposal.protocol_id,
proposal.spi.len(),
transforms
),
));
}
fields
}
fn encoded_len(&self) -> usize {
let body: usize = self.proposals.iter().map(|p| p.encoded_len()).sum();
super::GENERIC_PAYLOAD_HEADER_LEN + body
}
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!(IkeSaPayload);
}
impl_layer_div!(IkeSaPayload);
pub(crate) fn parse_transform_attribute(bytes: &[u8]) -> Result<(TransformAttribute, usize)> {
if bytes.len() < 4 {
return Err(CrafterError::buffer_too_short(
"ikev2.sa.attribute",
4,
bytes.len(),
));
}
let type_word = u16::from_be_bytes([bytes[0], bytes[1]]);
let attribute_type = type_word & !ATTRIBUTE_FORMAT_TV;
if type_word & ATTRIBUTE_FORMAT_TV != 0 {
let value = u16::from_be_bytes([bytes[2], bytes[3]]);
Ok((TransformAttribute::tv(attribute_type, value), 4))
} else {
let len = u16::from_be_bytes([bytes[2], bytes[3]]) as usize;
let end = 4 + len;
if bytes.len() < end {
return Err(CrafterError::buffer_too_short(
"ikev2.sa.attribute.value",
end,
bytes.len(),
));
}
Ok((
TransformAttribute::tlv(attribute_type, bytes[4..end].to_vec()),
end,
))
}
}
pub(crate) fn parse_transform(bytes: &[u8]) -> Result<(Transform, usize)> {
if bytes.len() < TRANSFORM_FIXED_LEN {
return Err(CrafterError::buffer_too_short(
"ikev2.sa.transform",
TRANSFORM_FIXED_LEN,
bytes.len(),
));
}
let length = u16::from_be_bytes([bytes[2], bytes[3]]) as usize;
if length < TRANSFORM_FIXED_LEN || bytes.len() < length {
return Err(CrafterError::buffer_too_short(
"ikev2.sa.transform.length",
length.max(TRANSFORM_FIXED_LEN),
bytes.len(),
));
}
let transform_type = bytes[4];
let transform_id = u16::from_be_bytes([bytes[6], bytes[7]]);
let mut transform = Transform::new(transform_type, transform_id);
let mut offset = TRANSFORM_FIXED_LEN;
while offset < length {
let (attribute, consumed) = parse_transform_attribute(&bytes[offset..length])?;
transform.push_attribute(attribute);
offset += consumed;
}
Ok((transform, length))
}
pub(crate) fn parse_proposal(bytes: &[u8]) -> Result<(Proposal, usize)> {
if bytes.len() < PROPOSAL_FIXED_LEN {
return Err(CrafterError::buffer_too_short(
"ikev2.sa.proposal",
PROPOSAL_FIXED_LEN,
bytes.len(),
));
}
let length = u16::from_be_bytes([bytes[2], bytes[3]]) as usize;
let num = bytes[4];
let protocol_id = bytes[5];
let spi_size = bytes[6] as usize;
let num_transforms = bytes[7] as usize;
if length < PROPOSAL_FIXED_LEN || bytes.len() < length {
return Err(CrafterError::buffer_too_short(
"ikev2.sa.proposal.length",
length.max(PROPOSAL_FIXED_LEN),
bytes.len(),
));
}
let spi_end = PROPOSAL_FIXED_LEN + spi_size;
if length < spi_end {
return Err(CrafterError::buffer_too_short(
"ikev2.sa.proposal.spi",
spi_end,
length,
));
}
let spi = bytes[PROPOSAL_FIXED_LEN..spi_end].to_vec();
let mut proposal = Proposal::new(num, protocol_id).spi(spi);
let mut offset = spi_end;
for _ in 0..num_transforms {
let (transform, consumed) = parse_transform(&bytes[offset..length])?;
proposal.push_transform(transform);
offset += consumed;
}
Ok((proposal, length))
}
pub(crate) fn parse_sa_payload_body(bytes: &[u8]) -> Result<IkeSaPayload> {
let mut payload = IkeSaPayload::new();
let mut offset = 0;
while offset < bytes.len() {
let (proposal, consumed) = parse_proposal(&bytes[offset..])?;
payload.push_proposal(proposal);
offset += consumed;
}
Ok(payload)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::{LayerContext, Packet, Raw};
use crate::protocols::ipsec::sa::{AUTH_HMAC_SHA2_256_128, ENCR_AES_GCM_16};
fn compile_payload(payload: IkeSaPayload) -> 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 sample_proposal() -> Proposal {
Proposal::new(1, PROTOCOL_ID_ESP)
.spi(vec![0x11, 0x22, 0x33, 0x44])
.with_transform(
Transform::encryption(ENCR_AES_GCM_16)
.with_attribute(TransformAttribute::key_length(256)),
)
.with_transform(Transform::integrity(AUTH_HMAC_SHA2_256_128))
.with_transform(Transform::key_exchange(14))
.with_transform(Transform::extended_sequence_numbers(1))
}
#[test]
fn substructure_constants_match_rfc() {
assert_eq!(SUBSTRUC_LAST, 0);
assert_eq!(SUBSTRUC_MORE_PROPOSAL, 2);
assert_eq!(SUBSTRUC_MORE_TRANSFORM, 3);
assert_eq!(PROPOSAL_FIXED_LEN, 8);
assert_eq!(TRANSFORM_FIXED_LEN, 8);
assert_eq!(TRANSFORM_TYPE_ENCR, 1);
assert_eq!(TRANSFORM_TYPE_PRF, 2);
assert_eq!(TRANSFORM_TYPE_INTEG, 3);
assert_eq!(TRANSFORM_TYPE_DH, 4);
assert_eq!(TRANSFORM_TYPE_ESN, 5);
assert_eq!(ATTRIBUTE_TYPE_KEY_LENGTH, 14);
assert_eq!(ATTRIBUTE_FORMAT_TV, 0x8000);
}
#[test]
fn payload_type_is_security_association() {
assert_eq!(
IkeSaPayload::new().payload_type(),
PayloadType::SecurityAssociation
);
assert_eq!(IkeSaPayload::new().name(), IKE_SA_PAYLOAD_NAME);
}
#[test]
fn key_length_attribute_is_tv_four_octets() {
let attribute = TransformAttribute::key_length(128);
assert!(attribute.is_tv());
assert_eq!(attribute.encoded_len(), 4);
let mut out = Vec::new();
attribute.write(&mut out);
assert_eq!(out, vec![0x80, 0x0E, 0x00, 0x80]);
}
#[test]
fn tlv_attribute_carries_length_and_value() {
let attribute = TransformAttribute::tlv(7, vec![0xDE, 0xAD, 0xBE]);
assert!(!attribute.is_tv());
assert_eq!(attribute.encoded_len(), 7);
let mut out = Vec::new();
attribute.write(&mut out);
assert_eq!(out, vec![0x00, 0x07, 0x00, 0x03, 0xDE, 0xAD, 0xBE]);
}
#[test]
fn single_transform_auto_fills_last_and_length() {
let transform = Transform::encryption(ENCR_AES_GCM_16)
.with_attribute(TransformAttribute::key_length(256));
let mut out = Vec::new();
transform.write(&mut out, true);
assert_eq!(out[0], SUBSTRUC_LAST);
assert_eq!(out[1], 0); assert_eq!(u16::from_be_bytes([out[2], out[3]]), 12); assert_eq!(out[4], TRANSFORM_TYPE_ENCR);
assert_eq!(out[5], 0); assert_eq!(u16::from_be_bytes([out[6], out[7]]), ENCR_AES_GCM_16);
assert_eq!(out.len(), 12);
}
#[test]
fn non_last_transform_uses_more_flag() {
let mut out = Vec::new();
Transform::integrity(AUTH_HMAC_SHA2_256_128).write(&mut out, false);
assert_eq!(out[0], SUBSTRUC_MORE_TRANSFORM);
}
#[test]
fn proposal_auto_fills_counts_and_length() {
let proposal = sample_proposal();
let mut out = Vec::new();
proposal.write(&mut out, true);
assert_eq!(out[0], SUBSTRUC_LAST);
assert_eq!(out[4], 1); assert_eq!(out[5], PROTOCOL_ID_ESP);
assert_eq!(out[6], 4); assert_eq!(out[7], 4); let length = u16::from_be_bytes([out[2], out[3]]) as usize;
assert_eq!(length, proposal.encoded_len());
assert_eq!(length, out.len());
assert_eq!(&out[8..12], &[0x11, 0x22, 0x33, 0x44]);
}
#[test]
fn payload_compiles_generic_header_then_body() {
let payload = IkeSaPayload::new().with_proposal(sample_proposal());
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());
}
#[test]
fn payload_honors_generic_header_overrides() {
let payload = IkeSaPayload::new()
.with_proposal(sample_proposal())
.next_payload(40)
.critical(true)
.payload_length(0xBEEF);
let bytes = compile_payload(payload);
assert_eq!(bytes[0], 40);
assert_eq!(bytes[1], 0x80); assert_eq!(u16::from_be_bytes([bytes[2], bytes[3]]), 0xBEEF);
}
#[test]
fn payload_chain_next_payload_points_at_sa() {
use crate::protocols::ipsec::ikev2::payload::{
following_next_payload, payload_type_for_layer_name, PAYLOAD_SA,
};
assert_eq!(
payload_type_for_layer_name(IKE_SA_PAYLOAD_NAME),
Some(PayloadType::SecurityAssociation)
);
let packet: Packet = Packet::from_layer(Raw::from_bytes([0u8; 0])) / IkeSaPayload::new();
let ctx = LayerContext::new(&packet, 0);
assert_eq!(following_next_payload(&ctx), PAYLOAD_SA);
}
#[test]
fn round_trip_preserves_transform_ids_and_attributes() {
let payload = IkeSaPayload::new().with_proposal(sample_proposal());
let bytes = compile_payload(payload);
let parsed = parse_sa_payload_body(&bytes[4..]).unwrap();
assert_eq!(parsed.proposals().len(), 1);
let proposal = &parsed.proposals()[0];
assert_eq!(proposal.proposal_num(), 1);
assert_eq!(proposal.protocol_id(), PROTOCOL_ID_ESP);
assert_eq!(proposal.spi_bytes(), &[0x11, 0x22, 0x33, 0x44]);
let transforms = proposal.transforms();
assert_eq!(transforms.len(), 4);
assert_eq!(transforms[0].transform_type(), TRANSFORM_TYPE_ENCR);
assert_eq!(transforms[0].transform_id(), ENCR_AES_GCM_16);
assert_eq!(transforms[0].attributes().len(), 1);
let key_length = &transforms[0].attributes()[0];
assert!(key_length.is_tv());
assert_eq!(key_length.attribute_type(), ATTRIBUTE_TYPE_KEY_LENGTH);
assert_eq!(key_length.tv_value(), Some(256));
assert_eq!(transforms[1].transform_type(), TRANSFORM_TYPE_INTEG);
assert_eq!(transforms[1].transform_id(), AUTH_HMAC_SHA2_256_128);
assert!(transforms[1].attributes().is_empty());
assert_eq!(transforms[2].transform_type(), TRANSFORM_TYPE_DH);
assert_eq!(transforms[2].transform_id(), 14);
assert_eq!(transforms[3].transform_type(), TRANSFORM_TYPE_ESN);
assert_eq!(transforms[3].transform_id(), 1);
}
#[test]
fn round_trip_preserves_tlv_attribute() {
let payload = IkeSaPayload::new().with_proposal(
Proposal::new(1, PROTOCOL_ID_ESP).with_transform(
Transform::encryption(ENCR_AES_GCM_16)
.with_attribute(TransformAttribute::tlv(99, vec![0x01, 0x02, 0x03])),
),
);
let bytes = compile_payload(payload);
let parsed = parse_sa_payload_body(&bytes[4..]).unwrap();
let attribute = &parsed.proposals()[0].transforms()[0].attributes()[0];
assert!(!attribute.is_tv());
assert_eq!(attribute.attribute_type(), 99);
assert_eq!(attribute.tlv_value(), Some(&[0x01, 0x02, 0x03][..]));
}
#[test]
fn multiple_proposals_use_more_then_last_flags() {
let payload = IkeSaPayload::new()
.with_proposal(
Proposal::new(1, PROTOCOL_ID_ESP)
.with_transform(Transform::encryption(ENCR_AES_GCM_16)),
)
.with_proposal(
Proposal::new(2, PROTOCOL_ID_ESP)
.with_transform(Transform::encryption(ENCR_AES_CBC_FOR_TEST)),
);
let bytes = compile_payload(payload);
let body = &bytes[4..];
assert_eq!(body[0], SUBSTRUC_MORE_PROPOSAL);
let first_len = u16::from_be_bytes([body[2], body[3]]) as usize;
assert_eq!(body[first_len], SUBSTRUC_LAST);
}
const ENCR_AES_CBC_FOR_TEST: u16 = 12;
#[test]
fn parse_rejects_truncated_proposal() {
let err = parse_sa_payload_body(&[0u8, 0, 0]).unwrap_err();
assert!(matches!(err, CrafterError::BufferTooShort { .. }));
}
}