1use crate::error::{Error, Result};
4use broadcast_common::{Parse, Serialize};
5
6pub const SEGMENT_TYPE: u8 = 0x11;
8pub const HEADER_LEN: usize = 6;
10pub const FIXED_LEN: usize = 10;
12pub const OBJECT_ENTRY_BASE_LEN: usize = 6;
14pub const OBJECT_EXTRA_LEN: usize = 2;
16const POS_HIGH_NIBBLE_MASK: u8 = 0x0F;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[repr(u8)]
24#[non_exhaustive]
25pub enum RegionLevelOfCompatibility {
26 Reserved0 = 0x00,
28 Clut2Bit = 0x01,
30 Clut4Bit = 0x02,
32 Clut8Bit = 0x03,
34 Reserved(u8),
36}
37
38impl RegionLevelOfCompatibility {
39 #[must_use]
41 pub fn name(&self) -> &'static str {
42 match self {
43 Self::Reserved0 => "reserved",
44 Self::Clut2Bit => "2-bit CLUT",
45 Self::Clut4Bit => "4-bit CLUT",
46 Self::Clut8Bit => "8-bit CLUT",
47 Self::Reserved(_) => "reserved",
48 }
49 }
50
51 fn to_bits(self) -> u8 {
52 match self {
53 Self::Reserved0 => 0x00,
54 Self::Clut2Bit => 0x01,
55 Self::Clut4Bit => 0x02,
56 Self::Clut8Bit => 0x03,
57 Self::Reserved(v) => v,
58 }
59 }
60}
61
62broadcast_common::impl_spec_display!(RegionLevelOfCompatibility, Reserved);
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize))]
67#[repr(u8)]
68#[non_exhaustive]
69pub enum RegionDepth {
70 Reserved0 = 0x00,
72 Depth2Bit = 0x01,
74 Depth4Bit = 0x02,
76 Depth8Bit = 0x03,
78 Reserved(u8),
80}
81
82impl RegionDepth {
83 #[must_use]
85 pub fn name(&self) -> &'static str {
86 match self {
87 Self::Reserved0 => "reserved",
88 Self::Depth2Bit => "2-bit",
89 Self::Depth4Bit => "4-bit",
90 Self::Depth8Bit => "8-bit",
91 Self::Reserved(_) => "reserved",
92 }
93 }
94
95 fn to_bits(self) -> u8 {
96 match self {
97 Self::Reserved0 => 0x00,
98 Self::Depth2Bit => 0x01,
99 Self::Depth4Bit => 0x02,
100 Self::Depth8Bit => 0x03,
101 Self::Reserved(v) => v,
102 }
103 }
104}
105
106broadcast_common::impl_spec_display!(RegionDepth, Reserved);
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize))]
111#[repr(u8)]
112#[non_exhaustive]
113pub enum ObjectType {
114 BasicBitmap = 0x00,
116 BasicCharacter = 0x01,
118 CompositeString = 0x02,
120 Reserved(u8),
122}
123
124impl ObjectType {
125 #[must_use]
127 pub fn name(&self) -> &'static str {
128 match self {
129 Self::BasicBitmap => "basic_bitmap",
130 Self::BasicCharacter => "basic_character",
131 Self::CompositeString => "composite_string",
132 Self::Reserved(_) => "reserved",
133 }
134 }
135
136 fn to_bits(self) -> u8 {
137 match self {
138 Self::BasicBitmap => 0x00,
139 Self::BasicCharacter => 0x01,
140 Self::CompositeString => 0x02,
141 Self::Reserved(v) => v,
142 }
143 }
144}
145
146broadcast_common::impl_spec_display!(ObjectType, Reserved);
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150#[cfg_attr(feature = "serde", derive(serde::Serialize))]
151#[repr(u8)]
152#[non_exhaustive]
153pub enum ObjectProviderFlag {
154 InStream = 0x00,
156 InRom = 0x01,
158 Reserved(u8),
160}
161
162impl ObjectProviderFlag {
163 #[must_use]
165 pub fn name(&self) -> &'static str {
166 match self {
167 Self::InStream => "in_stream",
168 Self::InRom => "in_rom",
169 Self::Reserved(_) => "reserved",
170 }
171 }
172
173 fn to_bits(self) -> u8 {
174 match self {
175 Self::InStream => 0x00,
176 Self::InRom => 0x01,
177 Self::Reserved(v) => v,
178 }
179 }
180}
181
182broadcast_common::impl_spec_display!(ObjectProviderFlag, Reserved);
183
184#[derive(Debug, Clone, PartialEq, Eq)]
186#[cfg_attr(feature = "serde", derive(serde::Serialize))]
187pub struct RegionObjectEntry {
188 pub object_id: u16,
190 pub object_type: ObjectType,
192 pub object_provider_flag: ObjectProviderFlag,
194 pub object_horizontal_position: u16,
196 pub object_vertical_position: u16,
198 pub foreground_pixel_code: Option<u8>,
200 pub background_pixel_code: Option<u8>,
202}
203
204impl RegionObjectEntry {
205 fn serialized_len(&self) -> usize {
206 if self.foreground_pixel_code.is_some() {
207 OBJECT_ENTRY_BASE_LEN + OBJECT_EXTRA_LEN
208 } else {
209 OBJECT_ENTRY_BASE_LEN
210 }
211 }
212
213 fn serialize_into(&self, buf: &mut [u8]) -> Result<()> {
214 buf[0..2].copy_from_slice(&self.object_id.to_be_bytes());
215 let hpos = self.object_horizontal_position;
216 let vpos = self.object_vertical_position;
217 broadcast_common::len::fit_bits(u64::from(hpos), 12, "object_horizontal_position")?;
222 broadcast_common::len::fit_bits(u64::from(vpos), 12, "object_vertical_position")?;
223 buf[2] = (self.object_type.to_bits() << 6)
224 | (self.object_provider_flag.to_bits() << 4)
225 | ((hpos >> 8) as u8 & POS_HIGH_NIBBLE_MASK);
226 buf[3] = hpos as u8;
227 buf[4] = (vpos >> 8) as u8 & POS_HIGH_NIBBLE_MASK;
231 buf[5] = vpos as u8;
232 if let (Some(fg), Some(bg)) = (self.foreground_pixel_code, self.background_pixel_code) {
233 buf[6] = fg;
234 buf[7] = bg;
235 }
236 Ok(())
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
242#[cfg_attr(feature = "serde", derive(serde::Serialize))]
243pub struct RegionCompositionSegment {
244 pub page_id: u16,
246 pub region_id: u8,
248 pub region_version_number: u8,
250 pub region_fill_flag: bool,
252 pub reserved_byte1: u8,
254 pub region_width: u16,
256 pub region_height: u16,
258 pub region_level_of_compatibility: RegionLevelOfCompatibility,
260 pub region_depth: RegionDepth,
262 pub reserved_byte6: u8,
264 pub clut_id: u8,
266 pub region_8bit_pixel_code: u8,
268 pub region_4bit_pixel_code: u8,
270 pub region_2bit_pixel_code: u8,
272 pub reserved_byte9: u8,
274 pub objects: alloc::vec::Vec<RegionObjectEntry>,
276}
277
278fn parse_object_entry(bytes: &[u8]) -> Result<(RegionObjectEntry, usize)> {
279 if bytes.len() < OBJECT_ENTRY_BASE_LEN {
280 return Err(Error::BufferTooShort {
281 need: OBJECT_ENTRY_BASE_LEN,
282 have: bytes.len(),
283 what: "region_object_entry",
284 });
285 }
286 let object_id = u16::from_be_bytes([bytes[0], bytes[1]]);
287 let obj_type_val = (bytes[2] >> 6) & 0x03;
288 let obj_provider_val = (bytes[2] >> 4) & 0x03;
289 let obj_hpos =
290 ((u16::from(bytes[2]) & u16::from(POS_HIGH_NIBBLE_MASK)) << 8) | u16::from(bytes[3]);
291 let reserved = (bytes[4] >> 4) & POS_HIGH_NIBBLE_MASK;
292 let _ = reserved;
294 let obj_vpos =
295 ((u16::from(bytes[4]) & u16::from(POS_HIGH_NIBBLE_MASK)) << 8) | u16::from(bytes[5]);
296
297 let obj_type = match obj_type_val {
298 0x00 => ObjectType::BasicBitmap,
299 0x01 => ObjectType::BasicCharacter,
300 0x02 => ObjectType::CompositeString,
301 v => ObjectType::Reserved(v),
302 };
303 let obj_provider = match obj_provider_val {
304 0x00 => ObjectProviderFlag::InStream,
305 0x01 => ObjectProviderFlag::InRom,
306 v => ObjectProviderFlag::Reserved(v),
307 };
308
309 let has_extra = obj_type_val == 0x01 || obj_type_val == 0x02;
310 if has_extra {
311 if bytes.len() < OBJECT_ENTRY_BASE_LEN + OBJECT_EXTRA_LEN {
312 return Err(Error::BufferTooShort {
313 need: OBJECT_ENTRY_BASE_LEN + OBJECT_EXTRA_LEN,
314 have: bytes.len(),
315 what: "region_object_entry extra",
316 });
317 }
318 Ok((
319 RegionObjectEntry {
320 object_id,
321 object_type: obj_type,
322 object_provider_flag: obj_provider,
323 object_horizontal_position: obj_hpos,
324 object_vertical_position: obj_vpos,
325 foreground_pixel_code: Some(bytes[6]),
326 background_pixel_code: Some(bytes[7]),
327 },
328 OBJECT_ENTRY_BASE_LEN + OBJECT_EXTRA_LEN,
329 ))
330 } else {
331 Ok((
332 RegionObjectEntry {
333 object_id,
334 object_type: obj_type,
335 object_provider_flag: obj_provider,
336 object_horizontal_position: obj_hpos,
337 object_vertical_position: obj_vpos,
338 foreground_pixel_code: None,
339 background_pixel_code: None,
340 },
341 OBJECT_ENTRY_BASE_LEN,
342 ))
343 }
344}
345
346impl<'a> Parse<'a> for RegionCompositionSegment {
347 type Error = Error;
348
349 fn parse(bytes: &'a [u8]) -> Result<Self> {
350 if bytes.len() < HEADER_LEN + FIXED_LEN {
351 return Err(Error::BufferTooShort {
352 need: HEADER_LEN + FIXED_LEN,
353 have: bytes.len(),
354 what: "region_composition_segment",
355 });
356 }
357 if bytes[0] != crate::pes_data_field::SYNC_BYTE {
358 return Err(Error::BadSyncByte(bytes[0]));
359 }
360 if bytes[1] != SEGMENT_TYPE {
361 return Err(Error::UnknownSegmentType(bytes[1]));
362 }
363 let page_id = u16::from_be_bytes([bytes[2], bytes[3]]);
364 let segment_length = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
365 let total = HEADER_LEN + segment_length;
366 if bytes.len() < total {
367 return Err(Error::BufferTooShort {
368 need: total,
369 have: bytes.len(),
370 what: "region_composition_segment data",
371 });
372 }
373 let body = &bytes[HEADER_LEN..HEADER_LEN + segment_length];
374 if body.len() < FIXED_LEN {
375 return Err(Error::BufferTooShort {
376 need: FIXED_LEN,
377 have: body.len(),
378 what: "region_composition_segment body",
379 });
380 }
381 let region_id = body[0];
382 let region_version_number = body[1] >> 4;
383 let region_fill_flag = (body[1] & 0x08) != 0;
384 let reserved_byte1 = body[1] & 0x07;
385 let region_width = u16::from_be_bytes([body[2], body[3]]);
386 let region_height = u16::from_be_bytes([body[4], body[5]]);
387 let rloc_val = (body[6] >> 5) & 0x07;
388 let region_level_of_compatibility = match rloc_val {
389 0x00 => RegionLevelOfCompatibility::Reserved0,
390 0x01 => RegionLevelOfCompatibility::Clut2Bit,
391 0x02 => RegionLevelOfCompatibility::Clut4Bit,
392 0x03 => RegionLevelOfCompatibility::Clut8Bit,
393 v => RegionLevelOfCompatibility::Reserved(v),
394 };
395 let rd_val = (body[6] >> 2) & 0x07;
396 let region_depth = match rd_val {
397 0x00 => RegionDepth::Reserved0,
398 0x01 => RegionDepth::Depth2Bit,
399 0x02 => RegionDepth::Depth4Bit,
400 0x03 => RegionDepth::Depth8Bit,
401 v => RegionDepth::Reserved(v),
402 };
403 let reserved_byte6 = body[6] & 0x03;
404 let clut_id = body[7];
405 let region_8bit_pixel_code = body[8];
406 let region_4bit_pixel_code = body[9] >> 4;
407 let region_2bit_pixel_code = (body[9] >> 2) & 0x03;
408 let reserved_byte9 = body[9] & 0x03;
409
410 let obj_data = &body[FIXED_LEN..];
411 let mut objects = alloc::vec::Vec::new();
412 let mut pos: usize = 0;
413 while pos < obj_data.len() {
414 let (entry, entry_len) = parse_object_entry(&obj_data[pos..])?;
415 objects.push(entry);
416 pos += entry_len;
417 }
418
419 Ok(RegionCompositionSegment {
420 page_id,
421 region_id,
422 region_version_number,
423 region_fill_flag,
424 reserved_byte1,
425 region_width,
426 region_height,
427 region_level_of_compatibility,
428 region_depth,
429 reserved_byte6,
430 clut_id,
431 region_8bit_pixel_code,
432 region_4bit_pixel_code,
433 region_2bit_pixel_code,
434 reserved_byte9,
435 objects,
436 })
437 }
438}
439
440impl Serialize for RegionCompositionSegment {
441 type Error = Error;
442
443 fn serialized_len(&self) -> usize {
444 HEADER_LEN
445 + FIXED_LEN
446 + self
447 .objects
448 .iter()
449 .map(|o| o.serialized_len())
450 .sum::<usize>()
451 }
452
453 fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
454 let len = self.serialized_len();
455 if buf.len() < len {
456 return Err(Error::BufferTooShort {
457 need: len,
458 have: buf.len(),
459 what: "region_composition_segment serialize",
460 });
461 }
462 buf[0] = 0x0F;
463 buf[1] = SEGMENT_TYPE;
464 buf[2..4].copy_from_slice(&self.page_id.to_be_bytes());
465 let seg_len = crate::segments::check_segment_length(len - HEADER_LEN)?;
466 buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
467
468 buf[6] = self.region_id;
469 buf[7] = (broadcast_common::len::fit_mask(
470 self.region_version_number,
471 0x0F,
472 "region_version_number",
473 )? << 4)
474 | (u8::from(self.region_fill_flag) << 3)
475 | broadcast_common::len::fit_mask(self.reserved_byte1, 0x07, "reserved_byte1")?;
476 buf[8..10].copy_from_slice(&self.region_width.to_be_bytes());
477 buf[10..12].copy_from_slice(&self.region_height.to_be_bytes());
478 buf[12] = (broadcast_common::len::fit_mask(
479 self.region_level_of_compatibility.to_bits(),
480 0x07,
481 "region_level_of_compatibility",
482 )? << 5)
483 | (broadcast_common::len::fit_mask(self.region_depth.to_bits(), 0x07, "region_depth")?
484 << 2)
485 | broadcast_common::len::fit_mask(self.reserved_byte6, 0x03, "reserved_byte6")?;
486 buf[13] = self.clut_id;
487 buf[14] = self.region_8bit_pixel_code;
488 buf[15] = (broadcast_common::len::fit_mask(
489 self.region_4bit_pixel_code,
490 0x0F,
491 "region_4bit_pixel_code",
492 )? << 4)
493 | (broadcast_common::len::fit_mask(
494 self.region_2bit_pixel_code,
495 0x03,
496 "region_2bit_pixel_code",
497 )? << 2)
498 | broadcast_common::len::fit_mask(self.reserved_byte9, 0x03, "reserved_byte9")?;
499
500 let mut off = HEADER_LEN + FIXED_LEN;
501 for obj in &self.objects {
502 obj.serialize_into(&mut buf[off..])?;
503 off += obj.serialized_len();
504 }
505 Ok(len)
506 }
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
512 use broadcast_common::{Parse, Serialize};
513
514 #[test]
515 fn round_trip_bitmap_objects() {
516 let bytes = [
517 0x0F, 0x11, 0x00, 0x01, 0x00, 0x16, 0x01, 0x88, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
518 0x00, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x02, 0x10, 0x14, 0x00,
519 0x14, ];
521 let seg = RegionCompositionSegment::parse(&bytes).unwrap();
522 assert_eq!(seg.region_id, 1);
523 assert!(seg.region_fill_flag);
524 assert_eq!(seg.region_width, 719);
525 assert_eq!(seg.objects.len(), 2);
526 assert_eq!(seg.objects[0].object_id, 1);
527 assert_eq!(seg.objects[0].object_type, ObjectType::BasicBitmap);
528 assert_eq!(seg.objects[0].object_horizontal_position, 10);
529 assert_eq!(seg.objects[1].object_id, 2);
530 assert_eq!(
531 seg.objects[1].object_provider_flag,
532 ObjectProviderFlag::InRom
533 );
534 let out = seg.try_to_bytes().unwrap();
535 assert_eq!(out, bytes);
536
537 let mut seg2 = seg.clone();
539 seg2.region_width = 1280;
540 let out2 = seg2.try_to_bytes().unwrap();
541 assert_ne!(out2, bytes);
542 let reparse = RegionCompositionSegment::parse(&out2).unwrap();
543 assert_eq!(reparse.region_width, 1280);
544 }
545
546 #[test]
548 fn rejects_bad_sync_byte() {
549 let bytes = [
550 0x00, 0x11, 0x00, 0x01, 0x00, 0x16, 0x01, 0x88, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
551 0x00, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x02, 0x10, 0x14, 0x00, 0x14,
552 ];
553 assert!(matches!(
554 RegionCompositionSegment::parse(&bytes),
555 Err(Error::BadSyncByte(0x00))
556 ));
557 }
558
559 #[test]
560 fn round_trip_character_object() {
561 let bytes = [
562 0x0F, 0x11, 0x00, 0x01, 0x00, 0x12, 0x01, 0x80, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
563 0x00, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x00, 0x1E, 0xAA, 0xBB,
564 ];
565 let seg = RegionCompositionSegment::parse(&bytes).unwrap();
566 assert_eq!(seg.objects.len(), 1);
567 assert_eq!(seg.objects[0].object_type, ObjectType::BasicCharacter);
568 assert_eq!(seg.objects[0].foreground_pixel_code, Some(0xAA));
569 assert_eq!(seg.objects[0].background_pixel_code, Some(0xBB));
570 let out = seg.try_to_bytes().unwrap();
571 assert_eq!(out, bytes);
572
573 let mut seg2 = seg.clone();
575 seg2.objects[0].foreground_pixel_code = Some(0xCC);
576 let out2 = seg2.try_to_bytes().unwrap();
577 assert_ne!(out2, bytes);
578 let reparse = RegionCompositionSegment::parse(&out2).unwrap();
579 assert_eq!(reparse.objects[0].foreground_pixel_code, Some(0xCC));
580 }
581
582 #[test]
590 fn round_trip_object_vertical_position_above_one_byte() {
591 let mut seg = RegionCompositionSegment::parse(&[
592 0x0F, 0x11, 0x00, 0x01, 0x00, 0x16, 0x01, 0x88, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
593 0x00, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x02, 0x10, 0x14, 0x00, 0x14,
594 ])
595 .unwrap();
596 seg.objects[1].object_vertical_position = 0x384;
599
600 let out = seg.try_to_bytes().unwrap();
601 let n = out.len();
608 assert_eq!(
609 out[n - 2],
610 0x03,
611 "vpos top nibble must land in the low nibble of byte 4, not the reserved high nibble"
612 );
613 assert_eq!(out[n - 1], 0x84);
614
615 let reparsed = RegionCompositionSegment::parse(&out).unwrap();
616 assert_eq!(
617 reparsed.objects[1].object_vertical_position, 0x384,
618 "byte-exact round-trip of a vertical position >= 256"
619 );
620 }
621
622 #[test]
626 fn oversized_object_position_is_rejected_not_masked() {
627 let mut seg = RegionCompositionSegment::parse(&[
628 0x0F, 0x11, 0x00, 0x01, 0x00, 0x16, 0x01, 0x88, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
629 0x00, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x02, 0x10, 0x14, 0x00, 0x14,
630 ])
631 .unwrap();
632 let mut buf = alloc::vec![0u8; 64];
633
634 seg.objects[1].object_vertical_position = 0x1000; assert!(matches!(
636 seg.serialize_into(&mut buf),
637 Err(Error::FieldOverflow(_))
638 ));
639
640 seg.objects[1].object_vertical_position = 0x0FFF; seg.objects[1].object_horizontal_position = 0x1000;
642 assert!(matches!(
643 seg.serialize_into(&mut buf),
644 Err(Error::FieldOverflow(_))
645 ));
646
647 seg.objects[1].object_horizontal_position = 0x0FFF; assert!(seg.serialize_into(&mut buf).is_ok());
649 }
650}