use asupersync::bytes::{Bytes, BytesMut};
use asupersync::http::h2::{
Connection, Header, HpackEncoder, Settings,
frame::{Frame, HeadersFrame, PriorityFrame, PrioritySpec, SettingsFrame},
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
const H2_REFERENCE_UNAVAILABLE: &str =
"h2 reference comparison unavailable in standalone frame harness";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PriorityTestVerdict {
Pass,
Fail,
ExpectedFailure, Skipped,
}
impl fmt::Display for PriorityTestVerdict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pass => write!(f, "PASS"),
Self::Fail => write!(f, "FAIL"),
Self::ExpectedFailure => write!(f, "XFAIL"),
Self::Skipped => write!(f, "SKIP"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RequirementLevel {
Must, Should, May, }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StreamPriorityState {
pub stream_id: u32,
pub exclusive: bool,
pub dependency: u32,
pub weight: u8,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SerializablePrioritySpec {
pub exclusive: bool,
pub dependency: u32,
pub weight: u8,
}
impl From<PrioritySpec> for SerializablePrioritySpec {
fn from(spec: PrioritySpec) -> Self {
Self {
exclusive: spec.exclusive,
dependency: spec.dependency,
weight: spec.weight,
}
}
}
impl From<SerializablePrioritySpec> for PrioritySpec {
fn from(spec: SerializablePrioritySpec) -> Self {
Self {
exclusive: spec.exclusive,
dependency: spec.dependency,
weight: spec.weight,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializablePriorityFrame {
pub stream_id: u32,
pub priority: SerializablePrioritySpec,
}
impl From<PriorityFrame> for SerializablePriorityFrame {
fn from(frame: PriorityFrame) -> Self {
Self {
stream_id: frame.stream_id,
priority: frame.priority.into(),
}
}
}
impl From<SerializablePriorityFrame> for PriorityFrame {
fn from(frame: SerializablePriorityFrame) -> Self {
Self {
stream_id: frame.stream_id,
priority: frame.priority.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriorityConformanceCase {
pub id: String,
pub description: String,
pub requirement_level: RequirementLevel,
pub priority_sequence: Vec<SerializablePriorityFrame>,
pub expected_priority_graph: Vec<StreamPriorityState>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriorityConformanceResult {
pub case_id: String,
pub verdict: PriorityTestVerdict,
pub error: Option<String>,
pub asupersync_priorities: Option<Vec<StreamPriorityState>>,
pub h2_priorities: Option<Vec<StreamPriorityState>>,
pub differences: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriorityComplianceSummary {
pub total_cases: usize,
pub passed: usize,
pub failed: usize,
pub expected_failures: usize,
pub skipped: usize,
pub compliance_score: f64, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriorityComplianceReport {
pub test_run_id: String,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub total_cases: usize,
pub results: Vec<PriorityConformanceResult>,
pub summary: PriorityComplianceSummary,
}
impl PriorityComplianceReport {
fn new(results: Vec<PriorityConformanceResult>) -> Self {
let total_cases = results.len();
let passed = results
.iter()
.filter(|r| r.verdict == PriorityTestVerdict::Pass)
.count();
let failed = results
.iter()
.filter(|r| r.verdict == PriorityTestVerdict::Fail)
.count();
let expected_failures = results
.iter()
.filter(|r| r.verdict == PriorityTestVerdict::ExpectedFailure)
.count();
let skipped = results
.iter()
.filter(|r| r.verdict == PriorityTestVerdict::Skipped)
.count();
let compliance_score = if total_cases > 0 {
(passed + expected_failures) as f64 / total_cases as f64
} else {
1.0
};
let summary = PriorityComplianceSummary {
total_cases,
passed,
failed,
expected_failures,
skipped,
compliance_score,
};
Self {
test_run_id: uuid::Uuid::new_v4().to_string(),
timestamp: chrono::Utc::now(),
total_cases,
results,
summary,
}
}
}
#[derive(Debug)]
pub struct PriorityConformanceTester {
pub test_cases: Vec<PriorityConformanceCase>,
}
impl PriorityConformanceTester {
pub fn new() -> Self {
Self {
test_cases: create_priority_test_cases(),
}
}
pub async fn run_all_tests(&self) -> PriorityComplianceReport {
let mut results = Vec::new();
for case in &self.test_cases {
let result = self.run_single_test(case).await;
results.push(result);
}
PriorityComplianceReport::new(results)
}
async fn run_single_test(&self, case: &PriorityConformanceCase) -> PriorityConformanceResult {
let asupersync_result = self.test_asupersync_priorities(case).await;
let h2_result = self.test_h2_priorities(case).await;
let (verdict, error, differences) = match (&asupersync_result, &h2_result) {
(Ok(asupersync_priorities), Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => {
let differences = self
.compare_priority_states(asupersync_priorities, &case.expected_priority_graph);
if differences.is_empty() {
(
PriorityTestVerdict::ExpectedFailure,
Some(format!(
"{h2_err}; live asupersync matched the RFC-expected state but vendor parity remains unexercised"
)),
differences,
)
} else {
(
PriorityTestVerdict::Fail,
Some(format!(
"Live asupersync PRIORITY state differed from expected RFC behavior while {h2_err}"
)),
differences,
)
}
}
(Err(asupersync_err), Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => (
PriorityTestVerdict::Fail,
Some(format!(
"Live asupersync PRIORITY processing failed while {h2_err}: {asupersync_err}"
)),
vec![format!("asupersync_error: {asupersync_err}")],
),
(Ok(asupersync_priorities), Ok(h2_priorities)) => {
let differences =
self.compare_priority_states(asupersync_priorities, h2_priorities);
if differences.is_empty() {
(PriorityTestVerdict::Pass, None, differences)
} else {
(
PriorityTestVerdict::Fail,
Some(format!(
"Priority state differences: {}",
differences.join(", ")
)),
differences,
)
}
}
(_, Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => (
PriorityTestVerdict::Skipped,
Some(h2_err.clone()),
Vec::new(),
),
(Err(asupersync_err), Err(h2_err)) => {
if asupersync_err == h2_err {
(PriorityTestVerdict::Pass, None, Vec::new())
} else {
(
PriorityTestVerdict::Fail,
Some(format!(
"Different error behaviors: asupersync={}, h2={}",
asupersync_err, h2_err
)),
vec![format!(
"Error divergence: {} vs {}",
asupersync_err, h2_err
)],
)
}
}
(Ok(_), Err(h2_err)) => (
PriorityTestVerdict::Fail,
Some(format!("asupersync succeeded, h2 failed: {}", h2_err)),
vec!["Implementation success divergence".to_string()],
),
(Err(asupersync_err), Ok(_)) => (
PriorityTestVerdict::Fail,
Some(format!(
"asupersync failed, h2 succeeded: {}",
asupersync_err
)),
vec!["Implementation success divergence".to_string()],
),
};
PriorityConformanceResult {
case_id: case.id.clone(),
verdict,
error,
asupersync_priorities: asupersync_result.as_ref().ok().cloned(),
h2_priorities: h2_result.as_ref().ok().cloned(),
differences,
}
}
async fn test_asupersync_priorities(
&self,
case: &PriorityConformanceCase,
) -> Result<Vec<StreamPriorityState>, String> {
let settings = Settings::default();
let mut connection = Connection::server(settings);
accept_peer_settings(&mut connection)?;
let stream_ids = priority_stream_ids(&case.priority_sequence);
for stream_id in &stream_ids {
initialize_remote_stream(&mut connection, *stream_id)?;
}
for serializable_frame in &case.priority_sequence {
let priority_frame: PriorityFrame = serializable_frame.clone().into();
if let Err(e) = process_live_priority_frame(&mut connection, &priority_frame) {
return Err(format!("Failed to process PRIORITY frame: {}", e));
}
}
let priority_states = extract_asupersync_priority_states(&connection, &stream_ids);
Ok(priority_states)
}
async fn test_h2_priorities(
&self,
_case: &PriorityConformanceCase,
) -> Result<Vec<StreamPriorityState>, String> {
Err(H2_REFERENCE_UNAVAILABLE.to_string())
}
fn compare_priority_states(
&self,
asupersync: &[StreamPriorityState],
h2: &[StreamPriorityState],
) -> Vec<String> {
let mut differences = Vec::new();
let asupersync_map: HashMap<u32, &StreamPriorityState> =
asupersync.iter().map(|s| (s.stream_id, s)).collect();
let h2_map: HashMap<u32, &StreamPriorityState> =
h2.iter().map(|s| (s.stream_id, s)).collect();
for &stream_id in asupersync_map.keys() {
if !h2_map.contains_key(&stream_id) {
differences.push(format!(
"Stream {} present in asupersync but not h2",
stream_id
));
}
}
for &stream_id in h2_map.keys() {
if !asupersync_map.contains_key(&stream_id) {
differences.push(format!(
"Stream {} present in h2 but not asupersync",
stream_id
));
}
}
for (&stream_id, &asupersync_state) in &asupersync_map {
if let Some(&h2_state) = h2_map.get(&stream_id) {
if asupersync_state.exclusive != h2_state.exclusive {
differences.push(format!(
"Stream {} exclusive flag differs: asupersync={}, h2={}",
stream_id, asupersync_state.exclusive, h2_state.exclusive
));
}
if asupersync_state.dependency != h2_state.dependency {
differences.push(format!(
"Stream {} dependency differs: asupersync={}, h2={}",
stream_id, asupersync_state.dependency, h2_state.dependency
));
}
if asupersync_state.weight != h2_state.weight {
differences.push(format!(
"Stream {} weight differs: asupersync={}, h2={}",
stream_id, asupersync_state.weight, h2_state.weight
));
}
}
}
differences
}
pub fn generate_markdown_report(&self, report: &PriorityComplianceReport) -> String {
let mut output = String::new();
output.push_str("# HTTP/2 PRIORITY Frame Conformance Report\n\n");
output.push_str(&format!("**Test Run ID:** {}\n", report.test_run_id));
output.push_str(&format!("**Timestamp:** {}\n", report.timestamp));
output.push_str(&format!("**Total Test Cases:** {}\n\n", report.total_cases));
output.push_str("## Summary\n\n");
output.push_str(&format!("- **Passed:** {}\n", report.summary.passed));
output.push_str(&format!("- **Failed:** {}\n", report.summary.failed));
output.push_str(&format!(
"- **Expected Failures:** {}\n",
report.summary.expected_failures
));
output.push_str(&format!("- **Skipped:** {}\n", report.summary.skipped));
output.push_str(&format!(
"- **Compliance Score:** {:.1}%\n\n",
report.summary.compliance_score * 100.0
));
if report.summary.failed > 0 {
output.push_str("## Failures\n\n");
for result in &report.results {
if result.verdict == PriorityTestVerdict::Fail {
output.push_str(&format!("### {}\n", result.case_id));
if let Some(error) = &result.error {
output.push_str(&format!("**Error:** {}\n", error));
}
if !result.differences.is_empty() {
output.push_str("**Differences:**\n");
for diff in &result.differences {
output.push_str(&format!("- {}\n", diff));
}
}
output.push('\n');
}
}
}
output.push_str("## All Results\n\n");
output.push_str("| Case ID | Verdict | Description |\n");
output.push_str("|---------|---------|-------------|\n");
for result in &report.results {
output.push_str(&format!(
"| {} | {} | Case {} |\n",
result.case_id, result.verdict, result.case_id
));
}
output
}
}
impl Default for PriorityConformanceTester {
fn default() -> Self {
Self::new()
}
}
fn accept_peer_settings(connection: &mut Connection) -> Result<(), String> {
let received = connection
.process_frame(Frame::Settings(SettingsFrame::new(vec![])))
.map_err(|err| err.to_string())?;
if received.is_some() {
return Err("SETTINGS handshake produced an application frame".to_string());
}
match connection.next_frame() {
Some(Frame::Settings(settings)) if settings.ack => Ok(()),
other => Err(format!(
"SETTINGS handshake should queue exactly one ACK, got {other:?}"
)),
}
}
fn request_header_block(stream_id: u32) -> Bytes {
let headers = [
Header::new(":method", "GET"),
Header::new(":scheme", "https"),
Header::new(":authority", "example.test"),
Header::new(":path", format!("/priority/{stream_id}")),
];
let mut encoder = HpackEncoder::new();
let mut block = BytesMut::new();
encoder.encode(&headers, &mut block);
block.freeze()
}
fn initialize_remote_stream(connection: &mut Connection, stream_id: u32) -> Result<(), String> {
let headers = HeadersFrame::new(stream_id, request_header_block(stream_id), false, true);
connection
.process_frame(Frame::Headers(headers))
.map_err(|err| err.to_string())?;
Ok(())
}
fn priority_stream_ids(sequence: &[SerializablePriorityFrame]) -> Vec<u32> {
let mut stream_ids = Vec::new();
for frame in sequence {
if frame.stream_id != 0 && !stream_ids.contains(&frame.stream_id) {
stream_ids.push(frame.stream_id);
}
}
stream_ids
}
fn process_live_priority_frame(
connection: &mut Connection,
priority_frame: &PriorityFrame,
) -> Result<(), String> {
let received = connection
.process_frame(Frame::Priority(*priority_frame))
.map_err(|err| err.to_string())?;
if received.is_some() {
return Err(format!(
"PRIORITY produced unexpected application frame: {received:?}"
));
}
Ok(())
}
fn extract_asupersync_priority_states(
connection: &Connection,
stream_ids: &[u32],
) -> Vec<StreamPriorityState> {
stream_ids
.iter()
.filter_map(|&stream_id| {
let priority = connection.stream(stream_id)?.priority();
Some(StreamPriorityState {
stream_id,
exclusive: priority.exclusive,
dependency: priority.dependency,
weight: priority.weight,
})
})
.collect()
}
fn create_priority_test_cases() -> Vec<PriorityConformanceCase> {
vec![
PriorityConformanceCase {
id: "priority-001".to_string(),
description: "Basic PRIORITY frame sets stream priority correctly".to_string(),
requirement_level: RequirementLevel::Must,
priority_sequence: vec![SerializablePriorityFrame {
stream_id: 1,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 0,
weight: 16,
},
}],
expected_priority_graph: vec![StreamPriorityState {
stream_id: 1,
exclusive: false,
dependency: 0,
weight: 16,
}],
},
PriorityConformanceCase {
id: "priority-002".to_string(),
description: "PRIORITY frame with exclusive dependency flag".to_string(),
requirement_level: RequirementLevel::Must,
priority_sequence: vec![SerializablePriorityFrame {
stream_id: 3,
priority: SerializablePrioritySpec {
exclusive: true,
dependency: 1,
weight: 32,
},
}],
expected_priority_graph: vec![StreamPriorityState {
stream_id: 3,
exclusive: true,
dependency: 1,
weight: 32,
}],
},
PriorityConformanceCase {
id: "priority-003".to_string(),
description: "Multiple streams with dependency chain".to_string(),
requirement_level: RequirementLevel::Must,
priority_sequence: vec![
SerializablePriorityFrame {
stream_id: 1,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 0,
weight: 16,
},
},
SerializablePriorityFrame {
stream_id: 3,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 1,
weight: 8,
},
},
SerializablePriorityFrame {
stream_id: 5,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 3,
weight: 4,
},
},
],
expected_priority_graph: vec![
StreamPriorityState {
stream_id: 1,
exclusive: false,
dependency: 0,
weight: 16,
},
StreamPriorityState {
stream_id: 3,
exclusive: false,
dependency: 1,
weight: 8,
},
StreamPriorityState {
stream_id: 5,
exclusive: false,
dependency: 3,
weight: 4,
},
],
},
PriorityConformanceCase {
id: "priority-004".to_string(),
description: "PRIORITY weight at minimum and maximum values".to_string(),
requirement_level: RequirementLevel::Must,
priority_sequence: vec![
SerializablePriorityFrame {
stream_id: 1,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 0,
weight: 1, },
},
SerializablePriorityFrame {
stream_id: 3,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 0,
weight: 255, },
},
],
expected_priority_graph: vec![
StreamPriorityState {
stream_id: 1,
exclusive: false,
dependency: 0,
weight: 1,
},
StreamPriorityState {
stream_id: 3,
exclusive: false,
dependency: 0,
weight: 255,
},
],
},
PriorityConformanceCase {
id: "priority-005".to_string(),
description: "Update priority on existing stream".to_string(),
requirement_level: RequirementLevel::Must,
priority_sequence: vec![
SerializablePriorityFrame {
stream_id: 1,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 0,
weight: 16,
},
},
SerializablePriorityFrame {
stream_id: 1,
priority: SerializablePrioritySpec {
exclusive: true,
dependency: 3,
weight: 64,
},
},
],
expected_priority_graph: vec![StreamPriorityState {
stream_id: 1,
exclusive: true,
dependency: 3,
weight: 64,
}],
},
PriorityConformanceCase {
id: "priority-006".to_string(),
description: "Multiple streams depending on the same parent".to_string(),
requirement_level: RequirementLevel::Should,
priority_sequence: vec![
SerializablePriorityFrame {
stream_id: 3,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 1,
weight: 32,
},
},
SerializablePriorityFrame {
stream_id: 5,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 1,
weight: 16,
},
},
SerializablePriorityFrame {
stream_id: 7,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 1,
weight: 8,
},
},
],
expected_priority_graph: vec![
StreamPriorityState {
stream_id: 3,
exclusive: false,
dependency: 1,
weight: 32,
},
StreamPriorityState {
stream_id: 5,
exclusive: false,
dependency: 1,
weight: 16,
},
StreamPriorityState {
stream_id: 7,
exclusive: false,
dependency: 1,
weight: 8,
},
],
},
PriorityConformanceCase {
id: "priority-007".to_string(),
description: "Handle circular dependency in priority graph".to_string(),
requirement_level: RequirementLevel::Must,
priority_sequence: vec![
SerializablePriorityFrame {
stream_id: 1,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 3,
weight: 16,
},
},
SerializablePriorityFrame {
stream_id: 3,
priority: SerializablePrioritySpec {
exclusive: false,
dependency: 1, weight: 16,
},
},
],
expected_priority_graph: vec![
StreamPriorityState {
stream_id: 1,
exclusive: false,
dependency: 3,
weight: 16,
},
StreamPriorityState {
stream_id: 3,
exclusive: false,
dependency: 1,
weight: 16,
},
],
},
]
}
#[cfg(test)]
mod tests {
use super::*;
use asupersync::http::h2::frame::{FrameHeader, FrameType, parse_frame};
#[tokio::test]
async fn h2_reference_unavailable_fails_closed_after_live_priority_assertions() {
let tester = PriorityConformanceTester::new();
let report = tester.run_all_tests().await;
assert_eq!(report.total_cases, 7);
assert_eq!(report.summary.passed, 0);
assert_eq!(report.summary.failed, 0);
assert_eq!(report.summary.expected_failures, 7);
assert_eq!(report.summary.skipped, 0);
assert!(
report
.results
.iter()
.all(|result| result.verdict == PriorityTestVerdict::ExpectedFailure),
"unwired h2 reference must not produce PASS verdicts"
);
assert!(
report.results.iter().all(|result| result
.error
.as_deref()
.is_some_and(|error| error.contains(H2_REFERENCE_UNAVAILABLE)
&& error.contains("vendor parity remains unexercised"))),
"each xfail must name the missing vendor reference"
);
assert!(
report
.results
.iter()
.all(|result| result.h2_priorities.is_none()),
"h2 reference is intentionally not wired for this harness"
);
assert!(
report
.results
.iter()
.all(|result| result.asupersync_priorities.is_some()),
"every case must exercise the live asupersync connection"
);
}
#[test]
fn priority_frame_updates_live_stream_priority() {
let mut connection = Connection::server(Settings::default());
accept_peer_settings(&mut connection).expect("SETTINGS handshake");
initialize_remote_stream(&mut connection, 1).expect("open stream");
let priority = PriorityFrame {
stream_id: 1,
priority: PrioritySpec {
exclusive: true,
dependency: 3,
weight: 64,
},
};
process_live_priority_frame(&mut connection, &priority).expect("PRIORITY should process");
let observed = connection.stream(1).unwrap().priority();
assert!(observed.exclusive);
assert_eq!(observed.dependency, 3);
assert_eq!(observed.weight, 64);
}
#[test]
fn priority_on_idle_stream_is_accepted_without_creating_stream() {
let mut connection = Connection::server(Settings::default());
accept_peer_settings(&mut connection).expect("SETTINGS handshake");
let priority = PriorityFrame {
stream_id: 9,
priority: PrioritySpec {
exclusive: false,
dependency: 0,
weight: 32,
},
};
process_live_priority_frame(&mut connection, &priority)
.expect("PRIORITY on an idle stream is legal at the frame seam");
assert!(
connection.stream(9).is_none(),
"current asupersync records priority only for existing streams"
);
}
#[test]
fn priority_cycle_metadata_is_reported_without_tree_rewrite() {
let mut connection = Connection::server(Settings::default());
accept_peer_settings(&mut connection).expect("SETTINGS handshake");
initialize_remote_stream(&mut connection, 1).expect("stream 1");
initialize_remote_stream(&mut connection, 3).expect("stream 3");
let first = PriorityFrame {
stream_id: 1,
priority: PrioritySpec {
exclusive: false,
dependency: 3,
weight: 16,
},
};
let second = PriorityFrame {
stream_id: 3,
priority: PrioritySpec {
exclusive: false,
dependency: 1,
weight: 16,
},
};
process_live_priority_frame(&mut connection, &first).expect("first priority");
process_live_priority_frame(&mut connection, &second).expect("second priority");
assert_eq!(connection.stream(1).unwrap().priority().dependency, 3);
assert_eq!(connection.stream(3).unwrap().priority().dependency, 1);
}
#[test]
fn priority_stream_zero_is_rejected_by_parser() {
let header = FrameHeader {
length: 5,
frame_type: FrameType::Priority as u8,
flags: 0,
stream_id: 0,
};
let payload = Bytes::from_static(&[0, 0, 0, 0, 16]);
assert!(
parse_frame(&header, payload).is_err(),
"PRIORITY on stream 0 must be rejected at the parser seam"
);
}
}