use super::{
CausalContext, Frame, MessageEnvelope, ProtocolError, SchemaId, decode, encode, encoded_len,
};
use crate::protocol::{
DELIVER_MESSAGE_ID_FLAG, DELIVER_MESSAGE_ID_VERSION, DeliveredId, PUBLISH_ACK_POSITION_VERSION,
ProtocolVersion,
};
const OLDER_DELIVER: [u8; 66] = [
0x19, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2C, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xBB, ];
fn envelope() -> MessageEnvelope {
MessageEnvelope::new(
SchemaId::new([0x11; SchemaId::WIRE_LEN]),
CausalContext::independent(),
vec![0xAA, 0xBB],
)
}
fn encode_frame(frame: &Frame) -> Result<Vec<u8>, ProtocolError> {
let mut bytes = vec![0_u8; encoded_len(frame)?];
let written = encode(frame, &mut bytes)?;
bytes.truncate(written);
Ok(bytes)
}
const FLAGS_BYTE: usize = 1;
const ID: DeliveredId = DeliveredId {
boot: 1_791_328_512_345,
id: 42,
};
fn deliver(message: Option<DeliveredId>) -> Frame {
Frame::Deliver {
flags: 0,
stream_id: 9,
delivery_seq: 3,
envelope: envelope(),
message,
}
}
#[test]
fn deliver_without_a_message_id_keeps_the_older_layout() -> Result<(), ProtocolError> {
assert_eq!(encode_frame(&deliver(None))?, OLDER_DELIVER.to_vec());
let (frame, consumed) = decode(&OLDER_DELIVER)?;
assert_eq!(consumed, OLDER_DELIVER.len());
assert_eq!(frame, deliver(None));
Ok(())
}
#[test]
fn the_message_id_is_carried_from_protocol_1_2() {
assert_eq!(DELIVER_MESSAGE_ID_VERSION, ProtocolVersion::new(1, 2));
assert!(DELIVER_MESSAGE_ID_VERSION > PUBLISH_ACK_POSITION_VERSION);
}
#[test]
fn deliver_with_a_message_id_appends_the_boot_then_the_id() -> Result<(), ProtocolError> {
let bytes = encode_frame(&deliver(Some(ID)))?;
assert_eq!(bytes.len(), OLDER_DELIVER.len() + 16);
assert_eq!(bytes.get(FLAGS_BYTE), Some(&DELIVER_MESSAGE_ID_FLAG));
assert_eq!(bytes.get(9), Some(&0x48), "the payload length grows by 16");
assert_eq!(bytes.get(10..OLDER_DELIVER.len()), OLDER_DELIVER.get(10..));
let mut tail = ID.boot.to_be_bytes().to_vec();
tail.extend_from_slice(&ID.id.to_be_bytes());
assert_eq!(bytes.get(OLDER_DELIVER.len()..), Some(tail.as_slice()));
Ok(())
}
#[test]
fn newer_deliver_decodes_with_its_message_id() -> Result<(), ProtocolError> {
let bytes = encode_frame(&deliver(Some(ID)))?;
let (frame, consumed) = decode(&bytes)?;
assert_eq!(consumed, bytes.len());
assert_eq!(frame.flags(), DELIVER_MESSAGE_ID_FLAG);
assert!(matches!(
frame,
Frame::Deliver {
message: Some(ID),
..
}
));
Ok(())
}
#[test]
fn the_bit_follows_the_field() -> Result<(), ProtocolError> {
let claimed = Frame::Deliver {
flags: DELIVER_MESSAGE_ID_FLAG,
stream_id: 9,
delivery_seq: 3,
envelope: envelope(),
message: None,
};
assert_eq!(encode_frame(&claimed)?, OLDER_DELIVER.to_vec());
Ok(())
}
#[test]
fn deliver_flag_without_its_message_id_is_refused() {
let mut bytes = OLDER_DELIVER;
bytes[FLAGS_BYTE] = DELIVER_MESSAGE_ID_FLAG;
assert!(matches!(
decode(&bytes),
Err(ProtocolError::CodecError { .. })
));
}
#[test]
fn deliver_with_half_its_message_id_is_refused() -> Result<(), ProtocolError> {
let mut bytes = encode_frame(&deliver(Some(ID)))?;
bytes.truncate(bytes.len() - 8);
if let Some(length) = bytes.get_mut(9) {
*length = 0x40;
}
assert!(matches!(
decode(&bytes),
Err(ProtocolError::CodecError { .. })
));
Ok(())
}
#[test]
fn a_delivered_id_names_its_reply_parent_as_boot_colon_id() {
let parent = ID.reply_parent();
assert_eq!(parent, super::MessageId::from("1791328512345:42"));
assert_eq!(DeliveredId::from_parent(&parent), Some(ID));
}
#[test]
fn a_parent_of_another_form_names_no_request() {
for other in [
"local:42",
"42",
"1:2:3",
"",
"1:",
":2",
"01:2",
"1:02",
"+1:2",
"1:+2",
"1: 2",
"-1:2",
"18446744073709551616:2",
] {
assert_eq!(
DeliveredId::from_parent(&super::MessageId::from(other)),
None,
"{other:?}"
);
}
assert_eq!(
DeliveredId::from_parent(&super::MessageId::from("0:0")),
Some(DeliveredId { boot: 0, id: 0 })
);
}