// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

use crate::otp::alert_handler_regs::*;
use crate::otp::lc_state::LcStateVal;
use crate::otp::otp_img::OtpRead;

use anyhow::{bail, Result};
use bitvec::prelude::*;
use crc::{Crc, Digest};
use num_enum::TryFromPrimitive;

use std::convert::TryFrom;

/// ALERT_HANDLER_ALERT_CLASS related register values.
#[derive(Clone, Copy, Debug, PartialEq)]
struct AlertClassRegs {
    regwen: u32,
    ctrl: u32,
    accum_thresh: u32,
    timeout_cyc: u32,
    phase_cycs: [u32; ALERT_HANDLER_PARAM_N_PHASES as usize],
}

/// Register values for alert_handler used in CRC32 calculation.
#[derive(Debug, PartialEq)]
pub struct AlertRegs {
    /// ALERT_HANDLER_LOC_ALERT_REGWEN
    regwen: [u32; ALERT_HANDLER_ALERT_REGWEN_MULTIREG_COUNT as usize],
    /// ALERT_HANDLER_ALERT_EN_SHADOWED
    en: [u32; ALERT_HANDLER_ALERT_EN_SHADOWED_MULTIREG_COUNT as usize],
    /// ALERT_HANDLER_ALERT_CLASS_SHADOWED
    class: [u32; ALERT_HANDLER_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize],
    /// ALERT_HANDLER_LOC_ALERT_REGWEN
    loc_regwen: [u32; ALERT_HANDLER_LOC_ALERT_REGWEN_MULTIREG_COUNT as usize],
    /// ALERT_HANDLER_LOC_ALERT_EN_SHADOWED
    loc_en: [u32; ALERT_HANDLER_LOC_ALERT_EN_SHADOWED_MULTIREG_COUNT as usize],
    /// ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED
    loc_class: [u32; ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize],
    /// Alert handler class registers
    class_regs: [AlertClassRegs; ALERT_HANDLER_PARAM_N_CLASSES as usize],
}

// TODO: Use bindgen to produce the following enum definitions.
/// Alert classification values.
///
/// Based on values generated by sparse-fsm-encode.py and defined in
/// sw/device/silicon_creator/lib/drivers/alert.h as alert_class_t.
#[derive(TryFromPrimitive)]
#[repr(u8)]
enum AlertClass {
    X = 0x94,
    A = 0xee,
    B = 0x64,
    C = 0xa7,
    D = 0x32,
}

impl AlertClass {
    fn index(&self) -> usize {
        match self {
            AlertClass::A => 0,
            AlertClass::B => 1,
            AlertClass::C => 2,
            AlertClass::D => 3,
            AlertClass::X => 0,
        }
    }

    fn from_index(index: usize) -> Self {
        match index {
            0 => AlertClass::A,
            1 => AlertClass::B,
            2 => AlertClass::C,
            3 => AlertClass::D,
            _ => AlertClass::X,
        }
    }
}

#[derive(TryFromPrimitive)]
#[repr(u8)]
enum AlertEnable {
    None = 0xa9,
    Enabled = 0x07,
    Locked = 0xd2,
}

#[derive(TryFromPrimitive)]
#[repr(u8)]
enum AlertEscalate {
    None = 0xd1,
    Phase0 = 0xb9,
    Phase1 = 0xcb,
    Phase2 = 0x25,
    Phase3 = 0x76,
}

struct AlertClassConfig {
    enabled: AlertEnable,
    escalate: AlertEscalate,
    accum_thresh: u32,
    timeout_cyc: u32,
    phase_cycs: [u32; ALERT_HANDLER_PARAM_N_PHASES as usize],
}

impl Default for AlertClassRegs {
    fn default() -> Self {
        AlertClassRegs {
            regwen: 1,
            ctrl: 0,
            accum_thresh: 0,
            timeout_cyc: 0,
            phase_cycs: [0; ALERT_HANDLER_PARAM_N_PHASES as usize],
        }
    }
}

