liminal-rs 0.16.4

A conversation-based messaging bus built on beamr
Documentation
//! ACCESS-005 R5: the reason-bearing close on the wire
//! (docs/design/protocol/REVOCATION-CLOSE.md). Every registered reason
//! round-trips exactly; the payload-free `Disconnect` keeps its bytes; an
//! unknown body version, an unknown or mismatched reason, an oversized name,
//! a truncated body, trailing bytes and a data-stream placement each refuse
//! to decode by name.

use super::super::tests_support::{disconnect_reason_frames, round_trip};
use super::super::{decode, encode, encoded_len};
use crate::protocol::{
    DELIVER_MESSAGE_ID_VERSION, DISCONNECT_REASON_BODY_MAX, DISCONNECT_REASON_BODY_VERSION,
    DISCONNECT_REASON_NAME_MAX, DisconnectReason, Frame, FrameType, ProtocolError, ProtocolVersion,
    REVOCATION_CLOSE_VERSION,
};

/// A whole `DisconnectReason` frame on `stream`: header, then `body`.
fn frame_bytes(stream: u32, body: &[u8]) -> Result<Vec<u8>, ProtocolError> {
    let len = u32::try_from(body.len())
        .map_err(|_| ProtocolError::codec("test body length cannot fit u32"))?;
    let mut bytes = vec![u8::from(FrameType::DisconnectReason), 0];
    bytes.extend_from_slice(&stream.to_be_bytes());
    bytes.extend_from_slice(&len.to_be_bytes());
    bytes.extend_from_slice(body);
    Ok(bytes)
}

/// A body naming `code` and `name` under `version`.
fn body(version: u8, code: u16, name: &[u8]) -> Result<Vec<u8>, ProtocolError> {
    let len = u32::try_from(name.len())
        .map_err(|_| ProtocolError::codec("test name length cannot fit u32"))?;
    let mut body = vec![version];
    body.extend_from_slice(&code.to_be_bytes());
    body.extend_from_slice(&len.to_be_bytes());
    body.extend_from_slice(name);
    Ok(body)
}

fn codec_message(result: Result<(Frame, usize), ProtocolError>) -> Option<String> {
    match result {
        Err(ProtocolError::CodecError { message }) => message,
        _ => None,
    }
}

#[test]
fn every_registered_reason_round_trips_exactly() -> Result<(), ProtocolError> {
    for frame in disconnect_reason_frames() {
        assert_eq!(round_trip(&frame)?, frame);
    }
    for reason in DisconnectReason::ALL {
        assert_eq!(
            DisconnectReason::from_wire(reason.code(), reason.name()),
            Some(reason)
        );
        assert!(reason.name().len() <= DISCONNECT_REASON_NAME_MAX);
        assert!(
            reason
                .name()
                .bytes()
                .all(|byte| byte.is_ascii_lowercase() || byte == b'_')
        );
    }
    Ok(())
}

#[test]
fn grant_revoked_encodes_to_its_pinned_bytes() -> Result<(), ProtocolError> {
    let frame = Frame::DisconnectReason {
        flags: 0,
        reason: DisconnectReason::GrantRevoked,
    };
    let mut buffer = vec![0_u8; encoded_len(&frame)?];
    let written = encode(&frame, &mut buffer)?;
    let expected = frame_bytes(0, &body(1, 0x0001, b"grant_revoked")?)?;
    assert_eq!(buffer.get(..written), Some(expected.as_slice()));
    // header 10 + version 1 + code 2 + name (4 + 13)
    assert_eq!(written, 10 + 1 + 2 + 4 + 13);
    assert!(
        written
            .checked_sub(10)
            .is_some_and(|len| len <= DISCONNECT_REASON_BODY_MAX)
    );
    assert_eq!(DISCONNECT_REASON_BODY_VERSION, 1);
    Ok(())
}

#[test]
fn the_payload_free_disconnect_keeps_its_bytes() -> Result<(), ProtocolError> {
    let frame = Frame::Disconnect { flags: 0 };
    let mut buffer = vec![0_u8; encoded_len(&frame)?];
    let written = encode(&frame, &mut buffer)?;
    assert_eq!(
        buffer.get(..written),
        Some(&[0x04_u8, 0, 0, 0, 0, 0, 0, 0, 0, 0][..])
    );
    Ok(())
}

