sdmmc-core 0.5.0

SD/MMC core data structures and algorithms
Documentation
//! Card Identification (`CID`) register.

use crate::lib_struct;
use crate::result::{Error, Result};

use crate::crc::Crc7;
pub use crate::response::End as Crc;

mod mdt;
mod mid;
mod oid;
mod pnm;
mod prv;
mod psn;

pub use mdt::*;
pub use mid::*;
pub use oid::*;
pub use pnm::*;
pub use prv::*;
pub use psn::*;

/// Represents the byte length of the [Cid] register.
pub const CID_LEN: usize = 16;

lib_struct! {
    /// Represents the Card Identification (Cid) register (128-bits wide).
    Cid {
        mid: ManufacturerId,
        oid: OemId,
        pnm: ProductName,
        prv: ProductRevision,
        psn: ProductSerialNumber,
        mdt: ManufacturingDate,
        crc: Crc,
    }
}

impl Cid {
    pub const LEN: usize = 16;

    /// Creates a new [Cid].
    pub const fn new() -> Self {
        Self {
            mid: ManufacturerId::new(),
            oid: OemId::new(),
            pnm: ProductName::new(),
            prv: ProductRevision::new(),
            psn: ProductSerialNumber::new(),
            mdt: ManufacturingDate::new(),
            crc: Crc::from_bits(0x13),
        }
    }

    /// Converts the [Cid] into a byte array.
    pub const fn bytes(&self) -> [u8; CID_LEN] {
        let mid = self.mid.bits();
        let [oid0, oid1] = self.oid.bytes();
        let [pnm0, pnm1, pnm2, pnm3, pnm4] = self.pnm.bytes();
        let prv = self.prv.bits();
        let [psn0, psn1, psn2, psn3] = self.psn.bytes();
        let [mdt0, mdt1] = self.mdt.bytes();
        let crc = self.crc.bits();

        [
            mid, oid0, oid1, pnm0, pnm1, pnm2, pnm3, pnm4, prv, psn0, psn1, psn2, psn3, mdt0, mdt1,
            crc,
        ]
    }

    /// Attempts to convert a byte slice into a [Cid].
    pub const fn try_from_bytes(val: &[u8]) -> Result<Self> {
        match val.len() {
            len if len < CID_LEN => Err(Error::InvalidVariant(len)),
            _ => {
                match (
                    ManufacturerId::from_bits(val[0]),
                    OemId::try_from_bytes(&[val[1], val[2]]),
                    ProductName::try_from_bytes(&[val[3], val[4], val[5], val[6], val[7]]),
                    ProductRevision::from_bits(val[8]),
                    ProductSerialNumber::from_bits(u32::from_be_bytes([
                        val[9], val[10], val[11], val[12],
                    ])),
                    ManufacturingDate::try_from_bytes(&[val[13], val[14]]),
                    Crc::try_from_bits(val[15]),
                ) {
                    (mid, Ok(oid), Ok(pnm), prv, psn, Ok(mdt), Ok(crc)) => Ok(Self {
                        mid,
                        oid,
                        pnm,
                        prv,
                        psn,
                        mdt,
                        crc,
                    }),
                    (_, Err(err), _, _, _, _, _) => Err(err),
                    (_, _, Err(err), _, _, _, _) => Err(err),
                    (_, _, _, _, _, Err(err), _) => Err(err),
                    (_, _, _, _, _, _, Err(err)) => Err(err),
                }
            }
        }
    }

    /// Validates the parsed CRC-7 matches the calculated value.
    pub const fn validate_crc(self) -> Result<Self> {
        let mid = self.mid.bits();
        let [oid0, oid1] = self.oid.bytes();
        let [pnm0, pnm1, pnm2, pnm3, pnm4] = self.pnm.bytes();
        let prv = self.prv.bits();
        let [psn0, psn1, psn2, psn3] = self.psn.bytes();
        let [mdt0, mdt1] = self.mdt.bytes();

        let exp_crc = self.crc.crc();
        let calc_crc = Crc7::calculate(&[
            mid, oid0, oid1, pnm0, pnm1, pnm2, pnm3, pnm4, prv, psn0, psn1, psn2, psn3, mdt0, mdt1,
        ]);

        if calc_crc.bits() == calc_crc.bits() {
            Ok(self)
        } else {
            Err(Error::InvalidCrc7 {
                calculated: calc_crc.bits(),
                invalid: exp_crc.bits(),
            })
        }
    }
}