impl Default for AlertRegs {
    fn default() -> Self {
        AlertRegs {
            regwen: [1; ALERT_HANDLER_ALERT_REGWEN_MULTIREG_COUNT as usize],
            loc_regwen: [1; ALERT_HANDLER_LOC_ALERT_REGWEN_MULTIREG_COUNT as usize],
            en: [0; ALERT_HANDLER_ALERT_EN_SHADOWED_MULTIREG_COUNT as usize],
            class: [0; ALERT_HANDLER_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize],
            loc_en: [0; ALERT_HANDLER_LOC_ALERT_EN_SHADOWED_MULTIREG_COUNT as usize],
            loc_class: [0; ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize],
            class_regs: [Default::default(); ALERT_HANDLER_PARAM_N_CLASSES as usize],
        }
    }
}

impl AlertRegs {
    /// Compute the CRC32 of the internal register values to match the value produced by
    /// `sw/device/silicon_creator/lib/drivers/alert.h:alert_config_crc32`.
    pub fn crc32(self) -> u32 {
        let crc = new_crc();
        let mut digest = crc.digest();
        self.crc32_add(&mut digest);
        digest.finalize()
    }

    /// Create the set of alert_handler register values from a given lifecycle state and OTP.
    ///
    /// The internal fields of `AlertRegs` should match those produced on the device after
    /// alert_handler is configured in `sw/device/silicon_creator/lib/shutdown.h:shutdown_init`.
    pub fn try_new<T: OtpRead>(lc_state: LcStateVal, otp: &T) -> Result<Self> {
        let mut alert = AlertRegs::default();

        let lc_shift = match lc_state {
            LcStateVal::Prod => 0,
            LcStateVal::ProdEnd => 1,
            LcStateVal::Dev => 2,
            LcStateVal::Rma => 3,
            LcStateVal::Test => return Ok(alert),
        };

        let class_enable = otp.read32("OWNER_SW_CFG_ROM_ALERT_CLASS_EN")?;
        let class_escalate = otp.read32("OWNER_SW_CFG_ROM_ALERT_ESCALATION")?;

        for i in 0..ALERT_HANDLER_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize {
            let value = otp.read32_offset("OWNER_SW_CFG_ROM_ALERT_CLASSIFICATION", i * 4)?;
            let cls = AlertClass::try_from(value.to_le_bytes()[lc_shift])?;
            let enable = AlertEnable::try_from(class_enable.to_le_bytes()[cls.index()])?;
            alert.configure(i, cls, enable)?;
        }

        for i in 0..ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize {
            let value = otp.read32_offset("OWNER_SW_CFG_ROM_LOCAL_ALERT_CLASSIFICATION", i * 4)?;
            let cls = AlertClass::try_from(value.to_le_bytes()[lc_shift])?;
            let enable = AlertEnable::try_from(class_enable.to_le_bytes()[cls.index()])?;
            alert.local_configure(i, cls, enable)?;
        }

        for i in 0..ALERT_HANDLER_PARAM_N_CLASSES as usize {
            let mut phase_cycs = [0; ALERT_HANDLER_PARAM_N_PHASES as usize];
            for phase in 0..ALERT_HANDLER_PARAM_N_PHASES as usize {
                phase_cycs[phase] = otp.read32_offset(
                    "OWNER_SW_CFG_ROM_ALERT_PHASE_CYCLES",
                    (i * phase_cycs.len() + phase) * 4,
                )?;
            }
            let config = AlertClassConfig {
                enabled: AlertEnable::try_from(class_enable.to_le_bytes()[i])?,
                escalate: AlertEscalate::try_from(class_escalate.to_le_bytes()[i])?,
                accum_thresh: otp.read32_offset("OWNER_SW_CFG_ROM_ALERT_ACCUM_THRESH", i * 4)?,
                timeout_cyc: otp.read32_offset("OWNER_SW_CFG_ROM_ALERT_TIMEOUT_CYCLES", i * 4)?,
                phase_cycs,
            };
            alert.class_configure(AlertClass::from_index(i), &config)?;
        }

        Ok(alert)
    }

