use alloc::{boxed::Box, string::String, vec, vec::Vec};
use super::{
CodecError, ParticipantFrame, ParticipantTransportRejected, ReceiverDirection, ServerValue,
TransportRejectionReason, decode, encode, encoded_len,
};
const FIXTURE: &str = include_str!("../../../../docs/wire/transport-reasons-6-9.json");
type TestResult = Result<(), CodecError>;
fn strings(field: &str) -> Result<Vec<&'static str>, Box<dyn core::error::Error>> {
let prefix = alloc::format!("\"{field}\": \"");
FIXTURE
.lines()
.filter_map(|line| line.trim().strip_prefix(prefix.as_str()))
.map(|value| {
let value = value.strip_suffix(',').unwrap_or(value);
let value = value.strip_suffix('"').ok_or("unclosed fixture string")?;
if value.contains(['\\', '"']) {
return Err("fixture strings must be literal unescaped bytes".into());
}
Ok(value)
})
.collect()
}
fn hex_bytes(value: &str) -> Result<Vec<u8>, Box<dyn core::error::Error>> {
if !value.len().is_multiple_of(2) || !value.is_ascii() {
return Err("fixture hex is not an even ASCII string".into());
}
(0..value.len())
.step_by(2)
.map(|offset| u8::from_str_radix(&value[offset..offset + 2], 16).map_err(Into::into))
.collect()
}
#[test]
fn transport_reasons_6_to_9_match_the_committed_complete_frames()
-> Result<(), Box<dyn core::error::Error>> {
let names = strings("name")?;
let texts = strings("text")?;
let frames = strings("frame_hex")?;
assert_eq!(
names,
[
"EnrollmentNotPermitted",
"EnrollmentConversationOutOfScope",
"CredentialAttachConversationOutOfScope",
"AccessRefused",
]
);
assert_eq!(texts.len(), 4);
assert_eq!(frames.len(), 4);
let numbers = FIXTURE
.lines()
.filter_map(|line| line.trim().strip_prefix("\"reason\": "))
.map(|value| value.trim_end_matches(',').parse::<u16>())
.collect::<Result<Vec<_>, _>>()?;
assert_eq!(numbers, [6, 7, 8, 9]);
let reasons = [
TransportRejectionReason::EnrollmentNotPermitted,
TransportRejectionReason::EnrollmentConversationOutOfScope,
TransportRejectionReason::CredentialAttachConversationOutOfScope,
TransportRejectionReason::AccessRefused {
refusal: String::from(texts[3]),
},
];
for (index, reason) in reasons.into_iter().enumerate() {
if index < 3 {
assert!(
texts[index].is_empty(),
"this reason has no text field on the wire"
);
} else {
assert!(
!texts[index].is_empty(),
"access refusal must pin real text"
);
}
let frame = ParticipantFrame::ServerValue(ServerValue::ParticipantTransportRejected(
ParticipantTransportRejected { reason },
));
let expected = hex_bytes(frames[index])?;
assert_codec_frame(&frame, &expected, names[index])
.map_err(|error| alloc::format!("CodecError for {}: {error:?}", names[index]))?;
}
Ok(())
}
fn assert_codec_frame(frame: &ParticipantFrame, expected: &[u8], name: &str) -> TestResult {
let mut encoded = vec![0; encoded_len(frame)?];
let written = encode(frame, &mut encoded)?;
assert_eq!(written, encoded.len());
assert_eq!(encoded.as_slice(), expected, "{name} bytes drifted");
assert_eq!(decode(&encoded, ReceiverDirection::Client)?, *frame);
Ok(())
}