impl Default for Cid {
    fn default() -> Self {
        Self::new()
    }
}

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

    fn raw_with_crc(mut raw: [u8; CID_LEN]) -> ([u8; CID_LEN], Crc) {
        let crc7 = Crc7::calculate(&raw[..15]);
        let raw_crc = (crc7.bits() << 1) | 1;
        raw[15] = raw_crc;

        (raw, Crc::try_from_bits(raw_crc).unwrap())
    }

    #[test]
    fn test_valid_default() {
        let (raw, crc) = raw_with_crc([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
        let raw_crc = 0x13;
        let exp_cid = Cid {
            crc,
            ..Default::default()
        };

        assert_eq!(Cid::try_from_bytes(raw.as_ref()), Ok(exp_cid));
        assert_eq!(crc.bits(), raw_crc);
    }

    #[test]
    fn test_valid_mid() {
        (0..=u8::MAX).for_each(|raw_mid| {
            let mid = ManufacturerId::from_bits(raw_mid);
            let (raw, crc) = raw_with_crc([raw_mid, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);

            let exp_cid = Cid {
                mid,
                crc,
                ..Default::default()
            };

            assert_eq!(Cid::try_from_bytes(raw.as_ref()), Ok(exp_cid));
            assert_eq!(exp_cid.bytes(), raw);
            assert_eq!(exp_cid.mid(), mid);
            assert_eq!(exp_cid.crc(), crc);
            assert_eq!(exp_cid.oid(), Default::default());
            assert_eq!(exp_cid.pnm(), Default::default());
            assert_eq!(exp_cid.prv(), Default::default());
            assert_eq!(exp_cid.psn(), Default::default());
            assert_eq!(exp_cid.mdt(), Default::default());
        });
    }

    #[test]
    fn test_valid_oid() {
        (0..=u16::MAX)
            .filter_map(|c| {
                let bytes = c.to_be_bytes();
                if bytes.iter().all(|&c| (c as char).is_ascii()) {
                    Some(bytes)
                } else {
                    None
                }
            })
            .for_each(|bytes| {
                let [oid0, oid1] = bytes;
                let oid = OemId::try_from_bytes(bytes.as_ref()).unwrap();
                let (raw, crc) =
                    raw_with_crc([0, oid0, oid1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);

                let exp_cid = Cid {
                    oid,
                    crc,
                    ..Default::default()
                };

                assert_eq!(Cid::try_from_bytes(raw.as_ref()), Ok(exp_cid));
                assert_eq!(exp_cid.bytes(), raw);
                assert_eq!(exp_cid.oid(), oid);
                assert_eq!(exp_cid.crc(), crc);
                assert_eq!(exp_cid.mid(), Default::default());
                assert_eq!(exp_cid.pnm(), Default::default());
                assert_eq!(exp_cid.prv(), Default::default());
                assert_eq!(exp_cid.psn(), Default::default());
                assert_eq!(exp_cid.mdt(), Default::default());
            });
    }

    #[test]
    fn test_valid_pnm() {
        (0..=u16::MAX)
            .filter_map(|c| {
                let bytes = c.to_be_bytes();
                if bytes.iter().all(|&c| (c as char).is_ascii()) {
                    Some(bytes)
                } else {
                    None
                }
            })
            .for_each(|bytes| {
                let [pnm0, pnm1] = bytes;
                let raw_pnm = [pnm0, pnm1, pnm0, pnm1, pnm0];
                let pnm = ProductName::try_from_bytes(raw_pnm.as_ref()).unwrap();
                let (raw, crc) = raw_with_crc([
                    0, 0, 0, pnm0, pnm1, pnm0, pnm1, pnm0, 0, 0, 0, 0, 0, 0, 1, 0,
                ]);

                let exp_cid = Cid {
                    pnm,
                    crc,
                    ..Default::default()
                };

                assert_eq!(Cid::try_from_bytes(raw.as_ref()), Ok(exp_cid));
                assert_eq!(exp_cid.bytes(), raw);
                assert_eq!(exp_cid.pnm(), pnm);
                assert_eq!(exp_cid.crc(), crc);
                assert_eq!(exp_cid.mid(), Default::default());
                assert_eq!(exp_cid.oid(), Default::default());
                assert_eq!(exp_cid.prv(), Default::default());
                assert_eq!(exp_cid.psn(), Default::default());
                assert_eq!(exp_cid.mdt(), Default::default());
            });
    }

    #[test]
    fn test_valid_prv() {
        (0..=u8::MAX).for_each(|raw_prv| {
            let prv = ProductRevision::from_bits(raw_prv);
            let (raw, crc) = raw_with_crc([0, 0, 0, 0, 0, 0, 0, 0, raw_prv, 0, 0, 0, 0, 0, 1, 0]);

            let exp_cid = Cid {
                prv,
                crc,
                ..Default::default()
            };

            assert_eq!(Cid::try_from_bytes(raw.as_ref()), Ok(exp_cid));
            assert_eq!(exp_cid.bytes(), raw);
            assert_eq!(exp_cid.prv(), prv);
            assert_eq!(exp_cid.crc(), crc);
            assert_eq!(exp_cid.mid(), Default::default());
            assert_eq!(exp_cid.oid(), Default::default());
            assert_eq!(exp_cid.pnm(), Default::default());
            assert_eq!(exp_cid.psn(), Default::default());
            assert_eq!(exp_cid.mdt(), Default::default());
        });
    }

    #[test]
    fn test_valid_psn() {
        (0..=u16::MAX)
            .filter_map(|c| {
                let bytes = c.to_be_bytes();
                if bytes.iter().all(|&c| (c as char).is_ascii()) {
                    Some(bytes)
                } else {
                    None
                }
            })
            .for_each(|bytes| {
                let [psn0, psn1] = bytes;
                let raw_psn = [psn0, psn1, psn0, psn1];
                let psn = ProductSerialNumber::try_from_bytes(raw_psn.as_ref()).unwrap();
                let (raw, crc) =
                    raw_with_crc([0, 0, 0, 0, 0, 0, 0, 0, 0, psn0, psn1, psn0, psn1, 0, 1, 0]);

                let exp_cid = Cid {
                    psn,
                    crc,
                    ..Default::default()
                };

                assert_eq!(Cid::try_from_bytes(raw.as_ref()), Ok(exp_cid));
                assert_eq!(exp_cid.psn(), psn);
                assert_eq!(exp_cid.crc(), crc);
                assert_eq!(exp_cid.mid(), Default::default());
                assert_eq!(exp_cid.oid(), Default::default());
                assert_eq!(exp_cid.pnm(), Default::default());
                assert_eq!(exp_cid.prv(), Default::default());
                assert_eq!(exp_cid.mdt(), Default::default());
            });
    }

    #[test]
    fn test_valid_mdt() {
        (0..=0xfff).for_each(|raw_mdt| {
            let year = (raw_mdt >> 8) as u8;
            let month = ((raw_mdt & 0xf0) | ((raw_mdt % 12) + 1)) as u8;
            let mdt = ManufacturingDate::try_from_bytes([year, month].as_ref()).unwrap();
            let (raw, crc) = raw_with_crc([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, year, month, 0]);

            let exp_cid = Cid {
                mdt,
                crc,
                ..Default::default()
            };

            assert_eq!(Cid::try_from_bytes(raw.as_ref()), Ok(exp_cid));
            assert_eq!(exp_cid.bytes(), raw);
            assert_eq!(exp_cid.mdt(), mdt);
            assert_eq!(exp_cid.crc(), crc);
            assert_eq!(exp_cid.mid(), Default::default());
            assert_eq!(exp_cid.oid(), Default::default());
            assert_eq!(exp_cid.pnm(), Default::default());
            assert_eq!(exp_cid.prv(), Default::default());
            assert_eq!(exp_cid.psn(), Default::default());
        });
    }

    #[test]
    fn test_valid_crc() {
        (0..=u8::MAX)
            .filter(|&c| (c as char).is_ascii())
            .for_each(|raw_val| {
                let (mut raw, crc) = raw_with_crc([raw_val; CID_LEN]);

                let mid = ManufacturerId::from_bits(raw[0]);
                let oid = OemId::try_from_bytes(raw[1..=2].as_ref()).unwrap();
                let pnm = ProductName::try_from_bytes(raw[3..=7].as_ref()).unwrap();
                let prv = ProductRevision::from_bits(raw[8]);
                let psn = ProductSerialNumber::from_bits(u32::from_be_bytes(
                    raw[9..=12].try_into().unwrap(),
                ));

                let year = (raw_val & 0xf0) >> 4;
                let month = (raw_val & 0xf0) | ((raw_val % 12) + 1);
                let mdt = ManufacturingDate::try_from_bytes([year, month].as_ref()).unwrap();

                raw[13] = year;
                raw[14] = month;

                let exp_cid = Cid {
                    mid,
                    oid,
                    pnm,
                    prv,
                    psn,
                    mdt,
                    crc,
                };

                assert_eq!(Cid::try_from_bytes(raw.as_ref()), Ok(exp_cid));
                assert_eq!(exp_cid.bytes(), raw);

                assert_eq!(exp_cid.mid(), mid);
                assert_eq!(exp_cid.oid(), oid);
                assert_eq!(exp_cid.pnm(), pnm);
                assert_eq!(exp_cid.prv(), prv);
                assert_eq!(exp_cid.psn(), psn);
                assert_eq!(exp_cid.mdt(), mdt);
                assert_eq!(exp_cid.crc(), crc);
            });
    }
}