use display_types::HdmiForumFrl;
use display_types::cea861::hdmi_forum::HdmiDscMaxSlices;
use crate::decoded::Decoded;
use crate::encode::{IntoPackets, SinglePacketIter};
use crate::error::DecodeError;
use crate::warn::HdmiForumVsiWarning;
pub(crate) const HDMI_FORUM_OUI: [u8; 3] = [0xD8, 0x5D, 0xC4];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HdmiForumVsi {
pub allm: bool,
pub frl_rate: HdmiForumFrl,
pub fapa_start_location: bool,
pub fva: bool,
pub vrr_en: bool,
pub m_const: bool,
pub qms_en: bool,
pub neg_mvrr: bool,
pub m_vrr: u16,
pub dsc_1p2: bool,
pub dsc_native_420: bool,
pub dsc_all_bpc: bool,
pub dsc_max_frl_rate: HdmiForumFrl,
pub dsc_max_slices: HdmiDscMaxSlices,
pub dsc_10bpc: bool,
pub dsc_12bpc: bool,
}
impl HdmiForumVsi {
pub fn decode(
packet: &[u8; 31],
) -> Result<Decoded<HdmiForumVsi, HdmiForumVsiWarning>, DecodeError> {
let length = packet[2];
if length > 27 {
return Err(DecodeError::Truncated { claimed: length });
}
let mut decoded = Decoded::new(HdmiForumVsi {
allm: false,
frl_rate: HdmiForumFrl::NotSupported,
fapa_start_location: false,
fva: false,
vrr_en: false,
m_const: false,
qms_en: false,
neg_mvrr: false,
m_vrr: 0,
dsc_1p2: false,
dsc_native_420: false,
dsc_all_bpc: false,
dsc_max_frl_rate: HdmiForumFrl::NotSupported,
dsc_max_slices: HdmiDscMaxSlices::NotSupported,
dsc_10bpc: false,
dsc_12bpc: false,
});
let total: u8 = packet.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
if total != 0x00 {
let expected = crate::checksum::compute_checksum(packet[..30].try_into().unwrap());
decoded.push_warning(HdmiForumVsiWarning::ChecksumMismatch {
expected,
found: packet[3],
});
}
let pb4 = packet[7];
for bit in [0u8, 1] {
if pb4 & (1 << bit) != 0 {
decoded.push_warning(HdmiForumVsiWarning::ReservedFieldNonZero { byte: 7, bit });
}
}
decoded.value.allm = pb4 & 0x80 != 0;
decoded.value.fapa_start_location = pb4 & 0x08 != 0;
decoded.value.fva = pb4 & 0x04 != 0;
let frl_raw = (pb4 >> 4) & 0x07;
if frl_raw > 6 {
decoded.push_warning(HdmiForumVsiWarning::UnknownEnumValue {
field: "frl_rate",
raw: frl_raw,
});
}
decoded.value.frl_rate = HdmiForumFrl::from_raw(frl_raw);
let pb5 = packet[8];
decoded.value.vrr_en = pb5 & 0x80 != 0;
decoded.value.m_const = pb5 & 0x40 != 0;
decoded.value.qms_en = pb5 & 0x20 != 0;
decoded.value.neg_mvrr = pb5 & 0x10 != 0;
let m_vrr_hi = (pb5 & 0x0F) as u16;
let m_vrr_lo = packet[9] as u16;
decoded.value.m_vrr = (m_vrr_hi << 8) | m_vrr_lo;
let pb7 = packet[10];
for bit in [3u8, 4] {
if pb7 & (1 << bit) != 0 {
decoded.push_warning(HdmiForumVsiWarning::ReservedFieldNonZero { byte: 10, bit });
}
}
decoded.value.dsc_1p2 = pb7 & 0x80 != 0;
decoded.value.dsc_native_420 = pb7 & 0x40 != 0;
decoded.value.dsc_all_bpc = pb7 & 0x20 != 0;
let dsc_frl_raw = pb7 & 0x07;
if dsc_frl_raw > 6 {
decoded.push_warning(HdmiForumVsiWarning::UnknownEnumValue {
field: "dsc_max_frl_rate",
raw: dsc_frl_raw,
});
}
decoded.value.dsc_max_frl_rate = HdmiForumFrl::from_raw(dsc_frl_raw);
let pb8 = packet[11];
for bit in [4u8, 5] {
if pb8 & (1 << bit) != 0 {
decoded.push_warning(HdmiForumVsiWarning::ReservedFieldNonZero { byte: 11, bit });
}
}
decoded.value.dsc_12bpc = pb8 & 0x80 != 0;
decoded.value.dsc_10bpc = pb8 & 0x40 != 0;
let slices_raw = pb8 & 0x0F;
if slices_raw > 7 {
decoded.push_warning(HdmiForumVsiWarning::UnknownEnumValue {
field: "dsc_max_slices",
raw: slices_raw,
});
}
decoded.value.dsc_max_slices = HdmiDscMaxSlices::from_raw(slices_raw);
Ok(decoded)
}
}
impl IntoPackets for HdmiForumVsi {
type Iter = SinglePacketIter;
type Warning = HdmiForumVsiWarning;
fn into_packets(self) -> crate::decoded::Decoded<SinglePacketIter, HdmiForumVsiWarning> {
let mut hp = [0u8; 30];
hp[0] = 0x81; hp[1] = 0x01; hp[2] = 8;
hp[3] = HDMI_FORUM_OUI[0];
hp[4] = HDMI_FORUM_OUI[1];
hp[5] = HDMI_FORUM_OUI[2];
let frl_raw = self.frl_rate as u8;
hp[6] = ((self.allm as u8) << 7)
| ((frl_raw & 0x07) << 4)
| ((self.fapa_start_location as u8) << 3)
| ((self.fva as u8) << 2);
hp[7] = ((self.vrr_en as u8) << 7)
| ((self.m_const as u8) << 6)
| ((self.qms_en as u8) << 5)
| ((self.neg_mvrr as u8) << 4)
| (((self.m_vrr >> 8) & 0x0F) as u8);
hp[8] = (self.m_vrr & 0xFF) as u8;
let dsc_frl_raw = self.dsc_max_frl_rate as u8;
hp[9] = ((self.dsc_1p2 as u8) << 7)
| ((self.dsc_native_420 as u8) << 6)
| ((self.dsc_all_bpc as u8) << 5)
| (dsc_frl_raw & 0x07);
let slices_raw = self.dsc_max_slices as u8;
hp[10] =
((self.dsc_12bpc as u8) << 7) | ((self.dsc_10bpc as u8) << 6) | (slices_raw & 0x0F);
let checksum = crate::checksum::compute_checksum(&hp);
let mut packet = [0u8; 31];
packet[..3].copy_from_slice(&hp[..3]);
packet[3] = checksum;
packet[4..].copy_from_slice(&hp[3..]);
crate::decoded::Decoded::new(SinglePacketIter::new(packet))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encode::IntoPackets;
fn full_frame() -> HdmiForumVsi {
HdmiForumVsi {
allm: true,
frl_rate: HdmiForumFrl::Rate10Gbps4Lanes,
fapa_start_location: true,
fva: false,
vrr_en: true,
m_const: false,
qms_en: true,
neg_mvrr: false,
m_vrr: 0x0ABC,
dsc_1p2: true,
dsc_native_420: false,
dsc_all_bpc: true,
dsc_max_frl_rate: HdmiForumFrl::Rate6Gbps4Lanes,
dsc_max_slices: HdmiDscMaxSlices::Slices8At400Mhz,
dsc_10bpc: true,
dsc_12bpc: false,
}
}
#[test]
fn round_trip() {
let frame = full_frame();
let packet = frame.clone().into_packets().value.next().unwrap();
let decoded = HdmiForumVsi::decode(&packet).unwrap();
assert!(decoded.iter_warnings().next().is_none());
assert_eq!(decoded.value, frame);
}
#[test]
fn oui_in_correct_positions() {
let packet = full_frame().into_packets().value.next().unwrap();
assert_eq!(&packet[4..7], &HDMI_FORUM_OUI);
}
#[test]
fn checksum_mismatch_warning() {
let mut packet = full_frame().into_packets().value.next().unwrap();
packet[3] = packet[3].wrapping_add(1);
let decoded = HdmiForumVsi::decode(&packet).unwrap();
assert!(
decoded
.iter_warnings()
.any(|w| matches!(w, HdmiForumVsiWarning::ChecksumMismatch { .. }))
);
assert_eq!(decoded.value, full_frame());
}
#[test]
fn truncated_length_is_error() {
let mut packet = full_frame().into_packets().value.next().unwrap();
packet[2] = 28;
assert!(matches!(
HdmiForumVsi::decode(&packet),
Err(DecodeError::Truncated { claimed: 28 })
));
}
#[test]
fn unknown_frl_rate_warns_and_decodes() {
let mut packet = full_frame().into_packets().value.next().unwrap();
packet[7] = (packet[7] & 0x8F) | (0x07 << 4);
let sum: u8 = packet.iter().fold(0u8, |a, &b| a.wrapping_add(b));
packet[3] = packet[3].wrapping_sub(sum);
let decoded = HdmiForumVsi::decode(&packet).unwrap();
assert!(decoded.iter_warnings().any(|w| matches!(
w,
HdmiForumVsiWarning::UnknownEnumValue {
field: "frl_rate",
raw: 7
}
)));
}
#[test]
fn reserved_bits_pb7_warning() {
let mut packet = full_frame().into_packets().value.next().unwrap();
packet[10] |= 0x08; let sum: u8 = packet.iter().fold(0u8, |a, &b| a.wrapping_add(b));
packet[3] = packet[3].wrapping_sub(sum);
let decoded = HdmiForumVsi::decode(&packet).unwrap();
assert!(decoded.iter_warnings().any(|w| matches!(
w,
HdmiForumVsiWarning::ReservedFieldNonZero { byte: 10, bit: 3 }
)));
}
#[test]
fn reserved_bits_pb8_warning() {
let mut packet = full_frame().into_packets().value.next().unwrap();
packet[11] |= 0x10; let sum: u8 = packet.iter().fold(0u8, |a, &b| a.wrapping_add(b));
packet[3] = packet[3].wrapping_sub(sum);
let decoded = HdmiForumVsi::decode(&packet).unwrap();
assert!(decoded.iter_warnings().any(|w| matches!(
w,
HdmiForumVsiWarning::ReservedFieldNonZero { byte: 11, bit: 4 }
)));
}
#[test]
fn unknown_dsc_max_frl_rate_warns() {
let mut packet = full_frame().into_packets().value.next().unwrap();
packet[10] = (packet[10] & 0xF8) | 0x07;
let sum: u8 = packet.iter().fold(0u8, |a, &b| a.wrapping_add(b));
packet[3] = packet[3].wrapping_sub(sum);
let decoded = HdmiForumVsi::decode(&packet).unwrap();
assert!(decoded.iter_warnings().any(|w| matches!(
w,
HdmiForumVsiWarning::UnknownEnumValue {
field: "dsc_max_frl_rate",
raw: 7
}
)));
}
#[test]
fn unknown_dsc_max_slices_warns() {
let mut packet = full_frame().into_packets().value.next().unwrap();
packet[11] = (packet[11] & 0xF0) | 0x0F;
let sum: u8 = packet.iter().fold(0u8, |a, &b| a.wrapping_add(b));
packet[3] = packet[3].wrapping_sub(sum);
let decoded = HdmiForumVsi::decode(&packet).unwrap();
assert!(decoded.iter_warnings().any(|w| matches!(
w,
HdmiForumVsiWarning::UnknownEnumValue {
field: "dsc_max_slices",
raw: 15
}
)));
}
#[test]
fn reserved_bit_warning() {
let mut packet = full_frame().into_packets().value.next().unwrap();
packet[7] |= 0x01; let sum: u8 = packet.iter().fold(0u8, |a, &b| a.wrapping_add(b));
packet[3] = packet[3].wrapping_sub(sum);
let decoded = HdmiForumVsi::decode(&packet).unwrap();
assert!(decoded.iter_warnings().any(|w| matches!(
w,
HdmiForumVsiWarning::ReservedFieldNonZero { byte: 7, bit: 0 }
)));
}
}