coap-message-implementations 0.1.7

Implementations of coap-message traits, and tools for building them
Documentation
// Conveniently, that *is* available when tests are run
extern crate alloc;

fn test_read(message: &impl coap_message::ReadableMessage<Code = u8>) {
    use coap_message::*;

    assert!(message.code() == 0x45);

    let expected_options = [
        (11, b"short".to_vec()),
        (11, b"very-long-option".to_vec()),
        (65000, b"".to_vec()),
    ];
    let found_options: alloc::vec::Vec<_> = message
        .options()
        .map(|o| (o.number(), o.value().to_vec()))
        .collect();

    assert!(found_options == expected_options);

    assert!(message.payload() == b"payload");
}

#[test]
fn test_inmemory() {
    let local_body =
        alloc::boxed::Box::new(b"\xb5short\r\x03very-long-option\xe0\xfc\xd0\xffpayload");

    let message = crate::inmemory::Message::new(0x45, local_body.as_slice());

    test_read(&message)
}

#[test]
#[cfg(feature = "downcast")]
fn test_inmemory_downcast() {
    let local_body =
        alloc::boxed::Box::new(b"\xb5short\r\x03very-long-option\xe0\xfc\xd0\xffpayload");

    let message = crate::inmemory::Message::new(0x45, local_body.as_slice());

    fn hide_type<'a>(
        message: &'a crate::inmemory::Message<'a>,
    ) -> &'a (impl coap_message::ReadableMessage + 'a) {
        message
    }

    let hidden = hide_type(&message);

    let message_again = crate::inmemory::Message::downcast_from(hidden).unwrap();

    test_read(message_again)
}

#[test]
fn test_inmemory_write() {
    use coap_message::*;

    let mut code = 0;
    let mut buffer = [0; 100];
    let mut message = crate::inmemory_write::Message::new(&mut code, &mut buffer);

    message.set_code(0x45);

    // These asserts ensure that the writable message is usable through ReadableMessage too
    assert!(
        message.options().next().is_none(),
        "Nascent message returned options"
    );
    assert!(
        message.payload().is_empty(),
        "Nascent message returned payload"
    );

    message.add_option(11, b"-----").unwrap();
    message.add_option(11, b"very-long-option").unwrap();
    message.add_option(65000, b"").unwrap();

    assert_eq!(
        message.options().count(),
        3,
        "Written options are not read back"
    );

    message.set_payload(b"payload123").unwrap();

    assert_eq!(
        message.payload(),
        b"payload123",
        "Written payloadis not read back"
    );

    message.truncate(7).unwrap();

    assert_eq!(
        message.payload(),
        b"payload",
        "Truncated payloadis not read back"
    );

    message.mutate_options(|n, v| {
        if n == 11 && v.len() == 5 {
            v.copy_from_slice(b"short");
        }
    });

    let len = message.finish();

    // These asserts ensure that the writable message actually serializes correctly into the buffer
    assert!(code == 0x45);
    assert!(&buffer[..len] == b"\xb5short\r\x03very-long-option\xe0\xfc\xd0\xffpayload");
}

#[test]
#[cfg(feature = "downcast")]
fn test_inmemory_write_downcast() {
    use coap_message::*;

    let mut code = 0;
    let mut buffer = [0; 100];
    let mut message = crate::inmemory_write::Message::new(&mut code, &mut buffer);

    fn hide_type_mut<'a, 'b>(
        message: &'a mut crate::inmemory_write::Message<'b>,
    ) -> &'a mut (impl coap_message::MinimalWritableMessage + 'b) {
        message
    }

    let hidden = hide_type_mut(&mut message);
    let concrete_again = crate::inmemory_write::Message::downcast_from(hidden).unwrap();

    concrete_again.set_code(0x45);

    concrete_again.add_option(11, b"-----").unwrap();
    concrete_again.add_option(11, b"very-long-option").unwrap();

    message.add_option(65000, b"").unwrap();
    message.set_payload(b"payload123").unwrap();
    message.truncate(7).unwrap();
    message.mutate_options(|n, v| {
        if n == 11 && v.len() == 5 {
            v.copy_from_slice(b"short");
        }
    });

    let len = message.finish();

    assert!(code == 0x45);
    assert!(&buffer[..len] == b"\xb5short\r\x03very-long-option\xe0\xfc\xd0\xffpayload");
}

#[test]
fn test_inmemory_mutable() {
    use coap_message::MutableWritableMessage;

    let mut code = 0x45;
    let mut buffer = *b"\xb5short\r\x03very-long-option\xe0\xfc\xd0\xffpayload";

    let mut message = crate::inmemory_write::Message::new_from_existing(&mut code, &mut buffer);

    test_read(&message);
    assert!(message.payload_mut_with_len(7).unwrap() == b"payload");
    message.payload_mut_with_len(7).unwrap()[6] = b'D';
    assert!(&buffer[buffer.len() - 1..] == b"D");
}

#[test]
fn test_inmemory_errors() {
    use coap_message::*;

    let bodies = [
        // Truncated option
        (b"\xb5sh".as_slice(), 0),
        // Payload-marker only in one of the two nibbles
        (b"\xbf".as_slice(), 0),
        // Option length overflows, being 269 + 0xffff (the overflow triggers before the truncated-option, even though we can't test that precisely that happens here).
        (b"\x0e\xff\xff".as_slice(), 0),
        // Zero-long option Option whose number barely does not overflow, and then one that does
        (b"\xe0\xfe\x00\xd0\xff".as_slice(), 1),
    ];

    for (body, discard_options) in bodies {
        let message = crate::inmemory::Message::new(0x45, body);

        // We don't guarantee the precise value, but still test to what we expect from the
        // implementation
        assert!(b"" == message.payload());

        let mut optit = message.options();

        for _ in 0..discard_options {
            assert!(
                optit
                    .next()
                    .map(|o| o.number() != crate::inmemory::OPTION_INVALID)
                    == Some(true)
            );
        }

        assert!(optit.next().map(|o| o.number()) == Some(crate::inmemory::OPTION_INVALID));
    }
}

// While it's technically a unit test, being so similar to the above makes it prefereable to have
// it here.
#[test]
fn test_encoding() {
    use crate::option_extension::encode_extensions;

    let example_options = [
        (11, &b"short"[..]),
        (11, &b"very-long-option"[..]),
        (65000, &b""[..]),
    ];

    let expected_encoding = b"\xb5short\r\x03very-long-option\xe0\xfc\xd0";

    let mut encoded = alloc::vec::Vec::new();
    let mut option = 0u16;
    for e in example_options.iter() {
        let delta = e.0 - option;
        option = e.0;
        encoded.extend_from_slice(encode_extensions(delta, e.1.len() as _).as_ref());
        encoded.extend_from_slice(e.1);
    }

    assert_eq!(&expected_encoding.as_ref(), &encoded);
}