feat: implement a full SSV_LireConfig output parsing, using deku for a declarative bytes parsing

Co-authored-by: theo <t.lettermann@criteo.com>
This commit is contained in:
2024-10-03 23:01:53 +02:00
parent ae68de8a3c
commit 0ef8ede764
9 changed files with 824 additions and 30 deletions

View File

@ -0,0 +1,102 @@
use std::{fmt, str::FromStr};
use deku::{deku_derive, DekuError};
use super::{ size_read, map_bytes_to_lossy_string };
pub mod ssv_lire_config;
/// # Convert a DataField to a specific type
/// Using this as deku map function to fill a field value from
/// a DataField
fn map_from_data_field<T>(data_field: DataField) -> Result<T, DekuError>
where
T: FromStr,
T::Err: std::fmt::Display,
{
let text = String::from_utf8(data_field.data)
.map_err(|e| DekuError::Parse(e.to_string().into()))?;
T::from_str(&text)
.map_err(|e| DekuError::Parse(e.to_string().into()))
}
// ------------------- DATA FIELD TYPES -------------------
/// # Data field structure
/// This structure is the core structure to read data fields
/// It is usually used by other structures implementing the
/// `#[deku(map = "map_from_data_field")]` attribute
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
struct DataField {
#[deku(temp, reader = "size_read(deku::reader)")]
pub data_size: u64,
#[deku(bytes_read = "data_size")]
pub data: Vec<u8>,
}
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)]
/// # Numeric string
/// TODO: check if all the characters are numeric
pub struct NumericString(
#[deku(map = "map_from_data_field")]
String
);
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)]
pub struct AlphaNumericString(
#[deku(map = "map_from_data_field")]
String
);
impl From<&str> for AlphaNumericString {
fn from(s: &str) -> Self {
AlphaNumericString(s.to_string())
}
}
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)]
#[deku(endian = "big")]
/// # Software version
/// An almost standard software version structure in FSV
/// It is composed of a version and a revision, encoded on 2 bytes each
pub struct SoftwareVersion {
#[deku(temp, reader = "size_read(deku::reader)", assert_eq = "4")]
data_size: u64,
#[deku(bytes= 2, map = "|x: [u8; 2]| map_bytes_to_lossy_string(&x)")]
pub version: String,
#[deku(bytes= 2, map = "|x: [u8; 2]| map_bytes_to_lossy_string(&x)")]
pub revision: String,
}
impl fmt::Display for SoftwareVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.version, self.revision)
}
}
#[cfg(test)]
mod test {
use deku::DekuContainerRead as _;
use super::*;
#[test]
fn test() {
let version_bytes: [u8; 2] = [48, 55];
let version = map_bytes_to_lossy_string(&version_bytes).unwrap();
assert_eq!(version, "07");
}
#[test]
fn test_software_version() {
// env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging
let data: [u8; 5] = [4, 48, 55, 50, 48];
let (_rest, software_version) = SoftwareVersion::from_bytes((&data, 0)).unwrap();
// assert_eq!(software_version.data_size, 4);
assert_eq!(software_version.version, "07");
assert_eq!(software_version.revision, "20");
}
}

View File

