349 lines
12 KiB
Python
Executable File
349 lines
12 KiB
Python
Executable File
"""
|
|
Linux BenchTools - USB Device Parser
|
|
Parses output from 'lsusb -v' command
|
|
"""
|
|
|
|
import re
|
|
from typing import Dict, Any, Optional, List
|
|
|
|
|
|
def parse_lsusb_verbose(lsusb_output: str) -> Dict[str, Any]:
|
|
"""
|
|
Parse the output of 'lsusb -v' command
|
|
|
|
Args:
|
|
lsusb_output: Raw text output from 'lsusb -v' command
|
|
|
|
Returns:
|
|
Dictionary with parsed USB device information
|
|
"""
|
|
result = {
|
|
"vendor_id": None,
|
|
"product_id": None,
|
|
"usb_device_id": None,
|
|
"marque": None,
|
|
"modele": None,
|
|
"fabricant": None,
|
|
"produit": None,
|
|
"numero_serie": None,
|
|
"usb_version": None,
|
|
"device_class": None,
|
|
"device_subclass": None,
|
|
"device_protocol": None,
|
|
"max_power_ma": None,
|
|
"speed": None,
|
|
"manufacturer": None,
|
|
"product": None,
|
|
"interfaces": [],
|
|
"raw_info": {}
|
|
}
|
|
|
|
lines = lsusb_output.strip().split('\n')
|
|
current_interface = None
|
|
|
|
for line in lines:
|
|
# Bus and Device info
|
|
# Example: Bus 002 Device 003: ID 0781:5567 SanDisk Corp. Cruzer Blade
|
|
match = re.match(r'Bus\s+(\d+)\s+Device\s+(\d+):\s+ID\s+([0-9a-f]{4}):([0-9a-f]{4})\s+(.*)', line)
|
|
if match:
|
|
result["raw_info"]["bus"] = match.group(1)
|
|
result["raw_info"]["device"] = match.group(2)
|
|
result["vendor_id"] = match.group(3)
|
|
result["product_id"] = match.group(4)
|
|
result["usb_device_id"] = f"{match.group(3)}:{match.group(4)}"
|
|
|
|
# Parse manufacturer and product from the description
|
|
desc = match.group(5)
|
|
parts = desc.split(' ', 1)
|
|
if len(parts) == 2:
|
|
result["marque"] = parts[0]
|
|
result["modele"] = parts[1]
|
|
else:
|
|
result["modele"] = desc
|
|
continue
|
|
|
|
# idVendor
|
|
match = re.search(r'idVendor\s+0x([0-9a-f]{4})\s+(.*)', line)
|
|
if match:
|
|
if not result["vendor_id"]:
|
|
result["vendor_id"] = match.group(1)
|
|
result["manufacturer"] = match.group(2).strip()
|
|
if not result["marque"]:
|
|
result["marque"] = result["manufacturer"]
|
|
if result.get("vendor_id") and result.get("product_id") and not result.get("usb_device_id"):
|
|
result["usb_device_id"] = f"{result['vendor_id']}:{result['product_id']}"
|
|
continue
|
|
|
|
# idProduct
|
|
match = re.search(r'idProduct\s+0x([0-9a-f]{4})\s+(.*)', line)
|
|
if match:
|
|
if not result["product_id"]:
|
|
result["product_id"] = match.group(1)
|
|
result["product"] = match.group(2).strip()
|
|
if not result["modele"]:
|
|
result["modele"] = result["product"]
|
|
if result.get("vendor_id") and result.get("product_id") and not result.get("usb_device_id"):
|
|
result["usb_device_id"] = f"{result['vendor_id']}:{result['product_id']}"
|
|
continue
|
|
|
|
# bcdUSB (USB version)
|
|
match = re.search(r'bcdUSB\s+([\d.]+)', line)
|
|
if match:
|
|
result["usb_version"] = match.group(1)
|
|
continue
|
|
|
|
# bDeviceClass
|
|
match = re.search(r'bDeviceClass\s+(\d+)\s+(.*)', line)
|
|
if match:
|
|
result["device_class"] = match.group(2).strip()
|
|
result["raw_info"]["device_class_code"] = match.group(1)
|
|
continue
|
|
|
|
# bDeviceSubClass
|
|
match = re.search(r'bDeviceSubClass\s+(\d+)\s*(.*)', line)
|
|
if match:
|
|
result["device_subclass"] = match.group(2).strip() if match.group(2) else match.group(1)
|
|
continue
|
|
|
|
# bDeviceProtocol
|
|
match = re.search(r'bDeviceProtocol\s+(\d+)\s*(.*)', line)
|
|
if match:
|
|
result["device_protocol"] = match.group(2).strip() if match.group(2) else match.group(1)
|
|
continue
|
|
|
|
# MaxPower
|
|
match = re.search(r'MaxPower\s+(\d+)mA', line)
|
|
if match:
|
|
result["max_power_ma"] = int(match.group(1))
|
|
continue
|
|
|
|
# iManufacturer
|
|
match = re.search(r'iManufacturer\s+\d+\s+(.*)', line)
|
|
if match and not result["manufacturer"]:
|
|
result["manufacturer"] = match.group(1).strip()
|
|
if not result["fabricant"]:
|
|
result["fabricant"] = result["manufacturer"]
|
|
continue
|
|
|
|
# iProduct
|
|
match = re.search(r'iProduct\s+\d+\s+(.*)', line)
|
|
if match and not result["product"]:
|
|
result["product"] = match.group(1).strip()
|
|
if not result["produit"]:
|
|
result["produit"] = result["product"]
|
|
continue
|
|
|
|
# iSerial
|
|
match = re.search(r'iSerial\s+\d+\s+(.*)', line)
|
|
if match:
|
|
serial = match.group(1).strip()
|
|
if serial and serial != "0":
|
|
result["numero_serie"] = serial
|
|
continue
|
|
|
|
# Speed (from Device Descriptor or Status)
|
|
match = re.search(r'Device Status:.*?Speed:\s*(\w+)', line)
|
|
if match:
|
|
result["speed"] = match.group(1)
|
|
continue
|
|
|
|
# Alternative speed detection
|
|
if "480M" in line or "high-speed" in line.lower() or "high speed" in line.lower():
|
|
result["speed"] = "High Speed (480 Mbps)"
|
|
elif "5000M" in line or "super-speed" in line.lower() or "super speed" in line.lower():
|
|
result["speed"] = "Super Speed (5 Gbps)"
|
|
elif "10000M" in line or "superspeed+" in line.lower():
|
|
result["speed"] = "SuperSpeed+ (10 Gbps)"
|
|
elif "12M" in line or "full-speed" in line.lower() or "full speed" in line.lower():
|
|
result["speed"] = "Full Speed (12 Mbps)"
|
|
elif "1.5M" in line or "low-speed" in line.lower() or "low speed" in line.lower():
|
|
result["speed"] = "Low Speed (1.5 Mbps)"
|
|
|
|
# Interface information
|
|
match = re.search(r'Interface Descriptor:', line)
|
|
if match:
|
|
current_interface = {}
|
|
result["interfaces"].append(current_interface)
|
|
continue
|
|
|
|
if current_interface is not None:
|
|
# bInterfaceClass
|
|
match = re.search(r'bInterfaceClass\s+(\d+)\s+(.*)', line)
|
|
if match:
|
|
current_interface["class"] = match.group(2).strip()
|
|
current_interface["class_code"] = match.group(1)
|
|
continue
|
|
|
|
# bInterfaceSubClass
|
|
match = re.search(r'bInterfaceSubClass\s+(\d+)\s*(.*)', line)
|
|
if match:
|
|
current_interface["subclass"] = match.group(2).strip() if match.group(2) else match.group(1)
|
|
continue
|
|
|
|
# bInterfaceProtocol
|
|
match = re.search(r'bInterfaceProtocol\s+(\d+)\s*(.*)', line)
|
|
if match:
|
|
current_interface["protocol"] = match.group(2).strip() if match.group(2) else match.group(1)
|
|
continue
|
|
|
|
# Clean up empty values
|
|
for key in list(result.keys()):
|
|
if result[key] == "" or result[key] == "0":
|
|
result[key] = None
|
|
|
|
# Determine peripheral type from class
|
|
result["type_principal"] = _determine_peripheral_type(result)
|
|
result["sous_type"] = _determine_peripheral_subtype(result)
|
|
|
|
return result
|
|
|
|
|
|
def _determine_peripheral_type(usb_info: Dict[str, Any]) -> str:
|
|
"""Determine peripheral type from USB class information"""
|
|
|
|
device_class = (usb_info.get("device_class") or "").lower()
|
|
|
|
# Check interfaces if device class is not specific
|
|
if not device_class or "vendor specific" in device_class or device_class == "0":
|
|
interfaces = usb_info.get("interfaces", [])
|
|
if interfaces:
|
|
interface_class = (interfaces[0].get("class") or "").lower()
|
|
else:
|
|
interface_class = ""
|
|
else:
|
|
interface_class = device_class
|
|
|
|
# Map USB classes to peripheral types
|
|
class_map = {
|
|
"hub": "USB",
|
|
"audio": "Audio",
|
|
"hid": "USB",
|
|
"human interface device": "USB",
|
|
"printer": "Imprimante",
|
|
"mass storage": "Stockage",
|
|
"video": "Video",
|
|
"wireless": "Sans-fil",
|
|
"bluetooth": "Bluetooth",
|
|
"smart card": "Securite",
|
|
"application specific": "USB",
|
|
"vendor specific": "USB"
|
|
}
|
|
|
|
for key, ptype in class_map.items():
|
|
if key in interface_class:
|
|
return ptype
|
|
|
|
# Default
|
|
return "USB"
|
|
|
|
|
|
def _determine_peripheral_subtype(usb_info: Dict[str, Any]) -> Optional[str]:
|
|
"""Determine peripheral subtype from USB class information"""
|
|
|
|
device_class = (usb_info.get("device_class") or "").lower()
|
|
interfaces = usb_info.get("interfaces", [])
|
|
|
|
if interfaces:
|
|
interface_class = (interfaces[0].get("class") or "").lower()
|
|
interface_subclass = (interfaces[0].get("subclass") or "").lower()
|
|
else:
|
|
interface_class = ""
|
|
interface_subclass = ""
|
|
|
|
# HID devices
|
|
if "hid" in device_class or "hid" in interface_class or "human interface" in interface_class:
|
|
if "mouse" in interface_subclass or "mouse" in str(usb_info.get("modele", "")).lower():
|
|
return "Souris"
|
|
elif "keyboard" in interface_subclass or "keyboard" in str(usb_info.get("modele", "")).lower():
|
|
return "Clavier"
|
|
elif "gamepad" in interface_subclass or "joystick" in interface_subclass:
|
|
return "Manette"
|
|
else:
|
|
return "Peripherique HID"
|
|
|
|
# Mass storage
|
|
if "mass storage" in interface_class:
|
|
model = str(usb_info.get("modele", "")).lower()
|
|
if "card reader" in model or "reader" in model:
|
|
return "Lecteur de cartes"
|
|
else:
|
|
return "Cle USB"
|
|
|
|
# Audio
|
|
if "audio" in interface_class:
|
|
if "microphone" in interface_subclass:
|
|
return "Microphone"
|
|
elif "speaker" in interface_subclass:
|
|
return "Haut-parleur"
|
|
else:
|
|
return "Audio"
|
|
|
|
# Video
|
|
if "video" in interface_class:
|
|
return "Webcam"
|
|
|
|
# Wireless
|
|
if "wireless" in interface_class or "bluetooth" in interface_class:
|
|
if "bluetooth" in interface_class:
|
|
return "Bluetooth"
|
|
else:
|
|
return "Adaptateur sans-fil"
|
|
|
|
# Printer
|
|
if "printer" in interface_class:
|
|
return "Imprimante"
|
|
|
|
return None
|
|
|
|
|
|
def parse_lsusb_simple(lsusb_output: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
Parse the output of simple 'lsusb' command (without -v)
|
|
|
|
Args:
|
|
lsusb_output: Raw text output from 'lsusb' command
|
|
|
|
Returns:
|
|
List of dictionaries with basic USB device information
|
|
"""
|
|
devices = []
|
|
|
|
for line in lsusb_output.strip().split('\n'):
|
|
# Example: Bus 002 Device 003: ID 0781:5567 SanDisk Corp. Cruzer Blade
|
|
match = re.match(r'Bus\s+(\d+)\s+Device\s+(\d+):\s+ID\s+([0-9a-f]{4}):([0-9a-f]{4})\s+(.*)', line)
|
|
if match:
|
|
desc = match.group(5)
|
|
parts = desc.split(' ', 1)
|
|
|
|
device = {
|
|
"bus": match.group(1),
|
|
"device": match.group(2),
|
|
"vendor_id": match.group(3),
|
|
"product_id": match.group(4),
|
|
"marque": parts[0] if len(parts) >= 1 else None,
|
|
"modele": parts[1] if len(parts) == 2 else desc,
|
|
"type_principal": "USB",
|
|
"sous_type": None
|
|
}
|
|
devices.append(device)
|
|
|
|
return devices
|
|
|
|
|
|
def create_device_name(usb_info: Dict[str, Any]) -> str:
|
|
"""Generate a readable device name from USB info"""
|
|
parts = []
|
|
|
|
if usb_info.get("marque"):
|
|
parts.append(usb_info["marque"])
|
|
|
|
if usb_info.get("modele"):
|
|
parts.append(usb_info["modele"])
|
|
|
|
if not parts:
|
|
parts.append("Peripherique USB")
|
|
if usb_info.get("vendor_id") and usb_info.get("product_id"):
|
|
parts.append(f"({usb_info['vendor_id']}:{usb_info['product_id']})")
|
|
|
|
return " ".join(parts)
|