mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2025-12-26 14:41:23 +00:00
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 <erik.schilling@linaro.org> Signed-off-by: Erik Schilling <erik.schilling@linaro.org> Signed-off-by: Gaelan Steele <gbs@canishe.com>
This commit is contained in:
parent
be1eaf3f79
commit
1de6d02fb8
@ -1,5 +1,5 @@
|
||||
{
|
||||
"coverage_score": 67.6,
|
||||
"coverage_score": 69.6,
|
||||
"exclude_path": "",
|
||||
"crate_features": ""
|
||||
}
|
||||
|
||||
@ -7,3 +7,5 @@ pub(crate) mod mode_page;
|
||||
mod response_data;
|
||||
pub(crate) mod target;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
198
crates/scsi/src/scsi/emulation/tests/bad_lun.rs
Normal file
198
crates/scsi/src/scsi/emulation/tests/bad_lun.rs
Normal file
@ -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,
|
||||
);
|
||||
}
|
||||
107
crates/scsi/src/scsi/emulation/tests/generic.rs
Normal file
107
crates/scsi/src/scsi/emulation/tests/generic.rs
Normal file
@ -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));
|
||||
}
|
||||
384
crates/scsi/src/scsi/emulation/tests/mod.rs
Normal file
384
crates/scsi/src/scsi/emulation/tests/mod.rs
Normal file
@ -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,
|
||||
);
|
||||
}
|
||||
@ -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
|
||||
],
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user