From 1bbc0ace69e201c23fd5a8725497b19b1303effe Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 30 Sep 2026 08:11:26 +0200 Subject: [PATCH] Media wire: the encoder refuses a record over the ceiling too A producer that builds an oversized record without going through the FEC policy would otherwise learn only when the receiver silently dropped it. encode_media_wire_record now refuses anything over GAMECULT_MEDIA_MAX_WIRE_BYTES, with the decoder's message. The decode ceiling test pads a valid record by one byte to stand in for a peer that sent one. Co-Authored-By: Claude Sonnet 5.5 --- packages/cultnet-rs/src/media_stream_wire.rs | 9 ++++++++- packages/cultnet-rs/tests/media_stream_wire.rs | 17 +++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/cultnet-rs/src/media_stream_wire.rs b/packages/cultnet-rs/src/media_stream_wire.rs index 78915a75..5c89a8c3 100644 --- a/packages/cultnet-rs/src/media_stream_wire.rs +++ b/packages/cultnet-rs/src/media_stream_wire.rs @@ -182,7 +182,14 @@ pub fn encode_media_wire_record( }, }; - encode_cultnet_message_to_vec(&message, CultNetWireContract::CultNetSchemaV0).map_err(Into::into) + let wire = encode_cultnet_message_to_vec(&message, CultNetWireContract::CultNetSchemaV0)?; + if wire.len() > GAMECULT_MEDIA_MAX_WIRE_BYTES { + return Err(anyhow!( + "media record is {} bytes, over the {GAMECULT_MEDIA_MAX_WIRE_BYTES}-byte ceiling", + wire.len() + )); + } + Ok(wire) } /// Unwraps a media record from the wire. diff --git a/packages/cultnet-rs/tests/media_stream_wire.rs b/packages/cultnet-rs/tests/media_stream_wire.rs index 530123f7..cad8882f 100644 --- a/packages/cultnet-rs/tests/media_stream_wire.rs +++ b/packages/cultnet-rs/tests/media_stream_wire.rs @@ -512,8 +512,10 @@ fn a_media_record_one_byte_over_the_ceiling_is_refused_at_decode() { decode_media_wire_record(&wire) .unwrap_or_else(|error| panic!("{name} at the ceiling is refused: {error}")); - let over = encode_media_wire_record(&wrap(at + 1), provenance()).unwrap(); - assert_eq!(over.len(), ceiling + 1); + // The encoder refuses to make an over-ceiling record, so one byte of + // padding stands in for a peer that sent one. + let mut over = wire.clone(); + over.push(0); let error = decode_media_wire_record(&over).unwrap_err(); assert!(error.to_string().contains("ceiling"), "{name}: {error}"); } @@ -639,3 +641,14 @@ fn a_session_refuses_to_reassemble_more_than_the_ceiling_on_the_media_channel() let error = deliver(GAMECULT_MEDIA_CHANNEL).unwrap_err(); assert!(error.to_string().contains("max_payload_bytes"), "{error}"); } + +/// A producer learns at encode time that a record is over the ceiling, rather +/// than the receiver dropping it. +#[test] +fn the_encoder_refuses_a_record_over_the_ceiling() { + let wrap = |payload: usize| GameCultMediaWireRecord::AudioParity(audio_parity(payload, 300)); + let at = payload_for_wire_len(GAMECULT_MEDIA_MAX_WIRE_BYTES, &wrap); + assert!(encode_media_wire_record(&wrap(at), provenance()).is_ok()); + let error = encode_media_wire_record(&wrap(at + 1), provenance()).unwrap_err(); + assert!(error.to_string().contains("ceiling"), "{error}"); +}