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