480 lines
17 KiB
Markdown
Executable File
480 lines
17 KiB
Markdown
Executable File
# Session 2025-12-31 : Mise en Conformité Spécifications USB
|
|
|
|
## Contexte
|
|
|
|
Suite aux spécifications techniques fournies par l'utilisateur, mise à jour complète du système de classification USB pour respecter les normes USB officielles.
|
|
|
|
## Problèmes Identifiés
|
|
|
|
### 1. Classification Mass Storage Incorrecte
|
|
**❌ AVANT** : Utilisation de `bDeviceClass` pour détecter les périphériques de stockage
|
|
```python
|
|
if device_class == "08": # bDeviceClass
|
|
return ("Stockage", "Clé USB")
|
|
```
|
|
|
|
**Problème** : Beaucoup de périphériques Mass Storage ont `bDeviceClass = 0 [unknown]`
|
|
|
|
### 2. Type USB Basé sur bcdUSB
|
|
**❌ AVANT** : Type USB déterminé par `bcdUSB` (version déclarée)
|
|
```python
|
|
usb_version = "3.20" # Ce que le périphérique déclare
|
|
```
|
|
|
|
**Problème** : `bcdUSB` indique la compatibilité maximale, pas le type réel
|
|
|
|
### 3. Champs Mal Mappés
|
|
**❌ AVANT** :
|
|
- `marque` = `iManufacturer` (chaîne texte)
|
|
- `modele` = non extrait
|
|
|
|
**Problème** : Perte de l'identifiant unique `idVendor`
|
|
|
|
### 4. Analyse de Puissance Absente
|
|
**❌ AVANT** : `MaxPower` extrait mais pas analysé
|
|
|
|
**Problème** : Impossible de savoir si le port peut alimenter le périphérique
|
|
|
|
## Corrections Appliquées
|
|
|
|
### 1. Classification via bInterfaceClass (NORMATIVE)
|
|
|
|
**✅ APRÈS** : Priorité à `bInterfaceClass`
|
|
|
|
**Fichier** : [backend/app/utils/device_classifier.py](../backend/app/utils/device_classifier.py)
|
|
|
|
```python
|
|
# INTERFACE class codes (normative)
|
|
USB_INTERFACE_CLASS_MAPPING = {
|
|
8: ("Stockage", "Clé USB"), # Mass Storage - NORMATIVE
|
|
3: ("USB", "Clavier"), # HID
|
|
14: ("Video", "Webcam"), # Video
|
|
9: ("USB", "Hub"), # Hub
|
|
224: ("Bluetooth", "Autre"), # Wireless Controller
|
|
255: ("USB", "Autre"), # Vendor Specific - requires firmware
|
|
}
|
|
|
|
def detect_from_usb_interface_class(interface_classes):
|
|
"""CRITICAL: This is the normative way to detect Mass Storage"""
|
|
for interface in interface_classes:
|
|
class_code = interface.get("code")
|
|
if class_code in USB_INTERFACE_CLASS_MAPPING:
|
|
return USB_INTERFACE_CLASS_MAPPING[class_code]
|
|
```
|
|
|
|
**Fichier** : [backend/app/utils/lsusb_parser.py](../backend/app/utils/lsusb_parser.py)
|
|
|
|
```python
|
|
def parse_device_info(device_section: str) -> Dict[str, Any]:
|
|
result = {
|
|
"interface_classes": [], # CRITICAL: bInterfaceClass from all interfaces
|
|
# ...
|
|
}
|
|
|
|
# CRITICAL: bInterfaceClass (this determines Mass Storage)
|
|
interface_class_match = re.search(r'bInterfaceClass\s+(\d+)\s+(.+?)$', line_stripped)
|
|
if interface_class_match:
|
|
class_code = int(interface_class_match.group(1))
|
|
class_name = interface_class_match.group(2).strip()
|
|
result["interface_classes"].append({
|
|
"code": class_code,
|
|
"name": class_name
|
|
})
|
|
|
|
# Check for Vendor Specific (255) - requires firmware
|
|
if class_code == 255:
|
|
result["requires_firmware"] = True
|
|
```
|
|
|
|
### 2. Type USB Basé sur Vitesse Négociée
|
|
|
|
**✅ APRÈS** : Détection du type réel depuis la vitesse négociée
|
|
|
|
**Fichier** : [backend/app/utils/lsusb_parser.py](../backend/app/utils/lsusb_parser.py)
|
|
|
|
```python
|
|
# Detect negotiated speed (determines actual USB type)
|
|
speed_patterns = [
|
|
(r'1\.5\s*Mb(?:it)?/s|Low\s+Speed', 'Low Speed', 'USB 1.1'),
|
|
(r'12\s*Mb(?:it)?/s|Full\s+Speed', 'Full Speed', 'USB 1.1'),
|
|
(r'480\s*Mb(?:it)?/s|High\s+Speed', 'High Speed', 'USB 2.0'),
|
|
(r'5000\s*Mb(?:it)?/s|5\s*Gb(?:it)?/s|SuperSpeed(?:\s+USB)?(?:\s+Gen\s*1)?', 'SuperSpeed', 'USB 3.0'),
|
|
(r'10\s*Gb(?:it)?/s|SuperSpeed\s+USB\s+Gen\s*2|SuperSpeed\+', 'SuperSpeed+', 'USB 3.1'),
|
|
(r'20\s*Gb(?:it)?/s|SuperSpeed\s+USB\s+Gen\s*2x2', 'SuperSpeed Gen 2x2', 'USB 3.2'),
|
|
]
|
|
|
|
for pattern, speed_name, usb_type in speed_patterns:
|
|
if re.search(pattern, line_stripped, re.IGNORECASE):
|
|
result["speed"] = speed_name
|
|
result["usb_type"] = usb_type # Type RÉEL
|
|
break
|
|
```
|
|
|
|
### 3. Mappings de Champs Conformes
|
|
|
|
**✅ APRÈS** : Mappings conformes aux spécifications USB
|
|
|
|
**Fichier** : [backend/app/api/endpoints/peripherals.py](../backend/app/api/endpoints/peripherals.py)
|
|
|
|
```python
|
|
# Field mappings per technical specs:
|
|
# - marque = idVendor (vendor_id)
|
|
# - modele = iProduct (product)
|
|
# - fabricant = iManufacturer (manufacturer)
|
|
suggested = {
|
|
"marque": device_info.get("vendor_id"), # idVendor (0x0781)
|
|
"modele": device_info.get("product"), # iProduct ("SanDisk 3.2Gen1")
|
|
"caracteristiques_specifiques": {
|
|
"vendor_id": device_info.get("vendor_id"), # idVendor
|
|
"product_id": device_info.get("product_id"), # idProduct
|
|
"fabricant": device_info.get("manufacturer"), # iManufacturer
|
|
# ...
|
|
}
|
|
}
|
|
```
|
|
|
|
**Fichier** : [backend/app/utils/usb_info_parser.py](../backend/app/utils/usb_info_parser.py)
|
|
|
|
```python
|
|
# Per technical specs:
|
|
# - marque = idVendor (vendor_id)
|
|
# - modele = iProduct (product string)
|
|
# - fabricant = iManufacturer (manufacturer string)
|
|
|
|
# Vendor ID - COMMUN (marque)
|
|
if match := re.search(r'Vendor\s+ID\s*:\s*(0x[0-9a-fA-F]+)', line):
|
|
vid = match.group(1).lower()
|
|
result["caracteristiques_specifiques"]["vendor_id"] = vid
|
|
result["general"]["marque"] = vid # idVendor = marque
|
|
|
|
# Product string / iProduct - modele
|
|
if match := re.search(r'(?:Product\s+string|iProduct)\s*:\s*(.+)', line):
|
|
product = match.group(1).strip()
|
|
if product and product != "0":
|
|
result["caracteristiques_specifiques"]["modele"] = product
|
|
result["general"]["modele"] = product # iProduct = modele
|
|
|
|
# Vendor string / iManufacturer - fabricant
|
|
if match := re.search(r'(?:Vendor\s+string|iManufacturer)\s*:\s*(.+)', line):
|
|
vendor = match.group(1).strip()
|
|
if vendor and vendor != "0":
|
|
result["caracteristiques_specifiques"]["fabricant"] = vendor
|
|
```
|
|
|
|
### 4. Analyse de Puissance Normative
|
|
|
|
**✅ APRÈS** : Calcul de suffisance basé sur capacité normative du port
|
|
|
|
**Fichier** : [backend/app/utils/lsusb_parser.py](../backend/app/utils/lsusb_parser.py)
|
|
|
|
```python
|
|
# MaxPower (extract numeric value in mA)
|
|
power_match = re.search(r'MaxPower\s+(\d+)\s*mA', line_stripped)
|
|
if power_match:
|
|
result["max_power"] = power_match.group(1).strip()
|
|
|
|
# bmAttributes (to determine Bus/Self powered)
|
|
attr_match = re.search(r'bmAttributes\s+0x([0-9a-fA-F]+)', line_stripped)
|
|
if attr_match:
|
|
attrs = int(attr_match.group(1), 16)
|
|
# Bit 6: Self Powered
|
|
result["is_self_powered"] = bool(attrs & 0x40)
|
|
result["is_bus_powered"] = not result["is_self_powered"]
|
|
|
|
# Determine power sufficiency based on USB type and MaxPower
|
|
if result["max_power"]:
|
|
max_power_ma = int(result["max_power"])
|
|
usb_type = result.get("usb_type", "USB 2.0")
|
|
|
|
# Normative port capacities
|
|
if "USB 3" in usb_type:
|
|
port_capacity = 900 # USB 3.x: 900 mA @ 5V = 4.5W
|
|
else:
|
|
port_capacity = 500 # USB 2.0: 500 mA @ 5V = 2.5W
|
|
|
|
result["power_sufficient"] = max_power_ma <= port_capacity
|
|
```
|
|
|
|
**Fichier** : [backend/app/utils/usb_info_parser.py](../backend/app/utils/usb_info_parser.py)
|
|
|
|
```python
|
|
# Puissance maximale (MaxPower)
|
|
if match := re.search(r'Puissance\s+maximale.*:\s*(\d+)\s*mA', line):
|
|
power_ma = int(match.group(1))
|
|
result["caracteristiques_specifiques"]["max_power_ma"] = power_ma
|
|
|
|
# Determine power sufficiency based on USB type
|
|
usb_type = result["caracteristiques_specifiques"].get("usb_type", "USB 2.0")
|
|
if "USB 3" in usb_type:
|
|
port_capacity = 900 # USB 3.x: 900 mA @ 5V = 4.5W
|
|
else:
|
|
port_capacity = 500 # USB 2.0: 500 mA @ 5V = 2.5W
|
|
|
|
result["caracteristiques_specifiques"]["power_sufficient"] = power_ma <= port_capacity
|
|
|
|
# Mode alimentation (Bus Powered vs Self Powered)
|
|
if match := re.search(r'Mode\s+d.alimentation\s*:\s*(.+)', line):
|
|
power_mode = match.group(1).strip()
|
|
result["caracteristiques_specifiques"]["power_mode"] = power_mode
|
|
result["caracteristiques_specifiques"]["is_bus_powered"] = "bus" in power_mode.lower()
|
|
result["caracteristiques_specifiques"]["is_self_powered"] = "self" in power_mode.lower()
|
|
```
|
|
|
|
### 5. Détection Firmware Requis
|
|
|
|
**✅ APRÈS** : Détection classe Vendor Specific (255)
|
|
|
|
**Fichier** : [backend/app/utils/lsusb_parser.py](../backend/app/utils/lsusb_parser.py)
|
|
|
|
```python
|
|
# CRITICAL: bInterfaceClass (this determines Mass Storage, not bDeviceClass)
|
|
interface_class_match = re.search(r'bInterfaceClass\s+(\d+)\s+(.+?)$', line_stripped)
|
|
if interface_class_match:
|
|
class_code = int(interface_class_match.group(1))
|
|
class_name = interface_class_match.group(2).strip()
|
|
result["interface_classes"].append({
|
|
"code": class_code,
|
|
"name": class_name
|
|
})
|
|
|
|
# Check for Vendor Specific (255) - requires firmware
|
|
if class_code == 255:
|
|
result["requires_firmware"] = True
|
|
```
|
|
|
|
## Nouveaux Champs dans caracteristiques_specifiques
|
|
|
|
```json
|
|
{
|
|
// Champs existants
|
|
"vendor_id": "0x0781",
|
|
"product_id": "0x55ab",
|
|
|
|
// NOUVEAUX CHAMPS
|
|
"fabricant": "SanDisk Corp.", // iManufacturer
|
|
"usb_version_declared": "USB 3.20", // bcdUSB (déclaré, non définitif)
|
|
"usb_type": "USB 3.0", // Type RÉEL basé sur vitesse
|
|
"negotiated_speed": "SuperSpeed (5 Gbps)", // Vitesse négociée
|
|
"interface_classes": [ // CRITIQUE : bInterfaceClass
|
|
{
|
|
"code": 8,
|
|
"name": "Mass Storage"
|
|
}
|
|
],
|
|
"requires_firmware": false, // True si classe 255
|
|
"max_power_ma": 896, // MaxPower en mA
|
|
"is_bus_powered": true, // Bus Powered ?
|
|
"is_self_powered": false, // Self Powered ?
|
|
"power_sufficient": true // Capacité port suffisante ?
|
|
}
|
|
```
|
|
|
|
## Fichiers Modifiés
|
|
|
|
### Backend
|
|
|
|
1. **[backend/app/utils/lsusb_parser.py](../backend/app/utils/lsusb_parser.py)**
|
|
- Lignes 104-240 : `parse_device_info()` complètement réécrite
|
|
- Extraction `interface_classes[]` avec code en int
|
|
- Détection vitesse négociée → `usb_type`
|
|
- Analyse puissance → `power_sufficient`
|
|
- Détection firmware → `requires_firmware`
|
|
- Extraction `bmAttributes` → `is_bus_powered`, `is_self_powered`
|
|
|
|
2. **[backend/app/utils/device_classifier.py](../backend/app/utils/device_classifier.py)**
|
|
- Lignes 153-171 : Nouveaux mappings `USB_INTERFACE_CLASS_MAPPING` et `USB_DEVICE_CLASS_MAPPING`
|
|
- Lignes 211-234 : `detect_from_usb_interface_class()` (nouvelle méthode prioritaire)
|
|
- Lignes 236-254 : `detect_from_usb_device_class()` renommée (fallback)
|
|
- Lignes 281-343 : `classify_device()` mise à jour avec priorité interface class
|
|
|
|
3. **[backend/app/utils/usb_info_parser.py](../backend/app/utils/usb_info_parser.py)**
|
|
- Lignes 30-135 : Section parsing complètement refaite avec mappings conformes
|
|
- Lignes 158-217 : `extract_interfaces()` stocke `code` en int
|
|
- Lignes 178-199 : Extraction `interface_classes` pour classification + détection firmware
|
|
|
|
4. **[backend/app/api/endpoints/peripherals.py](../backend/app/api/endpoints/peripherals.py)**
|
|
- Lignes 786-814 : `suggested` avec nouveaux mappings de champs
|
|
- Lignes 899-936 : Import USB structuré avec `interface_classes` passées au classificateur
|
|
|
|
### Documentation
|
|
|
|
5. **[docs/USB_TECHNICAL_SPECIFICATIONS.md](../docs/USB_TECHNICAL_SPECIFICATIONS.md)** (NOUVEAU)
|
|
- Spécifications complètes de conformité USB
|
|
- Mappings de champs détaillés
|
|
- Règles de classification normatives
|
|
- Exemples de classification avec analyses
|
|
- Stratégies de classification par ordre de priorité
|
|
- Tests de conformité
|
|
- Références normatives
|
|
|
|
6. **[CHANGELOG.md](../CHANGELOG.md)**
|
|
- Lignes 1-49 : Section complètement réécrite avec focus conformité USB
|
|
- Documentation des nouveaux champs
|
|
- Explication des détections normatives
|
|
|
|
7. **[docs/SESSION_2025-12-31_USB_COMPLIANCE.md](../docs/SESSION_2025-12-31_USB_COMPLIANCE.md)** (CE FICHIER)
|
|
- Résumé complet de la session
|
|
- Problèmes identifiés et corrections
|
|
- Exemples avant/après
|
|
|
|
## Impact sur les Données Existantes
|
|
|
|
### Migration Requise ? **NON**
|
|
|
|
Les nouveaux champs sont ajoutés à `caracteristiques_specifiques` (JSON), qui accepte dynamiquement de nouveaux champs sans migration de schéma.
|
|
|
|
### Périphériques Existants
|
|
|
|
Les périphériques déjà importés conservent leurs anciens champs. Lors d'une **mise à jour** (ré-import), les nouveaux champs seront ajoutés.
|
|
|
|
## Tests de Validation
|
|
|
|
### Test 1 : Clé USB SanDisk (Mass Storage via Interface)
|
|
|
|
**Entrée** :
|
|
```
|
|
Bus 004 Device 005: ID 0781:55ab SanDisk Corp.
|
|
bDeviceClass 0 [unknown]
|
|
Interface 0:
|
|
bInterfaceClass 8 Mass Storage
|
|
```
|
|
|
|
**Résultat Attendu** :
|
|
- `type_principal` = "Stockage" ✅
|
|
- `sous_type` = "Clé USB" ✅
|
|
- `interface_classes[0].code` = 8 ✅
|
|
|
|
**Statut** : ✅ PASS
|
|
|
|
### Test 2 : Adaptateur WiFi (Firmware Requis)
|
|
|
|
**Entrée** :
|
|
```
|
|
Bus 002 Device 005: ID 0bda:8176 Realtek
|
|
Interface 0:
|
|
bInterfaceClass 255 Vendor Specific
|
|
```
|
|
|
|
**Résultat Attendu** :
|
|
- `requires_firmware` = true ✅
|
|
- `type_principal` = "USB" ✅
|
|
- `sous_type` = "Adaptateur WiFi" (via mots-clés) ✅
|
|
|
|
**Statut** : ✅ PASS
|
|
|
|
### Test 3 : USB Type depuis Vitesse
|
|
|
|
**Entrée** :
|
|
```
|
|
bcdUSB 3.20
|
|
Negotiated Speed: High Speed (480 Mbps)
|
|
```
|
|
|
|
**Résultat Attendu** :
|
|
- `usb_version_declared` = "USB 3.20" ✅
|
|
- `usb_type` = "USB 2.0" (basé sur vitesse, pas bcdUSB) ✅
|
|
|
|
**Statut** : ✅ PASS
|
|
|
|
### Test 4 : Analyse Puissance
|
|
|
|
**Entrée** :
|
|
```
|
|
MaxPower 896mA
|
|
bmAttributes 0x80
|
|
Negotiated Speed: SuperSpeed (5 Gbps)
|
|
```
|
|
|
|
**Résultat Attendu** :
|
|
- `max_power_ma` = 896 ✅
|
|
- `is_bus_powered` = true ✅
|
|
- `power_sufficient` = true (896 ≤ 900 pour USB 3.x) ✅
|
|
|
|
**Statut** : ✅ PASS
|
|
|
|
## Compatibilité
|
|
|
|
### Frontend
|
|
|
|
**Nouvelle section "Informations USB Détaillées"** ajoutée au formulaire pour afficher tous les nouveaux champs techniques.
|
|
|
|
#### Fichiers Modifiés :
|
|
|
|
1. **`frontend/peripherals.html`** (Lignes 241-325)
|
|
- Nouvelle section avec grille responsive affichant 12 champs USB
|
|
- Section cachée par défaut, visible seulement si données USB présentes
|
|
- Champs en lecture seule (readonly)
|
|
|
|
2. **`frontend/css/peripherals.css`** (Lignes 668-685)
|
|
- Styles pour la grille `.usb-details-grid`
|
|
- Mise en forme des champs readonly
|
|
|
|
3. **`frontend/js/peripherals.js`**
|
|
- **Lignes 32-107** : Nouvelle fonction `fillUSBDetails(caracteristiques)`
|
|
- **Ligne 459** : Appel depuis `importSelectedUSBDevice()`
|
|
- **Ligne 629** : Appel depuis `importUSBStructured()`
|
|
|
|
#### Champs Affichés :
|
|
- ✅ Vendor ID (idVendor)
|
|
- ✅ Product ID (idProduct)
|
|
- ✅ Fabricant (iManufacturer)
|
|
- ✅ Type USB Réel (basé sur vitesse)
|
|
- ✅ Version USB Déclarée (bcdUSB)
|
|
- ✅ Vitesse Négociée
|
|
- ✅ Puissance Max (MaxPower)
|
|
- ✅ Mode Alimentation
|
|
- ✅ Alimentation Suffisante (avec indicateur ✅/⚠️)
|
|
- ✅ Firmware Requis (avec indicateur ✅/⚠️)
|
|
- ✅ Device Class (bDeviceClass)
|
|
- ✅ Interface Classes (bInterfaceClass - normative)
|
|
|
|
### API
|
|
|
|
**Backward compatible** : Les anciens appels API continuent de fonctionner. Les nouveaux champs sont optionnels.
|
|
|
|
### Base de Données
|
|
|
|
**Aucune migration requise** : Les champs JSON acceptent dynamiquement de nouvelles clés.
|
|
|
|
## Bénéfices
|
|
|
|
✅ **Conformité USB Normative** : Classification conforme aux spécifications officielles
|
|
✅ **Détection Mass Storage Fiable** : Via `bInterfaceClass` au lieu de `bDeviceClass`
|
|
✅ **Type USB Précis** : Basé sur vitesse négociée réelle, pas version déclarée
|
|
✅ **Analyse Puissance** : Prévention des problèmes d'alimentation insuffisante
|
|
✅ **Détection Firmware** : Indication claire des périphériques nécessitant pilotes
|
|
✅ **Mappings Clairs** : Champs cohérents avec terminologie USB (`idVendor`, `iProduct`, `iManufacturer`)
|
|
✅ **Traçabilité** : Tous les champs USB critiques stockés pour analyse ultérieure
|
|
✅ **Extensibilité** : Facile d'ajouter de nouvelles classes d'interface
|
|
|
|
## Limitations Connues
|
|
|
|
⚠️ **Périphériques Multi-Interface** : Si plusieurs interfaces avec classes différentes, seule la première trouvée dans le mapping est utilisée
|
|
⚠️ **Vitesse Non Mentionnée** : Si vitesse absente de lsusb, fallback sur bcdUSB
|
|
⚠️ **bmAttributes Absent** : Fallback sur parsing textuel "Bus Powered" / "Self Powered"
|
|
|
|
## Prochaines Étapes Suggérées
|
|
|
|
1. **Logs de Debug** : Ajouter logs pour voir quelle stratégie de classification a été utilisée
|
|
2. **Multi-Interface Support** : Détecter périphériques combinés (ex: Hub + Ethernet)
|
|
3. **USB4 / Thunderbolt** : Ajouter patterns pour vitesses 40 Gbps
|
|
4. **Power Delivery** : Support USB-C PD avec négociation > 5V
|
|
5. **Tests Automatisés** : Suite de tests unitaires pour validation conformité
|
|
|
|
## Résumé Exécutif
|
|
|
|
**Objectif** : Mise en conformité complète avec spécifications USB normatives
|
|
|
|
**Changements Majeurs** :
|
|
1. Classification Mass Storage via `bInterfaceClass` (normative) au lieu de `bDeviceClass`
|
|
2. Type USB basé sur vitesse négociée au lieu de `bcdUSB`
|
|
3. Mappings de champs conformes : `marque=idVendor`, `modele=iProduct`, `fabricant=iManufacturer`
|
|
4. Analyse de puissance normative avec calcul de suffisance
|
|
5. Détection firmware requis (classe Vendor Specific 255)
|
|
|
|
**Fichiers Modifiés** : 4 fichiers backend + 3 fichiers documentation
|
|
|
|
**Impact** : Aucune migration requise, backward compatible, amélioration massive de la précision
|
|
|
|
**Statut** : ✅ Déployé et fonctionnel
|