Skip to main content

uart_proto_detector/
lib.rs

1// Copyright (C) 2026 Jorge Andre Castro <georgeandrec@gmail.com>
2// SPDX-License-Identifier: GPL-2.0-or-later
3//
4// This program is free software; you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation; either version 2 of the License, or
7// (at your option) any later version.
8
9//! # uart-proto-detector
10//!
11//! Bibliothèque universelle `no_std` pour la détection et le découpage de trames
12//! UART sur systèmes embarqués.
13//!
14//! ## Fonctionnement
15//!
16//! Le parser accumule les octets reçus un à un, valide l'en-tête, détermine la
17//! longueur attendue selon la stratégie choisie, puis retourne la trame complète.
18//! La validation du CRC est volontairement **exclue** de cette bibliothèque : chaque
19//! composant implémentant son propre algorithme, cette responsabilité revient au code
20//! appelant.
21//!
22//! ## Exemple minimal
23//!
24//! ```rust
25//! use uart_proto_detector::{GenericParser, ParserConfig, PacketLengthStrategy};
26//!
27//! // Protocole avec en-tête [0xAA, 0x55] et longueur fixe de 8 octets
28//! let config = ParserConfig {
29//!     header: &[0xAA, 0x55],
30//!     length_strategy: PacketLengthStrategy::Fixed(8),
31//! };
32//!
33//! let mut parser = GenericParser::new(config);
34//!
35//! let stream: &[u8] = &[0xAA, 0x55, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
36//! for &byte in stream {
37//!     match parser.parse_byte(byte) {
38//!         Ok(Some(packet)) => { /* traiter le paquet complet */ }
39//!         Ok(None)         => { /* trame en cours d'accumulation */ }
40//!         Err(e)           => { /* gérer l'erreur */ }
41//!     }
42//! }
43//! ```
44
45#![no_std]
46#![forbid(unsafe_code)]
47#![warn(missing_docs)]
48
49// ─── Taille du buffer interne ───────────────────────────────────────────────
50
51/// Taille du buffer d'accumulation en octets.
52///
53/// Peut être surchargée à la compilation via la feature `large-buffer` ou en
54/// modifiant directement cette constante selon les contraintes mémoire de la
55/// cible.
56pub const BUFFER_SIZE: usize = 128;
57
58// ─── Erreurs ─────────────────────────────────────────────────────────────────
59
60/// Erreurs pouvant survenir lors du parsing d'une trame UART.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum ParserError {
63    /// Le buffer interne a été dépassé avant la fin d'une trame valide.
64    ///
65    /// Cela indique généralement un protocole mal configuré (longueur trop
66    /// grande) ou un flux de données corrompu.
67    BufferOverflow,
68
69    /// La longueur calculée à partir de l'octet de longueur dépasse
70    /// [`BUFFER_SIZE`], ce qui rendrait la trame impossible à stocker.
71    ///
72    /// Contient la longueur calculée.
73    PacketTooLarge(usize),
74
75    /// L'index de l'octet de longueur (champ `index` de
76    /// [`PacketLengthStrategy::LengthByte`]) est supérieur ou égal à
77    /// [`BUFFER_SIZE`], ce qui constitue une configuration invalide.
78    InvalidLengthByteIndex,
79}
80
81// ─── Stratégie de longueur ───────────────────────────────────────────────────
82
83/// Stratégie utilisée pour déterminer la longueur totale d'une trame.
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub enum PacketLengthStrategy {
86    /// La trame a une longueur totale fixe (en-tête inclus).
87    ///
88    /// # Exemple
89    ///
90    /// Pour un protocole dont chaque trame fait toujours 8 octets :
91    ///
92    /// ```rust
93    /// # use uart_proto_detector::PacketLengthStrategy;
94    /// let strategy = PacketLengthStrategy::Fixed(8);
95    /// ```
96    Fixed(usize),
97
98    /// Un octet situé à la position `index` dans le buffer indique le nombre
99    /// d'octets restants après lui-même. `offset` est ajouté à cette valeur
100    /// pour obtenir la longueur totale de la trame.
101    ///
102    /// **Longueur totale** = `buffer[index] as usize + offset`
103    ///
104    /// # Exemple
105    ///
106    /// Pour un protocole dont l'octet à l'index 2 indique la taille du payload,
107    /// et où la trame totale inclut 3 octets de structure supplémentaires
108    /// (en-tête + octet de longueur + CRC) :
109    ///
110    /// ```rust
111    /// # use uart_proto_detector::PacketLengthStrategy;
112    /// let strategy = PacketLengthStrategy::LengthByte { index: 2, offset: 3 };
113    /// ```
114    LengthByte {
115        /// Position de l'octet de longueur dans le buffer (base 0).
116        index: usize,
117        /// Valeur ajoutée à l'octet de longueur pour obtenir la longueur totale.
118        offset: usize,
119    },
120}
121
122// ─── Configuration ───────────────────────────────────────────────────────────
123
124/// Configuration complète d'un protocole UART.
125///
126/// Cette structure est `Copy` afin de pouvoir être stockée statiquement ou
127/// dupliquée sans coût mémoire significatif.
128///
129/// # Exemple
130///
131/// ```rust
132/// use uart_proto_detector::{ParserConfig, PacketLengthStrategy};
133///
134/// const MY_PROTOCOL: ParserConfig = ParserConfig {
135///     header: &[0xFF, 0xAA],
136///     length_strategy: PacketLengthStrategy::LengthByte { index: 2, offset: 4 },
137/// };
138/// ```
139#[derive(Clone, Copy, Debug)]
140pub struct ParserConfig {
141    /// Séquence d'octets d'en-tête identifiant le début d'une trame valide.
142    ///
143    /// Tous les octets reçus avant que l'en-tête complet soit reconnu sont
144    /// ignorés.
145    pub header: &'static [u8],
146
147    /// Méthode utilisée pour déterminer la longueur totale de la trame.
148    pub length_strategy: PacketLengthStrategy,
149}
150
151// ─── Parser ───────────────────────────────────────────────────────────────────
152
153/// Parser générique pour protocoles UART.
154///
155/// Accumule les octets reçus, valide l'en-tête et retourne une référence vers
156/// la trame complète dès qu'elle est disponible.
157///
158/// **Note :** La vérification du CRC n'est pas effectuée par ce parser. Elle
159/// doit être réalisée par le code appelant après réception d'une trame complète,
160/// car chaque protocole définit son propre algorithme.
161///
162/// # Exemple
163///
164/// ```rust
165/// use uart_proto_detector::{GenericParser, ParserConfig, PacketLengthStrategy};
166///
167/// let config = ParserConfig {
168///     header: &[0xAA, 0x55],
169///     length_strategy: PacketLengthStrategy::Fixed(6),
170/// };
171/// let mut parser = GenericParser::new(config);
172///
173/// // Simuler un flux d'octets
174/// let bytes: &[u8] = &[0xAA, 0x55, 0x01, 0x02, 0x03, 0x04];
175/// for &b in bytes {
176///     if let Ok(Some(packet)) = parser.parse_byte(b) {
177///         assert_eq!(packet.len(), 6);
178///     }
179/// }
180/// ```
181pub struct GenericParser {
182    config: ParserConfig,
183    buffer: [u8; BUFFER_SIZE],
184    bytes_read: usize,
185    expected_length: Option<usize>,
186}
187
188impl GenericParser {
189    /// Crée un nouveau parser à partir d'une [`ParserConfig`].
190    #[inline]
191    pub fn new(config: ParserConfig) -> Self {
192        Self {
193            config,
194            buffer: [0u8; BUFFER_SIZE],
195            bytes_read: 0,
196            expected_length: None,
197        }
198    }
199
200    /// Réinitialise l'état interne du parser.
201    ///
202    /// Appelé automatiquement après chaque trame complète ou en cas d'erreur.
203    /// Peut aussi être appelé manuellement en cas de timeout ou de flush du bus.
204    #[inline]
205    pub fn reset(&mut self) {
206        self.bytes_read = 0;
207        self.expected_length = None;
208    }
209
210    /// Soumet un octet au parser.
211    ///
212    /// # Valeur de retour
213    ///
214    /// | Résultat       | Signification                                              |
215    /// |----------------|------------------------------------------------------------|
216    /// | `Ok(None)`     | Trame en cours d'accumulation, aucune action requise.      |
217    /// | `Ok(Some(…))`  | Trame complète disponible dans la slice retournée.         |
218    /// | `Err(…)`       | Erreur de parsing ; le parser a été réinitialisé.          |
219    ///
220    /// La slice retournée par `Ok(Some(…))` pointe sur le buffer interne et
221    /// n'est valide que jusqu'au prochain appel à [`parse_byte`](Self::parse_byte)
222    /// ou [`reset`](Self::reset).
223    ///
224    /// # Erreurs
225    ///
226    /// Retourne [`ParserError::BufferOverflow`] si le buffer est plein avant
227    /// qu'une trame valide ne soit reçue, ou [`ParserError::PacketTooLarge`] si
228    /// la longueur calculée dépasse [`BUFFER_SIZE`].
229    pub fn parse_byte(&mut self, byte: u8) -> Result<Option<&[u8]>, ParserError> {
230        // Sécurité anti-débordement : ne devrait pas se produire en usage normal
231        if self.bytes_read >= BUFFER_SIZE {
232            self.reset();
233            return Err(ParserError::BufferOverflow);
234        }
235
236        self.buffer[self.bytes_read] = byte;
237        self.bytes_read += 1;
238
239        // ── 1. Validation de l'en-tête ────────────────────────────────────────
240        let header_len = self.config.header.len();
241        if self.bytes_read <= header_len {
242            let pos = self.bytes_read - 1;
243            if self.buffer[pos] != self.config.header[pos] {
244                // Mauvais en-tête : réinitialisation et attente du prochain octet
245                self.reset();
246            }
247            return Ok(None);
248        }
249
250        // ── 2. Détermination de la longueur attendue ──────────────────────────
251        if self.expected_length.is_none() {
252            match self.config.length_strategy {
253                PacketLengthStrategy::Fixed(len) => {
254                    if len > BUFFER_SIZE {
255                        self.reset();
256                        return Err(ParserError::PacketTooLarge(len));
257                    }
258                    self.expected_length = Some(len);
259                }
260
261                PacketLengthStrategy::LengthByte { index, offset } => {
262                    if index >= BUFFER_SIZE {
263                        self.reset();
264                        return Err(ParserError::InvalidLengthByteIndex);
265                    }
266                    if self.bytes_read > index {
267                        let total = self.buffer[index] as usize + offset;
268                        if total > BUFFER_SIZE {
269                            self.reset();
270                            return Err(ParserError::PacketTooLarge(total));
271                        }
272                        self.expected_length = Some(total);
273                    }
274                }
275            }
276        }
277
278        // ── 3. Vérification de complétude ─────────────────────────────────────
279        if let Some(target_len) = self.expected_length {
280            if self.bytes_read == target_len {
281                let final_len = self.bytes_read;
282                self.reset();
283                // SAFETY: final_len <= BUFFER_SIZE (garanti par les vérifications ci-dessus)
284                return Ok(Some(&self.buffer[..final_len]));
285            }
286        }
287
288        Ok(None)
289    }
290
291    /// Retourne le nombre d'octets actuellement accumulés dans le buffer.
292    #[inline]
293    pub fn bytes_accumulated(&self) -> usize {
294        self.bytes_read
295    }
296
297    /// Retourne la longueur de trame attendue, si elle a déjà été déterminée.
298    #[inline]
299    pub fn expected_length(&self) -> Option<usize> {
300        self.expected_length
301    }
302}