use super::{
AttachAttemptToken, AttachEnvelope, ClientRequest, CodecError, DetachAttemptToken,
DetachEnvelope, EnrollmentEnvelope, EnrollmentToken, Generation, LeaveAttemptToken,
LeaveEnvelope, ParticipantFrame, ReceiverDirection, RecordAdmissionAttemptToken,
RecordAdmissionEnvelope, RecordAdmissionFaultClass, RecordAdmissionProtocolFault, ServerValue,
SettlementAnswer, SettlementOutcome, SettlementQuery, WriteOutcomeUnknown, decode, encode,
encoded_len,
};
use alloc::{boxed::Box, vec, vec::Vec};
const FIXTURE: &str = include_str!("../../../../docs/wire/settlement-frames.json");
type TestResult = Result<(), CodecError>;
type Frames = Vec<(&'static str, ParticipantFrame)>;
#[test]
fn settlement_frames_match_the_committed_rust_encoder_bytes()
-> Result<(), Box<dyn core::error::Error>> {
let rows = strings("frame_hex")?;
let names = strings("name")?;
let frames = frames()?;
assert_eq!(rows.len(), frames.len());
assert_eq!(names.len(), frames.len());
for ((hex, name), (expected_name, frame)) in rows.into_iter().zip(names).zip(frames) {
assert_eq!(name, expected_name);
let expected = hex_bytes(hex)?;
pin(&frame, &expected)
.map_err(|error| alloc::format!("CodecError for {name}: {error:?}"))?;
}
Ok(())
}
fn pin(frame: &ParticipantFrame, expected: &[u8]) -> TestResult {
let mut encoded = vec![0; encoded_len(frame)?];
assert_eq!(encode(frame, &mut encoded)?, encoded.len());
assert_eq!(encoded.as_slice(), expected);
let receiver = if matches!(frame, ParticipantFrame::ClientRequest(_)) {
ReceiverDirection::Server
} else {
ReceiverDirection::Client
};
assert_eq!(decode(expected, receiver)?, *frame);
Ok(())
}
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(|s| {
let s = s.strip_suffix(',').unwrap_or(s);
let s = s.strip_suffix('"').ok_or("unclosed fixture string")?;
if s.contains(['\\', '"']) {
return Err("fixture fields must be literal unescaped bytes".into());
}
Ok(s)
})
.collect()
}
fn hex_bytes(hex: &str) -> Result<Vec<u8>, Box<dyn core::error::Error>> {
if !hex.is_ascii() || !hex.len().is_multiple_of(2) {
return Err("fixture hex must be even ASCII".into());
}
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).map_err(Into::into))
.collect()
}
fn frames() -> Result<Frames, Box<dyn core::error::Error>> {
let generation = Generation::new(3).ok_or("fixture generation is zero")?;
let token = RecordAdmissionAttemptToken::new([0x22; 16]);
let request = RecordAdmissionEnvelope {
conversation_id: 2102,
participant_id: 7,
capability_generation: generation,
record_admission_attempt_token: token,
};
let mut out = vec![(
"SettlementQuery",
ParticipantFrame::ClientRequest(ClientRequest::SettlementQuery(SettlementQuery {
conversation_id: 2102,
participant_id: 7,
capability_generation: generation,
record_admission_attempt_token: token,
payload_fingerprint: [0x33; 32],
})),
)];
out.extend(fault_frames(&request));
out.extend(unknown_frames(generation, request));
out.extend(answer_frames(token));
Ok(out)
}
fn fault_frames(request: &RecordAdmissionEnvelope) -> Frames {
let mut out = Vec::new();
for (name, class) in [
(
"RecordAdmissionProtocolFault.Projection",
RecordAdmissionFaultClass::Projection,
),
(
"RecordAdmissionProtocolFault.Order",
RecordAdmissionFaultClass::Order,
),
(
"RecordAdmissionProtocolFault.Sequence",
RecordAdmissionFaultClass::Sequence,
),
(
"RecordAdmissionProtocolFault.RequiredCapacity",
RecordAdmissionFaultClass::RequiredCapacity,
),
(
"RecordAdmissionProtocolFault.RefusalInvariant",
RecordAdmissionFaultClass::RefusalInvariant,
),
] {
out.push((
name,
ParticipantFrame::ServerValue(ServerValue::RecordAdmissionProtocolFault(
RecordAdmissionProtocolFault {
request: request.clone(),
class,
},
)),
));
}
out
}
fn unknown_frames(generation: Generation, request: RecordAdmissionEnvelope) -> Frames {
let mut out = Vec::new();
for (name, value) in [
(
"WriteOutcomeUnknown.Enrollment",
WriteOutcomeUnknown::Enrollment {
request: EnrollmentEnvelope {
conversation_id: 2102,
enrollment_token: EnrollmentToken::new([0x22; 16]),
},
},
),
(
"WriteOutcomeUnknown.CredentialAttach",
WriteOutcomeUnknown::CredentialAttach {
request: AttachEnvelope {
conversation_id: 2102,
participant_id: 7,
capability_generation: generation,
attach_attempt_token: AttachAttemptToken::new([0x22; 16]),
accept_marker_delivery_seq: None,
},
},
),
(
"WriteOutcomeUnknown.Detach",
WriteOutcomeUnknown::Detach {
request: DetachEnvelope {
conversation_id: 2102,
participant_id: 7,
capability_generation: generation,
detach_attempt_token: DetachAttemptToken::new([0x22; 16]),
},
},
),
(
"WriteOutcomeUnknown.Leave",
WriteOutcomeUnknown::Leave {
request: LeaveEnvelope {
conversation_id: 2102,
participant_id: 7,
capability_generation: generation,
leave_attempt_token: LeaveAttemptToken::new([0x22; 16]),
},
},
),
(
"WriteOutcomeUnknown.RecordAdmission",
WriteOutcomeUnknown::RecordAdmission { request },
),
] {
out.push((
name,
ParticipantFrame::ServerValue(ServerValue::WriteOutcomeUnknown(value)),
));
}
out
}
fn answer_frames(token: RecordAdmissionAttemptToken) -> Frames {
let mut out = Vec::new();
for (name, outcome) in [
(
"SettlementAnswer.Committed",
SettlementOutcome::Committed { delivery_seq: 12 },
),
("SettlementAnswer.Withdrawn", SettlementOutcome::Withdrawn),
("SettlementAnswer.Expired", SettlementOutcome::Expired),
(
"SettlementAnswer.NotCommitted",
SettlementOutcome::NotCommitted,
),
("SettlementAnswer.Unknown", SettlementOutcome::Unknown),
] {
out.push((
name,
ParticipantFrame::ServerValue(ServerValue::SettlementAnswer(SettlementAnswer {
conversation_id: 2102,
participant_id: 7,
record_admission_attempt_token: token,
outcome,
})),
));
}
out
}