#![allow(missing_docs)]
use super::header::{ExchangeType, Flags, HEADER_LEN, Header, IKE_VERSION};
use super::payload::{PAYLOAD_HEADER_LEN, PayloadKind, RawPayload, walk_chain};
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("{what} truncated: need {need} bytes, got {got}")]
Truncated {
what: &'static str,
need: usize,
got: usize,
},
#[error("{what} length field is invalid: {value}")]
BadLength { what: &'static str, value: usize },
#[error("unsupported IKE version 0x{0:02x} (expected 0x20)")]
BadVersion(u8),
#[error("declared length {declared} does not match buffer length {actual}")]
LengthMismatch { declared: usize, actual: usize },
}
#[derive(Debug)]
pub struct Message<'a> {
pub header: Header,
pub payloads: Vec<RawPayload<'a>>,
pub raw: &'a [u8],
}
impl<'a> Message<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, ParseError> {
let header = Header::parse(bytes)?;
if header.version != IKE_VERSION {
return Err(ParseError::BadVersion(header.version));
}
let declared = header.length as usize;
if declared < HEADER_LEN {
return Err(ParseError::BadLength {
what: "ike header.length",
value: declared,
});
}
if declared != bytes.len() {
return Err(ParseError::LengthMismatch {
declared,
actual: bytes.len(),
});
}
let payloads = walk_chain(header.next_payload, &bytes[HEADER_LEN..])?;
Ok(Self {
header,
payloads,
raw: bytes,
})
}
}
pub struct OutPayload {
pub kind: PayloadKind,
pub body: Vec<u8>,
}
pub fn build_message(
initiator_spi: u64,
responder_spi: u64,
exchange_type: ExchangeType,
flags: Flags,
message_id: u32,
payloads: &[OutPayload],
) -> Vec<u8> {
let body_len: usize = payloads
.iter()
.map(|p| PAYLOAD_HEADER_LEN + p.body.len())
.sum();
let total = HEADER_LEN + body_len;
let first_kind = payloads
.first()
.map(|p| p.kind)
.unwrap_or(PayloadKind::None);
let header = Header {
initiator_spi,
responder_spi,
next_payload: first_kind,
version: IKE_VERSION,
exchange_type,
flags,
message_id,
length: total as u32,
};
let mut out = Vec::with_capacity(total);
let mut hdr_buf = [0u8; HEADER_LEN];
header.write_into(&mut hdr_buf);
out.extend_from_slice(&hdr_buf);
for (i, p) in payloads.iter().enumerate() {
let next = payloads
.get(i + 1)
.map(|n| n.kind)
.unwrap_or(PayloadKind::None);
let plen = (PAYLOAD_HEADER_LEN + p.body.len()) as u16;
out.push(next.as_u8());
out.push(0); out.extend_from_slice(&plen.to_be_bytes());
out.extend_from_slice(&p.body);
}
debug_assert_eq!(out.len(), total);
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ike::payload::PayloadKind;
#[test]
fn parse_minimal_init() {
let mut payload = vec![0x00u8, 0x00, 0x00, 0x0c]; payload.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03, 0x04]);
let total_len = (HEADER_LEN + payload.len()) as u32;
let mut hdr = [0u8; HEADER_LEN];
hdr[0..8].copy_from_slice(&0x1122_3344_5566_7788u64.to_be_bytes());
hdr[16] = PayloadKind::Nonce.as_u8();
hdr[17] = IKE_VERSION;
hdr[18] = 34; hdr[19] = 0x08; hdr[20..24].copy_from_slice(&0u32.to_be_bytes());
hdr[24..28].copy_from_slice(&total_len.to_be_bytes());
let mut datagram = hdr.to_vec();
datagram.extend_from_slice(&payload);
let msg = Message::parse(&datagram).expect("parse");
assert_eq!(msg.header.initiator_spi, 0x1122_3344_5566_7788);
assert!(msg.header.flags.is_initiator());
assert!(!msg.header.flags.is_response());
assert_eq!(msg.payloads.len(), 1);
assert_eq!(msg.payloads[0].kind, PayloadKind::Nonce);
assert_eq!(msg.payloads[0].body.len(), 8);
}
#[test]
fn rejects_truncated_header() {
let short = [0u8; 10];
assert!(matches!(
Message::parse(&short),
Err(ParseError::Truncated { .. })
));
}
#[test]
fn parse_real_ios_ike_sa_init() {
let bytes = hex_literal();
let msg = Message::parse(&bytes).expect("parse");
assert_eq!(
msg.header.exchange_type,
super::super::ExchangeType::IkeSaInit
);
assert!(msg.header.flags.is_initiator());
assert!(!msg.header.flags.is_response());
assert_eq!(msg.header.message_id, 0);
assert_eq!(msg.header.responder_spi, 0);
let kinds: Vec<_> = msg.payloads.iter().map(|p| p.kind).collect();
assert_eq!(
kinds,
vec![
PayloadKind::SecurityAssociation,
PayloadKind::KeyExchange,
PayloadKind::Nonce,
PayloadKind::Notify,
PayloadKind::Notify,
PayloadKind::Notify,
PayloadKind::Notify,
PayloadKind::Notify,
PayloadKind::Notify,
]
);
}
fn hex_literal() -> Vec<u8> {
const HEX: &str = "
36 19 5E 76 EB 4B 9A 11 00 00 00 00 00 00 00 00
21 20 22 08 00 00 00 00 00 00 02 3A 22 00 01 64
02 00 00 2C 01 01 00 04 03 00 00 0C 01 00 00 14
80 0E 01 00 03 00 00 08 02 00 00 05 03 00 00 08
06 00 00 24 00 00 00 08 04 00 00 13 02 00 00 2C
02 01 00 04 03 00 00 0C 01 00 00 14 80 0E 01 00
03 00 00 08 02 00 00 05 03 00 00 08 06 00 00 24
00 00 00 08 04 00 00 0E 02 00 00 34 03 01 00 05
03 00 00 0C 01 00 00 0C 80 0E 01 00 03 00 00 08
03 00 00 0C 03 00 00 08 02 00 00 05 03 00 00 08
06 00 00 24 00 00 00 08 04 00 00 13 02 00 00 34
04 01 00 05 03 00 00 0C 01 00 00 0C 80 0E 01 00
03 00 00 08 03 00 00 0C 03 00 00 08 02 00 00 05
03 00 00 08 06 00 00 24 00 00 00 08 04 00 00 0E
02 00 00 24 05 01 00 03 03 00 00 0C 01 00 00 14
80 0E 01 00 03 00 00 08 02 00 00 05 00 00 00 08
04 00 00 13 02 00 00 24 06 01 00 03 03 00 00 0C
01 00 00 14 80 0E 01 00 03 00 00 08 02 00 00 05
00 00 00 08 04 00 00 0E 02 00 00 2C 07 01 00 04
03 00 00 0C 01 00 00 0C 80 0E 01 00 03 00 00 08
03 00 00 0C 03 00 00 08 02 00 00 05 00 00 00 08
04 00 00 13 00 00 00 2C 08 01 00 04 03 00 00 0C
01 00 00 0C 80 0E 01 00 03 00 00 08 03 00 00 0C
03 00 00 08 02 00 00 05 00 00 00 08 04 00 00 0E
28 00 00 48 00 13 00 00 76 4C 55 51 F0 73 E8 8E
AE 62 8C 82 10 32 D3 DB 10 7A 24 27 9C B4 A0 F0
A0 31 C3 FF 8E D5 10 7A 01 43 CC 3F 51 0A 66 4A
15 7D EF 81 D0 55 FD 58 60 BC 71 9A C7 FA 2C 13
EB 8A DD CA 3E 71 53 2D 29 00 00 14 08 CE DD 13
DE 02 33 7B 8A 72 3F 21 7B 07 2C C2 29 00 00 08
00 00 40 16 29 00 00 1C 00 00 40 04 FE 75 8E 84
ED 11 8A 37 8D FB F9 05 0B CB 40 D8 9C 88 24 25
29 00 00 1C 00 00 40 05 D2 DC 28 01 6E 4C 2F 7B
2B 61 97 BD 71 00 13 35 8C 6C 4D B1 29 00 00 08
00 00 40 2E 29 00 00 0E 00 00 40 2F 00 04 00 03
00 02 00 00 00 08 00 00 40 36
";
HEX.split_ascii_whitespace()
.map(|h| u8::from_str_radix(h, 16).unwrap())
.collect()
}
#[test]
fn rejects_length_mismatch() {
let mut hdr = [0u8; HEADER_LEN];
hdr[16] = 0; hdr[17] = IKE_VERSION;
hdr[18] = 34;
hdr[24..28].copy_from_slice(&100u32.to_be_bytes()); assert!(matches!(
Message::parse(&hdr),
Err(ParseError::LengthMismatch { .. })
));
}
}