milstd1553b_parser/
core.rs

1//! Core types and structures for MIL-STD-1553B protocol
2
3use crate::error::{ParseError, Result};
4
5/// Bus identification
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub enum Bus {
9    /// Bus A (primary)
10    BusA,
11    /// Bus B (redundant)
12    BusB,
13}
14
15impl Bus {
16    /// Convert bus to bit representation
17    pub fn as_bit(&self) -> u8 {
18        match self {
19            Bus::BusA => 0,
20            Bus::BusB => 1,
21        }
22    }
23}
24
25impl std::fmt::Display for Bus {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            Bus::BusA => write!(f, "Bus A"),
29            Bus::BusB => write!(f, "Bus B"),
30        }
31    }
32}
33
34/// Device address (0-30, with 31 being broadcast)
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct Address(u8);
38
39impl Address {
40    /// Minimum address value (0)
41    pub const MIN: u8 = 0;
42    /// Maximum address value (31)
43    pub const MAX: u8 = 31;
44    /// Broadcast address
45    pub const BROADCAST: u8 = 31;
46
47    /// Create a new address, validating it's within range [0, 31]
48    pub fn new(addr: u8) -> Result<Self> {
49        if addr > Self::MAX {
50            return Err(ParseError::invalid_address(format!(
51                "Address {} out of range [0, {}]",
52                addr,
53                Self::MAX
54            )));
55        }
56        Ok(Address(addr))
57    }
58
59    /// Create a broadcast address
60    pub fn broadcast() -> Self {
61        Address(Self::BROADCAST)
62    }
63
64    /// Get the raw address value
65    pub fn value(&self) -> u8 {
66        self.0
67    }
68
69    /// Check if this is a broadcast address
70    pub fn is_broadcast(&self) -> bool {
71        self.0 == Self::BROADCAST
72    }
73
74    /// Check if this is a valid Remote Terminal address (0-30)
75    pub fn is_remote_terminal(&self) -> bool {
76        self.0 < 30
77    }
78}
79
80impl std::fmt::Display for Address {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        if self.is_broadcast() {
83            write!(f, "BC (broadcast)")
84        } else {
85            write!(f, "RT-{}", self.0)
86        }
87    }
88}
89
90/// Word type in MIL-STD-1553B
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93pub enum WordType {
94    /// Command word (from Bus Controller)
95    Command,
96    /// Data word
97    Data,
98    /// Status word (from Remote Terminal)
99    Status,
100    /// Mode code (special command)
101    ModeCode,
102}
103
104impl std::fmt::Display for WordType {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        match self {
107            WordType::Command => write!(f, "Command"),
108            WordType::Data => write!(f, "Data"),
109            WordType::Status => write!(f, "Status"),
110            WordType::ModeCode => write!(f, "Mode Code"),
111        }
112    }
113}
114
115/// A single MIL-STD-1553B word
116///
117/// Format:
118/// - 1 start bit (always 0 for valid Manchester encoding)
119/// - 16 data bits
120/// - 1 parity bit (odd parity over all 17 bits)
121/// - 2 synchronization bits
122///
123/// Total: 20 bits
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126pub struct Word {
127    /// 20-bit word value
128    data: u32,
129    /// Type of word
130    word_type: WordType,
131}
132
133impl Word {
134    /// Create a new word with validation
135    ///
136    /// The 16 data bits should be in bits 16:1, parity in bit 17
137    pub fn new(data: u32, word_type: WordType) -> Result<Self> {
138        // Validate that only 20 bits are used
139        if data > 0xFFFFF {
140            return Err(ParseError::invalid_word(
141                "Word data exceeds 20 bits".to_string(),
142            ));
143        }
144
145        // Validate parity
146        Self::validate_parity(data)?;
147
148        Ok(Word { data, word_type })
149    }
150
151    /// Create a word without parity validation
152    ///
153    /// Use with caution - only for constructing test data or when parity
154    /// will be verified separately
155    pub fn new_unchecked(data: u32, word_type: WordType) -> Self {
156        Word { data, word_type }
157    }
158
159    /// Get the raw word data (20 bits)
160    pub fn data(&self) -> u32 {
161        self.data
162    }
163
164    /// Get the word type
165    pub fn word_type(&self) -> WordType {
166        self.word_type
167    }
168
169    /// Extract the 16 data bits (bits 16-1)
170    pub fn get_data_bits(&self) -> u16 {
171        ((self.data >> 1) & 0xFFFF) as u16
172    }
173
174    /// Extract the parity bit (bit 17)
175    pub fn get_parity_bit(&self) -> bool {
176        ((self.data >> 17) & 1) != 0
177    }
178
179    /// Extract the sync bits (bits 19-18)
180    pub fn get_sync_bits(&self) -> u8 {
181        ((self.data >> 18) & 0x3) as u8
182    }
183
184    /// Validate odd parity across all 17 bits (bits 16-0)
185    ///
186    /// In MIL-STD-1553B, odd parity is used over the start bit (0) and
187    /// the 16 data bits, and the result is stored in the parity bit.
188    fn validate_parity(data: u32) -> Result<()> {
189        // Count the number of 1s in bits [16:0]
190        let count_bits = (data & 0x1FFFF).count_ones();
191
192        // With odd parity, the total number of 1s (including parity bit) should be odd
193        let parity_bit = ((data >> 17) & 1) != 0;
194        let total_ones = count_bits + if parity_bit { 1 } else { 0 };
195
196        if total_ones % 2 == 0 {
197            return Err(ParseError::parity_error(
198                "Parity check failed: even number of 1s detected".to_string(),
199            ));
200        }
201
202        Ok(())
203    }
204
205    /// Calculate and set the correct parity bit for a word
206    pub fn calculate_parity(data_bits: u16) -> u8 {
207        // Start bit is always 0
208        // Count 1s in the data bits (16 bits)
209        let count_ones = data_bits.count_ones();
210
211        // For odd parity, if we have an even number of 1s, we need a parity bit of 1
212        if count_ones % 2 == 0 {
213            1
214        } else {
215            0
216        }
217    }
218}
219
220impl std::fmt::Display for Word {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        write!(
223            f,
224            "Word(type={}, data=0x{:05X})",
225            self.word_type, self.data
226        )
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    #[test]
235    fn test_address_creation() {
236        assert!(Address::new(0).is_ok());
237        assert!(Address::new(31).is_ok());
238        assert!(Address::new(32).is_err());
239    }
240
241    #[test]
242    fn test_address_broadcast() {
243        let addr = Address::broadcast();
244        assert!(addr.is_broadcast());
245    }
246
247    #[test]
248    fn test_word_creation() {
249        // Create a simple word with valid parity
250        let data_bits = 0xAAAAu16;
251        let parity = Word::calculate_parity(data_bits) as u32;
252        let word_data = (parity << 17) | ((data_bits as u32) << 1) | 0;
253
254        let word = Word::new(word_data, WordType::Data);
255        assert!(word.is_ok());
256    }
257
258    #[test]
259    fn test_word_parity_validation() {
260        // Create a word with wrong parity
261        let word_data = 0xAAAAA; // This likely has wrong parity
262
263        let result = Word::new(word_data, WordType::Data);
264        // May or may not fail depending on the actual parity bits
265        let _ = result;
266    }
267
268    #[test]
269    fn test_calculate_parity() {
270        // Odd parity: total number of 1s (including parity bit) should be odd
271        let parity = Word::calculate_parity(0x0000);
272        assert_eq!(parity, 1); // 0 ones (even) → need parity=1 to make total odd
273
274        let parity = Word::calculate_parity(0xFFFF);
275        assert_eq!(parity, 1); // 16 ones (even) → need parity=1 to make total odd
276
277        let parity = Word::calculate_parity(0x0001);
278        assert_eq!(parity, 0); // 1 one (odd) → parity=0, total stays odd
279    }
280
281    #[test]
282    fn test_bus_display() {
283        assert_eq!(Bus::BusA.to_string(), "Bus A");
284        assert_eq!(Bus::BusB.to_string(), "Bus B");
285    }
286}