From 84beae52a926a8f9fd35b207cf19716e0d66f6f5 Mon Sep 17 00:00:00 2001 From: zleyyij Date: Sun, 18 Feb 2024 13:46:29 -0700 Subject: [PATCH] feat: bulk usb processing and perf improvements --- README.md | 29 ++++++++++++++++++++++++++++- src/main.rs | 36 +++++++++++++++++++++++++++++------- src/pcie/mod.rs | 3 +-- src/usb/mod.rs | 22 +++++++++++++--------- 4 files changed, 71 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 0165ec7..77de109 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,32 @@ Here's an example curl request: curl "http://127.0.0.1:3000/api/usbs/?identifier=USB%5CVID_1532%26PID_0084%26MI_03%5C6%2638C0FA5D%260%260003" ``` +For bulk processing, you may submit a `POST` request to the same endpoint with a `Content-Type` of `application/json` and a payload containing an array of USB device identifier strings. + +The endpoint will return an array of objects (same shape as the `GET` request), or if an identifier string was unable to be processed successfully, `null` will substitute in the response. + +Here's an example curl request: +``` +curl -X POST http://127.0.0.1:3000/api/usbs/ -H "Content-Type: application/json" -d '["USB\\\\VID_1038&PID_1729\\\\6&28B2 +9415&0&4","USB\\\\VID_413C&PID_2105\\\\6&3B66B33D&0&9","USB\\\\ROOT_HUB30\\\\5&381F2DE&0&0","USB\\\\VID_1038&PID_1729&MI_00\\\\7&381E8103&0&0000","USB\\\\VID_0B05&PID_184C\\\\123456","US +B\\\\VID_1038&PID_1729&MI_01\\\\7&381E8103&0&0001","USB\\\\ROOT_HUB30\\\\5&3B7A03C3&0&0"]' +``` + +And here's an example response (truncated): +```json +[ + { + "vendor":"SteelSeries ApS", + "device":null + }, + { + "vendor":"Dell Computer Corp.", + "device":"Model L100 Keyboard" + }, + null +] +``` + ### PCIe To interact with the PCIe API, submit a `GET` request to `/api/pcie/?identifier=[PCIE_IDENTIFIER_STRING]`, where `[PCIE_IDENTIFIER_STRING]` is a valid [PCIe identifier](https://learn.microsoft.com/en-us/windows-hardware/drivers/install/identifiers-for-pci-devices). @@ -109,7 +135,8 @@ curl -X POST http://127.0.0.1:3000/api/pcie/ -H "Content-Type: application/json" VEN_1022&DEV_43B4&SUBSYS_33061B21&REV_02\\\\5&3B34128B&0&28020B","PCI\\\\VEN_1022&DEV_1441&SUBSYS_00000000&REV_00\\\\3&11583659&0&C1","PCI\\\\VEN_1022&DEV_43B4&SUBSYS_33061B21&REV_02\\\\ 5&3B34128B&0&38020B"]' ``` -And here's example response (cropped): + +And here's example response (truncated): ``` [ { diff --git a/src/main.rs b/src/main.rs index 5fb3ccc..29419e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,13 +140,11 @@ async fn post_pcie_handler( let mut response: Vec> = Vec::with_capacity(16); for entry in query { match state.pcie_cache.find(&entry) { - Ok(r) => { - response.push(Some(PcieResponse { - vendor: r.0.map(|v| v.name), - device: r.1.map(|d| d.name), - subsystem: r.2.map(|s| s.name), - })) - }, + Ok(r) => response.push(Some(PcieResponse { + vendor: r.0.map(|v| v.name), + device: r.1.map(|d| d.name), + subsystem: r.2.map(|s| s.name), + })), Err(e) => { warn!("post pcie handler error: when processing the device identifier {:?}, an error was returned: {:?}", entry, e); response.push(None); @@ -156,6 +154,29 @@ async fn post_pcie_handler( Ok(Json(response)) } +/// This handler accepts a `POST` request to `/api/usbs/`, with a body containing a serialized array of usb device identifier strings. +/// It relies on a globally shared [AppState] to re-use the pcie cache, and is largely identical to [get_usb_handler], but +/// is intended for batching +async fn post_usbs_handler( + State(state): State, + Json(query): Json>, +) -> Result>>, StatusCode> { + let mut response: Vec> = Vec::with_capacity(16); + for entry in query { + match state.usb_cache.find(&entry) { + Ok(r) => response.push(Some(UsbResponse { + vendor: r.0.map(|v| v.name), + device: r.1.map(|d| d.name), + })), + Err(e) => { + warn!("post usb handler error: when processing the device identifier {:?}, an error was returned: {:?}", entry, e); + response.push(None); + } + } + } + Ok(Json(response)) +} + #[derive(Debug, Deserialize, Serialize)] struct CpuQuery { pub name: String, @@ -190,6 +211,7 @@ async fn main() -> Result<(), Box> { let app = Router::new() .route("/api/cpus/", get(get_cpu_handler)) .route("/api/usbs/", get(get_usb_handler)) + .route("/api/usbs/", post(post_usbs_handler)) .route("/api/pcie/", get(get_pcie_handler)) .route("/api/pcie/", post(post_pcie_handler)) .layer(CorsLayer::new().allow_origin("*".parse::().unwrap())) diff --git a/src/pcie/mod.rs b/src/pcie/mod.rs index 8951d46..7b28900 100644 --- a/src/pcie/mod.rs +++ b/src/pcie/mod.rs @@ -75,8 +75,7 @@ impl PcieCache { subsystem = dev .subsystems .iter() - .filter(|s| s.id == parsed_identifier.1) - .nth(0) + .find(|s| s.id == parsed_identifier.1) .cloned(); } Ok((vendor.cloned(), device.cloned(), subsystem)) diff --git a/src/usb/mod.rs b/src/usb/mod.rs index f18045d..ef7e745 100644 --- a/src/usb/mod.rs +++ b/src/usb/mod.rs @@ -1,8 +1,12 @@ +use std::collections::HashMap; + use nom::bytes::complete::{tag, take, take_until}; use nom::character::complete::char; use nom::sequence::{delimited, preceded}; use nom::IResult; - +// https://stackoverflow.com/a/70552843 +// this library is used for a very fast hashmap implementation because we're not worried about DOS attacks +use nohash_hasher::BuildNoHashHasher; use crate::NomError; // The input file was obtained from http://www.linux-usb.org/ @@ -25,13 +29,17 @@ pub struct Device { #[derive(Clone)] pub struct UsbCache { - vendors: Vec, + vendors: HashMap>, } impl UsbCache { pub fn new() -> Self { + let mut vendors: HashMap> = HashMap::with_capacity_and_hasher(1024, BuildNoHashHasher::default()); + for vendor in parse_usb_db() { + vendors.insert(vendor.id, vendor); + } Self { - vendors: parse_usb_db(), + vendors, } } @@ -47,18 +55,14 @@ impl UsbCache { let parsed_identifier = parse_device_identifier(input)?; // first search for a vendor let matching_vendor = self - .vendors - .iter() - .filter(|ven| ven.id == parsed_identifier.0) - .nth(0); + .vendors.get(&parsed_identifier.0); let mut device: Option = None; if let Some(vendor) = matching_vendor { device = vendor .devices .iter() - .filter(|dev| dev.id == parsed_identifier.1) - .nth(0) + .find(|dev| dev.id == parsed_identifier.1) .cloned(); }