#[test]
fn an_old_disconnect_with_a_trailing_reason_is_refused() -> Result<(), ProtocolError> {
    let reason = body(1, 0x0001, b"grant_revoked")?;
    let len = u32::try_from(reason.len())
        .map_err(|_| ProtocolError::codec("test body length cannot fit u32"))?;
    let mut bytes = vec![u8::from(FrameType::Disconnect), 0, 0, 0, 0, 0];
    bytes.extend_from_slice(&len.to_be_bytes());
    bytes.extend_from_slice(&reason);
    assert!(decode(&bytes).is_err());
    Ok(())
}

#[test]
fn an_unknown_body_version_is_refused_by_name() -> Result<(), ProtocolError> {
    for version in [0_u8, 2, 0xFF] {
        let bytes = frame_bytes(0, &body(version, 0x0001, b"grant_revoked")?)?;
        assert_eq!(
            codec_message(decode(&bytes)).as_deref(),
            Some("disconnect reason body version is not supported")
        );
    }
    Ok(())
}

#[test]
fn an_unknown_or_mismatched_reason_is_refused_by_name() -> Result<(), ProtocolError> {
    for (code, name) in [
        (0x0003_u16, &b"grant_revoked"[..]),
        (0x0002, b"grant_revoked"),
        (0x0001, b"revocation_log_reset"),
        (0x0001, b"Grant_Revoked"),
        (0x0001, b"grant_revoked "),
        (0x0000, b""),
        (0xFFFF, b"future_reason"),
    ] {
        let bytes = frame_bytes(0, &body(1, code, name)?)?;
        assert_eq!(
            codec_message(decode(&bytes)).as_deref(),
            Some("disconnect reason code and name are not one registered reason")
        );
    }
    Ok(())
}

#[test]
fn a_name_over_its_bound_is_refused_by_name() -> Result<(), ProtocolError> {
    let at_bound = vec![b'a'; DISCONNECT_REASON_NAME_MAX];
    let over_len = DISCONNECT_REASON_NAME_MAX
        .checked_add(1)
        .ok_or_else(|| ProtocolError::codec("bound overflow"))?;
    let over = vec![b'a'; over_len];
    let at_bound_message = codec_message(decode(&frame_bytes(0, &body(1, 1, &at_bound)?)?));
    assert_eq!(
        at_bound_message.as_deref(),
        Some("disconnect reason code and name are not one registered reason")
    );
    let over_message = codec_message(decode(&frame_bytes(0, &body(1, 1, &over)?)?));
    assert_eq!(
        over_message.as_deref(),
        Some("disconnect reason name exceeds its bound")
    );
    Ok(())
}

#[test]
fn a_truncated_or_padded_body_is_refused() -> Result<(), Box<dyn std::error::Error>> {
    let whole = body(1, 0x0001, b"grant_revoked")?;
    for cut in 0..whole.len() {
        let truncated = whole.get(..cut).ok_or("cut within body")?;
        assert!(decode(&frame_bytes(0, truncated)?).is_err(), "cut at {cut}");
    }
    let mut padded = whole;
    padded.push(0);
    assert!(decode(&frame_bytes(0, &padded)?).is_err());
    Ok(())
}

#[test]
fn the_reason_bearing_close_lives_on_the_control_stream() -> Result<(), ProtocolError> {
    let bytes = frame_bytes(1, &body(1, 0x0001, b"grant_revoked")?)?;
    assert!(matches!(
        decode(&bytes),
        Err(ProtocolError::InvalidStream { .. })
    ));
    assert!(FrameType::DisconnectReason.is_control());
    Ok(())
}

#[test]
fn the_close_version_follows_every_earlier_version() {
    assert_eq!(REVOCATION_CLOSE_VERSION, ProtocolVersion::new(1, 3));
    assert!(REVOCATION_CLOSE_VERSION > DELIVER_MESSAGE_ID_VERSION);
}

#[test]
fn the_reason_debug_names_the_reason_only() {
    let frame = Frame::DisconnectReason {
        flags: 0,
        reason: DisconnectReason::GrantRevoked,
    };
    assert_eq!(
        format!("{frame:?}"),
        "DisconnectReason { flags: 0, reason: GrantRevoked }"
    );
}