crafter 0.3.4

Packet-level network interaction for Rust tools and agents.
Documentation
use std::net::{Ipv4Addr, Ipv6Addr};

use crafter::prelude::*;

const IPV4_HEADER_LEN: usize = 20;
const IPV6_HEADER_LEN: usize = 40;
const UDP_HEADER_LEN: usize = 8;
const IPV4_SRC: Ipv4Addr = Ipv4Addr::new(192, 0, 2, 10);
const SSDP_IPV4_GROUP: Ipv4Addr = Ipv4Addr::new(239, 255, 255, 250);

fn ipv4_search_message() -> Ssdp {
    Ssdp::m_search()
        .with_raw_header(SSDP_HEADER_HOST, SSDP_IPV4_MULTICAST_HOST)
        .expect("HOST header is valid")
        .with_raw_header(SSDP_HEADER_MAN, SSDP_MAN_DISCOVER)
        .expect("MAN header is valid")
        .with_raw_header(SSDP_HEADER_MX, "1")
        .expect("MX header is valid")
        .with_raw_header(SSDP_HEADER_ST, SSDP_ST_ALL)
        .expect("ST header is valid")
}

fn ipv6_response_message() -> Ssdp {
    Ssdp::response_ok()
        .with_raw_header(SSDP_HEADER_EXT, "")
        .expect("EXT header is valid")
        .with_raw_header(SSDP_HEADER_ST, SSDP_ST_ALL)
        .expect("ST header is valid")
        .with_raw_header(SSDP_HEADER_USN, "uuid:device-1::ssdp:all")
        .expect("USN header is valid")
}

#[test]
fn ipv4_ssdp_construct_compile_decode_and_inspect() -> crafter::Result<()> {
    let ssdp = ipv4_search_message();
    let ssdp_len = ssdp.to_bytes().len();
    let udp_len = UDP_HEADER_LEN + ssdp_len;
    let total_len = IPV4_HEADER_LEN + udp_len;
    let packet = Ipv4::new().src(IPV4_SRC).dst(SSDP_IPV4_GROUP) / Ssdp::udp().sport(49_152) / ssdp;

    let compiled = packet.compile()?;
    assert_eq!(compiled.as_bytes().len(), total_len);
    assert_eq!(
        &compiled.as_bytes()[2..4],
        &(total_len as u16).to_be_bytes()
    );
    assert_eq!(
        &compiled.as_bytes()[IPV4_HEADER_LEN + 4..IPV4_HEADER_LEN + 6],
        &(udp_len as u16).to_be_bytes()
    );

    let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, compiled.as_bytes())?;
    let ipv4 = decoded.layer::<Ipv4>().expect("IPv4 layer");
    let udp = decoded.layer::<Udp>().expect("UDP layer");
    let ssdp = decoded.layer::<Ssdp>().expect("SSDP layer");

    assert_eq!(ipv4.total_length_value(), Some(total_len as u16));
    assert_eq!(ipv4.protocol_value(), IPPROTO_UDP);
    assert_eq!(ipv4.checksum_status(), Ipv4ChecksumStatus::Valid);
    assert_eq!(udp.source_port_value(), 49_152);
    assert_eq!(udp.destination_port_value(), SSDP_UDP_PORT);
    assert_eq!(udp.length_value(), Some(udp_len as u16));
    assert_eq!(udp.checksum_status(), UdpChecksumStatus::Valid);
    assert!(decoded.layer::<Raw>().is_none());

    let request = ssdp
        .message()
        .start_line()
        .as_request()
        .expect("SSDP request line");
    assert_eq!(request.method(), &SsdpMethod::MSearch);
    assert!(request.target().is_asterisk());
    assert!(request.version().is_http_1_1());
    assert_eq!(
        ssdp.headers()
            .get_first(SsdpHeaderNameKind::St)
            .expect("ST header")
            .as_bytes(),
        SSDP_ST_ALL.as_bytes()
    );

    let summary = decoded.summary();
    assert!(summary.contains("Ipv4("), "{summary}");
    assert!(summary.contains("Udp("), "{summary}");
    assert!(summary.contains("SSDP("), "{summary}");
    assert!(summary.contains("M-SEARCH * HTTP/1.1"), "{summary}");

    Ok(())
}

