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 & 0x07,
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 & 0x07,
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 & 0x03,
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 & 0x03,
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] = (self.region_version_number << 4)
470 | (u8::from(self.region_fill_flag) << 3)
471 | (self.reserved_byte1 & 0x07);
472 buf[8..10].copy_from_slice(&self.region_width.to_be_bytes());
473 buf[10..12].copy_from_slice(&self.region_height.to_be_bytes());
474 buf[12] = (self.region_level_of_compatibility.to_bits() << 5)
475 | (self.region_depth.to_bits() << 2)
476 | (self.reserved_byte6 & 0x03);
477 buf[13] = self.clut_id;
478 buf[14] = self.region_8bit_pixel_code;
479 buf[15] = (self.region_4bit_pixel_code << 4)
480 | (self.region_2bit_pixel_code << 2)
481 | (self.reserved_byte9 & 0x03);
482
483 let mut off = HEADER_LEN + FIXED_LEN;
484 for obj in &self.objects {
485 obj.serialize_into(&mut buf[off..])?;
486 off += obj.serialized_len();
487 }
488 Ok(len)
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495 use broadcast_common::{Parse, Serialize};
496
497 #[test]
498 fn round_trip_bitmap_objects() {
499 let bytes = [
500 0x0F, 0x11, 0x00, 0x01, 0x00, 0x16, 0x01, 0x88, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
501 0x00, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x02, 0x10, 0x14, 0x00,
502 0x14, ];
504 let seg = RegionCompositionSegment::parse(&bytes).unwrap();
505 assert_eq!(seg.region_id, 1);
506 assert!(seg.region_fill_flag);
507 assert_eq!(seg.region_width, 719);
508 assert_eq!(seg.objects.len(), 2);
509 assert_eq!(seg.objects[0].object_id, 1);
510 assert_eq!(seg.objects[0].object_type, ObjectType::BasicBitmap);
511 assert_eq!(seg.objects[0].object_horizontal_position, 10);
512 assert_eq!(seg.objects[1].object_id, 2);
513 assert_eq!(
514 seg.objects[1].object_provider_flag,
515 ObjectProviderFlag::InRom
516 );
517 let out = seg.to_bytes();
518 assert_eq!(out, bytes);
519
520 let mut seg2 = seg.clone();
522 seg2.region_width = 1280;
523 let out2 = seg2.to_bytes();
524 assert_ne!(out2, bytes);
525 let reparse = RegionCompositionSegment::parse(&out2).unwrap();
526 assert_eq!(reparse.region_width, 1280);
527 }
528
529 #[test]
531 fn rejects_bad_sync_byte() {
532 let bytes = [
533 0x00, 0x11, 0x00, 0x01, 0x00, 0x16, 0x01, 0x88, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
534 0x00, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x02, 0x10, 0x14, 0x00, 0x14,
535 ];
536 assert!(matches!(
537 RegionCompositionSegment::parse(&bytes),
538 Err(Error::BadSyncByte(0x00))
539 ));
540 }
541
542 #[test]
543 fn round_trip_character_object() {
544 let bytes = [
545 0x0F, 0x11, 0x00, 0x01, 0x00, 0x12, 0x01, 0x80, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
546 0x00, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x00, 0x1E, 0xAA, 0xBB,
547 ];
548 let seg = RegionCompositionSegment::parse(&bytes).unwrap();
549 assert_eq!(seg.objects.len(), 1);
550 assert_eq!(seg.objects[0].object_type, ObjectType::BasicCharacter);
551 assert_eq!(seg.objects[0].foreground_pixel_code, Some(0xAA));
552 assert_eq!(seg.objects[0].background_pixel_code, Some(0xBB));
553 let out = seg.to_bytes();
554 assert_eq!(out, bytes);
555
556 let mut seg2 = seg.clone();
558 seg2.objects[0].foreground_pixel_code = Some(0xCC);
559 let out2 = seg2.to_bytes();
560 assert_ne!(out2, bytes);
561 let reparse = RegionCompositionSegment::parse(&out2).unwrap();
562 assert_eq!(reparse.objects[0].foreground_pixel_code, Some(0xCC));
563 }
564
565 #[test]
573 fn round_trip_object_vertical_position_above_one_byte() {
574 let mut seg = RegionCompositionSegment::parse(&[
575 0x0F, 0x11, 0x00, 0x01, 0x00, 0x16, 0x01, 0x88, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
576 0x00, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x02, 0x10, 0x14, 0x00, 0x14,
577 ])
578 .unwrap();
579 seg.objects[1].object_vertical_position = 0x384;
582
583 let out = seg.to_bytes();
584 let n = out.len();
591 assert_eq!(
592 out[n - 2],
593 0x03,
594 "vpos top nibble must land in the low nibble of byte 4, not the reserved high nibble"
595 );
596 assert_eq!(out[n - 1], 0x84);
597
598 let reparsed = RegionCompositionSegment::parse(&out).unwrap();
599 assert_eq!(
600 reparsed.objects[1].object_vertical_position, 0x384,
601 "byte-exact round-trip of a vertical position >= 256"
602 );
603 }
604
605 #[test]
609 fn oversized_object_position_is_rejected_not_masked() {
610 let mut seg = RegionCompositionSegment::parse(&[
611 0x0F, 0x11, 0x00, 0x01, 0x00, 0x16, 0x01, 0x88, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
612 0x00, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x02, 0x10, 0x14, 0x00, 0x14,
613 ])
614 .unwrap();
615 let mut buf = alloc::vec![0u8; 64];
616
617 seg.objects[1].object_vertical_position = 0x1000; assert!(matches!(
619 seg.serialize_into(&mut buf),
620 Err(Error::FieldOverflow(_))
621 ));
622
623 seg.objects[1].object_vertical_position = 0x0FFF; seg.objects[1].object_horizontal_position = 0x1000;
625 assert!(matches!(
626 seg.serialize_into(&mut buf),
627 Err(Error::FieldOverflow(_))
628 ));
629
630 seg.objects[1].object_horizontal_position = 0x0FFF; assert!(seg.serialize_into(&mut buf).is_ok());
632 }
633}