1use asupersync::bytes::{Bytes, BytesMut};
9use asupersync::http::h2::{
10 Connection, Header, HpackEncoder, Settings,
11 connection::ReceivedFrame,
12 frame::{DataFrame, Frame, HeadersFrame, SettingsFrame},
13};
14use serde::{Deserialize, Serialize};
15use std::fmt;
16
17const H2_REFERENCE_UNAVAILABLE: &str =
18 "h2 reference comparison unavailable in standalone frame harness";
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub enum DataEndStreamTestVerdict {
23 Pass,
24 Fail,
25 ExpectedFailure, Skipped,
27}
28
29impl fmt::Display for DataEndStreamTestVerdict {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 Self::Pass => write!(f, "PASS"),
33 Self::Fail => write!(f, "FAIL"),
34 Self::ExpectedFailure => write!(f, "XFAIL"),
35 Self::Skipped => write!(f, "SKIP"),
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub enum RequirementLevel {
43 Must, Should, May, }
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50pub struct StreamEndStreamState {
51 pub stream_id: u32,
52 pub state: String,
54 pub can_recv: bool,
56 pub can_send: bool,
58 pub error_code: Option<String>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct DataEndStreamConnectionState {
65 pub connection_state: String,
67 pub stream_states: Vec<StreamEndStreamState>,
69 pub has_errors: bool,
71 pub error_messages: Vec<String>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct SerializableDataFrame {
78 pub stream_id: u32,
79 pub data: Vec<u8>,
80 pub end_stream: bool,
81}
82
83impl From<DataFrame> for SerializableDataFrame {
84 fn from(frame: DataFrame) -> Self {
85 Self {
86 stream_id: frame.stream_id,
87 data: frame.data.to_vec(),
88 end_stream: frame.end_stream,
89 }
90 }
91}
92
93impl From<SerializableDataFrame> for DataFrame {
94 fn from(frame: SerializableDataFrame) -> Self {
95 Self::new(frame.stream_id, Bytes::from(frame.data), frame.end_stream)
96 }
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct DataEndStreamConformanceCase {
102 pub id: String,
103 pub description: String,
104 pub requirement_level: RequirementLevel,
105 pub initial_streams: Vec<u32>,
107 pub data_sequence: Vec<SerializableDataFrame>,
109 pub expected_connection_state: DataEndStreamConnectionState,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct DataEndStreamConformanceResult {
116 pub case_id: String,
117 pub verdict: DataEndStreamTestVerdict,
118 pub error: Option<String>,
119 pub asupersync_state: Option<DataEndStreamConnectionState>,
121 pub h2_state: Option<DataEndStreamConnectionState>,
123 pub differences: Vec<String>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct DataEndStreamComplianceSummary {
130 pub total_cases: usize,
131 pub passed: usize,
132 pub failed: usize,
133 pub expected_failures: usize,
134 pub skipped: usize,
135 pub compliance_score: f64, }
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct DataEndStreamComplianceReport {
141 pub test_run_id: String,
142 pub timestamp: chrono::DateTime<chrono::Utc>,
143 pub total_cases: usize,
144 pub results: Vec<DataEndStreamConformanceResult>,
145 pub summary: DataEndStreamComplianceSummary,
146}
147
148impl DataEndStreamComplianceReport {
149 fn new(results: Vec<DataEndStreamConformanceResult>) -> Self {
151 let total_cases = results.len();
152 let passed = results
153 .iter()
154 .filter(|r| r.verdict == DataEndStreamTestVerdict::Pass)
155 .count();
156 let failed = results
157 .iter()
158 .filter(|r| r.verdict == DataEndStreamTestVerdict::Fail)
159 .count();
160 let expected_failures = results
161 .iter()
162 .filter(|r| r.verdict == DataEndStreamTestVerdict::ExpectedFailure)
163 .count();
164 let skipped = results
165 .iter()
166 .filter(|r| r.verdict == DataEndStreamTestVerdict::Skipped)
167 .count();
168
169 let compliance_score = if total_cases > 0 {
170 passed as f64 / total_cases as f64
171 } else {
172 1.0
173 };
174
175 let summary = DataEndStreamComplianceSummary {
176 total_cases,
177 passed,
178 failed,
179 expected_failures,
180 skipped,
181 compliance_score,
182 };
183
184 Self {
185 test_run_id: uuid::Uuid::new_v4().to_string(),
186 timestamp: chrono::Utc::now(),
187 total_cases,
188 results,
189 summary,
190 }
191 }
192}
193
194#[derive(Debug)]
196pub struct DataEndStreamConformanceTester {
197 pub test_cases: Vec<DataEndStreamConformanceCase>,
198}
199
200impl DataEndStreamConformanceTester {
201 pub fn new() -> Self {
203 Self {
204 test_cases: create_data_end_stream_test_cases(),
205 }
206 }
207
208 pub async fn run_all_tests(&self) -> DataEndStreamComplianceReport {
210 let mut results = Vec::new();
211
212 for case in &self.test_cases {
213 let result = self.run_single_test(case).await;
214 results.push(result);
215 }
216
217 DataEndStreamComplianceReport::new(results)
218 }
219
220 async fn run_single_test(
222 &self,
223 case: &DataEndStreamConformanceCase,
224 ) -> DataEndStreamConformanceResult {
225 let asupersync_result = self.test_asupersync_data_end_stream(case).await;
227
228 let h2_result = self.test_h2_data_end_stream(case).await;
232
233 let (verdict, error, differences) = match (&asupersync_result, &h2_result) {
235 (Ok(asupersync_state), Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => {
236 let differences = self
237 .compare_connection_states(asupersync_state, &case.expected_connection_state);
238 if differences.is_empty() {
239 (
240 DataEndStreamTestVerdict::ExpectedFailure,
241 Some(format!(
242 "{h2_err}; live asupersync matched the RFC-expected state but vendor parity remains unexercised"
243 )),
244 differences,
245 )
246 } else {
247 (
248 DataEndStreamTestVerdict::Fail,
249 Some(format!(
250 "Live asupersync DATA END_STREAM state differed from expected RFC behavior while {h2_err}"
251 )),
252 differences,
253 )
254 }
255 }
256 (Err(asupersync_err), Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => (
257 DataEndStreamTestVerdict::Fail,
258 Some(format!(
259 "Live asupersync DATA END_STREAM processing failed while {h2_err}: {asupersync_err}"
260 )),
261 vec![format!("asupersync_error: {asupersync_err}")],
262 ),
263 (Ok(asupersync_state), Ok(h2_state)) => {
264 let differences = self.compare_connection_states(asupersync_state, h2_state);
265 if differences.is_empty() {
266 (DataEndStreamTestVerdict::Pass, None, differences)
267 } else {
268 (
269 DataEndStreamTestVerdict::Fail,
270 Some(format!(
271 "Connection state differences: {}",
272 differences.join(", ")
273 )),
274 differences,
275 )
276 }
277 }
278 (_, Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => (
279 DataEndStreamTestVerdict::Skipped,
280 Some(h2_err.clone()),
281 Vec::new(),
282 ),
283 (Err(asupersync_err), Err(h2_err)) => {
284 if asupersync_err == h2_err {
286 (DataEndStreamTestVerdict::Pass, None, Vec::new())
287 } else {
288 (
289 DataEndStreamTestVerdict::Fail,
290 Some(format!(
291 "Different error behaviors: asupersync={}, h2={}",
292 asupersync_err, h2_err
293 )),
294 vec![format!(
295 "Error divergence: {} vs {}",
296 asupersync_err, h2_err
297 )],
298 )
299 }
300 }
301 (Ok(_), Err(h2_err)) => (
302 DataEndStreamTestVerdict::Fail,
303 Some(format!("asupersync succeeded, h2 failed: {}", h2_err)),
304 vec!["Implementation success divergence".to_string()],
305 ),
306 (Err(asupersync_err), Ok(_)) => (
307 DataEndStreamTestVerdict::Fail,
308 Some(format!(
309 "asupersync failed, h2 succeeded: {}",
310 asupersync_err
311 )),
312 vec!["Implementation success divergence".to_string()],
313 ),
314 };
315
316 DataEndStreamConformanceResult {
317 case_id: case.id.clone(),
318 verdict,
319 error,
320 asupersync_state: asupersync_result.as_ref().ok().cloned(),
321 h2_state: h2_result.as_ref().ok().cloned(),
322 differences,
323 }
324 }
325
326 async fn test_asupersync_data_end_stream(
328 &self,
329 case: &DataEndStreamConformanceCase,
330 ) -> Result<DataEndStreamConnectionState, String> {
331 let settings = Settings::default();
332 let mut connection = Connection::server(settings);
333 let mut error_messages = Vec::new();
334 accept_peer_settings(&mut connection)?;
335
336 for &stream_id in &case.initial_streams {
338 if let Err(e) = initialize_remote_stream(&mut connection, stream_id) {
339 return Err(format!("Failed to initialize stream {}: {}", stream_id, e));
340 }
341 }
342
343 for serializable_frame in &case.data_sequence {
345 let data_frame: DataFrame = serializable_frame.clone().into();
346 match process_live_data_frame(&mut connection, &data_frame) {
347 Ok(_) => {}
348 Err(e) => {
349 error_messages.push(format!(
350 "DATA frame error on stream {}: {}",
351 data_frame.stream_id, e
352 ));
353 }
354 }
355 }
356
357 let connection_state = extract_asupersync_data_end_stream_state(
359 &connection,
360 &case.initial_streams,
361 error_messages,
362 );
363 Ok(connection_state)
364 }
365
366 async fn test_h2_data_end_stream(
368 &self,
369 _case: &DataEndStreamConformanceCase,
370 ) -> Result<DataEndStreamConnectionState, String> {
371 Err(H2_REFERENCE_UNAVAILABLE.to_string())
372 }
373
374 fn compare_connection_states(
376 &self,
377 asupersync: &DataEndStreamConnectionState,
378 h2: &DataEndStreamConnectionState,
379 ) -> Vec<String> {
380 let mut differences = Vec::new();
381
382 if asupersync.connection_state != h2.connection_state {
383 differences.push(format!(
384 "connection_state differs: asupersync={}, h2={}",
385 asupersync.connection_state, h2.connection_state
386 ));
387 }
388
389 if asupersync.has_errors != h2.has_errors {
390 differences.push(format!(
391 "has_errors differs: asupersync={}, h2={}",
392 asupersync.has_errors, h2.has_errors
393 ));
394 }
395
396 if asupersync.stream_states.len() != h2.stream_states.len() {
398 differences.push(format!(
399 "stream_states count differs: asupersync={}, h2={}",
400 asupersync.stream_states.len(),
401 h2.stream_states.len()
402 ));
403 } else {
404 for (asupersync_stream, h2_stream) in
405 asupersync.stream_states.iter().zip(&h2.stream_states)
406 {
407 if asupersync_stream.stream_id != h2_stream.stream_id {
408 differences.push(format!(
409 "stream_id mismatch: asupersync={}, h2={}",
410 asupersync_stream.stream_id, h2_stream.stream_id
411 ));
412 }
413 if asupersync_stream.state != h2_stream.state {
414 differences.push(format!(
415 "stream {} state differs: asupersync={}, h2={}",
416 asupersync_stream.stream_id, asupersync_stream.state, h2_stream.state
417 ));
418 }
419 if asupersync_stream.can_recv != h2_stream.can_recv {
420 differences.push(format!(
421 "stream {} can_recv differs: asupersync={}, h2={}",
422 asupersync_stream.stream_id, asupersync_stream.can_recv, h2_stream.can_recv
423 ));
424 }
425 if asupersync_stream.can_send != h2_stream.can_send {
426 differences.push(format!(
427 "stream {} can_send differs: asupersync={}, h2={}",
428 asupersync_stream.stream_id, asupersync_stream.can_send, h2_stream.can_send
429 ));
430 }
431 }
432 }
433
434 if asupersync.error_messages.len() != h2.error_messages.len() {
436 differences.push(format!(
437 "error_messages count differs: asupersync={}, h2={}",
438 asupersync.error_messages.len(),
439 h2.error_messages.len()
440 ));
441 } else {
442 let mut asupersync_errors = asupersync.error_messages.clone();
443 let mut h2_errors = h2.error_messages.clone();
444 asupersync_errors.sort();
445 h2_errors.sort();
446
447 if asupersync_errors != h2_errors {
448 differences.push("error_messages content differs".to_string());
449 }
450 }
451
452 differences
453 }
454
455 pub fn generate_markdown_report(&self, report: &DataEndStreamComplianceReport) -> String {
457 let mut output = String::new();
458 output.push_str("# HTTP/2 DATA Frame END_STREAM Conformance Report\n\n");
459
460 output.push_str(&format!("**Test Run ID:** {}\n", report.test_run_id));
461 output.push_str(&format!("**Timestamp:** {}\n", report.timestamp));
462 output.push_str(&format!("**Total Test Cases:** {}\n\n", report.total_cases));
463
464 output.push_str("## Summary\n\n");
465 output.push_str(&format!("- **Passed:** {}\n", report.summary.passed));
466 output.push_str(&format!("- **Failed:** {}\n", report.summary.failed));
467 output.push_str(&format!(
468 "- **Expected Failures:** {}\n",
469 report.summary.expected_failures
470 ));
471 output.push_str(&format!("- **Skipped:** {}\n", report.summary.skipped));
472 output.push_str(&format!(
473 "- **Compliance Score:** {:.1}%\n\n",
474 report.summary.compliance_score * 100.0
475 ));
476
477 if report.summary.failed > 0 {
478 output.push_str("## Failures\n\n");
479 for result in &report.results {
480 if result.verdict == DataEndStreamTestVerdict::Fail {
481 output.push_str(&format!("### {}\n", result.case_id));
482 if let Some(error) = &result.error {
483 output.push_str(&format!("**Error:** {}\n", error));
484 }
485 if !result.differences.is_empty() {
486 output.push_str("**Differences:**\n");
487 for diff in &result.differences {
488 output.push_str(&format!("- {}\n", diff));
489 }
490 }
491 output.push('\n');
492 }
493 }
494 }
495
496 output.push_str("## All Results\n\n");
497 output.push_str("| Case ID | Verdict | Description |\n");
498 output.push_str("|---------|---------|-------------|\n");
499 for result in &report.results {
500 output.push_str(&format!(
501 "| {} | {} | Case {} |\n",
502 result.case_id, result.verdict, result.case_id
503 ));
504 }
505
506 output
507 }
508}
509
510impl Default for DataEndStreamConformanceTester {
511 fn default() -> Self {
512 Self::new()
513 }
514}
515
516fn accept_peer_settings(connection: &mut Connection) -> Result<(), String> {
517 let received = connection
518 .process_frame(Frame::Settings(SettingsFrame::new(vec![])))
519 .map_err(|err| err.to_string())?;
520 if received.is_some() {
521 return Err("SETTINGS handshake produced an application frame".to_string());
522 }
523
524 match connection.next_frame() {
525 Some(Frame::Settings(settings)) if settings.ack => Ok(()),
526 other => Err(format!(
527 "SETTINGS handshake should queue exactly one ACK, got {other:?}"
528 )),
529 }
530}
531
532fn request_header_block(stream_id: u32) -> Bytes {
533 let headers = [
534 Header::new(":method", "GET"),
535 Header::new(":scheme", "https"),
536 Header::new(":authority", "example.test"),
537 Header::new(":path", format!("/stream/{stream_id}")),
538 ];
539 let mut encoder = HpackEncoder::new();
540 let mut block = BytesMut::new();
541 encoder.encode(&headers, &mut block);
542 block.freeze()
543}
544
545fn initialize_remote_stream(connection: &mut Connection, stream_id: u32) -> Result<(), String> {
547 let headers = HeadersFrame::new(stream_id, request_header_block(stream_id), false, true);
548 match connection
549 .process_frame(Frame::Headers(headers))
550 .map_err(|err| err.to_string())?
551 {
552 Some(ReceivedFrame::Headers {
553 stream_id: received_stream_id,
554 end_stream,
555 ..
556 }) if received_stream_id == stream_id && !end_stream => Ok(()),
557 other => Err(format!(
558 "HEADERS stream initialization produced unexpected frame: {other:?}"
559 )),
560 }
561}
562
563fn process_live_data_frame(
565 connection: &mut Connection,
566 data_frame: &DataFrame,
567) -> Result<(), String> {
568 match connection.process_frame(Frame::Data(data_frame.clone())) {
569 Ok(Some(ReceivedFrame::Data {
570 stream_id,
571 end_stream,
572 ..
573 })) if stream_id == data_frame.stream_id && end_stream == data_frame.end_stream => Ok(()),
574 Ok(None) => Ok(()),
575 Ok(other) => Err(format!("unexpected DATA result frame: {other:?}")),
576 Err(err) => Err(format!("{:?}", err.code)),
577 }
578}
579
580fn extract_asupersync_data_end_stream_state(
582 connection: &Connection,
583 stream_ids: &[u32],
584 error_messages: Vec<String>,
585) -> DataEndStreamConnectionState {
586 let stream_states = stream_ids
587 .iter()
588 .filter_map(|&stream_id| {
589 let stream = connection.stream(stream_id)?;
590 let state = stream.state();
591 Some(StreamEndStreamState {
592 stream_id,
593 state: format!("{state:?}"),
594 can_recv: state.can_recv(),
595 can_send: state.can_send(),
596 error_code: stream.error_code().map(|code| format!("{code:?}")),
597 })
598 })
599 .collect();
600
601 DataEndStreamConnectionState {
602 connection_state: format!("{:?}", connection.state()),
603 stream_states,
604 has_errors: !error_messages.is_empty(),
605 error_messages,
606 }
607}
608
609fn create_data_end_stream_test_cases() -> Vec<DataEndStreamConformanceCase> {
611 vec![
612 DataEndStreamConformanceCase {
614 id: "data-end-stream-001".to_string(),
615 description: "Basic DATA frame with END_STREAM closes stream correctly".to_string(),
616 requirement_level: RequirementLevel::Must,
617 initial_streams: vec![1],
618 data_sequence: vec![SerializableDataFrame {
619 stream_id: 1,
620 data: b"Hello, World!".to_vec(),
621 end_stream: true,
622 }],
623 expected_connection_state: DataEndStreamConnectionState {
624 connection_state: "Open".to_string(),
625 stream_states: vec![StreamEndStreamState {
626 stream_id: 1,
627 state: "HalfClosedRemote".to_string(),
628 can_recv: false,
629 can_send: true,
630 error_code: None,
631 }],
632 has_errors: false,
633 error_messages: Vec::new(),
634 },
635 },
636 DataEndStreamConformanceCase {
638 id: "data-end-stream-002".to_string(),
639 description: "DATA frame after END_STREAM is rejected with StreamClosed error"
640 .to_string(),
641 requirement_level: RequirementLevel::Must,
642 initial_streams: vec![1],
643 data_sequence: vec![
644 SerializableDataFrame {
645 stream_id: 1,
646 data: b"First message".to_vec(),
647 end_stream: true,
648 },
649 SerializableDataFrame {
650 stream_id: 1,
651 data: b"Should be rejected".to_vec(),
652 end_stream: false,
653 },
654 ],
655 expected_connection_state: DataEndStreamConnectionState {
656 connection_state: "Open".to_string(),
657 stream_states: vec![StreamEndStreamState {
658 stream_id: 1,
659 state: "HalfClosedRemote".to_string(),
660 can_recv: false,
661 can_send: true,
662 error_code: None,
663 }],
664 has_errors: true,
665 error_messages: vec!["DATA frame error on stream 1: StreamClosed".to_string()],
666 },
667 },
668 DataEndStreamConformanceCase {
670 id: "data-end-stream-003".to_string(),
671 description: "Multiple DATA frames, only last has END_STREAM".to_string(),
672 requirement_level: RequirementLevel::Must,
673 initial_streams: vec![1],
674 data_sequence: vec![
675 SerializableDataFrame {
676 stream_id: 1,
677 data: b"Chunk 1".to_vec(),
678 end_stream: false,
679 },
680 SerializableDataFrame {
681 stream_id: 1,
682 data: b"Chunk 2".to_vec(),
683 end_stream: false,
684 },
685 SerializableDataFrame {
686 stream_id: 1,
687 data: b"Final chunk".to_vec(),
688 end_stream: true,
689 },
690 ],
691 expected_connection_state: DataEndStreamConnectionState {
692 connection_state: "Open".to_string(),
693 stream_states: vec![StreamEndStreamState {
694 stream_id: 1,
695 state: "HalfClosedRemote".to_string(),
696 can_recv: false,
697 can_send: true,
698 error_code: None,
699 }],
700 has_errors: false,
701 error_messages: Vec::new(),
702 },
703 },
704 DataEndStreamConformanceCase {
706 id: "data-end-stream-004".to_string(),
707 description: "Empty DATA frame with END_STREAM closes stream".to_string(),
708 requirement_level: RequirementLevel::Must,
709 initial_streams: vec![1],
710 data_sequence: vec![SerializableDataFrame {
711 stream_id: 1,
712 data: Vec::new(), end_stream: true,
714 }],
715 expected_connection_state: DataEndStreamConnectionState {
716 connection_state: "Open".to_string(),
717 stream_states: vec![StreamEndStreamState {
718 stream_id: 1,
719 state: "HalfClosedRemote".to_string(),
720 can_recv: false,
721 can_send: true,
722 error_code: None,
723 }],
724 has_errors: false,
725 error_messages: Vec::new(),
726 },
727 },
728 DataEndStreamConformanceCase {
730 id: "data-end-stream-005".to_string(),
731 description: "Multiple streams each closed with END_STREAM".to_string(),
732 requirement_level: RequirementLevel::Should,
733 initial_streams: vec![1, 3, 5],
734 data_sequence: vec![
735 SerializableDataFrame {
736 stream_id: 1,
737 data: b"Stream 1 data".to_vec(),
738 end_stream: true,
739 },
740 SerializableDataFrame {
741 stream_id: 3,
742 data: b"Stream 3 data".to_vec(),
743 end_stream: true,
744 },
745 SerializableDataFrame {
746 stream_id: 5,
747 data: b"Stream 5 data".to_vec(),
748 end_stream: true,
749 },
750 ],
751 expected_connection_state: DataEndStreamConnectionState {
752 connection_state: "Open".to_string(),
753 stream_states: vec![
754 StreamEndStreamState {
755 stream_id: 1,
756 state: "HalfClosedRemote".to_string(),
757 can_recv: false,
758 can_send: true,
759 error_code: None,
760 },
761 StreamEndStreamState {
762 stream_id: 3,
763 state: "HalfClosedRemote".to_string(),
764 can_recv: false,
765 can_send: true,
766 error_code: None,
767 },
768 StreamEndStreamState {
769 stream_id: 5,
770 state: "HalfClosedRemote".to_string(),
771 can_recv: false,
772 can_send: true,
773 error_code: None,
774 },
775 ],
776 has_errors: false,
777 error_messages: Vec::new(),
778 },
779 },
780 DataEndStreamConformanceCase {
782 id: "data-end-stream-006".to_string(),
783 description: "Large DATA frame with END_STREAM handles correctly".to_string(),
784 requirement_level: RequirementLevel::Should,
785 initial_streams: vec![1],
786 data_sequence: vec![SerializableDataFrame {
787 stream_id: 1,
788 data: vec![0u8; 8192], end_stream: true,
790 }],
791 expected_connection_state: DataEndStreamConnectionState {
792 connection_state: "Open".to_string(),
793 stream_states: vec![StreamEndStreamState {
794 stream_id: 1,
795 state: "HalfClosedRemote".to_string(),
796 can_recv: false,
797 can_send: true,
798 error_code: None,
799 }],
800 has_errors: false,
801 error_messages: Vec::new(),
802 },
803 },
804 DataEndStreamConformanceCase {
806 id: "data-end-stream-007".to_string(),
807 description: "Multiple END_STREAM frames on same stream should be rejected".to_string(),
808 requirement_level: RequirementLevel::Must,
809 initial_streams: vec![1],
810 data_sequence: vec![
811 SerializableDataFrame {
812 stream_id: 1,
813 data: b"First end".to_vec(),
814 end_stream: true,
815 },
816 SerializableDataFrame {
817 stream_id: 1,
818 data: b"Second end".to_vec(),
819 end_stream: true, },
821 ],
822 expected_connection_state: DataEndStreamConnectionState {
823 connection_state: "Open".to_string(),
824 stream_states: vec![StreamEndStreamState {
825 stream_id: 1,
826 state: "HalfClosedRemote".to_string(),
827 can_recv: false,
828 can_send: true,
829 error_code: None,
830 }],
831 has_errors: true,
832 error_messages: vec!["DATA frame error on stream 1: StreamClosed".to_string()],
833 },
834 },
835 ]
836}
837
838#[cfg(test)]
839mod tests {
840 use super::*;
841 use asupersync::http::h2::ErrorCode;
842
843 #[tokio::test]
844 async fn h2_reference_unavailable_still_runs_live_data_assertions() {
845 let tester = DataEndStreamConformanceTester::new();
846 let report = tester.run_all_tests().await;
847
848 assert_eq!(report.total_cases, 7);
849 assert_eq!(report.summary.passed, 0);
850 assert_eq!(report.summary.failed, 0);
851 assert_eq!(report.summary.expected_failures, 7);
852 assert_eq!(report.summary.skipped, 0);
853 assert_eq!(report.summary.compliance_score, 0.0);
854 assert!(
855 report
856 .results
857 .iter()
858 .all(|result| result.h2_state.is_none()),
859 "h2 reference is intentionally not wired for this harness"
860 );
861 assert!(
862 report
863 .results
864 .iter()
865 .all(|result| result.asupersync_state.is_some()),
866 "every case must exercise the live asupersync connection"
867 );
868 }
869
870 #[tokio::test]
871 async fn h2_reference_gap_is_reported_as_expected_failure_not_pass() {
872 let tester = DataEndStreamConformanceTester::new();
873 let report = tester.run_all_tests().await;
874
875 assert!(
876 report
877 .results
878 .iter()
879 .all(|result| result.verdict == DataEndStreamTestVerdict::ExpectedFailure),
880 "unwired h2 vendor parity must not be reported as full pass: {:?}",
881 report.results
882 );
883 assert!(
884 report.results.iter().all(|result| result
885 .error
886 .as_deref()
887 .is_some_and(|error| error.contains(H2_REFERENCE_UNAVAILABLE)
888 && error.contains("vendor parity remains unexercised"))),
889 "each expected failure should explain the missing h2 reference parity"
890 );
891 }
892
893 #[test]
894 fn data_end_stream_moves_stream_half_closed_remote() {
895 let mut connection = Connection::server(Settings::default());
896 accept_peer_settings(&mut connection).expect("SETTINGS handshake");
897 initialize_remote_stream(&mut connection, 1).expect("open stream");
898
899 let data = DataFrame::new(1, Bytes::from_static(b"done"), true);
900 process_live_data_frame(&mut connection, &data).expect("DATA should process");
901
902 let stream = connection.stream(1).expect("stream exists");
903 assert_eq!(format!("{:?}", stream.state()), "HalfClosedRemote");
904 assert!(!stream.state().can_recv());
905 assert!(stream.state().can_send());
906 }
907
908 #[test]
909 fn data_after_end_stream_reports_stream_closed() {
910 let mut connection = Connection::server(Settings::default());
911 accept_peer_settings(&mut connection).expect("SETTINGS handshake");
912 initialize_remote_stream(&mut connection, 1).expect("open stream");
913
914 let first = DataFrame::new(1, Bytes::from_static(b"done"), true);
915 process_live_data_frame(&mut connection, &first).expect("first DATA should process");
916
917 let second = DataFrame::new(1, Bytes::from_static(b"again"), false);
918 let error = process_live_data_frame(&mut connection, &second)
919 .expect_err("DATA after END_STREAM must fail");
920 assert_eq!(error, "StreamClosed");
921 }
922
923 #[test]
924 fn data_frame_updates_connection_and_stream_receive_windows() {
925 let mut connection = Connection::server(Settings::default());
926 accept_peer_settings(&mut connection).expect("SETTINGS handshake");
927 initialize_remote_stream(&mut connection, 1).expect("open stream");
928
929 let connection_window_before = connection.recv_window();
930 let stream_window_before = connection.stream(1).unwrap().recv_window();
931 let data = DataFrame::new(1, Bytes::from_static(b"windowed"), false);
932 process_live_data_frame(&mut connection, &data).expect("DATA should process");
933
934 assert_eq!(connection.recv_window(), connection_window_before - 8);
935 assert_eq!(
936 connection.stream(1).unwrap().recv_window(),
937 stream_window_before - 8
938 );
939 assert_eq!(
940 format!("{:?}", connection.stream(1).unwrap().state()),
941 "Open"
942 );
943 }
944
945 #[test]
946 fn headers_after_data_end_stream_reports_stream_closed() {
947 let mut connection = Connection::server(Settings::default());
948 accept_peer_settings(&mut connection).expect("SETTINGS handshake");
949 initialize_remote_stream(&mut connection, 1).expect("open stream");
950
951 let data = DataFrame::new(1, Bytes::from_static(b"done"), true);
952 process_live_data_frame(&mut connection, &data).expect("DATA should close remote side");
953
954 let trailers = HeadersFrame::new(1, Bytes::new(), true, true);
955 let error = connection
956 .process_frame(Frame::Headers(trailers))
957 .expect_err("HEADERS after END_STREAM must fail");
958 assert_eq!(error.code, ErrorCode::StreamClosed);
959 assert_eq!(error.stream_id, Some(1));
960 }
961}