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:
102
crates/fsv/src/fsv_parsing/groups/mod.rs
Normal file
102
crates/fsv/src/fsv_parsing/groups/mod.rs
Normal 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");
|
||||
}
|
||||
}
|
295
crates/fsv/src/fsv_parsing/groups/ssv_lire_config.rs
Normal file
295
crates/fsv/src/fsv_parsing/groups/ssv_lire_config.rs
Normal 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 d’utilisation d’un
|
||||
//! lecteur homologué sesam-vitale uniquement et non en cas
|
||||
//! d’utilisation de lecteur(s) PC/SC. dans le cas d’un 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");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user