Skip to main content

galileo_osnma/
bitfields.rs

1//! Message bit fields.
2//!
3//! This module contains structures that give acccess to each of the fields in
4//! the messages used by OSNMA. As a general rule, the structures are a wrapper
5//! over a `&[u8]` or `&[u8; N]`.
6
7pub use crate::tesla::NmaHeader;
8use crate::tesla::{AdkdCheckError, Key, MacseqCheckError};
9use crate::types::{
10    BitSlice, MACK_MESSAGE_BYTES, MERKLE_TREE_NODE_BYTES, MackMessage, MerkleTreeNode, Towh,
11};
12use crate::validation::{NotValidated, Validated};
13use crate::{Gst, Svn, Wn};
14use bitvec::prelude::*;
15use core::fmt;
16use ecdsa::{PrimeCurve, Signature, SignatureSize};
17use sha2::{Digest, Sha256};
18use signature::Verifier;
19
20/// Status of the NMA chain.
21///
22/// This represents the values of the NMAS field of the [`NmaHeader`]
23/// as defined in Section 3.1.1 of the
24/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
25#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
26pub enum NmaStatus {
27    /// Reserved value (NMAS = 0),
28    Reserved,
29    /// Test (NMAS = 1),
30    Test,
31    /// Operational (NMAS = 2).
32    Operational,
33    /// Don't use (NMAS = 3).
34    DontUse,
35}
36
37/// Chain and Public Key status.
38///
39/// This represents the valus of the CPKS field of the [`NmaHeader`]
40/// as defined in Section 3.1.3 of the
41/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
42#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
43pub enum ChainAndPubkeyStatus {
44    /// Reserved value (CPKS = 0).
45    Reserved,
46    /// Nominal (CPKS = 1).
47    Nominal,
48    /// End of chain (EOC) (CPKS = 2).
49    EndOfChain,
50    /// Chain revoked (CREV) (CPKS = 3).
51    ChainRevoked,
52    /// New public key (NPK) (CPKS = 4).
53    NewPublicKey,
54    /// Public key revoked (PKREV) (CPKS = 5).
55    PublicKeyRevoked,
56    /// New Merkle tree (NMT) (CPKS = 6).
57    NewMerkleTree,
58    /// Alert Message (AM) (CPKS = 7)
59    AlertMessage,
60}
61
62/// DSM header.
63///
64/// The DSM header found in the second byte of an HKROOT message.
65/// See Figure 5 in the
66/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
67#[derive(Copy, Clone, Eq, PartialEq, Hash)]
68pub struct DsmHeader<'a>(
69    /// Reference to an array containing the 1-byte header data.
70    pub &'a [u8; 1],
71);
72
73/// Type of the DSM message.
74///
75/// This is derived from the DSM ID field according to Section 3.2.1.1 in the
76/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
77#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
78pub enum DsmType {
79    /// DSM-KROOT.
80    ///
81    /// This message is used to transmit the TESLA root key. It corresponds to
82    /// DSM IDs 0 to 11.
83    Kroot,
84    /// DSM-PKR.
85    ///
86    /// This message is used to transmit a new ECDSA public key. It corresponds
87    /// to DSM IDs 12 to 15.
88    Pkr,
89}
90
91impl DsmHeader<'_> {
92    fn bits(&self) -> &BitSlice {
93        BitSlice::from_slice(self.0)
94    }
95
96    /// Gives the value of the DSM ID field.
97    pub fn dsm_id(&self) -> u8 {
98        self.bits()[..4].load_be()
99    }
100
101    /// Gives the value of the DSM block ID field.
102    pub fn dsm_block_id(&self) -> u8 {
103        self.bits()[4..8].load_be()
104    }
105
106    /// Gives the type of DSM message, according to the DSM ID field.
107    pub fn dsm_type(&self) -> DsmType {
108        if self.dsm_id() >= 12 {
109            DsmType::Pkr
110        } else {
111            DsmType::Kroot
112        }
113    }
114}
115
116impl fmt::Debug for DsmHeader<'_> {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.debug_struct("DsmHeader")
119            .field("dsm_id", &self.dsm_id())
120            .field("dsm_block_id", &self.dsm_block_id())
121            .finish()
122    }
123}
124
125/// DSM-PKR message.
126///
127/// The DSM-PKR message, as defined in Figure 6 of the
128/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
129#[derive(Copy, Clone, Eq, PartialEq, Hash)]
130pub struct DsmPkr<'a>(
131    /// Reference to a slice containing the DSM-PKR message data.
132    ///
133    /// # Panics
134    ///
135    /// This slice should be long enough to contain the full DSM-PKR
136    /// message. Otherwise the methods of `DsmPkr` may panic.
137    pub &'a [u8],
138);
139
140/// New Public Key Type (NPKT).
141///
142/// This represents the values of the New Public Key Type (NPKT) field in the
143/// DSM-PKR message. See Table 5 in the
144/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
145#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
146pub enum NewPublicKeyType {
147    /// An ECDSA key, as defined by the enum [`EcdsaFunction`].
148    EcdsaKey(EcdsaFunction),
149    /// OSNMA Alert Message (OAM).
150    OsnmaAlertMessage,
151    /// Reserved value.
152    Reserved,
153}
154
155impl DsmPkr<'_> {
156    fn bits(&self) -> &BitSlice {
157        BitSlice::from_slice(self.0)
158    }
159
160    /// Gives the number of DSM-PKR blocks.
161    ///
162    /// The number is computed according to the value of the NB_DP field and
163    /// Table 3 in the
164    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
165    ///
166    /// If the NB_DP field contains a reserved value, `None` is returned.
167    pub fn number_of_blocks(&self) -> Option<usize> {
168        let v = self.bits()[..4].load_be::<u8>();
169        match v {
170            7..=10 => Some(usize::from(v) + 6),
171            _ => None, // reserved value
172        }
173    }
174
175    /// Gives the value of the Message ID (MID) field.
176    pub fn message_id(&self) -> u8 {
177        self.bits()[4..8].load_be::<u8>()
178    }
179
180    /// Gives the value of an interemediate tree node.
181    ///
182    /// The DSM-PKR contains 4 256-bit intermediate tree nodes. This returns the
183    /// 256-bit slice corresponding to the intermediate tree node in position
184    /// `node_number` (where `node_number` can be 0, 1, 2, or 3).
185    ///
186    /// # Panics
187    ///
188    /// This function panics if `node` number is not 0, 1, 2, or 3.
189    ///
190    pub fn intermediate_tree_node(&self, node_number: usize) -> &MerkleTreeNode {
191        assert!(node_number < 4);
192        (&self.0[1 + node_number * MERKLE_TREE_NODE_BYTES
193            ..1 + (node_number + 1) * MERKLE_TREE_NODE_BYTES])
194            .try_into()
195            .unwrap()
196    }
197
198    /// Gives the value of the New Public Key Type (NPKT) field.
199    pub fn new_public_key_type(&self) -> NewPublicKeyType {
200        match self.bits()[1032..1036].load_be::<u8>() {
201            1 => NewPublicKeyType::EcdsaKey(EcdsaFunction::P256Sha256),
202            3 => NewPublicKeyType::EcdsaKey(EcdsaFunction::P521Sha512),
203            4 => NewPublicKeyType::OsnmaAlertMessage,
204            _ => NewPublicKeyType::Reserved,
205        }
206    }
207
208    /// Gives the value of the New Public Key ID (NPKID) field.
209    pub fn new_public_key_id(&self) -> u8 {
210        self.bits()[1036..1040].load_be::<u8>()
211    }
212
213    /// Gives the size of the New Public Key field in bytes.
214    ///
215    /// The size is computed according to the value of the NPKT field and Table 6 in the
216    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
217    /// If the NPKT field contains a reserved value, `None` is returned.
218    pub fn key_size(&self) -> Option<usize> {
219        match self.new_public_key_type() {
220            NewPublicKeyType::EcdsaKey(EcdsaFunction::P256Sha256) => Some(264 / 8),
221            NewPublicKeyType::EcdsaKey(EcdsaFunction::P521Sha512) => Some(536 / 8),
222            NewPublicKeyType::OsnmaAlertMessage => {
223                self.number_of_blocks().map(|n| n * (104 / 8) - 1040 / 8)
224            }
225            NewPublicKeyType::Reserved => None,
226        }
227    }
228
229    /// Gives a slice containing the New Public Key field.
230    ///
231    /// If the size of the New Public Key field cannot be determined because
232    /// some other fields contain reserved values, `None` is returned.
233    pub fn new_public_key(&self) -> Option<&[u8]> {
234        self.key_size().map(|s| &self.0[1040 / 8..1040 / 8 + s])
235    }
236
237    /// Gives a slice containing the padding field.
238    ///
239    /// If the size of the New Public Key field cannot be determined because
240    /// some other fields contain reserved values, `None` is returned.
241    pub fn padding(&self) -> Option<&[u8]> {
242        if let (Some(ks), Some(nb)) = (self.key_size(), self.number_of_blocks()) {
243            Some(&self.0[1040 / 8 + ks..nb * 104 / 8])
244        } else {
245            None
246        }
247    }
248
249    /// Gives the Merkle tree leaf corresponding to this message.
250    ///
251    /// The tree leaf is defined in Section 6.2 of the
252    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
253    ///
254    /// If the size of the New Public Key field cannot be determined because
255    /// some other fields contain reserved values, `None` is returned.
256    pub fn merkle_tree_leaf(&self) -> Option<&[u8]> {
257        self.key_size().map(|s| &self.0[1032 / 8..1040 / 8 + s])
258    }
259
260    /// Checks the contents of the padding field.
261    /// The contents are checked according to Eq. 4 in the
262    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
263    ///
264    /// If the contents are correct, this returns `true`. Otherwise, this
265    /// returns `false`. If `self.padding()` returns `None`, then this function
266    /// returns `false`.
267    pub fn check_padding(&self, merkle_tree_root: &MerkleTreeNode) -> bool {
268        let Some(padding) = self.padding() else {
269            return false;
270        };
271        if padding.is_empty() {
272            // This happens for OSNMA Alert Messages: The padding is empty and
273            // does not need to be checked.
274            return true;
275        }
276        let mut hash = Sha256::new();
277        hash.update(merkle_tree_root);
278        // merkle_tree_leaf should not panic, because self.padding() is not None
279        hash.update(self.merkle_tree_leaf().unwrap());
280        let hash = hash.finalize();
281        let truncated = &hash[..padding.len()];
282        truncated == padding
283    }
284}
285
286impl fmt::Debug for DsmPkr<'_> {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        f.debug_struct("DsmPkr")
289            .field("number_of_blocks", &self.number_of_blocks())
290            .field("message_id", &self.message_id())
291            .field("intermediate_tree_node_0", &self.intermediate_tree_node(0))
292            .field("intermediate_tree_node_1", &self.intermediate_tree_node(1))
293            .field("intermediate_tree_node_2", &self.intermediate_tree_node(2))
294            .field("intermediate_tree_node_3", &self.intermediate_tree_node(3))
295            .field("new_public_key_type", &self.new_public_key_type())
296            .field("new_public_key_id", &self.new_public_key_id())
297            .field("new_public_key", &self.new_public_key())
298            .field("padding", &self.padding())
299            .finish()
300    }
301}
302
303/// DSM-KROOT message.
304///
305/// The DSM-KROOT message, as defined in Figure 7 of the
306/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
307#[derive(Copy, Clone, Eq, PartialEq, Hash)]
308pub struct DsmKroot<'a>(
309    /// Reference to a slice containing the DSM-KROOT message data.
310    ///
311    /// # Panics
312    ///
313    /// This slice should be long enough to contain the full DSM-KROOT
314    /// message. Otherwise the methods of `DsmKroot` may panic.
315    pub &'a [u8],
316);
317
318/// Hash function.
319///
320/// This represents the values of the Hash Function (HF) field of the DSM-KROOT
321/// message. See Table 8 in the
322/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
323#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
324pub enum HashFunction {
325    /// SHA-256 (HF = 0).
326    Sha256,
327    /// SHA3-256 (HF = 2).
328    Sha3_256,
329    /// Reserved value (HF = 1, 3).
330    Reserved,
331}
332
333/// MAC function.
334///
335/// This represents the values of the MAC Function (MF) field of the DSM-KROOT
336/// message. See Table 9 in the
337/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
338#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
339pub enum MacFunction {
340    /// HMAC-SHA-256 (MF = 0).
341    HmacSha256,
342    /// CMAC-AES (MF = 1).
343    CmacAes,
344    /// Reserved value (MF = 2, 3).
345    Reserved,
346}
347
348/// ECDSA function.
349///
350/// This represents the key types available for ECDSA signatures. See Table 15
351/// in the
352/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
353#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
354pub enum EcdsaFunction {
355    /// ECDSA P-256/SHA-256.
356    P256Sha256,
357    /// ECDSA P-521/SHA-512
358    P521Sha512,
359}
360
361impl DsmKroot<'_> {
362    fn bits(&self) -> &BitSlice {
363        BitSlice::from_slice(self.0)
364    }
365
366    /// Gives the number of DSM-KROOT blocks.
367    ///
368    /// The number is computed according to the value of the NB_DK field and
369    /// Table 7 in the
370    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
371    ///
372    /// If the NB_DK field contains a reserved value, `None` is returned.
373    pub fn number_of_blocks(&self) -> Option<usize> {
374        let v = self.bits()[..4].load_be::<u8>();
375        match v {
376            1..=8 => Some(usize::from(v) + 6),
377            _ => None, // reserved value
378        }
379    }
380
381    /// Gives the value of the PKID (public key ID) field.
382    pub fn public_key_id(&self) -> u8 {
383        self.bits()[4..8].load_be::<u8>()
384    }
385
386    /// Gives the value of the CIDKR (KROOT chain ID) field.
387    pub fn kroot_chain_id(&self) -> u8 {
388        self.bits()[8..10].load_be::<u8>()
389    }
390
391    /// Gives the value of the hash function field.
392    pub fn hash_function(&self) -> HashFunction {
393        match self.bits()[12..14].load_be::<u8>() {
394            0 => HashFunction::Sha256,
395            2 => HashFunction::Sha3_256,
396            _ => HashFunction::Reserved,
397        }
398    }
399
400    /// Gives the value of the MAC function field.
401    pub fn mac_function(&self) -> MacFunction {
402        match self.bits()[14..16].load_be::<u8>() {
403            0 => MacFunction::HmacSha256,
404            1 => MacFunction::CmacAes,
405            _ => MacFunction::Reserved,
406        }
407    }
408
409    /// Gives the TESLA key size in bits.
410    ///
411    /// The size is computed according to the value of the KS field and
412    /// Table 10 in the
413    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
414    ///
415    /// If the KS field contains a reserved value, `None` is returned.
416    pub fn key_size(&self) -> Option<usize> {
417        // note that all the key sizes are a multiple of 8 bits
418        let size = match self.bits()[16..20].load_be::<u8>() {
419            0 => Some(96),
420            1 => Some(104),
421            2 => Some(112),
422            3 => Some(120),
423            4 => Some(128),
424            5 => Some(160),
425            6 => Some(192),
426            7 => Some(224),
427            8 => Some(256),
428            _ => None,
429        };
430        if let Some(s) = size {
431            debug_assert!(s % 8 == 0);
432        }
433        size
434    }
435
436    /// Gives the MAC tag size in bits.
437    ///
438    /// The size is computed according to the value of the TS field and
439    /// Table 11 in the
440    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
441    ///
442    /// If the TS field contains a reserved value, `None` is returned.
443    pub fn tag_size(&self) -> Option<usize> {
444        match self.bits()[20..24].load_be::<u8>() {
445            5 => Some(20),
446            6 => Some(24),
447            7 => Some(28),
448            8 => Some(32),
449            9 => Some(40),
450            _ => None,
451        }
452    }
453
454    /// Gives the value of the MACLT (MAC look-up table) field.
455    pub fn mac_lookup_table(&self) -> u8 {
456        self.bits()[24..32].load_be()
457    }
458
459    /// Gives the KROOT week number.
460    ///
461    /// This is the value of the WNK field.
462    pub fn kroot_wn(&self) -> Wn {
463        self.bits()[36..48].load_be()
464    }
465
466    /// Gives the KROOT time of week in hours.
467    ///
468    /// This is the value of the TOWHK field.
469    pub fn kroot_towh(&self) -> Towh {
470        self.bits()[48..56].load_be()
471    }
472
473    /// Gives the value of the random pattern alpha.
474    ///
475    /// The random pattern alpha is a 48-bit value. Here it is given in a `u64`.
476    pub fn alpha(&self) -> u64 {
477        self.bits()[56..104].load_be()
478    }
479
480    /// Returns a slice reference to the KROOT in the DSM-KROOT message.
481    ///
482    /// This is the contents of the KROOT field. The length of the returned slice
483    /// depends on the TESLA key size.
484    ///
485    /// # Panics
486    ///
487    /// Panics if the key size field in the DSM-KROOT message contains a reserved
488    /// value.
489    pub fn kroot(&self) -> &[u8] {
490        let size = self
491            .key_size()
492            .expect("attempted to extract kroot of DSM with reserved key size");
493        let size_bytes = size / 8;
494        &self.0[13..13 + size_bytes]
495    }
496
497    /// Returns the ECDSA function used by this DSM-KROOT message.
498    ///
499    /// The ECDSA function is guessed from the size of the ECDSA signature
500    /// in the message.
501    ///
502    /// # Panics
503    ///
504    /// Panics if the ECDSA function cannot be guessed because the size of
505    /// the signature is neither 512 bits (for P-256) nor 1056 bits (for P-521).
506    pub fn ecdsa_function(&self) -> EcdsaFunction {
507        // Although the ICD is not clear about this, we can guess the
508        // ECDSA function in use from the size of the DSM-KROOT
509        let total_len = self.0.len();
510        let fixed_len = 13;
511        let kroot_len = self.kroot().len();
512        let remaining_len = total_len - fixed_len - kroot_len;
513        let b = 13; // block size
514        let p256_bytes = 64; // 512 bits
515        let p521_bytes = 132; // 1056 bits
516        let p256_padding = (b - (kroot_len + p256_bytes) % b) % b;
517        let p521_padding = (b - (kroot_len + p521_bytes) % b) % b;
518        if remaining_len == p256_bytes + p256_padding {
519            EcdsaFunction::P256Sha256
520        } else if remaining_len == p521_bytes + p521_padding {
521            EcdsaFunction::P521Sha512
522        } else {
523            panic!(
524                "failed to guess ECDSA function with DSM-KROOT total len = {total_len}\
525                    and kroot len = {kroot_len}"
526            );
527        }
528    }
529
530    /// Returns a slice reference to the ECDSA signature in the DSM-KROOT message.
531    ///
532    /// This is the contents of the digital signature (DS) field. The length of
533    /// the returned slice depend on the ECDSA function in use.
534    ///
535    /// # Panics
536    ///
537    /// Panics if the ECDSA function cannot be guessed because the size of
538    /// the signature is neither 512 bits (for P-256) nor 1056 bits (for P-521).
539    pub fn digital_signature(&self) -> &[u8] {
540        let size = match self.ecdsa_function() {
541            EcdsaFunction::P256Sha256 => 64,
542            EcdsaFunction::P521Sha512 => 132,
543        };
544        let start = 13 + self.kroot().len();
545        &self.0[start..start + size]
546    }
547
548    /// Gives the contents of the DSM-KROOT padding (P_DK) field.
549    pub fn padding(&self) -> &[u8] {
550        let start = 13 + self.kroot().len() + self.digital_signature().len();
551        &self.0[start..]
552    }
553
554    // message for digital signature verification
555    fn signature_message(&self, nma_header: NmaHeader<NotValidated>) -> ([u8; 209], usize) {
556        let mut m = [0; 209];
557        m[0] = nma_header.data();
558        let end = 13 + self.kroot().len();
559        // we skip the NB_DK and PKID fields in self.0
560        m[1..end].copy_from_slice(&self.0[1..end]);
561        (m, end)
562    }
563
564    /// Checks the contents of the padding field.
565    ///
566    /// The contents are checked according to Eq. 7 in the
567    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
568    ///
569    /// If the contents are correct, this returns `true`. Otherwise, this
570    /// returns `false`.
571    pub fn check_padding(&self, nma_header: NmaHeader<NotValidated>) -> bool {
572        let (message, size) = self.signature_message(nma_header);
573        let message = &message[..size];
574        let mut hash = Sha256::new();
575        hash.update(message);
576        hash.update(self.digital_signature());
577        let hash = hash.finalize();
578        let padding = self.padding();
579        let truncated = &hash[..padding.len()];
580        truncated == padding
581    }
582
583    /// Checks the P256 ECDSA signature.
584    ///
585    /// This verifies that the P256 ECDSA signature of the DSM-KROOT message is
586    /// correct. The algorithm in Section 6.3 of the
587    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
588    /// is followed.
589    ///
590    /// # Panics
591    ///
592    /// Panics if the DSM-KROOT message does not use a P256 ECDSA signature.
593    ///
594    pub fn check_signature_p256(
595        &self,
596        nma_header: NmaHeader<NotValidated>,
597        pubkey: &p256::ecdsa::VerifyingKey,
598    ) -> bool {
599        assert_eq!(self.ecdsa_function(), EcdsaFunction::P256Sha256);
600        self.check_signature(nma_header, pubkey)
601    }
602
603    /// Checks the P512 ECDSA signature.
604    ///
605    /// This verifies that the P512 ECDSA signature of the DSM-KROOT message is
606    /// correct. The algorithm in Section 6.3 of the
607    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
608    /// is followed.
609    ///
610    /// # Panics
611    ///
612    /// Panics if the DSM-KROOT message does not use a P512 ECDSA signature.
613    ///
614    #[cfg(feature = "p521")]
615    pub fn check_signature_p521(
616        &self,
617        nma_header: NmaHeader<NotValidated>,
618        pubkey: &p521::ecdsa::VerifyingKey,
619    ) -> bool {
620        assert_eq!(self.ecdsa_function(), EcdsaFunction::P521Sha512);
621        self.check_signature(nma_header, pubkey)
622    }
623
624    // Generic function to check the ECDSA signature. This works for either:
625    //
626    // - VK = p256::ecdsa::VerifyingKey, C = p256::NistP256
627    // - VK = p512::ecdsa::VerifyingKey, C = p521::NistP521
628    //
629    // The function can also be called with other type parameters, but it doesn't
630    // make sense to do so.
631    //
632    // # Panics
633    //
634    // The function panics if the ECDSA signature cannot be serialized, which
635    // can happen if the chosen type parameters do not match the signature
636    // length in the DSM-KROOT message.
637    fn check_signature<VK, C>(&self, nma_header: NmaHeader<NotValidated>, pubkey: &VK) -> bool
638    where
639        VK: Verifier<Signature<C>>,
640        C: PrimeCurve,
641        SignatureSize<C>: crypto_common::generic_array::ArrayLength<u8>,
642    {
643        let (message, size) = self.signature_message(nma_header);
644        let message = &message[..size];
645        let signature = Signature::from_bytes(self.digital_signature().into())
646            .expect("error serializing ECDSA signature");
647        pubkey.verify(message, &signature).is_ok()
648    }
649}
650
651impl fmt::Debug for DsmKroot<'_> {
652    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
653        f.debug_struct("DsmKroot")
654            .field("number_of_blocks", &self.number_of_blocks())
655            .field("public_key_id", &self.public_key_id())
656            .field("kroot_chain_id", &self.kroot_chain_id())
657            .field("hash_function", &self.hash_function())
658            .field("mac_function", &self.mac_function())
659            .field("key_size", &self.key_size())
660            .field("tag_size", &self.tag_size())
661            .field("mac_loopkup_table", &self.mac_lookup_table())
662            .field("kroot_wn", &self.kroot_wn())
663            .field("kroot_towh", &self.kroot_towh())
664            .field("alpha", &self.alpha())
665            .field("kroot", &self.kroot())
666            .field("digital_signature", &self.digital_signature())
667            .field("padding", &self.padding())
668            .finish()
669    }
670}
671
672/// MACK message.
673///
674/// The MACK message, as defined in Figure 8 of the
675/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
676///
677/// This is one of the few structs in [bitfields](crate::bitfields) that is not
678/// a simple wrapper around a slice. The reason is that to interpret the MACK
679/// message, it is necessary to know the key and tag sizes, so `Mack` holds
680/// these values as well.
681///
682/// The `V` type parameter is used to indicate the validation status of the MACK
683/// message. Validation of a MACK message corresponds to checking its MACSEQ
684/// field and that its ADKDs match the corresponding look-up table. See
685/// [validation](crate::validation) for a description of validation type
686/// parameters.
687#[derive(Copy, Clone, Eq, PartialEq, Hash)]
688pub struct Mack<'a, V> {
689    data: &'a BitSlice,
690    key_size: usize,
691    tag_size: usize,
692    _validated: V,
693}
694
695impl Mack<'_, NotValidated> {
696    /// Constructs a new MACK message.
697    ///
698    /// The `data` should be a reference to an array containing the 60 bytes of
699    /// the MACK message. The `key_size` in bits and `tag_size` in bits should
700    /// be taken from the parameters of the current TESLA chain. The MACK
701    /// message is marked as [`NotValidated`].
702    pub fn new(data: &MackMessage, key_size: usize, tag_size: usize) -> Mack<'_, NotValidated> {
703        Mack {
704            data: BitSlice::from_slice(data),
705            key_size,
706            tag_size,
707            _validated: NotValidated {},
708        }
709    }
710}
711
712impl<V> Mack<'_, V> {
713    /// Gives the key size in bits corresponding to the MACK message.
714    ///
715    /// This returns the value that has been given in [`Mack::new`].
716    pub fn key_size(&self) -> usize {
717        self.key_size
718    }
719
720    /// Gives the key size in bits corresponding to the MACK message.
721    ///
722    /// This returns the value that has been given in [`Mack::new`].
723    pub fn tag_size(&self) -> usize {
724        self.tag_size
725    }
726
727    /// Gives the tag0 field contained in the MACK header of the MACK message.
728    ///
729    /// See Figure 9 in the
730    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
731    pub fn tag0(&self) -> &BitSlice {
732        &self.data[..self.tag_size()]
733    }
734
735    /// Gives the value of the MACSEQ field contained in the MACK header of the MACK message.
736    ///
737    /// See Figure 9 in the
738    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
739    /// The MACSEQ is a 12-bit integer, which is returned as a `u16`.
740    pub fn macseq(&self) -> u16 {
741        let macseq_size = 12;
742        self.data[self.tag_size()..self.tag_size() + macseq_size].load_be::<u16>()
743    }
744
745    /// Gives the value of the COP field contained in the MACK header of the MACK message.
746    ///
747    /// See Figure 9 in the
748    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
749    /// The COP is a 4-bit integer, which is returned as a `u8`.
750    pub fn cop(&self) -> u8 {
751        let macseq_size = 12;
752        let cop_offset = self.tag_size() + macseq_size;
753        let cop_size = 4;
754        self.data[cop_offset..cop_offset + cop_size].load_be::<u8>()
755    }
756
757    /// Returns the number of tags in the MACK message.
758    ///
759    /// The number of tags is computed according to the tag size.
760    pub fn num_tags(&self) -> usize {
761        (8 * MACK_MESSAGE_BYTES - self.key_size()) / (self.tag_size() + 16)
762    }
763
764    /// Gives the Key field of the MACK message.
765    ///
766    /// This fields contains a TESLA key. See Figure 8 in the
767    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
768    pub fn key(&self) -> &BitSlice {
769        let start = (self.tag_size() + 16) * self.num_tags();
770        &self.data[start..start + self.key_size()]
771    }
772}
773
774/// MACK validation error
775///
776/// This enum lists the possible errors that can happen when a MACK message
777/// validation using [`Mack::validate`] is attempted.
778#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
779pub enum MackValidationError {
780    /// The MACSEQ field could not be verified.
781    ///
782    /// The MACSEQ field is checked using the algorithm in Section 6.6 of the
783    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
784    MacseqError(MacseqCheckError),
785    /// One of the ADKD fields is not correct.
786    WrongAdkd {
787        /// The index of the first tag whose ADKD is not correct.
788        tag_index: usize,
789        /// The reason why the ADKD field is not correct.
790        error: AdkdCheckError,
791    },
792}
793
794impl fmt::Display for MackValidationError {
795    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
796        match self {
797            MackValidationError::MacseqError(err) => err.fmt(f),
798            MackValidationError::WrongAdkd { tag_index, error } => {
799                write!(f, "incorrect ADKD field at tag {tag_index} ({error})")
800            }
801        }
802    }
803}
804
805impl From<MacseqCheckError> for MackValidationError {
806    fn from(value: MacseqCheckError) -> MackValidationError {
807        MackValidationError::MacseqError(value)
808    }
809}
810
811#[cfg(feature = "std")]
812impl std::error::Error for MackValidationError {
813    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
814        match self {
815            MackValidationError::MacseqError(err) => Some(err),
816            MackValidationError::WrongAdkd { error, .. } => Some(error),
817        }
818    }
819}
820
821impl<V: Clone> Mack<'_, V> {
822    /// Gives an object representing one of the Tag-Info sections in the MACK message.
823    ///
824    /// The Tag-Info section is defined in Figure 11 of the
825    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
826    /// The parameter `n` corresponds to the index of the Tag-Info in the MACK
827    /// message. The first Tag-Info has `n = 1`, since `n = 0` would correspond
828    /// to the Tag0 field, which does not have an associated info field and is
829    /// obtained with [`Mack::tag0`].
830    ///
831    /// The validation status of the Tag-Info is inherited from the validation
832    /// status of the MACK message. There is no way to validate Tag-Info
833    /// sections once they have been separated from the MACK message. If a
834    /// validated Tag-Info is needed, the whole MACK message should be validated
835    /// first using [`Mack::validate`] before calling [`Mack::tag_and_info`].
836    ///
837    /// # Panics
838    ///
839    /// Panics if `n` is not between 1 and `self.num_tags() - 1`.
840    pub fn tag_and_info(&self, n: usize) -> TagAndInfo<'_, V> {
841        assert!(0 < n && n < self.num_tags());
842        let size = self.tag_size() + 16;
843        TagAndInfo {
844            data: &self.data[size * n..size * (n + 1)],
845            _validated: self._validated.clone(),
846        }
847    }
848}
849
850impl<'a, V: Clone> Mack<'a, V> {
851    /// Try to validate the MACK message.
852    ///
853    /// Given the TESLA `key` transmitted on the next subframe, this will
854    /// attempt to validate the MACSEQ field and the ADKD fields of the MACK
855    /// message. The MACSEQ field is checked using the algorithm in Section 6.6
856    /// of the
857    /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
858    /// The sequence of ADKD fields is checked against the MAC look-up table
859    /// using the chain parameters held by the TESLA key.
860    ///
861    /// The parameter `prna` should be the SVN of the satellite that transmitted
862    /// this MACK message, and `gst_mack` corresponds to the GST at the start of
863    /// the subframe in which the MACK message was transmitted. The `maclt`
864    /// parameter indicates the active MAC Look-up Table id. It is used to
865    /// determine which tags are flexible.
866    ///
867    /// If the validation is successful, this returns a copy of `self` with the
868    /// validation type parameter `V` set to `Validated`. Otherwise, an error
869    /// indicating which check was not satisfied is returned.
870    pub fn validate(
871        &self,
872        key: &Key<Validated>,
873        prna: Svn,
874        gst_mack: Gst,
875    ) -> Result<Mack<'a, Validated>, MackValidationError> {
876        key.validate_macseq(self, prna, gst_mack)?;
877
878        for j in 1..self.num_tags() {
879            let tag = self.tag_and_info(j);
880            if let Err(e) = key.chain().validate_adkd(j, tag, prna, gst_mack) {
881                return Err(MackValidationError::WrongAdkd {
882                    tag_index: j,
883                    error: e,
884                });
885            }
886        }
887        Ok(Mack {
888            data: self.data,
889            key_size: self.key_size,
890            tag_size: self.tag_size,
891            _validated: Validated {},
892        })
893    }
894}
895
896impl<V: fmt::Debug + Clone> fmt::Debug for Mack<'_, V> {
897    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
898        let mut dbg = f.debug_struct("Mack");
899        dbg.field("tag0", &self.tag0())
900            .field("macseq", &self.macseq());
901        for tag in 1..self.num_tags() {
902            dbg.field("tag", &self.tag_and_info(tag));
903        }
904        dbg.field("key", &self.key())
905            .field("_validated", &self._validated)
906            .finish()
907    }
908}
909
910/// Tag-Info section.
911///
912/// The Tag-Info section is defined in Figure 11 of the
913/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
914/// A Tag-Info field is obtained from a MACK message with [`Mack::tag_and_info`].
915#[derive(Copy, Clone, Eq, PartialEq, Hash)]
916pub struct TagAndInfo<'a, V> {
917    data: &'a BitSlice,
918    _validated: V,
919}
920
921/// PRND (PRN of the satellite transmitting the authenticated data).
922///
923/// This represents the values of the PRND field in a Tag-Info section, as
924/// described in Table 12 in the
925/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
926#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
927pub enum Prnd {
928    /// Galileo SVID (PRND = 1 - 36).
929    GalileoSvid(
930        /// The Galileo SVID value (between 1 and 36).
931        u8,
932    ),
933    /// Galileo constellation-related information (PRND = 255).
934    GalileoConstellation,
935    /// Reserved value (any other value of the PRND field).
936    Reserved,
937}
938
939impl TryFrom<Prnd> for u8 {
940    type Error = ();
941    fn try_from(value: Prnd) -> Result<u8, ()> {
942        match value {
943            Prnd::GalileoSvid(svid) => Ok(svid),
944            Prnd::GalileoConstellation => Ok(255),
945            Prnd::Reserved => Err(()),
946        }
947    }
948}
949
950/// ADKD (Authentication Data and Key Delay).
951///
952/// Represents the values of the ADKD (Authentication Data and Key Delay) field,
953/// as defined in Table 14 in the
954/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
955#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
956pub enum Adkd {
957    /// Galileo I/NAV ephemeris, clock and status (ADKD = 0).
958    InavCed,
959    /// Galileo I/NAV timing parameters (ADKD = 4).
960    InavTiming,
961    /// Slow MAC. Galileo I/NAV ephemeris, clock and status (ADKD = 12).
962    SlowMac,
963    /// Reserved value (any other ADKD value).
964    Reserved,
965}
966
967impl<V> TagAndInfo<'_, V> {
968    /// Gives the tag field.
969    pub fn tag(&self) -> &BitSlice {
970        &self.data[..self.data.len() - 16]
971    }
972
973    /// Returns the tag-info section as a [`BitSlice`].
974    ///
975    /// The methods below return individual fields of the tag-info section.
976    pub fn tag_info(&self) -> &BitSlice {
977        &self.data[self.data.len() - 16..]
978    }
979
980    /// Gives the value of the PRND field in the Tag-Info section.
981    pub fn prnd(&self) -> Prnd {
982        let len = self.data.len();
983        match self.data[len - 16..len - 8].load_be::<u8>() {
984            n @ 1..=36 => Prnd::GalileoSvid(n),
985            255 => Prnd::GalileoConstellation,
986            _ => Prnd::Reserved,
987        }
988    }
989
990    /// Gives the value of the ADKD field in the Tag-Info section.
991    pub fn adkd(&self) -> Adkd {
992        let len = self.data.len();
993        match self.data[len - 8..len - 4].load_be::<u8>() {
994            0 => Adkd::InavCed,
995            4 => Adkd::InavTiming,
996            12 => Adkd::SlowMac,
997            _ => Adkd::Reserved,
998        }
999    }
1000
1001    /// Gives the value of the COP field in the Tag-Info section.
1002    ///
1003    /// The COP is a 4-bit integer, which is returned as a `u8`.
1004    pub fn cop(&self) -> u8 {
1005        let len = self.data.len();
1006        self.data[len - 4..].load_be::<u8>()
1007    }
1008}
1009
1010impl<V: fmt::Debug> fmt::Debug for TagAndInfo<'_, V> {
1011    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1012        f.debug_struct("TagAndInfo")
1013            .field("tag", &self.tag())
1014            .field("prnd", &self.prnd())
1015            .field("adkd", &self.adkd())
1016            .field("_validated", &self._validated)
1017            .finish()
1018    }
1019}
1020
1021#[cfg(test)]
1022mod test {
1023    use super::*;
1024    use hex_literal::hex;
1025
1026    #[test]
1027    fn nma_header() {
1028        // NMA header broadcast on 2022-03-07
1029        let nma_header = NmaHeader::new(0x52);
1030        assert_eq!(nma_header.nma_status(), NmaStatus::Test);
1031        assert_eq!(nma_header.chain_id(), 1);
1032        assert_eq!(
1033            nma_header.chain_and_pubkey_status(),
1034            ChainAndPubkeyStatus::Nominal
1035        );
1036    }
1037
1038    #[test]
1039    fn dsm_header() {
1040        let header = [0x17];
1041        let dsm_header = DsmHeader(&header);
1042        assert_eq!(dsm_header.dsm_id(), 1);
1043        assert_eq!(dsm_header.dsm_block_id(), 7);
1044        assert_eq!(dsm_header.dsm_type(), DsmType::Kroot);
1045    }
1046
1047    #[test]
1048    fn dsm_pkr() {
1049        // DSM-PKR broadcast on 2023-12-12 12:00 UTC
1050        let dsm = hex!(
1051            "
1052            70 01 63 1b dc ed 79 d4 31 7b c2 87 0e e3 89 5b
1053            d5 9c f2 b6 ea 51 6f ab bf df 1d 73 96 26 14 6f
1054            fe 31 6f a9 28 5f 5a 1e 44 04 24 13 bd af 18 aa
1055            3c f6 84 72 33 97 d7 b8 32 5a ec a1 eb ca 9f 0f
1056            64 99 05 42 4c be 48 2a 1a 32 b0 10 64 f8 5d 0c
1057            36 df 03 8e 52 ce 12 8e 7e c5 f3 23 e1 65 b1 82
1058            a7 15 37 bd b0 10 97 2e b4 a3 b9 0b aa cd 14 94
1059            1e f4 0d a2 cb 2b 82 d3 78 b3 15 c0 08 de ce fd
1060            8e 11 03 74 a9 25 cf a0 ff 18 05 e5 c5 a5 8f db
1061            a3 1b f0 14 5d 5b 5b e2 f0 62 d3 f8 bb 2e e9 8f
1062            0f 6d b0 e8 23 c5 e7 5e 78"
1063        );
1064        let dsm = DsmPkr(&dsm);
1065        assert_eq!(dsm.number_of_blocks(), Some(13));
1066        assert_eq!(dsm.message_id(), 0);
1067        assert_eq!(
1068            dsm.intermediate_tree_node(0),
1069            &hex!(
1070                "01 63 1b dc ed 79 d4 31 7b c2 87 0e e3 89 5b d5
1071                 9c f2 b6 ea 51 6f ab bf df 1d 73 96 26 14 6f fe"
1072            )
1073        );
1074        let itn1 = hex!(
1075            "31 6f a9 28 5f 5a 1e 44 04 24 13 bd af 18 aa 3c
1076             f6 84 72 33 97 d7 b8 32 5a ec a1 eb ca 9f 0f 64"
1077        );
1078        assert_eq!(dsm.intermediate_tree_node(1), &itn1);
1079        let itn2 = hex!(
1080            "99 05 42 4c be 48 2a 1a 32 b0 10 64 f8 5d 0c 36
1081             df 03 8e 52 ce 12 8e 7e c5 f3 23 e1 65 b1 82 a7"
1082        );
1083        assert_eq!(dsm.intermediate_tree_node(2), &itn2);
1084        let itn3 = hex!(
1085            "15 37 bd b0 10 97 2e b4 a3 b9 0b aa cd 14 94 1e
1086             f4 0d a2 cb 2b 82 d3 78 b3 15 c0 08 de ce fd 8e"
1087        );
1088        assert_eq!(dsm.intermediate_tree_node(3), &itn3);
1089        assert_eq!(
1090            dsm.new_public_key_type(),
1091            NewPublicKeyType::EcdsaKey(EcdsaFunction::P256Sha256)
1092        );
1093        assert_eq!(dsm.new_public_key_id(), 1);
1094        assert_eq!(
1095            dsm.new_public_key(),
1096            Some(
1097                &hex!(
1098                    "03 74 a9 25 cf a0 ff 18 05 e5 c5 a5 8f db a3 1b
1099                     f0 14 5d 5b 5b e2 f0 62 d3 f8 bb 2e e9 8f 0f 6d b0"
1100                )[..]
1101            )
1102        );
1103        assert_eq!(dsm.padding(), Some(&hex!("e8 23 c5 e7 5e 78")[..]));
1104        // Obtained from OSNMA_MerkleTree_20231213105954_PKID_1.xml
1105        let merkle_tree_root =
1106            hex!("0E63F552C8021709043C239032EFFE941BF22C8389032F5F2701E0FBC80148B8");
1107        assert!(dsm.check_padding(&merkle_tree_root));
1108
1109        // DSM-PKR broadcast on 2023-12-15 00:00 UTC
1110        let dsm = hex!(
1111            "
1112            71 e5 53 0a 33 d5 cb 60 c9 50 16 b8 ae c7 45 93
1113            db cd f2 71 1d 39 9e a2 48 69 17 3c a2 29 37 9a
1114            15 31 6f a9 28 5f 5a 1e 44 04 24 13 bd af 18 aa
1115            3c f6 84 72 33 97 d7 b8 32 5a ec a1 eb ca 9f 0f
1116            64 99 05 42 4c be 48 2a 1a 32 b0 10 64 f8 5d 0c
1117            36 df 03 8e 52 ce 12 8e 7e c5 f3 23 e1 65 b1 82
1118            a7 15 37 bd b0 10 97 2e b4 a3 b9 0b aa cd 14 94
1119            1e f4 0d a2 cb 2b 82 d3 78 b3 15 c0 08 de ce fd
1120            8e 12 03 35 78 e5 c7 11 a9 c3 bd dd 1c a4 ee 85
1121            f7 c5 1b 36 78 97 cb 40 b8 85 68 a0 c8 97 da 30
1122            ef b7 c3 24 e0 22 2c 90 80"
1123        );
1124        let dsm = DsmPkr(&dsm);
1125        assert_eq!(dsm.number_of_blocks(), Some(13));
1126        assert_eq!(dsm.message_id(), 1);
1127        assert_eq!(
1128            dsm.intermediate_tree_node(0),
1129            &hex!(
1130                "e5 53 0a 33 d5 cb 60 c9 50 16 b8 ae c7 45 93 db
1131                 cd f2 71 1d 39 9e a2 48 69 17 3c a2 29 37 9a 15"
1132            )
1133        );
1134        assert_eq!(dsm.intermediate_tree_node(1), &itn1);
1135        assert_eq!(dsm.intermediate_tree_node(2), &itn2);
1136        assert_eq!(dsm.intermediate_tree_node(3), &itn3);
1137        assert_eq!(
1138            dsm.new_public_key_type(),
1139            NewPublicKeyType::EcdsaKey(EcdsaFunction::P256Sha256)
1140        );
1141        assert_eq!(dsm.new_public_key_id(), 2);
1142        assert_eq!(
1143            dsm.new_public_key(),
1144            Some(
1145                &hex!(
1146                    "03 35 78 e5 c7 11 a9 c3 bd dd 1c a4 ee 85 f7 c5
1147                     1b 36 78 97 cb 40 b8 85 68 a0 c8 97 da 30 ef b7 c3"
1148                )[..]
1149            )
1150        );
1151        assert_eq!(dsm.padding(), Some(&hex!("24 e0 22 2c 90 80")[..]));
1152        assert!(dsm.check_padding(&merkle_tree_root));
1153    }
1154
1155    #[test]
1156    fn dsm_kroot() {
1157        // DSM-KROOT broadcast on 2022-03-07 9:00 UTC
1158        let dsm = hex!(
1159            "
1160            22 50 49 21 04 98 21 25 d3 96 4d a3 a2 84 1e 1d
1161            e4 d4 58 c0 e9 84 24 76 e0 04 66 6c f3 79 58 de
1162            28 51 97 a2 63 53 f1 a4 c6 6d 7e 3d 29 18 53 ba
1163            5a 13 c9 c3 48 4a 26 77 70 11 2a 13 38 3e a5 2d
1164            3a 01 9d 5b 6e 1d d1 87 b9 45 3c df 06 ca 7f 34
1165            ea 14 97 52 5a af 18 f1 f9 f1 fc cb 12 29 89 77
1166            35 c0 21 b0 41 73 93 b5"
1167        );
1168        let dsm = DsmKroot(&dsm);
1169        assert_eq!(dsm.number_of_blocks(), Some(8));
1170        assert_eq!(dsm.public_key_id(), 2);
1171        assert_eq!(dsm.kroot_chain_id(), 1);
1172        assert_eq!(dsm.hash_function(), HashFunction::Sha256);
1173        assert_eq!(dsm.mac_function(), MacFunction::HmacSha256);
1174        assert_eq!(dsm.key_size(), Some(128));
1175        assert_eq!(dsm.tag_size(), Some(40));
1176        assert_eq!(dsm.mac_lookup_table(), 0x21);
1177        assert_eq!(dsm.kroot_wn(), 0x498);
1178        assert_eq!(dsm.kroot_towh(), 0x21);
1179        assert_eq!(dsm.alpha(), 0x25d3964da3a2);
1180        assert_eq!(
1181            dsm.kroot(),
1182            hex!("84 1e 1d e4 d4 58 c0 e9 84 24 76 e0 04 66 6c f3")
1183        );
1184        assert_eq!(dsm.ecdsa_function(), EcdsaFunction::P256Sha256);
1185        assert_eq!(
1186            dsm.digital_signature(),
1187            hex!(
1188                "79 58 de 28 51 97 a2 63 53 f1 a4 c6 6d 7e 3d 29
1189                 18 53 ba 5a 13 c9 c3 48 4a 26 77 70 11 2a 13 38
1190                 3e a5 2d 3a 01 9d 5b 6e 1d d1 87 b9 45 3c df 06
1191                 ca 7f 34 ea 14 97 52 5a af 18 f1 f9 f1 fc cb 12"
1192            )
1193        );
1194        assert_eq!(dsm.padding(), hex!("29 89 77 35 c0 21 b0 41 73 93 b5"));
1195        let nma_header = NmaHeader::new(0x52);
1196        assert!(dsm.check_padding(nma_header));
1197    }
1198
1199    #[test]
1200    fn mack() {
1201        // MACK broadcast on 2022-03-07 9:00 UTC
1202        let mack = hex!(
1203            "
1204            11 55 d3 71 f2 1f 30 a8 e4 ec e0 c0 1b 07 6d 17
1205            7d 64 03 12 05 d4 02 7e 77 13 15 c0 4c ca 1c 16
1206            99 1a 05 48 91 07 a7 f7 0e c5 42 b4 19 da 6a da
1207            1c 0a 3d 6f 56 a5 e5 dc 59 a7 00 00"
1208        );
1209        let key_size = 128;
1210        let tag_size = 40;
1211        let mack = Mack::new(&mack, key_size, tag_size);
1212        assert_eq!(mack.key_size(), key_size);
1213        assert_eq!(mack.tag_size(), tag_size);
1214        assert_eq!(mack.tag0(), BitSlice::from_slice(&hex!("11 55 d3 71 f2")));
1215        assert_eq!(mack.macseq(), 0x1f3);
1216        assert_eq!(mack.num_tags(), 6);
1217        assert_eq!(
1218            mack.tag_and_info(1).tag(),
1219            BitSlice::from_slice(&hex!("a8 e4 ec e0 c0"))
1220        );
1221        assert_eq!(mack.tag_and_info(1).prnd(), Prnd::GalileoSvid(0x1b));
1222        assert_eq!(mack.tag_and_info(1).adkd(), Adkd::InavCed);
1223        assert_eq!(
1224            mack.tag_and_info(2).tag(),
1225            BitSlice::from_slice(&hex!("6d 17 7d 64 03"))
1226        );
1227        assert_eq!(mack.tag_and_info(2).prnd(), Prnd::GalileoSvid(0x12));
1228        assert_eq!(mack.tag_and_info(2).adkd(), Adkd::InavCed);
1229        assert_eq!(
1230            mack.tag_and_info(3).tag(),
1231            BitSlice::from_slice(&hex!("d4 02 7e 77 13"))
1232        );
1233        assert_eq!(mack.tag_and_info(3).prnd(), Prnd::GalileoSvid(0x15));
1234        assert_eq!(mack.tag_and_info(3).adkd(), Adkd::SlowMac);
1235        assert_eq!(
1236            mack.tag_and_info(4).tag(),
1237            BitSlice::from_slice(&hex!("4c ca 1c 16 99"))
1238        );
1239        assert_eq!(mack.tag_and_info(4).prnd(), Prnd::GalileoSvid(0x1a));
1240        assert_eq!(mack.tag_and_info(4).adkd(), Adkd::InavCed);
1241        assert_eq!(
1242            mack.tag_and_info(5).tag(),
1243            BitSlice::from_slice(&hex!("48 91 07 a7 f7"))
1244        );
1245        assert_eq!(mack.tag_and_info(5).prnd(), Prnd::GalileoSvid(0x0e));
1246        assert_eq!(mack.tag_and_info(5).adkd(), Adkd::SlowMac);
1247        assert_eq!(
1248            mack.key(),
1249            BitSlice::from_slice(&hex!("42 b4 19 da 6a da 1c 0a 3d 6f 56 a5 e5 dc 59 a7"))
1250        );
1251    }
1252}