The SGFR Rust-to-webview envelope redefined KEYFRAME/CODEC_CONFIG/ DISCONTINUITY with the same names as sigil_protocol::FrameFlags but with the codec-config and discontinuity bits swapped. Correctness relied on a field-by-field boolean remap; forwarding FrameFlags::bits() directly would have silently swapped the two meanings. The channel constants are now derived from FrameFlags itself, the portal parser mirrors the corrected positions, and both sides pin the bit assignments in tests.
- commit
- f09bb621643f4a5290478bf94e17a4dcf3a3b9b7
- parent
- 0bd0cd95c807400b7de34a2110bda529a238ef57
- author
- Dami <dami.etoile@gmail.com>
| M | portal/ | 6 | ++++-- |
| M | portal/ | 14 | ++++++++++++- |
| M | src-tauri/ | 32 | ++++++++++++++++++++++++++---- |
@@ -5,10 +5,12 @@ export const MAX_FRAME_PIXELS = 7680 * 4320;
55 66 const MAGIC = [0x53, 0x47, 0x46, 0x52]; // SGFR77 const VERSION = 1;8+// Flag bits mirror sigil-protocol's FrameFlags (crates/sigil-protocol/src/media.rs)9+// exactly; the Rust channel encoder derives its values from that type.810 const KNOWN_FLAGS = 0b00000111;911 const KEYFRAME_FLAG = 1 << 0;10-const DISCONTINUITY_FLAG = 1 << 1;11-const CODEC_CONFIG_FLAG = 1 << 2;12+const CODEC_CONFIG_FLAG = 1 << 1;13+const DISCONTINUITY_FLAG = 1 << 2;1214 const OPTIONAL_U64_NONE = 0xffffffffffffffffn;1315 const OPTIONAL_I64_NONE = -0x8000000000000000n;1416 const MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);@@ -64,6 +64,18 @@ test('parses an exact v1 frame envelope without copying its payload', () => {
6464 assert.equal(frame.data[0], 9);6565 });6666 67+test('flag bit positions match the sigil-protocol wire assignments', () => {68+ const discontinuityOnly = parseFrameEnvelope(frameEnvelope({ flags: 0b100 }));69+ assert.equal(discontinuityOnly.keyframe, false);70+ assert.equal(discontinuityOnly.codecConfig, false);71+ assert.equal(discontinuityOnly.discontinuity, true);72+73+ const keyframeConfig = parseFrameEnvelope(frameEnvelope({ flags: 0b011 }));74+ assert.equal(keyframeConfig.keyframe, true);75+ assert.equal(keyframeConfig.codecConfig, true);76+ assert.equal(keyframeConfig.discontinuity, false);77+});78+6779 test('maps optional integer sentinels to null', () => {6880 const frame = parseFrameEnvelope(frameEnvelope({6981 sequence: 0xffffffffffffffffn,@@ -83,7 +95,7 @@ test('rejects malformed identity, version, codec, flags, and reserved fields', (
8395 (bytes) => { bytes[5] = 2; },8496 (bytes) => { bytes[5] = 3; },8597 (bytes) => { bytes[6] = 0x80; },86- (bytes) => { bytes[6] = 0b100; },98+ (bytes) => { bytes[6] = 0b010; },8799 (bytes) => { bytes[7] = 1; },88100 ];89101 for (const mutate of mutations) {@@ -1,7 +1,7 @@
11 use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};22 33 use serde::Serialize;4-use sigil_protocol::{MAX_MEDIA_PAYLOAD_LEN, MAX_VIDEO_DIMENSION, MAX_VIDEO_PIXELS};4+use sigil_protocol::{FrameFlags, MAX_MEDIA_PAYLOAD_LEN, MAX_VIDEO_DIMENSION, MAX_VIDEO_PIXELS};55 use tauri::{AppHandle, Emitter};66 77 use crate::commands::state::AppState;@@ -52,9 +52,12 @@ pub(crate) const CLIENT_FRAME_CHANNEL_CAPACITY: usize = 4;
5252 const FRAME_CHANNEL_MAGIC: [u8; 4] = *b"SGFR";5353 const FRAME_CHANNEL_VERSION: u8 = 1;5454 const FRAME_CHANNEL_HEADER_LEN: usize = 40;55-const FRAME_CHANNEL_FLAG_KEYFRAME: u8 = 1 << 0;56-const FRAME_CHANNEL_FLAG_DISCONTINUITY: u8 = 1 << 1;57-const FRAME_CHANNEL_FLAG_CODEC_CONFIG: u8 = 1 << 2;55+// The channel flag byte uses the exact sigil-protocol wire bit assignments;56+// deriving them here keeps the two layers incapable of drifting apart.57+// portal/frame-envelope.mjs mirrors the same values.58+const FRAME_CHANNEL_FLAG_KEYFRAME: u8 = FrameFlags::KEYFRAME.bits();59+const FRAME_CHANNEL_FLAG_CODEC_CONFIG: u8 = FrameFlags::CODEC_CONFIG.bits();60+const FRAME_CHANNEL_FLAG_DISCONTINUITY: u8 = FrameFlags::DISCONTINUITY.bits();5861 const FRAME_CHANNEL_OPTIONAL_U64_NONE: u64 = u64::MAX;5962 const FRAME_CHANNEL_OPTIONAL_I64_NONE: i64 = i64::MIN;6063 @@ -313,6 +316,27 @@ mod tests {
313316 assert_eq!(&envelope[40..], payload);314317 }315318 319+ 320+ fn channel_flag_bits_match_protocol_wire_assignments() {321+ let envelope = encode_frame_envelope(322+ FrameEnvelopeMetadata {323+ width: 1,324+ height: 1,325+ codec: "h264",326+ keyframe: false,327+ discontinuity: true,328+ codec_config: false,329+ sequence: None,330+ capture_timestamp_micros: None,331+ pts_micros: None,332+ },333+ &[1],334+ )335+ .unwrap();336+ assert_eq!(envelope[6], FrameFlags::DISCONTINUITY.bits());337+ assert_eq!(envelope[6], 0b100);338+ }339+316340 317341 fn binary_frame_envelope_uses_explicit_optional_sentinels() {318342 let envelope = encode_frame_envelope(