    fn configure(&mut self, index: usize, cls: AlertClass, enabled: AlertEnable) -> Result<()> {
        if index >= ALERT_HANDLER_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize {
            bail!("Bad alert index {}", index);
        }

        self.class[index] = match cls {
            AlertClass::A => ALERT_HANDLER_ALERT_CLASS_SHADOWED_0_CLASS_A_0_VALUE_CLASSA,
            AlertClass::B => ALERT_HANDLER_ALERT_CLASS_SHADOWED_0_CLASS_A_0_VALUE_CLASSB,
            AlertClass::C => ALERT_HANDLER_ALERT_CLASS_SHADOWED_0_CLASS_A_0_VALUE_CLASSC,
            AlertClass::D => ALERT_HANDLER_ALERT_CLASS_SHADOWED_0_CLASS_A_0_VALUE_CLASSD,
            AlertClass::X => return Ok(()),
        };

        match enabled {
            AlertEnable::None => {}
            AlertEnable::Enabled => self.en[index] = 1,
            AlertEnable::Locked => {
                self.en[index] = 1;
                self.regwen[index] = 0;
            }
        };

        Ok(())
    }

    fn local_configure(
        &mut self,
        index: usize,
        cls: AlertClass,
        enabled: AlertEnable,
    ) -> Result<()> {
        if index >= ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize {
            bail!("Bad local alert index {}", index);
        }

        self.loc_class[index] = match cls {
            AlertClass::A => ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_0_CLASS_LA_0_VALUE_CLASSA,
            AlertClass::B => ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_0_CLASS_LA_0_VALUE_CLASSB,
            AlertClass::C => ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_0_CLASS_LA_0_VALUE_CLASSC,
            AlertClass::D => ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_0_CLASS_LA_0_VALUE_CLASSD,
            AlertClass::X => return Ok(()),
        };

        match enabled {
            AlertEnable::None => {}
            AlertEnable::Enabled => self.loc_en[index] = 1,
            AlertEnable::Locked => {
                self.loc_en[index] = 1;
                self.loc_regwen[index] = 0;
            }
        };

        Ok(())
    }

    fn class_configure(&mut self, cls: AlertClass, config: &AlertClassConfig) -> Result<()> {
        let index = match cls {
            AlertClass::A => 0,
            AlertClass::B => 1,
            AlertClass::C => 2,
            AlertClass::D => 3,
            AlertClass::X => bail!("Bad class"),
        };

        let mut reg = 0_u32;

        // TODO(lowRISC/opentitan#15443): Fix this lint (clippy::erasing_op):
        //reg |= (0 & ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E0_MASK)
        //    << ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E0_OFFSET;
        reg |= (1 & ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E1_MASK)
            << ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E1_OFFSET;
        reg |= (2 & ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E2_MASK)
            << ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E2_OFFSET;
        reg |= (3 & ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E3_MASK)
            << ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E3_OFFSET;

        let reg_bits = reg.view_bits_mut::<Lsb0>();

        match config.enabled {
            AlertEnable::None => {}
            AlertEnable::Enabled => {
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_BIT as usize, true);
            }
            AlertEnable::Locked => {
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_LOCK_BIT as usize, true);
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_BIT as usize, true)
            }
        }

        match config.escalate {
            AlertEscalate::Phase0 => {
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E0_BIT as usize, true)
            }
            AlertEscalate::Phase1 => {
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E0_BIT as usize, true);
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E1_BIT as usize, true);
            }
            AlertEscalate::Phase2 => {
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E0_BIT as usize, true);
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E1_BIT as usize, true);
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E2_BIT as usize, true);
            }
            AlertEscalate::Phase3 => {
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E0_BIT as usize, true);
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E1_BIT as usize, true);
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E2_BIT as usize, true);
                reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E3_BIT as usize, true);
            }
            AlertEscalate::None => {}
        }

        self.class_regs[index].ctrl = reg;
        self.class_regs[index].accum_thresh = config.accum_thresh;
        self.class_regs[index].timeout_cyc = config.timeout_cyc;
        self.class_regs[index].phase_cycs = config.phase_cycs;

        Ok(())
    }
}

