Skip to content

Commit

Permalink
feat: bulk usb processing and perf improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
zleyyij committed Feb 18, 2024
1 parent 1ffa957 commit 84beae5
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 19 deletions.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down Expand Up @@ -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):
```
[
{
Expand Down
36 changes: 29 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,11 @@ async fn post_pcie_handler(
let mut response: Vec<Option<PcieResponse>> = 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);
Expand All @@ -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<AppState>,
Json(query): Json<Vec<String>>,
) -> Result<Json<Vec<Option<UsbResponse>>>, StatusCode> {
let mut response: Vec<Option<UsbResponse>> = 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,
Expand Down Expand Up @@ -190,6 +211,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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::<HeaderValue>().unwrap()))
Expand Down
3 changes: 1 addition & 2 deletions src/pcie/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
22 changes: 13 additions & 9 deletions src/usb/mod.rs
Original file line number Diff line number Diff line change
@@ -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/
Expand All @@ -25,13 +29,17 @@ pub struct Device {

#[derive(Clone)]
pub struct UsbCache {
vendors: Vec<Vendor>,
vendors: HashMap<u16, Vendor, BuildNoHashHasher<u16>>,
}

impl UsbCache {
pub fn new() -> Self {
let mut vendors: HashMap<u16, Vendor, BuildNoHashHasher<u16>> = 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,
}
}

Expand All @@ -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<Device> = 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();
}

Expand Down

0 comments on commit 84beae5

Please sign in to comment.