Skip to main content

dvb_subtitle/segments/
disparity_signalling.rs

1//! Disparity Signalling Segment — ETSI EN 300 743 §7.2.7, Table 29 (segment_type 0x15).
2//!
3//! Supports plano-stereoscopic 3DTV subtitling by allowing disparity values
4//! to be ascribed to regions or subregions.
5
6use crate::error::{Error, Result};
7use broadcast_common::len::{need_in, need_out};
8use broadcast_common::{Parse, Serialize};
9
10/// The disparity_signalling_segment segment_type.
11pub const SEGMENT_TYPE: u8 = 0x15;
12/// Header: 6 bytes.
13pub const HEADER_LEN: usize = 6;
14/// Fixed body: dss_version_number(4b)+page_flag(1b)+reserved(3b) + page_default_disparity_shift(1) = 2 bytes.
15pub const FIXED_LEN: usize = 2;
16
17/// A disparity shift update sequence as defined in Table 30.
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20pub struct DisparityShiftUpdateSequence {
21    /// Interval duration in 90kHz STC units (24-bit).
22    pub interval_duration: u32,
23    /// Interval count + disparity shift integer pairs. `division_period_count`
24    /// (Table 30) is exactly this list's length — "the number of ... time
25    /// intervals within the following ... 'for' loop" — so it is derived on
26    /// serialize rather than stored (#1108/DS-W3: a stored count could
27    /// diverge from the data it's supposed to describe).
28    pub intervals: alloc::vec::Vec<DisparityShiftInterval>,
29}
30
31/// An interval within a disparity shift update sequence.
32#[derive(Debug, Clone, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize))]
34pub struct DisparityShiftInterval {
35    /// Interval count multiplier.
36    pub interval_count: u8,
37    /// Disparity shift integer part (signed).
38    pub disparity_shift_integer: i8,
39}
40
41impl DisparityShiftUpdateSequence {
42    fn serialized_len(&self) -> usize {
43        1 + 3 + 1 + self.intervals.len() * 2
44    }
45
46    fn serialize_into(&self, buf: &mut [u8]) -> Result<()> {
47        let body_len = 3 + 1 + self.intervals.len() * 2;
48        buf[0] = broadcast_common::len::fit_u8(body_len, "disparity_shift_update_sequence_length")?;
49        broadcast_common::len::fit_bits(
50            u64::from(self.interval_duration),
51            24,
52            "interval_duration",
53        )?;
54        buf[1] = (self.interval_duration >> 16) as u8;
55        buf[2] = (self.interval_duration >> 8) as u8;
56        buf[3] = self.interval_duration as u8;
57        buf[4] = broadcast_common::len::fit_u8(self.intervals.len(), "division_period_count")?;
58        for (i, interval) in self.intervals.iter().enumerate() {
59            let off = 5 + i * 2;
60            buf[off] = interval.interval_count;
61            buf[off + 1] = interval.disparity_shift_integer as u8;
62        }
63        Ok(())
64    }
65
66    fn parse(bytes: &[u8]) -> Result<(Self, usize)> {
67        need_in(bytes, 5, "disparity_shift_update_sequence")?;
68        let seq_len = bytes[0] as usize;
69        need_in(bytes, 1 + seq_len, "disparity_shift_update_sequence data")?;
70        let dur = ((bytes[1] as u32) << 16) | ((bytes[2] as u32) << 8) | (bytes[3] as u32);
71        let count = bytes[4];
72        let total = 1 + seq_len;
73        let mut intervals = alloc::vec::Vec::new();
74        let mut ipos = 5;
75        for _ in 0..count {
76            if ipos + 2 > total {
77                return Err(Error::BufferTooShort(
78                    broadcast_common::error::BufferTooShort {
79                        need: ipos + 2,
80                        have: total,
81                        what: "disparity_shift_update_sequence interval",
82                    },
83                ));
84            }
85            intervals.push(DisparityShiftInterval {
86                interval_count: bytes[ipos],
87                disparity_shift_integer: bytes[ipos + 1] as i8,
88            });
89            ipos += 2;
90        }
91        Ok((
92            DisparityShiftUpdateSequence {
93                interval_duration: dur,
94                intervals,
95            },
96            total,
97        ))
98    }
99}
100
101/// A subregion within a region for disparity signalling.
102#[derive(Debug, Clone, PartialEq, Eq)]
103#[cfg_attr(feature = "serde", derive(serde::Serialize))]
104pub struct Subregion {
105    /// Horizontal position relative to page (only if multiple subregions).
106    pub subregion_horizontal_position: Option<u16>,
107    /// Width in pixels (only if multiple subregions).
108    pub subregion_width: Option<u16>,
109    /// Disparity shift integer part (signed).
110    pub subregion_disparity_shift_integer: i8,
111    /// Disparity shift fractional part (unsigned, in 1/16 pixel).
112    pub subregion_disparity_shift_fractional: u8,
113    /// Reserved bits in the subregion shift/frac byte (bits `[3:0]`).
114    pub reserved: u8,
115    /// Update sequence if flag is set.
116    pub update_sequence: Option<DisparityShiftUpdateSequence>,
117}
118
119impl Subregion {
120    fn serialized_len(&self) -> usize {
121        let base = if self.subregion_horizontal_position.is_some() {
122            4 + 2
123        } else {
124            2
125        };
126        base + self
127            .update_sequence
128            .as_ref()
129            .map_or(0, |u| u.serialized_len())
130    }
131
132    /// `has_pos` comes from the *region*, not this subregion (Table 29: the
133    /// position fields are present for every subregion in a region, or none
134    /// of them, per `number_of_subregions_minus_1`) — see DisparityRegion::
135    /// serialize_into. Errors if this subregion's stored `Option`s disagree
136    /// with that (#1108/DS-W3): writing this subregion's own state instead
137    /// could silently misframe the following subregions/regions.
138    fn serialize_into(&self, buf: &mut [u8], has_pos: bool) -> Result<()> {
139        let stored_has_pos =
140            self.subregion_horizontal_position.is_some() && self.subregion_width.is_some();
141        if stored_has_pos != has_pos {
142            return Err(Error::SubregionPositionMismatch);
143        }
144        let mut off = 0;
145        if let (Some(h), Some(w)) = (self.subregion_horizontal_position, self.subregion_width) {
146            buf[0..2].copy_from_slice(&h.to_be_bytes());
147            buf[2..4].copy_from_slice(&w.to_be_bytes());
148            off = 4;
149        }
150        buf[off] = self.subregion_disparity_shift_integer as u8;
151        buf[off + 1] = (broadcast_common::len::fit_mask(
152            self.subregion_disparity_shift_fractional,
153            0x0F,
154            "subregion_disparity_shift_fractional",
155        )? << 4)
156            | broadcast_common::len::fit_mask(self.reserved, 0x0F, "reserved")?;
157        off += 2;
158        if let Some(ref seq) = self.update_sequence {
159            seq.serialize_into(&mut buf[off..])?;
160        }
161        Ok(())
162    }
163}
164
165/// A region entry in the disparity signalling segment.
166#[derive(Debug, Clone, PartialEq, Eq)]
167#[cfg_attr(feature = "serde", derive(serde::Serialize))]
168pub struct DisparityRegion {
169    /// Region identifier.
170    pub region_id: u8,
171    /// Whether an update sequence follows for this region's subregions.
172    pub update_sequence_region_flag: bool,
173    /// Reserved bits in the region flags byte (bits `[6:2]`).
174    pub reserved_flags: u8,
175    /// Subregions within this region.
176    pub subregions: alloc::vec::Vec<Subregion>,
177}
178
179impl DisparityRegion {
180    fn serialized_len(&self) -> usize {
181        2 + self
182            .subregions
183            .iter()
184            .map(|s| s.serialized_len())
185            .sum::<usize>()
186    }
187
188    fn serialize_into(&self, buf: &mut [u8]) -> Result<()> {
189        let count = self.subregions.len();
190        // number_of_subregions_minus_1 is 2 bits (Table 29): only 1..=4
191        // subregions are representable. Pre-fix, `as u8` + `& 0x03` silently
192        // wrapped an out-of-range count instead of rejecting it (#1108/DS-W3).
193        if count == 0 || count > 4 {
194            return Err(Error::InvalidSubregionCount(count));
195        }
196        let num = (count - 1) as u8;
197        buf[0] = self.region_id;
198        buf[1] = (u8::from(self.update_sequence_region_flag) << 7)
199            | broadcast_common::len::fit_mask(self.reserved_flags, 0x7C, "reserved_flags")?
200            | (num & 0x03);
201        // Position fields are present for every subregion, or none, per
202        // whether the region itself has more than one (Table 29's
203        // `if (number_of_subregions_minus_1 > 0)` guards the whole `for` body).
204        let has_pos = count > 1;
205        let mut off = 2;
206        for sub in &self.subregions {
207            sub.serialize_into(&mut buf[off..], has_pos)?;
208            off += sub.serialized_len();
209        }
210        Ok(())
211    }
212}
213
214/// Disparity Signalling Segment.
215#[derive(Debug, Clone, PartialEq, Eq)]
216#[cfg_attr(feature = "serde", derive(serde::Serialize))]
217pub struct DisparitySignallingSegment {
218    /// The page_id from the segment header.
219    pub page_id: u16,
220    /// DSS version number (modulo 16).
221    pub dss_version_number: u8,
222    /// Whether a page-level update sequence follows.
223    pub update_sequence_page_flag: bool,
224    /// Reserved bits in body byte 0 (bits `[2:0]`).
225    pub reserved: u8,
226    /// Default disparity shift for the whole page (signed).
227    pub page_default_disparity_shift: i8,
228    /// Page-level update sequence if flag is set.
229    pub page_update_sequence: Option<DisparityShiftUpdateSequence>,
230    /// Region entries.
231    pub regions: alloc::vec::Vec<DisparityRegion>,
232}
233
234impl<'a> Parse<'a> for DisparitySignallingSegment {
235    type Error = Error;
236
237    fn parse(bytes: &'a [u8]) -> Result<Self> {
238        need_in(
239            bytes,
240            HEADER_LEN + FIXED_LEN,
241            "disparity_signalling_segment",
242        )?;
243        if bytes[0] != crate::pes_data_field::SYNC_BYTE {
244            return Err(Error::InvalidSync(broadcast_common::error::InvalidSync {
245                what: "sync_byte",
246                expected: u32::from(crate::pes_data_field::SYNC_BYTE),
247                found: u32::from(bytes[0]),
248            }));
249        }
250        if bytes[1] != SEGMENT_TYPE {
251            return Err(Error::UnknownSegmentType(bytes[1]));
252        }
253        let page_id = u16::from_be_bytes([bytes[2], bytes[3]]);
254        let segment_length = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
255        let total = HEADER_LEN + segment_length;
256        need_in(bytes, total, "disparity_signalling_segment data")?;
257        let body = &bytes[HEADER_LEN..HEADER_LEN + segment_length];
258        need_in(body, FIXED_LEN, "disparity_signalling_segment body")?;
259        let dss_version_number = body[0] >> 4;
260        let update_sequence_page_flag = (body[0] & 0x08) != 0;
261        let reserved = body[0] & 0x07;
262        let page_default_disparity_shift = body[1] as i8;
263
264        let mut pos = FIXED_LEN;
265        let page_update_sequence = if update_sequence_page_flag {
266            let (seq, seq_len) = DisparityShiftUpdateSequence::parse(&body[pos..])?;
267            pos += seq_len;
268            Some(seq)
269        } else {
270            None
271        };
272
273        let mut regions = alloc::vec::Vec::new();
274        while pos < body.len() {
275            // Table 29's region loop is `while (processed_length <
276            // segment_length)` with no stuffing provision, so 1 leftover
277            // byte means the segment is malformed, not that it should be
278            // silently dropped (#1108/DS-W6: this used to just `break`).
279            need_in(body, pos + 2, "disparity_signalling_segment region header")?;
280            let region_id = body[pos];
281            pos += 1;
282            let update_flag = (body[pos] & 0x80) != 0;
283            let reserved_flags = body[pos] & 0x7C;
284            let num_subregions_minus_1 = body[pos] & 0x03;
285            pos += 1;
286
287            let mut subregions = alloc::vec::Vec::new();
288            for _n in 0..=num_subregions_minus_1 {
289                let has_pos = num_subregions_minus_1 > 0;
290                let entry_len = if has_pos { 4 + 2 } else { 2 };
291                need_in(body, pos + entry_len, "subregion entry")?;
292
293                let (hpos, width) = if has_pos {
294                    let h = u16::from_be_bytes([body[pos], body[pos + 1]]);
295                    let w = u16::from_be_bytes([body[pos + 2], body[pos + 3]]);
296                    pos += 4;
297                    (Some(h), Some(w))
298                } else {
299                    (None, None)
300                };
301
302                let shift_int = body[pos] as i8;
303                let shift_frac = body[pos + 1] >> 4;
304                let reserved = body[pos + 1] & 0x0F;
305                pos += 2;
306
307                let sub_update = if update_flag {
308                    let (seq, seq_len) = DisparityShiftUpdateSequence::parse(&body[pos..])?;
309                    pos += seq_len;
310                    Some(seq)
311                } else {
312                    None
313                };
314
315                subregions.push(Subregion {
316                    subregion_horizontal_position: hpos,
317                    subregion_width: width,
318                    subregion_disparity_shift_integer: shift_int,
319                    subregion_disparity_shift_fractional: shift_frac,
320                    reserved,
321                    update_sequence: sub_update,
322                });
323            }
324
325            regions.push(DisparityRegion {
326                region_id,
327                update_sequence_region_flag: update_flag,
328                reserved_flags,
329                subregions,
330            });
331        }
332
333        Ok(DisparitySignallingSegment {
334            page_id,
335            dss_version_number,
336            update_sequence_page_flag,
337            reserved,
338            page_default_disparity_shift,
339            page_update_sequence,
340            regions,
341        })
342    }
343}
344
345impl Serialize for DisparitySignallingSegment {
346    type Error = Error;
347
348    fn serialized_len(&self) -> usize {
349        HEADER_LEN
350            + FIXED_LEN
351            + self
352                .page_update_sequence
353                .as_ref()
354                .map_or(0, |s| s.serialized_len())
355            + self
356                .regions
357                .iter()
358                .map(|r| r.serialized_len())
359                .sum::<usize>()
360    }
361
362    fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
363        let len = self.serialized_len();
364        need_out(buf, len)?;
365        buf[0] = 0x0F;
366        buf[1] = SEGMENT_TYPE;
367        buf[2..4].copy_from_slice(&self.page_id.to_be_bytes());
368        let seg_len = crate::segments::check_segment_length(len - HEADER_LEN)?;
369        buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
370
371        buf[6] =
372            (broadcast_common::len::fit_mask(self.dss_version_number, 0x0F, "dss_version_number")?
373                << 4)
374                | (u8::from(self.update_sequence_page_flag) << 3)
375                | broadcast_common::len::fit_mask(self.reserved, 0x07, "reserved")?;
376        buf[7] = self.page_default_disparity_shift as u8;
377
378        let mut off = HEADER_LEN + FIXED_LEN;
379        if let Some(ref seq) = self.page_update_sequence {
380            seq.serialize_into(&mut buf[off..])?;
381            off += seq.serialized_len();
382        }
383        for region in &self.regions {
384            region.serialize_into(&mut buf[off..])?;
385            off += region.serialized_len();
386        }
387        Ok(len)
388    }
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394    use broadcast_common::{Parse, Serialize};
395
396    #[test]
397    fn round_trip_simple() {
398        let bytes = [
399            0x0F, 0x15, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
400        ];
401        let seg = DisparitySignallingSegment::parse(&bytes).unwrap();
402        assert_eq!(seg.dss_version_number, 0);
403        assert!(!seg.update_sequence_page_flag);
404        assert_eq!(seg.regions.len(), 1);
405        assert_eq!(seg.regions[0].region_id, 1);
406        assert_eq!(seg.regions[0].subregions.len(), 1);
407        let out = seg.try_to_bytes().unwrap();
408        assert_eq!(out, bytes);
409
410        // Biting test
411        let mut seg2 = seg.clone();
412        seg2.regions[0].region_id = 5;
413        let out2 = seg2.try_to_bytes().unwrap();
414        assert_ne!(out2, bytes);
415        let reparse = DisparitySignallingSegment::parse(&out2).unwrap();
416        assert_eq!(reparse.regions[0].region_id, 5);
417    }
418
419    /// #1129: `interval_duration` is 24 bits; a wider value must error.
420    #[test]
421    fn interval_duration_over_24_bits_is_rejected() {
422        let mut seq = DisparityShiftUpdateSequence {
423            interval_duration: 0x0100_0000,
424            intervals: alloc::vec![],
425        };
426        let mut buf = [0u8; 16];
427        assert!(matches!(
428            seq.serialize_into(&mut buf),
429            Err(Error::FieldOverflow(_))
430        ));
431        seq.interval_duration = 0x00FF_FFFF;
432        assert!(seq.serialize_into(&mut buf).is_ok());
433        assert_eq!(&buf[1..4], &[0xFF, 0xFF, 0xFF]);
434    }
435
436    /// DS-W2 (#1108): sync_byte wasn't checked by any typed segment parser.
437    #[test]
438    fn rejects_bad_sync_byte() {
439        let bytes = [
440            0x00, 0x15, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
441        ];
442        assert!(matches!(
443            DisparitySignallingSegment::parse(&bytes),
444            Err(Error::InvalidSync(broadcast_common::error::InvalidSync {
445                found: 0x00,
446                ..
447            }))
448        ));
449    }
450
451    /// DS-W6 (#1108): Table 29's region loop has no stuffing provision, so a
452    /// single leftover byte after the last complete region is now rejected,
453    /// not silently dropped via a bare `break`.
454    #[test]
455    fn rejects_trailing_partial_region() {
456        let bytes = [
457            0x0F, 0x15, 0x00, 0x01, 0x00, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
458        ];
459        assert!(matches!(
460            DisparitySignallingSegment::parse(&bytes),
461            Err(Error::BufferTooShort(..))
462        ));
463    }
464
465    #[test]
466    fn round_trip_with_page_update_seq() {
467        let bytes = [
468            0x0F, 0x15, 0x00, 0x01, 0x00, 0x0B, 0x88, 0x05, 0x08, 0x00, 0x00, 0x0A, 0x02, 0x01,
469            0x10, 0x03, 0x20,
470        ];
471        let seg = DisparitySignallingSegment::parse(&bytes).unwrap();
472        assert_eq!(seg.dss_version_number, 8);
473        assert!(seg.update_sequence_page_flag);
474        assert_eq!(seg.regions.len(), 0);
475        let out = seg.try_to_bytes().unwrap();
476        assert_eq!(out, bytes);
477
478        // Biting test
479        let mut seg2 = seg.clone();
480        seg2.page_default_disparity_shift = 10;
481        let out2 = seg2.try_to_bytes().unwrap();
482        assert_ne!(out2, bytes);
483        let reparse = DisparitySignallingSegment::parse(&out2).unwrap();
484        assert_eq!(reparse.page_default_disparity_shift, 10);
485    }
486
487    #[test]
488    fn round_trip_with_update_sequence() {
489        let bytes = [
490            0x0F, 0x15, 0x00, 0x01, 0x00, 0x16, 0x88, 0x05, 0x08, 0x00, 0x00, 0x0A, 0x02, 0x01,
491            0x10, 0x03, 0x20, 0x01, 0x80, 0x10, 0x00, 0x06, 0x00, 0x00, 0x14, 0x01, 0x01, 0x08,
492        ];
493        let seg = DisparitySignallingSegment::parse(&bytes).unwrap();
494        assert_eq!(seg.dss_version_number, 8);
495        assert!(seg.update_sequence_page_flag);
496        assert_eq!(seg.page_default_disparity_shift, 5);
497        let seq = seg.page_update_sequence.as_ref().unwrap();
498        assert_eq!(seq.intervals.len(), 2);
499        assert_eq!(seg.regions.len(), 1);
500        assert_eq!(seg.regions[0].region_id, 1);
501        assert!(seg.regions[0].update_sequence_region_flag);
502        assert_eq!(seg.regions[0].subregions.len(), 1);
503        let out = seg.try_to_bytes().unwrap();
504        assert_eq!(out, bytes);
505
506        // Biting test
507        let mut seg2 = seg.clone();
508        seg2.dss_version_number = 3;
509        let out2 = seg2.try_to_bytes().unwrap();
510        assert_ne!(out2, bytes);
511        let reparse = DisparitySignallingSegment::parse(&out2).unwrap();
512        assert_eq!(reparse.dss_version_number, 3);
513    }
514
515    /// DS-W3 (#1108): `division_period_count` is now derived from
516    /// `intervals.len()`, so appending an interval must grow the on-wire
517    /// count byte and re-parse with the new interval intact.
518    #[test]
519    fn division_period_count_tracks_intervals_len() {
520        let bytes = [
521            0x0F, 0x15, 0x00, 0x01, 0x00, 0x0B, 0x88, 0x05, 0x08, 0x00, 0x00, 0x0A, 0x02, 0x01,
522            0x10, 0x03, 0x20,
523        ];
524        let seg = DisparitySignallingSegment::parse(&bytes).unwrap();
525        let mut seg2 = seg.clone();
526        seg2.page_update_sequence
527            .as_mut()
528            .unwrap()
529            .intervals
530            .push(DisparityShiftInterval {
531                interval_count: 5,
532                disparity_shift_integer: -1,
533            });
534        let out2 = seg2.try_to_bytes().unwrap();
535        assert_ne!(out2, bytes);
536        // division_period_count is body[4] of the update sequence, which
537        // starts right after the 8-byte fixed header (offset HEADER_LEN+FIXED_LEN).
538        let seq_off = HEADER_LEN + FIXED_LEN;
539        assert_eq!(
540            out2[seq_off + 4],
541            3,
542            "division_period_count must track intervals.len()"
543        );
544        let reparse = DisparitySignallingSegment::parse(&out2).unwrap();
545        assert_eq!(reparse.page_update_sequence.unwrap().intervals.len(), 3);
546    }
547
548    /// DS-W3 (#1108): `number_of_subregions_minus_1` is 2 bits (Table 29), so
549    /// 0 or more than 4 subregions must be rejected, not silently wrapped by
550    /// `as u8 & 0x03`.
551    #[test]
552    fn rejects_out_of_range_subregion_count() {
553        let region = DisparityRegion {
554            region_id: 1,
555            update_sequence_region_flag: false,
556            reserved_flags: 0,
557            subregions: alloc::vec::Vec::new(),
558        };
559        let mut buf = [0u8; 16];
560        assert!(matches!(
561            region.serialize_into(&mut buf),
562            Err(Error::InvalidSubregionCount(0))
563        ));
564
565        let sub = Subregion {
566            subregion_horizontal_position: None,
567            subregion_width: None,
568            subregion_disparity_shift_integer: 0,
569            subregion_disparity_shift_fractional: 0,
570            reserved: 0,
571            update_sequence: None,
572        };
573        let region5 = DisparityRegion {
574            region_id: 1,
575            update_sequence_region_flag: false,
576            reserved_flags: 0,
577            subregions: alloc::vec![sub; 5],
578        };
579        let mut buf = [0u8; 64];
580        assert!(matches!(
581            region5.serialize_into(&mut buf),
582            Err(Error::InvalidSubregionCount(5))
583        ));
584    }
585
586    /// DS-W3 (#1108): a region with 2+ subregions requires the position
587    /// fields present on ALL of them (Table 29); one subregion disagreeing
588    /// with the others must be rejected, not silently misframed.
589    #[test]
590    fn rejects_inconsistent_subregion_position() {
591        let with_pos = Subregion {
592            subregion_horizontal_position: Some(10),
593            subregion_width: Some(20),
594            subregion_disparity_shift_integer: 0,
595            subregion_disparity_shift_fractional: 0,
596            reserved: 0,
597            update_sequence: None,
598        };
599        let without_pos = Subregion {
600            subregion_horizontal_position: None,
601            subregion_width: None,
602            subregion_disparity_shift_integer: 0,
603            subregion_disparity_shift_fractional: 0,
604            reserved: 0,
605            update_sequence: None,
606        };
607        let region = DisparityRegion {
608            region_id: 1,
609            update_sequence_region_flag: false,
610            reserved_flags: 0,
611            subregions: alloc::vec![with_pos, without_pos],
612        };
613        let mut buf = [0u8; 32];
614        assert!(matches!(
615            region.serialize_into(&mut buf),
616            Err(Error::SubregionPositionMismatch)
617        ));
618    }
619}