trait Crc32Add {
    fn crc32_add(self, diegst: &mut Digest<u32>);
}

impl Crc32Add for u32 {
    fn crc32_add(self, digest: &mut Digest<u32>) {
        digest.update(self.to_le_bytes().as_slice())
    }
}

impl<T: Crc32Add, const N: usize> Crc32Add for [T; N] {
    fn crc32_add(self, digest: &mut Digest<u32>) {
        self.map(|v| v.crc32_add(digest));
    }
}

impl Crc32Add for AlertClassRegs {
    fn crc32_add(self, digest: &mut Digest<u32>) {
        self.regwen.crc32_add(digest);
        self.ctrl.crc32_add(digest);
        self.accum_thresh.crc32_add(digest);
        self.timeout_cyc.crc32_add(digest);
        self.phase_cycs.crc32_add(digest);
    }
}

impl Crc32Add for AlertRegs {
    fn crc32_add(self, digest: &mut Digest<u32>) {
        self.regwen.crc32_add(digest);
        self.en.crc32_add(digest);
        self.class.crc32_add(digest);
        self.loc_regwen.crc32_add(digest);
        self.loc_en.crc32_add(digest);
        self.loc_class.crc32_add(digest);
        self.class_regs.crc32_add(digest);
    }
}

fn new_crc() -> Crc<u32> {
    Crc::<u32>::new(&crc::CRC_32_ISO_HDLC)
}

#[cfg(test)]
mod test {
    use super::*;

    // Register values dumped from device after alert_handler initialization.
    const TEST_REGS: AlertRegs = AlertRegs {
        regwen: [
            0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
            0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
            0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
            0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
            0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
            0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
            0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
            0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
            0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
            0x00000001, 0x00000001,
        ],
        en: [
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000,
        ],
        class: [
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
            0x00000000, 0x00000000,
        ],
        loc_regwen: [
            0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
        ],
        loc_en: [
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
        ],
        loc_class: [
            0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
        ],
        class_regs: [
            AlertClassRegs {
                regwen: 0x00000001,
                ctrl: 0x00003900,
                accum_thresh: 0x00000000,
                timeout_cyc: 0x00000000,
                phase_cycs: [0x00000000, 0x0000000a, 0x0000000a, 0xffffffff],
            },
            AlertClassRegs {
                regwen: 0x00000001,
                ctrl: 0x00003900,
                accum_thresh: 0x00000000,
                timeout_cyc: 0x00000000,
                phase_cycs: [0x00000000, 0x0000000a, 0x0000000a, 0xffffffff],
            },
            AlertClassRegs {
                regwen: 0x00000001,
                ctrl: 0x00003900,
                accum_thresh: 0x00000000,
                timeout_cyc: 0x00000000,
                phase_cycs: [0x00000000, 0x00000000, 0x00000000, 0x00000000],
            },
            AlertClassRegs {
                regwen: 0x00000001,
                ctrl: 0x00003900,
                accum_thresh: 0x00000000,
                timeout_cyc: 0x00000000,
                phase_cycs: [0x00000000, 0x00000000, 0x00000000, 0x00000000],
            },
        ],
    };

    struct TestOtpAlertsDisabled {}

