Skip to main content

ds_rom/rom/raw/
hmac_sha1_signature.rs

1use std::{backtrace::Backtrace, fmt::Display, num::ParseIntError, str::FromStr};
2
3use bytemuck::{Pod, PodCastError, Zeroable};
4use serde::{Deserialize, Deserializer, Serialize};
5use snafu::Snafu;
6
7use crate::crypto::hmac_sha1::HmacSha1;
8
9/// HMAC-SHA1 signature, used for overlays and overlay tables.
10#[repr(C)]
11#[derive(Clone, Copy, Zeroable, Pod, PartialEq, Eq)]
12pub struct HmacSha1Signature {
13    /// The HMAC-SHA1 hash.
14    pub hash: [u8; 20],
15}
16
17/// Errors related to [`HmacSha1Signature`].
18#[derive(Debug, Snafu)]
19pub enum HmacSha1SignatureError {
20    /// Occurs when the input is not evenly divisible into a slice of [`HmacSha1Signature`].
21    #[snafu(display("the HMAC-SHA1 signature table must be a multiple of {} bytes:\n{backtrace}", size_of::<HmacSha1Signature>()))]
22    InvalidSize {
23        /// Backtrace to the source of the error.
24        backtrace: Backtrace,
25    },
26    /// Occurs when the input is less aligned than [`HmacSha1Signature`].
27    #[snafu(display("expected {expected}-alignment for HMAC-SHA1 signature table but got {actual}-alignment:\n{backtrace}"))]
28    Misaligned {
29        /// Expected alignment.
30        expected: usize,
31        /// Actual input alignment.
32        actual: usize,
33        /// Backtrace to the source of the error.
34        backtrace: Backtrace,
35    },
36}
37
38impl HmacSha1Signature {
39    /// Creates a new [`HmacSha1Signature`] by hashing the given data.
40    pub fn from_hmac_sha1(hmac_sha1: &HmacSha1, data: &[u8]) -> Self {
41        let hash = hmac_sha1.compute(data);
42        Self { hash }
43    }
44
45    /// Sets the hash to the given value.
46    pub fn set(&mut self, hash: [u8; 20]) {
47        self.hash = hash;
48    }
49
50    fn check_size(data: &[u8]) -> Result<(), HmacSha1SignatureError> {
51        let size = size_of::<Self>();
52        if !data.len().is_multiple_of(size) {
53            InvalidSizeSnafu {}.fail()
54        } else {
55            Ok(())
56        }
57    }
58
59    fn handle_pod_cast<T>(result: Result<T, PodCastError>, addr: usize) -> Result<T, HmacSha1SignatureError> {
60        match result {
61            Ok(signatures) => Ok(signatures),
62            Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) => {
63                MisalignedSnafu { expected: size_of::<Self>(), actual: addr }.fail()
64            }
65            Err(PodCastError::AlignmentMismatch) => panic!(),
66            Err(PodCastError::OutputSliceWouldHaveSlop) => panic!(),
67            Err(PodCastError::SizeMismatch) => unreachable!(),
68        }
69    }
70
71    /// Reinterprets a `&[u8]` as a slice of [`HmacSha1Signature`].
72    ///
73    /// # Errors
74    ///
75    /// This function will return an error if the input is the wrong size, or not aligned enough.
76    pub fn borrow_from_slice(data: &'_ [u8]) -> Result<&'_ [Self], HmacSha1SignatureError> {
77        Self::check_size(data)?;
78        let addr = data as *const [u8] as *const () as usize;
79        Self::handle_pod_cast(bytemuck::try_cast_slice(data), addr)
80    }
81
82    /// Reinterprets a `&mut [u8]` as a mutable slice of [`HmacSha1Signature`].
83    ///
84    /// # Errors
85    ///
86    /// This function will return an error if the input is the wrong size, or not aligned enough.
87    pub fn borrow_from_slice_mut(data: &'_ mut [u8]) -> Result<&'_ mut [Self], HmacSha1SignatureError> {
88        Self::check_size(data)?;
89        let addr = data as *const [u8] as *const () as usize;
90        Self::handle_pod_cast(bytemuck::try_cast_slice_mut(data), addr)
91    }
92}
93
94impl Display for HmacSha1Signature {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        for byte in &self.hash {
97            write!(f, "{:02x}", byte)?;
98        }
99        Ok(())
100    }
101}
102
103/// Errors related to parsing an HMAC-SHA1 signature from a string.
104#[derive(Debug, Snafu)]
105pub enum HmacSha1SignatureParseError {
106    /// Occurs when the input is not a valid length.
107    #[snafu(display("invalid length: {length}:\n{backtrace}"))]
108    InvalidLength {
109        /// The invalid length.
110        length: usize,
111        /// Backtrace to the source of the error.
112        backtrace: Backtrace,
113    },
114    /// Occurs when the input is not a valid hex string.
115    #[snafu(display("invalid hex string '{string}':{error}\n{backtrace}"))]
116    ParseInt {
117        /// The original error.
118        error: ParseIntError,
119        /// The invalid hex string.
120        string: String,
121        /// Backtrace to the source of the error.
122        backtrace: Backtrace,
123    },
124}
125
126impl FromStr for HmacSha1Signature {
127    type Err = HmacSha1SignatureParseError;
128
129    fn from_str(s: &str) -> Result<Self, Self::Err> {
130        if s.len() != 40 {
131            return InvalidLengthSnafu { length: s.len() }.fail();
132        }
133
134        let mut hash = [0u8; 20];
135        for i in 0..20 {
136            let byte_str = &s[i * 2..i * 2 + 2];
137            hash[i] = u8::from_str_radix(byte_str, 16)
138                .map_err(|error| ParseIntSnafu { error, string: byte_str.to_string() }.build())?;
139        }
140
141        Ok(Self { hash })
142    }
143}
144
145impl Serialize for HmacSha1Signature {
146    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
147    where
148        S: serde::Serializer,
149    {
150        serializer.serialize_str(self.to_string().as_str())
151    }
152}
153
154impl<'de> Deserialize<'de> for HmacSha1Signature {
155    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
156    where
157        D: Deserializer<'de>,
158    {
159        let s = String::deserialize(deserializer)?;
160        Self::from_str(&s).map_err(serde::de::Error::custom)
161    }
162}