//--------------------------------------------------------------------------- // // SCSI Target Emulator PiSCSI // for Raspberry Pi // // Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" #include "shared/scsi.h" #include "shared/piscsi_exceptions.h" #include "devices/primary_device.h" #include "devices/device_factory.h" #include "devices/scsi_command_util.h" using namespace scsi_defs; using namespace scsi_command_util; pair, shared_ptr> CreatePrimaryDevice(int id = 0) { auto controller = make_shared>(id); auto device = make_shared(0); EXPECT_TRUE(device->Init({})); EXPECT_TRUE(controller->AddDevice(device)); return { controller, device }; } TEST(PrimaryDeviceTest, GetId) { const int ID = 5; auto [controller, device] = CreatePrimaryDevice(ID); EXPECT_EQ(ID, device->GetId()); } TEST(PrimaryDeviceTest, PhaseChange) { auto [controller, device] = CreatePrimaryDevice(); EXPECT_CALL(*controller, Status); device->EnterStatusPhase(); EXPECT_CALL(*controller, DataIn); device->EnterDataInPhase(); EXPECT_CALL(*controller, DataOut); device->EnterDataOutPhase(); } TEST(PrimaryDeviceTest, Reset) { auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) << "Device must be reserved for initiator ID 1"; device->Reset(); EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) << "Device must not be reserved anymore for initiator ID 1"; } TEST(PrimaryDeviceTest, CheckReservation) { auto [controller, device] = CreatePrimaryDevice(); EXPECT_TRUE(device->CheckReservation(0, scsi_command::eCmdTestUnitReady, false)) << "Device must not be reserved for initiator ID 0"; device->Dispatch(scsi_command::eCmdReserve6); EXPECT_TRUE(device->CheckReservation(0, scsi_command::eCmdTestUnitReady, false)) << "Device must not be reserved for initiator ID 0"; EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) << "Device must be reserved for initiator ID 1"; EXPECT_FALSE(device->CheckReservation(-1, scsi_command::eCmdTestUnitReady, false)) << "Device must be reserved for unknown initiator"; EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdInquiry, false)) << "Device must not be reserved for INQUIRY"; EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdRequestSense, false)) << "Device must not be reserved for REQUEST SENSE"; EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdRelease6, false)) << "Device must not be reserved for RELEASE (6)"; EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdPreventAllowMediumRemoval, false)) << "Device must not be reserved for PREVENT ALLOW MEDIUM REMOVAL with prevent bit not set"; EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdPreventAllowMediumRemoval, true)) << "Device must be reserved for PREVENT ALLOW MEDIUM REMOVAL with prevent bit set"; } TEST(PrimaryDeviceTest, ReserveReleaseUnit) { auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) << "Device must be reserved for initiator ID 1"; device->Dispatch(scsi_command::eCmdRelease6); EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) << "Device must not be reserved anymore for initiator ID 1"; ON_CALL(*controller, GetInitiatorId).WillByDefault(Return(-1)); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) << "Device must be reserved for unknown initiator"; device->Dispatch(scsi_command::eCmdRelease6); EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) << "Device must not be reserved anymore for unknown initiator"; } TEST(PrimaryDeviceTest, DiscardReservation) { auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) << "Device must be reserved for initiator ID 1"; device->DiscardReservation(); EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) << "Device must not be reserved anymore for initiator ID 1"; } TEST(PrimaryDeviceTest, TestUnitReady) { auto [controller, device] = CreatePrimaryDevice(); // Required by the bullseye clang++ compiler auto d = device; device->SetReset(true); device->SetAttn(true); device->SetReady(false); EXPECT_CALL(*controller, DataIn).Times(0); EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::unit_attention), Property(&scsi_exception::get_asc, asc::power_on_or_reset)))); device->SetReset(false); EXPECT_CALL(*controller, DataIn).Times(0); EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::unit_attention), Property(&scsi_exception::get_asc, asc::not_ready_to_ready_change)))); device->SetReset(true); device->SetAttn(false); EXPECT_CALL(*controller, DataIn).Times(0); EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::unit_attention), Property(&scsi_exception::get_asc, asc::power_on_or_reset)))); device->SetReset(false); device->SetAttn(true); EXPECT_CALL(*controller, DataIn).Times(0); EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::unit_attention), Property(&scsi_exception::get_asc, asc::not_ready_to_ready_change)))); device->SetAttn(false); EXPECT_CALL(*controller, DataIn).Times(0); EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::not_ready), Property(&scsi_exception::get_asc, asc::medium_not_present)))); device->SetReady(true); EXPECT_CALL(*controller, Status); device->Dispatch(scsi_command::eCmdTestUnitReady); EXPECT_EQ(status::good, controller->GetStatus()); } TEST(PrimaryDeviceTest, Inquiry) { auto [controller, device] = CreatePrimaryDevice(); // Required by the bullseye clang++ compiler auto d = device; // ALLOCATION LENGTH controller->SetCmdByte(4, 255); ON_CALL(*d, InquiryInternal()).WillByDefault([&d]() { return d->HandleInquiry(device_type::processor, scsi_level::spc_3, false); }); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); ON_CALL(*controller, GetEffectiveLun()).WillByDefault(Return(1)); device->Dispatch(scsi_command::eCmdInquiry); EXPECT_EQ(0x7f, controller->GetBuffer()[0]) << "Invalid LUN was not reported"; ON_CALL(*controller, GetEffectiveLun()).WillByDefault(Return(0)); EXPECT_FALSE(controller->AddDevice(make_shared(0))) << "Duplicate LUN was not rejected"; EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); EXPECT_EQ(device_type::processor, (device_type)controller->GetBuffer()[0]); EXPECT_EQ(0x00, controller->GetBuffer()[1]) << "Device was not reported as non-removable"; EXPECT_EQ(scsi_level::spc_3, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; EXPECT_EQ(scsi_level::scsi_2, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; ON_CALL(*device, InquiryInternal()).WillByDefault([&d]() { return d->HandleInquiry(device_type::direct_access, scsi_level::scsi_1_ccs, true); }); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); EXPECT_EQ(device_type::direct_access, (device_type)controller->GetBuffer()[0]); EXPECT_EQ(0x80, controller->GetBuffer()[1]) << "Device was not reported as removable"; EXPECT_EQ(scsi_level::scsi_1_ccs, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; EXPECT_EQ(scsi_level::scsi_1_ccs, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; controller->SetCmdByte(1, 0x01); EXPECT_CALL(*controller, DataIn).Times(0); EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::illegal_request), Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "EVPD bit is not supported"; controller->SetCmdByte(2, 0x01); EXPECT_CALL(*controller, DataIn).Times(0); EXPECT_THAT([&d] { d->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::illegal_request), Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "PAGE CODE field is not supported"; controller->SetCmdByte(1, 0); controller->SetCmdByte(2, 0); // ALLOCATION LENGTH controller->SetCmdByte(4, 1); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; EXPECT_EQ(1, controller->GetLength()) << "Wrong ALLOCATION LENGTH handling"; } TEST(PrimaryDeviceTest, RequestSense) { auto [controller, device] = CreatePrimaryDevice(); // Required by the bullseye clang++ compiler auto d = device; // ALLOCATION LENGTH controller->SetCmdByte(4, 255); device->SetReady(false); EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRequestSense); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::not_ready), Property(&scsi_exception::get_asc, asc::medium_not_present)))); device->SetReady(true); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdRequestSense); EXPECT_EQ(status::good, controller->GetStatus()); } TEST(PrimaryDeviceTest, SendDiagnostic) { auto [controller, device] = CreatePrimaryDevice(); // Required by the bullseye clang++ compiler auto d = device; EXPECT_CALL(*controller, Status); device->Dispatch(scsi_command::eCmdSendDiagnostic); EXPECT_EQ(status::good, controller->GetStatus()); controller->SetCmdByte(1, 0x10); EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::illegal_request), Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because PF bit is not supported"; controller->SetCmdByte(1, 0); controller->SetCmdByte(3, 1); EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::illegal_request), Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because parameter list is not supported"; controller->SetCmdByte(3, 0); controller->SetCmdByte(4, 1); EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::illegal_request), Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because parameter list is not supported"; } TEST(PrimaryDeviceTest, ReportLuns) { const int LUN1 = 1; const int LUN2 = 4; auto controller = make_shared(0); auto device1 = make_shared(LUN1); auto device2 = make_shared(LUN2); EXPECT_TRUE(device1->Init({})); EXPECT_TRUE(device2->Init({})); controller->AddDevice(device1); EXPECT_TRUE(controller->HasDeviceForLun(LUN1)); controller->AddDevice(device2); EXPECT_TRUE(controller->HasDeviceForLun(LUN2)); // ALLOCATION LENGTH controller->SetCmdByte(9, 255); EXPECT_CALL(*controller, DataIn); device1->Dispatch(scsi_command::eCmdReportLuns); const vector& buffer = controller->GetBuffer(); EXPECT_EQ(0, GetInt16(buffer, 0)) << "Wrong data length"; EXPECT_EQ(16, GetInt16(buffer, 2)) << "Wrong data length"; EXPECT_EQ(0, GetInt16(buffer, 8)) << "Wrong LUN1 number"; EXPECT_EQ(0, GetInt16(buffer, 10)) << "Wrong LUN1 number"; EXPECT_EQ(0, GetInt16(buffer, 12)) << "Wrong LUN1 number"; EXPECT_EQ(LUN1, GetInt16(buffer, 14)) << "Wrong LUN1 number"; EXPECT_EQ(0, GetInt16(buffer, 16)) << "Wrong LUN2 number"; EXPECT_EQ(0, GetInt16(buffer, 18)) << "Wrong LUN2 number"; EXPECT_EQ(0, GetInt16(buffer, 20)) << "Wrong LUN2 number"; EXPECT_EQ(LUN2, GetInt16(buffer, 22)) << "Wrong LUN2 number"; controller->SetCmdByte(2, 0x01); EXPECT_THAT([&] { device1->Dispatch(scsi_command::eCmdReportLuns); }, Throws(AllOf( Property(&scsi_exception::get_sense_key, sense_key::illegal_request), Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Only SELECT REPORT mode 0 is supported"; } TEST(PrimaryDeviceTest, Dispatch) { auto [controller, device] = CreatePrimaryDevice(); EXPECT_THROW(device->Dispatch(static_cast(0x1f)), scsi_exception) << "Unknown command"; } TEST(PrimaryDeviceTest, WriteByteSequence) { auto [controller, device] = CreatePrimaryDevice(); EXPECT_FALSE(device->WriteByteSequence({})) << "Primary device does not support writing byte sequences"; } TEST(PrimaryDeviceTest, GetSetSendDelay) { MockPrimaryDevice device(0); EXPECT_EQ(-1, device.GetSendDelay()) << "Wrong delay default value"; device.SetSendDelay(1234); EXPECT_EQ(1234, device.GetSendDelay()); } TEST(PrimaryDeviceTest, Init) { param_map params; MockPrimaryDevice device(0); EXPECT_TRUE(device.Init(params)) << "Initialization of primary device must not fail"; }