    // OTP values that corrispond to the above `TEST_REG` values.
    impl OtpRead for TestOtpAlertsDisabled {
        fn read32_offset(&self, name: &str, offset: usize) -> Result<u32> {
            Ok(match name {
                "OWNER_SW_CFG_ROM_ALERT_CLASS_EN" => 0xa9a9a9a9,
                "OWNER_SW_CFG_ROM_ALERT_ESCALATION" => 0xd1d1d1d1,
                "OWNER_SW_CFG_ROM_ALERT_CLASSIFICATION"
                | "OWNER_SW_CFG_ROM_LOCAL_ALERT_CLASSIFICATION" => 0x94949494,
                "OWNER_SW_CFG_ROM_ALERT_PHASE_CYCLES" => [
                    0x00000000, 0x0000000a, 0x0000000a, 0xffffffff, // Class 0
                    0x00000000, 0x0000000a, 0x0000000a, 0xffffffff, // Class 1
                    0x00000000, 0x00000000, 0x00000000, 0x00000000, // Class 2
                    0x00000000, 0x00000000, 0x00000000, 0x00000000, // Class 3
                ][offset / 4],
                "OWNER_SW_CFG_ROM_ALERT_ACCUM_THRESH" | "OWNER_SW_CFG_ROM_ALERT_TIMEOUT_CYCLES" => {
                    0x00000000
                }
                _ => panic!("No such OTP value {}", name),
            })
        }
    }

    struct TestOtpAlertsEnabled {}

    // OTP values with `*_CLASS_EN` vales set to `kAlertEnableEnabled`
    impl OtpRead for TestOtpAlertsEnabled {
        fn read32_offset(&self, name: &str, offset: usize) -> Result<u32> {
            Ok(match name {
                "OWNER_SW_CFG_ROM_ALERT_CLASS_EN" => 0x07070707,
                "OWNER_SW_CFG_ROM_ALERT_ESCALATION" => 0xd1d1d1d1,
                "OWNER_SW_CFG_ROM_ALERT_CLASSIFICATION"
                | "OWNER_SW_CFG_ROM_LOCAL_ALERT_CLASSIFICATION" => 0x94949494,
                "OWNER_SW_CFG_ROM_ALERT_PHASE_CYCLES" => [
                    0x00000000, 0x0000000a, 0x0000000a, 0xffffffff, // Class 0
                    0x00000000, 0x0000000a, 0x0000000a, 0xffffffff, // Class 1
                    0x00000000, 0x00000000, 0x00000000, 0x00000000, // Class 2
                    0x00000000, 0x00000000, 0x00000000, 0x00000000, // Class 3
                ][offset / 4],
                "OWNER_SW_CFG_ROM_ALERT_ACCUM_THRESH" | "OWNER_SW_CFG_ROM_ALERT_TIMEOUT_CYCLES" => {
                    0x00000000
                }
                _ => panic!("No such OTP value {}", name),
            })
        }
    }

    // A sanity test to make sure the correct CRC algorithm is being used.
    //
    // These values are taken from the CRC32 unit tests in
    // `sw/device/silicon_creator/lib/crc32_unittest.cc`.
    #[test]
    fn test_new_crc() {
        let crc = new_crc();
        let mut digest = crc.digest();
        digest.update(b"123456789");
        assert_eq!(digest.finalize(), 0xcbf43926);

        let crc = new_crc();
        let mut digest = crc.digest();
        digest.update(b"The quick brown fox jumps over the lazy dog");
        assert_eq!(digest.finalize(), 0x414fa339);

        let crc = new_crc();
        let mut digest = crc.digest();
        digest.update(b"\xfe\xca\xfe\xca\x02\xb0\xad\x1b");
        assert_eq!(digest.finalize(), 0x9508ac14);
    }

    #[test]
    fn test_crc_from_regs() {
        assert_eq!(TEST_REGS.crc32(), 0xf9616122);
    }

    #[test]
    fn test_regs_from_otp() {
        assert_eq!(
            TEST_REGS,
            AlertRegs::try_new(LcStateVal::Dev, &TestOtpAlertsDisabled {}).unwrap()
        );
    }

    #[test]
    fn test_crc_disabled() {
        assert_eq!(
            AlertRegs::try_new(LcStateVal::Dev, &TestOtpAlertsDisabled {})
                .unwrap()
                .crc32(),
            0xf9616122
        );
    }

    #[test]
    fn test_crc_enabled() {
        assert_eq!(
            AlertRegs::try_new(LcStateVal::Dev, &TestOtpAlertsEnabled {})
                .unwrap()
                .crc32(),
            0x561bcb14
        );
    }
}