@ -0,0 +1,295 @@
//! # Structures de parsing des données de la fonction SSV_LireConfig
//! Le groupe `ReaderConfig61` décrit ci-dessous est renseigné en cas dutilisation dun
//! lecteur homologué sesam-vitale uniquement et non en cas
//! dutilisation de lecteur(s) PC/SC. dans le cas dun TL ou TLA
//! configuré en mode PC/SC, un groupe `ReaderConfig61` est restitué pour chaque
//! lecteur exposé par le gestionnaire de ressources PC/SC. les
//! informations sont alors dupliquées dans chacun des groupes `ReaderConfig61`.
//! les informations sur les lecteurs PC/SC sont disponibles
//! dans les groupes `PCSCReaderConfig67`.
use deku::deku_derive;
use super::{AlphaNumericString, NumericString, SoftwareVersion};
/// # En-tête de configuration
/// 1 occurence
pub mod group_60_header_config {
use super::*;
/// Groupe 60 - En-tête de configuration
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct ConfigHeader {
pub ssv_version: SSVVersionNumber,
pub galss_version: GALSSVersionNumber,
pub pss_version: PSSVersionNumber,
}
// Fields
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct SSVVersionNumber(pub SoftwareVersion);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct GALSSVersionNumber(pub SoftwareVersion);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct PSSVersionNumber(pub SoftwareVersion);
}
/// # Configuration du lecteur
/// 0 à 15 occurences
pub mod group_61_reader_config {
use super::*;
/// Groupe 61 - Configuration du lecteur
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct ReaderConfig {
pub manufacturer_name: AlphaNumericString, // 15 CA
pub reader_type: AlphaNumericString, // 30 CA
pub serial_number: AlphaNumericString, // 20 CA
pub os: NumericString, // 2 CN
pub software_count: NumericString, // 2 CN
pub software_name: AlphaNumericString, // 30 CA
pub software_version: ReaderSoftwareVersion, // 4 CA
pub reader_datetime: ReaderSoftwareDate, // 12 CN
pub software_checksum: AlphaNumericString, // 4 CA
}
// Fields
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct ReaderSoftwareVersion(pub SoftwareVersion);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
/// Format « AAAAMMJJhhmm »
/// TODO: Build a generic date-time structure
/// TODO: Implement a date parsing, like chrono crate
pub struct ReaderSoftwareDate(pub AlphaNumericString);
}
/// # Configuration SESAM-Vitale
/// N occurences
pub mod group_64_sv_config {
use super::*;
/// Groupe 64 - Configuration SESAM-Vitale
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct SVComponentsConfig {
pub id: ComponentID,
pub description: ComponentDescription,
pub version: ComponentVersion,
}
// Fields
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct ComponentID(pub NumericString);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct ComponentDescription(pub AlphaNumericString);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct ComponentVersion(pub AlphaNumericString);
}
/// # Configuration du lecteur PC/SC
/// N occurences
pub mod group_67_pcsc_config {
use super::*;
/// Groupe 67 - Configuration du lecteur PC/SC
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct PCSCReaderConfig {
pub name: ReaderName,
pub card_type: CardType,
}
// Fields
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct ReaderName(pub AlphaNumericString);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct CardType(pub NumericString);
}
#[cfg(test)]
mod tests {
use deku::DekuContainerRead as _;
use crate::fsv_parsing::blocks::{BlockHeader, Data, DataBlock, DataGroup};
mod data {
pub const BUFFER: &[u8] = &[
0, 60, // Block ID
15, // Block Size
4, // SSV Version
48, 55, 50, 48, // 0720
4, // GALSS Version
48, 48, 48, 48, // 0000
4, // PSS Version
48, 48, 48, 48, // 0000
0, 67, // Block ID
42, // Block Size
39, // PCSC Reader Name
71, 101, 109, 97, 108, 116, 111, 32, 80, 67,
32, 84, 119, 105, 110, 32, 82, 101, 97, 100,
101, 114, 32, 40, 54, 52, 53, 68, 57, 52,
67, 51, 41, 32, 48, 48, 32, 48, 48,
1, // Card type
50,
0, 64, // Block ID
44, // Block Size
2, // Component ID
49, 49,
35, // Component label
86, 69, 82, 83, 73, 79, 78, 32, 68, 69,
32, 76, 65, 32, 66, 73, 66, 76, 73, 79,
84, 72, 69, 81, 85, 69, 32, 68, 85, 32,
71, 65, 76, 83, 83,
4, // Component version
48, 48, 48, 48,
0, 64, // Block ID
69, // Block Size
3, // Component ID
49, 53, 49,
27, // Component label
73, 68, 69, 78, 84, 73, 70, 73, 65, 78,
84, 32, 85, 78, 73, 81, 85, 69, 32, 68,
85, 32, 80, 79, 83, 84, 69,
36, // Component version
50, 54, 57, 102, 99, 55, 101, 98, 45, 49,
100, 56, 53, 45, 52, 55, 57, 51, 45, 98,
55, 48, 101, 45, 51, 55, 49, 99, 51, 56,
102, 57, 49, 54, 51, 52,
0, 61, // Block ID
62, // Block Size
17, // Manufacturer Name
84, 69, 83, 84, 32, 77, 65, 78, 85, 70,
65, 67, 84, 85, 82, 69, 82,
4, // Reader Type
84, 69, 83, 84,
4, // Serial Number
84, 69, 83, 84,
2, // OS
79, 83,
2, // Software Count
48, 49,
4, // Software Name
84, 69, 83, 84,
4, // Software Version
48, 49, 53, 53,
12, // Reader Datetime
50, 48, 50, 52, // 2024
48, 54, 50, 53, // 06-25
49, 50, 52, 53, // 12:45
4, // Software Checksum
49, 50, 51, 52,
];
}
#[test]
fn test_lire_config_first_header() {
// env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging
let buffer = data::BUFFER;
let offset: usize = 0;
let ((_rest, _offset), block_header) = BlockHeader::from_bytes((buffer, offset)).unwrap();
assert_eq!(block_header.group_id.0, 60, "Header ID");
// assert_eq!(block_header.data_size, 15, "Header Size");
}
#[test]
fn test_lire_config_first_block() {
// env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging
let buffer = data::BUFFER;
let offset: usize = 0;
let ((_rest, _offset), block) = DataBlock::from_bytes((buffer, offset)).unwrap();
let header = block.header;
let content = match block.content {
DataGroup::LireConfig_Group60_ConfigHeader(content) => content,
_ => panic!("Unexpected data block type"),
};
assert_eq!(header.group_id.0, 60, "Header ID");
assert_eq!(header.data_size, 15, "Header Size");
assert_eq!(content.ssv_version.0.version, "07", "SSV Version");
assert_eq!(content.ssv_version.0.revision, "20", "SSV Revision");
assert_eq!(content.galss_version.0.to_string(), "00.00", "GALSS Version");
assert_eq!(content.pss_version.0.to_string(), "00.00", "PSS Version");
}
#[test]
fn test_lire_config_all() {
// env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging
let buffer = data::BUFFER;
let offset: usize = 0;
let ((_rest, _offset), data) = Data::from_bytes((buffer, offset)).unwrap();
let blocks = data.blocks;
assert_eq!(blocks.len(), 5, "Number of blocks");
for block in blocks {
match block.content {
DataGroup::LireConfig_Group60_ConfigHeader(content) => {
assert_eq!(block.header.group_id.0, 60, "Header ID");
assert_eq!(block.header.data_size, 15, "Header Size");
assert_eq!(content.ssv_version.0.version, "07", "SSV Version");
assert_eq!(content.ssv_version.0.revision, "20", "SSV Revision");
assert_eq!(content.galss_version.0.to_string(), "00.00", "GALSS Version");
assert_eq!(content.pss_version.0.to_string(), "00.00", "PSS Version");
},
DataGroup::LireConfig_Group61_ReaderConfig(content) => {
assert_eq!(block.header.group_id.0, 61, "Header ID");
assert_eq!(block.header.data_size, 62, "Header Size");
assert_eq!(content.manufacturer_name.0, "TEST MANUFACTURER", "Manufacturer Name");
assert_eq!(content.reader_type.0, "TEST", "Reader Type");
assert_eq!(content.serial_number.0, "TEST", "Serial Number");
assert_eq!(content.os.0, "OS", "OS");
assert_eq!(content.software_count.0, "01", "Software Count");
assert_eq!(content.software_name.0, "TEST", "Software Name");
assert_eq!(content.software_version.0.version, "01", "Software Version");
assert_eq!(content.software_version.0.revision, "55", "Software Revision");
assert_eq!(content.reader_datetime.0.0, "202406251245", "Reader Datetime");
assert_eq!(content.software_checksum.0, "1234", "Software Checksum");
},
DataGroup::LireConfig_Group64_SVComponentsConfig(content) => {
assert_eq!(block.header.group_id.0, 64, "Header ID");
match content.id.0.0.as_str() {
"11" => {
assert_eq!(block.header.data_size, 44, "Header Size");
assert_eq!(content.id.0.0, "11", "G64 - 11 : Component ID");
assert_eq!(content.description.0.0, "VERSION DE LA BIBLIOTHEQUE DU GALSS", "G64 - 11 : Component Description");
assert_eq!(content.version.0.0, "0000", "G64 - 11 : Component Version");
},
"151" => {
assert_eq!(block.header.data_size, 69, "Header Size");
assert_eq!(content.id.0.0, "151", "G64 - 151 : Component ID");
assert_eq!(content.description.0.0, "IDENTIFIANT UNIQUE DU POSTE", "G64 - 151 : Component Description");
assert_eq!(content.version.0.0, "269fc7eb-1d85-4793-b70e-371c38f91634", "G64 - 151 : Component Version");
},
_ => panic!("Unexpected Component ID"),
}
},
DataGroup::LireConfig_Group67_PCSCReaderConfig(content) => {
assert_eq!(block.header.group_id.0, 67, "Header ID");
assert_eq!(block.header.data_size, 42, "Header Size");
assert_eq!(content.name.0.0, "Gemalto PC Twin Reader (645D94C3) 00 00", "Reader Name");
assert_eq!(content.card_type.0.0, "2", "Card Type");
},
}
}
}
}