From 1de6d02fb8221e300a2f343e99ae5722cb85adbd Mon Sep 17 00:00:00 2001 From: Gaelan Steele Date: Wed, 8 Mar 2023 16:02:08 +0100 Subject: [PATCH] scsi: Add tests for the emulated target The vast majority of this work was done by Gaelan Steele as part of a GSoC project [1][2]. [1] https://github.com/rust-vmm/vhost-device/pull/4 [2] https://gist.github.com/Gaelan/febec4e4606e1320026a0924c3bf74d0 Co-developed-by: Erik Schilling Signed-off-by: Erik Schilling Signed-off-by: Gaelan Steele --- coverage_config_x86_64.json | 2 +- crates/scsi/src/scsi/emulation/mod.rs | 2 + .../scsi/src/scsi/emulation/tests/bad_lun.rs | 198 +++++++++ .../scsi/src/scsi/emulation/tests/generic.rs | 107 +++++ crates/scsi/src/scsi/emulation/tests/mod.rs | 384 ++++++++++++++++ .../tests/report_supported_operation_codes.rs | 420 ++++++++++++++++++ 6 files changed, 1112 insertions(+), 1 deletion(-) create mode 100644 crates/scsi/src/scsi/emulation/tests/bad_lun.rs create mode 100644 crates/scsi/src/scsi/emulation/tests/generic.rs create mode 100644 crates/scsi/src/scsi/emulation/tests/mod.rs create mode 100644 crates/scsi/src/scsi/emulation/tests/report_supported_operation_codes.rs diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index e3d7752..9cf6dcc 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 67.6, + "coverage_score": 69.6, "exclude_path": "", "crate_features": "" } diff --git a/crates/scsi/src/scsi/emulation/mod.rs b/crates/scsi/src/scsi/emulation/mod.rs index 377b9a4..d697842 100644 --- a/crates/scsi/src/scsi/emulation/mod.rs +++ b/crates/scsi/src/scsi/emulation/mod.rs @@ -7,3 +7,5 @@ pub(crate) mod mode_page; mod response_data; pub(crate) mod target; +#[cfg(test)] +mod tests; diff --git a/crates/scsi/src/scsi/emulation/tests/bad_lun.rs b/crates/scsi/src/scsi/emulation/tests/bad_lun.rs new file mode 100644 index 0000000..38b7e4a --- /dev/null +++ b/crates/scsi/src/scsi/emulation/tests/bad_lun.rs @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use super::{do_command_fail_lun, do_command_in_lun, null_image}; +use crate::scsi::{ + emulation::{block_device::BlockDevice, target::EmulatedTarget}, + sense, +}; + +#[test] +fn test_report_luns() { + let mut target = EmulatedTarget::new(); + for _ in 0..5 { + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + } + + let select_reports = &[0x0, 0x2]; // all but well known, all + + for &sr in select_reports { + do_command_in_lun( + &mut target, + 6, + &[ + 0xa0, // REPORT LUNS + 0, // reserved + sr, // select report + 0, 0, 0, // reserved + 0, 0, 1, 0, // alloc length: 256 + 0, 0, + ], + &[], + &[ + 0, 0, 0, 40, // length: 5*8 = 40 + 0, 0, 0, 0, // reserved + 0, 0, 0, 0, 0, 0, 0, 0, // LUN 0 + 0, 1, 0, 0, 0, 0, 0, 0, // LUN 1 + 0, 2, 0, 0, 0, 0, 0, 0, // LUN 2 + 0, 3, 0, 0, 0, 0, 0, 0, // LUN 3 + 0, 4, 0, 0, 0, 0, 0, 0, // LUN 4 + ], + ); + } +} + +#[test] +fn test_report_luns_empty() { + let mut target = EmulatedTarget::new(); + for _ in 0..5 { + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + } + + // well-known only and several modes explictly defined to return an empty list + // for all but ceratin types of recieving LUNs + let select_reports = &[0x1, 0x10, 0x11, 0x12]; + + for &sr in select_reports { + do_command_in_lun( + &mut target, + 6, + &[ + 0xa0, // REPORT LUNS + 0, // reserved + sr, // select report + 0, 0, 0, // reserved + 0, 0, 1, 0, // alloc length: 256 + 0, 0, + ], + &[], + &[ + 0, 0, 0, 0, // length: 0 + 0, 0, 0, 0, // reserved + ], + ); + } +} + +#[test] +fn test_request_sense() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in_lun( + &mut target, + 1, + &[ + 0x3, // REQUEST SENSE + 0, // fixed format sense data + 0, 0, // reserved + 255, // alloc length + 0, // control + ], + &[], + &sense::LOGICAL_UNIT_NOT_SUPPORTED.to_fixed_sense(), + ); +} + +#[test] +fn test_request_sense_descriptor_format() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_fail_lun( + &mut target, + 1, + &[ + 0x3, // REQUEST SENSE + 1, // descriptor format sense data + 0, 0, // reserved + 255, // alloc length + 0, // control + ], + sense::INVALID_FIELD_IN_CDB, + ); +} + +#[test] +fn test_inquiry() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in_lun( + &mut target, + 1, + &[ + 0x12, // INQUIRY + 0, // EVPD bit: 0 + 0, // page code + 1, 0, // alloc length: 256 + 0, // control + ], + &[], + // some empty comments to get rustfmt to do something vaguely sensible + &[ + 0x7f, // device not accessible, unknown type + 0, // features + 0x7, // version + 0x12, // response data format v2, HiSup = 1 + 91, // addl length + 0, 0, 0, // unsupported features + // vendor + b'r', b'u', b's', b't', b'-', b'v', b'm', b'm', // + // product + b'v', b'h', b'o', b's', b't', b'-', b'u', b's', b'e', b'r', b'-', b's', b'c', b's', + b'i', b' ', // + // revision + b'v', b'0', b' ', b' ', // + // reserved/obselete/vendor specific + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // version descriptors + 0x0, 0xc0, // SAM-6 + 0x05, 0xc0, // SPC-5 (no code assigned for 6 yet) + 0x06, 0x0, // SBC-4 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + // reserved + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + ); +} + +#[test] +fn test_other_command() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_fail_lun( + &mut target, + 1, + &[ + 0, // TEST UNIT READY + 0, 0, 0, 0, // reserved + 0, // control + ], + sense::LOGICAL_UNIT_NOT_SUPPORTED, + ); +} + +#[test] +fn test_invalid_command() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_fail_lun( + &mut target, + 1, + &[ + 0xff, // vendor specific + 0, 0, 0, 0, // reserved + 0, // control + ], + sense::INVALID_COMMAND_OPERATION_CODE, + ); +} diff --git a/crates/scsi/src/scsi/emulation/tests/generic.rs b/crates/scsi/src/scsi/emulation/tests/generic.rs new file mode 100644 index 0000000..19f4bd4 --- /dev/null +++ b/crates/scsi/src/scsi/emulation/tests/generic.rs @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +//! Tests for stuff shared between commands. + +use std::io::ErrorKind; + +use super::{do_command_fail, test_image}; +use crate::scsi::{ + emulation::{block_device::BlockDevice, target::EmulatedTarget}, + sense, CmdError, Request, Target, TaskAttr, +}; + +#[test] +fn test_invalid_opcode() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(test_image()); + target.add_lun(Box::new(dev)); + + do_command_fail( + &mut target, + &[ + 0xff, // vendor specific, unused by us + 0, 0, 0, 0, 0, + ], + sense::INVALID_COMMAND_OPERATION_CODE, + ); +} + +#[test] +fn test_invalid_service_action() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(test_image()); + target.add_lun(Box::new(dev)); + + do_command_fail( + &mut target, + &[ + 0xa3, // MAINTAINANCE IN + 0x1f, // vendor specific, unused by us + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + sense::INVALID_FIELD_IN_CDB, + ); +} + +#[test] +fn test_short_data_out_buffer() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(test_image()); + target.add_lun(Box::new(dev)); + + let mut data_in: &mut [u8] = &mut []; + let mut data_out: &[u8] = &[0_u8; 511]; + + let res = target.execute_command( + 0, + &mut data_out, + &mut data_in, + Request { + id: 0, + cdb: &[ + 0x28, // READ (10) + 0, // flags + 0, 0, 0, 15, // LBA: 5 + 0, // reserved, group # + 0, 1, // transfer length: 1 + 0, // control + ], + task_attr: TaskAttr::Simple, + crn: 0, + prio: 0, + }, + ); + + if let CmdError::DataIn(e) = res.unwrap_err() { + assert_eq!(e.kind(), ErrorKind::WriteZero); + } else { + panic!(); + } +} + +#[test] +fn test_short_cdb() { + let mut target: EmulatedTarget = EmulatedTarget::new(); + let dev = BlockDevice::new(test_image()); + target.add_lun(Box::new(dev)); + + let mut data_in: &mut [u8] = &mut []; + let mut data_out: &[u8] = &[]; + + let res = target.execute_command( + 0, + &mut data_out, + &mut data_in, + Request { + id: 0, + cdb: &[ + 0x28, // READ (10) + ], + task_attr: TaskAttr::Simple, + crn: 0, + prio: 0, + }, + ); + + assert!(matches!(res.unwrap_err(), CmdError::CdbTooShort)); +} diff --git a/crates/scsi/src/scsi/emulation/tests/mod.rs b/crates/scsi/src/scsi/emulation/tests/mod.rs new file mode 100644 index 0000000..b112ba0 --- /dev/null +++ b/crates/scsi/src/scsi/emulation/tests/mod.rs @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +#![cfg(test)] + +mod bad_lun; +mod generic; +mod report_supported_operation_codes; + +use std::{fs::File, io::Write}; + +use tempfile::tempfile; + +use super::{ + block_device::{BlockDevice, FileBackend}, + target::EmulatedTarget, +}; +use crate::scsi::{ + sense::{self, SenseTriple}, + CmdOutput, Request, Target, TaskAttr, +}; + +fn null_image() -> FileBackend { + FileBackend::new(File::open("/dev/null").unwrap()) +} + +fn test_image() -> FileBackend { + let mut f = tempfile().unwrap(); + // generate 16 512-byte sectors, each of which consist of a single + // repeated hex character, i.e. + // sector 00: 0000000....0000 + // sector 15: fffffff....ffff + for chr in b'0'..=b'9' { + f.write_all(&[chr; 512]).unwrap(); + } + for chr in b'a'..=b'f' { + f.write_all(&[chr; 512]).unwrap(); + } + FileBackend::new(f) +} + +fn do_command_in_lun( + target: &mut EmulatedTarget, + lun: u16, + cdb: &[u8], + data_out: &[u8], + expected_data_in: &[u8], +) { + let mut data_in = Vec::new(); + + let res = target.execute_command( + lun, + &mut &data_out[..], + &mut data_in, + Request { + id: 0, + cdb, + task_attr: TaskAttr::Simple, + crn: 0, + prio: 0, + }, + ); + + assert_eq!(res.unwrap(), CmdOutput::ok()); + assert_eq!(&data_in, expected_data_in); +} + +fn do_command_fail_lun( + target: &mut EmulatedTarget, + lun: u16, + cdb: &[u8], + expected_error: SenseTriple, +) { + let mut data_in = Vec::new(); + let mut data_out: &[u8] = &[]; + + let res = target.execute_command( + lun, + &mut data_out, + &mut data_in, + Request { + id: 0, + cdb, + task_attr: TaskAttr::Simple, + crn: 0, + prio: 0, + }, + ); + + assert_eq!(res.unwrap(), CmdOutput::check_condition(expected_error)); + assert_eq!(&data_in, &[]); +} + +fn do_command_in( + target: &mut EmulatedTarget, + cdb: &[u8], + data_out: &[u8], + expected_data_in: &[u8], +) { + do_command_in_lun(target, 0, cdb, data_out, expected_data_in); +} + +fn do_command_fail(target: &mut EmulatedTarget, cdb: &[u8], expected_error: SenseTriple) { + do_command_fail_lun(target, 0, cdb, expected_error); +} + +#[test] +fn test_test_unit_ready() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in(&mut target, &[0, 0, 0, 0, 0, 0], &[], &[]); +} + +#[test] +fn test_report_luns() { + let mut target = EmulatedTarget::new(); + for _ in 0..5 { + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + } + + do_command_in( + &mut target, + &[ + 0xa0, // REPORT LUNS + 0, // reserved + 0, // select report + 0, 0, 0, // reserved + 0, 0, 1, 0, // alloc length: 256 + 0, 0, + ], + &[], + &[ + 0, 0, 0, 40, // length: 5*8 = 40 + 0, 0, 0, 0, // reserved + 0, 0, 0, 0, 0, 0, 0, 0, // LUN 0 + 0, 1, 0, 0, 0, 0, 0, 0, // LUN 1 + 0, 2, 0, 0, 0, 0, 0, 0, // LUN 2 + 0, 3, 0, 0, 0, 0, 0, 0, // LUN 3 + 0, 4, 0, 0, 0, 0, 0, 0, // LUN 4 + ], + ); +} + +#[test] +fn test_read_10() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(test_image()); + target.add_lun(Box::new(dev)); + + // TODO: this test relies on the default logical block size of 512. We should + // make that explicit. + + do_command_in( + &mut target, + &[ + 0x28, // READ (10) + 0, // flags + 0, 0, 0, 5, // LBA: 5 + 0, // reserved, group # + 0, 1, // transfer length: 1 + 0, // control + ], + &[], + &[b'5'; 512], + ); +} + +#[test] +fn test_read_10_last_block() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(test_image()); + target.add_lun(Box::new(dev)); + + // TODO: this test relies on the default logical block size of 512. We should + // make that explicit. + + do_command_in( + &mut target, + &[ + 0x28, // READ (10) + 0, // flags + 0, 0, 0, 15, // LBA: 5 + 0, // reserved, group # + 0, 1, // transfer length: 1 + 0, // control + ], + &[], + &[b'f'; 512], + ); +} + +#[test] +fn test_read_10_out_of_range() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(test_image()); + target.add_lun(Box::new(dev)); + + // TODO: this test relies on the default logical block size of 512. We should + // make that explicit. + + do_command_fail( + &mut target, + &[ + 0x28, // READ (10) + 0, // flags + 0, 0, 0, 16, // LBA: 16 + 0, // reserved, group # + 0, 1, // transfer length: 1 + 0, // control + ], + sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE, + ); +} + +#[test] +fn test_read_10_cross_out() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + // TODO: this test relies on the default logical block size of 512. We should + // make that explicit. + + do_command_fail( + &mut target, + &[ + 0x28, // READ (10) + 0, // flags + 0, 0, 0, 15, // LBA: 15 + 0, // reserved, group # + 0, 2, // transfer length: 2 + 0, // control + ], + sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE, + ); +} + +#[test] +fn test_read_capacity_10() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(test_image()); + target.add_lun(Box::new(dev)); + + // TODO: this test relies on the default logical block size of 512. We should + // make that explicit. + + // TODO: we should test behavior with ≥ 2 TiB images. But not sure how we + // can do that reliably without risking using 2 TiB of disk + + do_command_in( + &mut target, + &[ + 0x25, // READ CAPACITY (10) + 0, 0, 0, 0, 0, 0, 0, 0, // flags + 0, // control + ], + &[], + &[ + 0, 0, 0, 15, // returned LBA (last valid LBA), + 0, 0, 2, 0, // block size (512) + ], + ); +} + +#[test] +fn test_read_capacity_16() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(test_image()); + target.add_lun(Box::new(dev)); + + // TODO: this test relies on the default logical block size of 512. We should + // make that explicit. + + do_command_in( + &mut target, + &[ + 0x9e, 0x10, // READ CAPACITY (16) + 0, 0, 0, 0, 0, 0, 0, 0, // obsolete + 0, 0, 0, 32, // allocation length: 32 + 0, // obselete/reserved + 0, // control + ], + &[], + &[ + 0, 0, 0, 0, 0, 0, 0, 15, // returned LBA (last valid LBA), + 0, 0, 2, 0, // block size (512) + 0, // reserved, zoned stuff, protection stuff + 0, // one PB per LB + 0xc0, // thin provisioning, unmapped blocks read 0 + 0, // LBA 0 is aligned (top bits above) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // reserved + ], + ); +} + +#[test] +fn test_inquiry() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0x12, // INQUIRY + 0, // EVPD bit: 0 + 0, // page code + 1, 0, // alloc length: 256 + 0, // control + ], + &[], + // some empty comments to get rustfmt to do something vaguely sensible + &[ + 0, // accessible; direct acccess block device + 0, // features + 0x7, // version + 0x12, // response data format v2, HiSup = 1 + 91, // addl length + 0, 0, 0, // unsupported features + // vendor + b'r', b'u', b's', b't', b'-', b'v', b'm', b'm', // + // product + b'v', b'h', b'o', b's', b't', b'-', b'u', b's', b'e', b'r', b'-', b's', b'c', b's', + b'i', b' ', // + // revision + b'v', b'0', b' ', b' ', // + // reserved/obselete/vendor specific + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // version descriptors + 0x0, 0xc0, // SAM-6 + 0x05, 0xc0, // SPC-5 (no code assigned for 6 yet) + 0x06, 0, // SBC-4 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + // reserved + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + ); +} + +#[test] +fn test_request_sense() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0x3, // INQUIRY + 0, // desc bit: 0 + 0, 0, // reserved + 255, // alloc length + 0, // control + ], + &[], + // We'll always return this - modern SCSI has autosense, so any errors are sent with the + // response to the command that caused them (and therefore immediately cleared), and + // REQUEST SENSE returns an actual error only under some exceptional circumstances + // we don't implement. + &sense::NO_ADDITIONAL_SENSE_INFORMATION.to_fixed_sense(), + ); +} + +#[test] +fn test_request_sense_descriptor_format() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_fail( + &mut target, + &[ + 0x3, // INQUIRY + 1, // desc bit: 1 + 0, 0, // reserved + 255, // alloc length + 0, // control + ], + // We don't support descriptor format sense data. + sense::INVALID_FIELD_IN_CDB, + ); +} diff --git a/crates/scsi/src/scsi/emulation/tests/report_supported_operation_codes.rs b/crates/scsi/src/scsi/emulation/tests/report_supported_operation_codes.rs new file mode 100644 index 0000000..016d6a6 --- /dev/null +++ b/crates/scsi/src/scsi/emulation/tests/report_supported_operation_codes.rs @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use super::{do_command_fail, do_command_in, null_image}; +use crate::scsi::{ + emulation::{block_device::BlockDevice, target::EmulatedTarget}, + sense, +}; + +#[test] +fn test_one_command() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b1, // reporting options: one command + 0, 1, 2, // opcode: TEST UNIT READY, SA ignored + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b11, // flags, supported + 0, 6, // cdb len + 0, 0, 0, 0, 0, 0b0100, // usage data + ], + ); +} + +#[test] +fn test_one_command_with_timeout_descriptor() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0x81, // request timeout descs, reporting options: one command + 0, 1, 2, // opcode: TEST UNIT READY, SA ignored + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b11, // flags, supported + 0, 6, // cdb len + 0, 0, 0, 0, 0, 0b0100, // usage data + 0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // no timeouts + ], + ); +} + +#[test] +fn test_one_command_unsupported() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b1, // reporting options: one command + 0xff, 1, 2, // opcode: vendor specific, SA ignored + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b01, // flags, not supported + 0, 0, // cdb len + ], + ); +} + +#[test] +fn test_one_command_valid_service_action() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_fail( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b1, // reporting options: one command + 0x9e, 0, 0x10, // SERVICE ACTION IN (16), READ CAPACITY (16) + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + sense::INVALID_FIELD_IN_CDB, + ); +} + +#[test] +fn test_one_command_invalid_service_action() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_fail( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b1, // reporting options: one command + 0x9e, 0, 0xff, // SERVICE ACTION IN (16), invalid + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + sense::INVALID_FIELD_IN_CDB, + ); +} + +#[test] +fn test_one_service_action() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b10, // reporting options: one service action + 0x9e, 0, 0x10, // SERVICE ACTION IN (16), READ CAPACITY (16) + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b11, // flags, supported + 0, 16, // cdb len + 0x9e, 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, + 0b0100, // usage data + ], + ); +} + +#[test] +fn test_one_service_action_with_timeout_descriptor() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0x82, // request timeout descs, reporting options: one service action + 0x9e, 0, 0x10, // SERVICE ACTION IN (16), READ CAPACITY (16) + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b11, // flags, supported + 0, 16, // cdb len + 0x9e, 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, + 0b0100, // usage data + 0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // no timeouts + ], + ); +} + +#[test] +fn test_one_service_action_unknown_opcode() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + // not entirely sure this behavior is correct; see comment in implementation + do_command_fail( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b10, // reporting options: one service action + 0xff, 1, 2, // opcode: vendor specific, unimplemented + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + sense::INVALID_FIELD_IN_CDB, + ); +} + +#[test] +fn test_one_service_action_unknown_service_action() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b10, // reporting options: one service action + 0x9e, 0, 0xff, // SERVICE ACTION IN (16), invalid SA + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b01, // flags, not supported + 0, 0, // cdb len + ], + ); +} + +#[test] +fn test_one_service_action_not_service_action() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_fail( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b10, // reporting options: one service action + 0, 1, 2, // TEST UNIT READY + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + sense::INVALID_FIELD_IN_CDB, + ); +} + +// rest of these tests are for "mode 3", which the spec calls 011b and our +// implementation calls OneCommandOrServiceAction, but that's a mouthful so just +// use "mode 3" for test names + +#[test] +fn test_mode_3_opcode_without_service_action() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b11, // reporting options: mode 3 + 0, 0, 0, // opcode: TEST UNIT READY, SA: 0 + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b11, // flags, supported + 0, 6, // cdb len + 0, 0, 0, 0, 0, 0b0100, // usage data + ], + ); +} + +#[test] +fn test_mode_3_with_timeout_descriptor() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0x83, // request timeout descs, reporting options: mode 3 + 0, 0, 0, // opcode: TEST UNIT READY, SA: 0 + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b11, // flags, supported + 0, 6, // cdb len + 0, 0, 0, 0, 0, 0b0100, // usage data + 0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // no timeouts + ], + ); +} + +#[test] +fn test_mode_3_opcode_with_unnecessary_service_action() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b11, // reporting options: mode 3 + 0, 0, 1, // opcode: TEST UNIT READY, SA: 1 + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b01, // flags, not supported + 0, 0, // cdb len + ], + ); +} + +#[test] +fn test_mode_3_invalid_opcode() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b11, // reporting options: mode 3 + 0xff, 0, 0, // opcode: vendor specific + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b01, // flags, not supported + 0, 0, // cdb len + ], + ); +} + +#[test] +fn test_mode_3_service_action() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b11, // reporting options: mode 3 + 0x9e, 0, 0x10, // opcode: SERVICE ACTION IN (16), READ CAPACITY (16) + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b11, // flags, supported + 0, 16, // cdb len + 0x9e, 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, + 0b0100, // usage data + ], + ); +} + +#[test] +fn test_mode_3_service_action_with_timeout_descriptor() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0x83, // request timeout desc, tireporting options: mode 3 + 0x9e, 0, 0x10, // opcode: SERVICE ACTION IN (16), READ CAPACITY (16) + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b11, // flags, supported + 0, 16, // cdb len + 0x9e, 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, + 0b0100, // usage data + 0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // no timeouts + ], + ); +} + +#[test] +fn test_mode_3_invalid_service_action() { + let mut target = EmulatedTarget::new(); + let dev = BlockDevice::new(null_image()); + target.add_lun(Box::new(dev)); + + do_command_in( + &mut target, + &[ + 0xa3, 0x0c, // REPORT SUPPORTED OPERATION CODES + 0b11, // reporting options: mode 3 + 0x9e, 0, 0xff, // opcode: SERVICE ACTION IN (16), invalid SA + 0, 0, 1, 0, // allocation length: 256 + 0, // reserved + 0, // control + ], + &[], + &[ + 0, 0b01, // flags, not supported + 0, 0, // cdb len + ], + ); +}