#[test]
fn ipv6_ssdp_construct_compile_decode_and_inspect() -> crafter::Result<()> {
    let source = "2001:db8::10".parse::<Ipv6Addr>().unwrap();
    let destination = SSDP_IPV6_LINK_LOCAL_MULTICAST.parse::<Ipv6Addr>().unwrap();
    let ssdp = ipv6_response_message();
    let ssdp_len = ssdp.to_bytes().len();
    let udp_len = UDP_HEADER_LEN + ssdp_len;
    let packet = Ipv6::new().src(source).dst(destination) / Ssdp::udp().dport(49_153) / ssdp;

    let compiled = packet.compile()?;
    assert_eq!(compiled.as_bytes().len(), IPV6_HEADER_LEN + udp_len);
    assert_eq!(&compiled.as_bytes()[4..6], &(udp_len as u16).to_be_bytes());
    assert_eq!(compiled.as_bytes()[6], IPPROTO_UDP);
    assert_eq!(
        &compiled.as_bytes()[IPV6_HEADER_LEN + 4..IPV6_HEADER_LEN + 6],
        &(udp_len as u16).to_be_bytes()
    );

    let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, compiled.as_bytes())?;
    let ipv6 = decoded.layer::<Ipv6>().expect("IPv6 layer");
    let udp = decoded.layer::<Udp>().expect("UDP layer");
    let ssdp = decoded.layer::<Ssdp>().expect("SSDP layer");

    assert_eq!(ipv6.payload_length_value(), Some(udp_len as u16));
    assert_eq!(ipv6.next_header_value(), IPPROTO_UDP);
    assert_eq!(udp.source_port_value(), SSDP_UDP_PORT);
    assert_eq!(udp.destination_port_value(), 49_153);
    assert_eq!(udp.length_value(), Some(udp_len as u16));
    assert_eq!(udp.checksum_status(), UdpChecksumStatus::Valid);
    assert!(decoded.layer::<Raw>().is_none());

    let response = ssdp
        .message()
        .start_line()
        .as_response()
        .expect("SSDP response line");
    assert!(response.code().is_ok());
    assert!(response.reason().is_ok());
    assert_eq!(
        ssdp.headers()
            .get_first(SsdpHeaderNameKind::Ext)
            .expect("EXT header")
            .as_bytes(),
        b""
    );

    let summary = decoded.summary();
    assert!(summary.contains("Ipv6("), "{summary}");
    assert!(summary.contains("Udp("), "{summary}");
    assert!(summary.contains("SSDP("), "{summary}");
    assert!(summary.contains("HTTP/1.1 200 OK"), "{summary}");

    Ok(())
}

#[test]
fn overrides_survive_compile_for_malformed_udp_and_ssdp_fields() -> crafter::Result<()> {
    let ssdp = Ssdp::request(
        SsdpMethod::Unknown("BAD METHOD".to_string()),
        SsdpRequestTarget::new("/device.xml").expect("valid explicit target"),
        SsdpVersion::new("HTTP/1.0").expect("valid explicit version"),
    )
    .with_raw_header(SSDP_HEADER_HOST, "example.invalid")
    .expect("HOST header is valid")
    .with_body(b"body".to_vec());
    let packet = Ipv4::new()
        .src(IPV4_SRC)
        .dst(Ipv4Addr::new(198, 51, 100, 20))
        / Udp::new()
            .sport(1_901)
            .dport(1_902)
            .length(0x1234)
            .checksum(0xbeef)
        / ssdp;

    let compiled = packet.compile()?;
    let bytes = compiled.as_bytes();
    let payload = &bytes[IPV4_HEADER_LEN + UDP_HEADER_LEN..];

    assert_eq!(u16::from_be_bytes([bytes[20], bytes[21]]), 1_901);
    assert_eq!(u16::from_be_bytes([bytes[22], bytes[23]]), 1_902);
    assert_eq!(&bytes[24..26], &0x1234u16.to_be_bytes());
    assert_eq!(&bytes[26..28], &0xbeefu16.to_be_bytes());
    assert_eq!(
        payload,
        b"BAD METHOD /device.xml HTTP/1.0\r\nHOST: example.invalid\r\n\r\nbody"
    );

    Ok(())
}