Skip to content

Commit

Permalink
Add hardware-in-the-loop test using Cynthion analyzer.
Browse files Browse the repository at this point in the history
The test requires a Cynthion r0.6 or higher board, with the analyzer
gateware including a self-test capability.

The host must be connected to both the CONTROL and TARGET-C ports. The
TARGET-A port must be connected to the AUX port.

This test is optional and requires the 'test-cynthion' feature to be
enabled.
  • Loading branch information
martinling committed Jun 9, 2024
1 parent c4de174 commit c385e5a
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 4 deletions.
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,16 @@ step-decoder = []
record-ui-test = ["serde", "serde_json"]
test-ui-replay = ["serde", "serde_json"]
debug-region-map = []
test-cynthion = []

[[test]]
name = "test_replay"
path = "src/test_replay.rs"
harness = false
required-features = ["test-ui-replay"]

[[test]]
name = "test_cynthion"
path = "src/test_cynthion.rs"
harness = false
required-features = ["test-cynthion"]
4 changes: 2 additions & 2 deletions src/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1005,15 +1005,15 @@ impl CaptureReader {
}

impl EndpointReader {
fn transfer_data_range(&mut self, range: &Range<EndpointTransactionId>)
pub fn transfer_data_range(&mut self, range: &Range<EndpointTransactionId>)
-> Result<Range<EndpointDataEvent>, Error>
{
let first_data_id = self.data_transactions.bisect_left(&range.start)?;
let last_data_id = self.data_transactions.bisect_left(&range.end)?;
Ok(first_data_id..last_data_id)
}

fn transfer_data_length(&mut self, range: &Range<EndpointDataEvent>)
pub fn transfer_data_length(&mut self, range: &Range<EndpointDataEvent>)
-> Result<u64, Error>
{
if range.start == range.end {
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#[macro_use]
extern crate bitfield;

mod backend;
mod capture;
pub mod backend;
pub mod capture;
mod compact_index;
mod data_stream;
pub mod decoder;
Expand Down
88 changes: 88 additions & 0 deletions src/test_cynthion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use packetry::backend::cynthion::{CynthionDevice, CynthionUsability, Speed};
use packetry::capture::{create_capture, CaptureReader, EndpointId, EndpointTransferId};
use packetry::decoder::Decoder;

use anyhow::{Context, Error};
use futures_lite::future::block_on;
use nusb::transfer::RequestBuffer;

const TRANSFER_LENGTH: usize = 0x1000;

fn main() {
test().unwrap();
}

fn test() -> Result<(), Error> {
// Create capture and decoder.
let (writer, mut reader) = create_capture()
.context("Failed to create capture")?;
let mut decoder = Decoder::new(writer)
.context("Failed to create decoder")?;

// Open analyzer device.
let analyzer = CynthionDevice::scan()
.context("Failed to scan for analyzers")?
.iter()
.find(|dev| matches!(dev.usability, CynthionUsability::Usable(..)))
.context("No usable analyzer found")?
.open()
.context("Failed to open analyzer")?;

// Start capture.
let (packets, stop_handle) = analyzer
.start(Speed::High,
|err| err.context("Failure in capture thread").unwrap())
.context("Failed to start analyzer")?;

// Open test device on AUX port.
let test_device = nusb::list_devices()
.context("Failed to list USB devices")?
.find(|dev| dev.vendor_id() == 0x1209 && dev.product_id() == 0x000A)
.context("Test device not found")?
.open()
.context("Failed to open test device")?;
let test_interface = test_device.claim_interface(0)
.context("Failed to claim interface 0 on test device")?;

// Read some data from the test device.
println!("Starting read from test device");
let buf = RequestBuffer::new(TRANSFER_LENGTH);
let transfer = test_interface.bulk_in(0x81, buf);
let completion = block_on(transfer);
completion.status.context("Transfer from test device failed")?;
println!("Read {} bytes from test device", completion.data.len());
assert_eq!(completion.data.len(), TRANSFER_LENGTH);

// Stop analyzer.
stop_handle.stop()
.context("Failed to stop analyzer")?;

// Decode all packets that were received.
for packet in packets {
decoder.handle_raw_packet(&packet)
.context("Error decoding packet")?;
}

// Look up the endpoint we're interested in and count payload bytes.
let bytes_captured = bytes_on_endpoint(&mut reader)
.context("Error counting captured bytes on endpoint")?;
println!("Captured {}/{} bytes of data read from test device",
bytes_captured, TRANSFER_LENGTH);
assert_eq!(bytes_captured, TRANSFER_LENGTH as u64);

Ok(())
}

fn bytes_on_endpoint(reader: &mut CaptureReader) -> Result<u64, Error> {
// Endpoint IDs 0 and 1 are special (used for invalid and framing packets).
// The first normal endpoint in the capture will have endpoint ID 2.
let endpoint_id = EndpointId::from(2);
// We're looking for the first and only transfer on the endpoint.
let ep_transfer_id = EndpointTransferId::from(0);
let ep_traf = reader.endpoint_traffic(endpoint_id)?;
let ep_transaction_ids = ep_traf.transfer_index.target_range(
ep_transfer_id, ep_traf.transaction_ids.len())?;
let data_range = ep_traf.transfer_data_range(&ep_transaction_ids)?;
let data_length = ep_traf.transfer_data_length(&data_range)?;
Ok(data_length)
}

0 comments on commit c385e5a

Please sign in to comment.