1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! Subframe collection.
//!
//! This module contains the [`CollectSubframe`] struct, which is used to
//! collect all the OSNMA data messages in a particular subframe in order to
//! recompose the HKROOT and MACK messages of that subframe.
//!
//! The data for the 36 satellites in the Galileo constellation is collected in
//! parallel.

use crate::types::{
    HkrootMessage, HkrootSection, MackMessage, MackSection, OsnmaDataMessage, HKROOT_MESSAGE_BYTES,
    HKROOT_SECTION_BYTES, MACK_MESSAGE_BYTES, MACK_SECTION_BYTES, NUM_SVNS,
};
use crate::{Gst, Svn, Tow, Wn};

const WORDS_PER_SUBFRAME: u8 = 15;
const SECONDS_PER_SUBFRAME: Tow = 30;

/// Subframe collector.
///
/// This struct collects HKROOT and MACK sections from the OSNMA data in INAV
/// words and produces the complete HKROOT and MACK messages transmitted in that
/// subframe.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct CollectSubframe {
    hkroot: [HkrootMessage; NUM_SVNS],
    mack: [MackMessage; NUM_SVNS],
    num_valid: [u8; NUM_SVNS],
    wn: Wn,
    subframe: Tow,
}

impl CollectSubframe {
    /// Constructs a new, empty subframe collector.
    pub fn new() -> CollectSubframe {
        CollectSubframe {
            hkroot: [[0; HKROOT_MESSAGE_BYTES]; NUM_SVNS],
            mack: [[0; MACK_MESSAGE_BYTES]; NUM_SVNS],
            num_valid: [0; NUM_SVNS],
            wn: 0,
            subframe: 0,
        }
    }

    /// Feed a new OSNMA data message into the subframe collector.
    ///
    /// If this data message completes the HKROOT and MACK message, the
    /// corresponding messages, together with the GST at the start of the
    /// subframe are returned. Otherwise, this returns `None`.
    ///
    /// The `svn` parameter corresponds to the SVN of the satellite transmitting
    /// the INAV word. This should be obtained from the PRN used for tracking.
    ///
    /// The `gst` parameter gives the GST at the start of the INAV page
    /// transmission. It the `gst` corresponds to a new subframe, the data for
    /// the old subframe is discarded, and collection of data for a new subframe
    /// begins. This assumes that the OSNMA data for different satellites is fed
    /// in chronological order.
    pub fn feed(
        &mut self,
        osnma_data: &OsnmaDataMessage,
        svn: Svn,
        gst: Gst,
    ) -> Option<(&HkrootMessage, &MackMessage, Gst)> {
        let hkroot_section: HkrootSection = osnma_data[..HKROOT_SECTION_BYTES].try_into().unwrap();
        let mack_section: MackSection = osnma_data[HKROOT_SECTION_BYTES..].try_into().unwrap();
        let word_num = (gst.tow() / 2) % Tow::from(WORDS_PER_SUBFRAME);
        log::trace!(
            "feeding hkroot = {:02x?}, mack = {:02x?} for {} (GST = {:?}, word number = {})",
            hkroot_section,
            mack_section,
            svn,
            gst,
            word_num
        );
        let subframe = gst.tow() / SECONDS_PER_SUBFRAME;
        if gst.wn() != self.wn || subframe != self.subframe {
            log::debug!("valid sections per SVN: {:?}", &self.num_valid);
            log::info!("starting collection of new subframe (GST {:?})", gst);
            self.wn = gst.wn();
            self.subframe = subframe;
            for s in 0..NUM_SVNS {
                self.num_valid[s] = 0;
            }
        }
        let svn_idx = usize::from(svn) - 1;
        if word_num != u32::from(self.num_valid[svn_idx]) {
            log::trace!(
                "there are missing words for {} (GST {:?}), \
                 word number = {}, valid words = {}",
                svn,
                gst,
                word_num,
                self.num_valid[svn_idx]
            );
            return None;
        }
        let valid = usize::from(self.num_valid[svn_idx]);
        let hkroot_idx = valid * HKROOT_SECTION_BYTES;
        let mack_idx = valid * MACK_SECTION_BYTES;
        self.hkroot[svn_idx][hkroot_idx..hkroot_idx + HKROOT_SECTION_BYTES]
            .copy_from_slice(&hkroot_section);
        self.mack[svn_idx][mack_idx..mack_idx + MACK_SECTION_BYTES].copy_from_slice(&mack_section);
        self.num_valid[svn_idx] += 1;
        if self.num_valid[svn_idx] == WORDS_PER_SUBFRAME {
            log::trace!(
                "completed collection for {} (GST {:?})\n\
                 hkroot = {:02x?}\nmack = {:02x?}",
                svn,
                gst,
                self.hkroot[svn_idx],
                self.mack[svn_idx],
            );
            Some((
                &self.hkroot[svn_idx],
                &self.mack[svn_idx],
                Gst::new(self.wn, self.subframe * SECONDS_PER_SUBFRAME),
            ))
        } else {
            None
        }
    }
}

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

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

    #[test]
    fn collect() {
        // This test starts delivering data for a CollectSubframe for a single
        // satellite at some point already inside a subframe. Then it continues
        // until one full subframe is delivered. The test checks the return value
        // of CollectSubframe::feed every time it is called.
        //
        // The data that is supplied as part of the HKROOT section and MACK
        // section is different each time, so that we can check that the data
        // has been assembled correctly into the HKROOT and MACK messages.
        let svn = Svn::try_from(1).unwrap();
        let wn = 1234;
        let mut collector = CollectSubframe::new();

        // Start delivering data 5 seconds into the subframe
        let delta = 5;
        let tow0 = 123 * SECONDS_PER_SUBFRAME + delta;
        let tow1 = tow0 + (SECONDS_PER_SUBFRAME) - delta;
        let mut counter = 0;
        const N: usize = HKROOT_SECTION_BYTES + MACK_SECTION_BYTES;
        for tow in (tow0..tow1).step_by(2) {
            let mut data = [counter; N];
            data[0] ^= 0xff;
            assert!(collector.feed(&data, svn, Gst::new(wn, tow)).is_none());
            counter += 1;
        }
        let counter0 = counter;
        // Now we start a new subframe
        let tow2 = tow1 + SECONDS_PER_SUBFRAME;
        for tow in (tow1..tow2).step_by(2) {
            let mut data = [counter; N];
            data[0] ^= 0xff;
            let ret = collector.feed(&data, svn, Gst::new(wn, tow));
            counter += 1;
            if tow != tow2 - 2 {
                assert!(ret.is_none())
            } else {
                let mut expected_hkroot = Vec::new();
                let mut expected_mack = Vec::new();
                for j in 0..WORDS_PER_SUBFRAME {
                    let a = counter0 + j;
                    expected_hkroot.extend_from_slice(&[a ^ 0xff; HKROOT_SECTION_BYTES]);
                    expected_mack.extend_from_slice(&[a; MACK_SECTION_BYTES]);
                }
                let expected_hkroot: HkrootMessage = expected_hkroot[..].try_into().unwrap();
                let expected_mack: MackMessage = expected_mack[..].try_into().unwrap();
                let expected = Some((&expected_hkroot, &expected_mack, Gst::new(wn, tow1)));
                assert_eq!(ret, expected);
            }
        }
    }
}