Skip to main content

sbf_tools/blocks/
differential.rs

1//! Differential correction blocks (DiffCorrIn)
2
3use crate::error::{SbfError, SbfResult};
4use crate::header::SbfHeader;
5
6use super::block_ids;
7use super::SbfBlockParse;
8
9// ============================================================================
10// DiffCorrIn Block
11// ============================================================================
12
13/// DiffCorrIn block (Block ID 5919)
14///
15/// Incoming RTCM or CMR differential correction message.
16#[derive(Debug, Clone)]
17pub struct DiffCorrInBlock {
18    tow_ms: u32,
19    wnc: u16,
20    /// Differential mode
21    pub mode: u8,
22    /// Correction source
23    pub source: u8,
24    /// Raw message content (RTCM or CMR bytes)
25    pub message_content: Vec<u8>,
26}
27
28impl DiffCorrInBlock {
29    pub fn tow_seconds(&self) -> f64 {
30        self.tow_ms as f64 * 0.001
31    }
32    pub fn tow_ms(&self) -> u32 {
33        self.tow_ms
34    }
35    pub fn wnc(&self) -> u16 {
36        self.wnc
37    }
38}
39
40impl SbfBlockParse for DiffCorrInBlock {
41    const BLOCK_ID: u16 = block_ids::DIFF_CORR_IN;
42
43    fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
44        const MIN_LEN: usize = 14;
45        if data.len() < MIN_LEN {
46            return Err(SbfError::ParseError("DiffCorrIn too short".into()));
47        }
48
49        let mode = data[12];
50        let source = data[13];
51        let message_content = data[14..].to_vec();
52
53        Ok(Self {
54            tow_ms: header.tow_ms,
55            wnc: header.wnc,
56            mode,
57            source,
58            message_content,
59        })
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn test_diff_corr_in_parse() {
69        let header = SbfHeader {
70            crc: 0,
71            block_id: block_ids::DIFF_CORR_IN,
72            block_rev: 0,
73            length: 20,
74            tow_ms: 5000,
75            wnc: 2100,
76        };
77        let mut data = vec![0u8; 20];
78        data[0..2].copy_from_slice(&0u16.to_le_bytes());
79        data[2..4].copy_from_slice(&block_ids::DIFF_CORR_IN.to_le_bytes());
80        data[4..6].copy_from_slice(&20u16.to_le_bytes());
81        data[6..10].copy_from_slice(&5000u32.to_le_bytes());
82        data[10..12].copy_from_slice(&2100u16.to_le_bytes());
83        data[12] = 1; // mode
84        data[13] = 2; // source
85        data[14..18].copy_from_slice(&[0xD3, 0x00, 0x13, 0x40]); // sample RTCM prefix
86
87        let block = DiffCorrInBlock::parse(&header, &data).unwrap();
88        assert_eq!(block.tow_ms(), 5000);
89        assert_eq!(block.wnc(), 2100);
90        assert_eq!(block.tow_seconds(), 5.0);
91        assert_eq!(block.mode, 1);
92        assert_eq!(block.source, 2);
93        assert_eq!(block.message_content.len(), 6);
94        assert_eq!(block.message_content[0], 0xD3);
95    }
96
97    #[test]
98    fn test_diff_corr_in_dnu_empty_message() {
99        let header = SbfHeader {
100            crc: 0,
101            block_id: block_ids::DIFF_CORR_IN,
102            block_rev: 0,
103            length: 14,
104            tow_ms: 0,
105            wnc: 0,
106        };
107        let mut data = vec![0u8; 14];
108        data[12] = 0;
109        data[13] = 0;
110
111        let block = DiffCorrInBlock::parse(&header, &data).unwrap();
112        assert!(block.message_content.is_empty());
113    }
114}