scsir 0.3.0

A simple library for issuing SCSI commands
Documentation
#![allow(dead_code)]

use modular_bitfield_msb::prelude::*;

use crate::{
    command::{get_array, inquiry::InquiryCommand},
    data_wrapper::FlexibleStruct,
    shortcut::inquiry::ProtocolIdentifier,
};

#[derive(Debug)]
pub struct DeviceIdentification {
    pub descriptors: Vec<IdentificationDescriptor>,
}

#[derive(Debug)]
pub struct IdentificationDescriptor {
    pub protocol_identifier: ProtocolIdentifier,
    pub association: Association,
    pub identifier_type: u8,
    pub identifier: Identifier,
}

#[derive(Debug)]
pub enum Identifier {
    Binary(Vec<u8>),
    Ascii(String),
    Unknown(Vec<u8>),
}

#[derive(Debug)]
pub enum Association {
    AddressedPhysicalOrLogicalDevice,
    PortThatReceivedTheRequest,
    ScsiTargetDeviceThatContainsTheAddressedLogicalUnit,
    Other(u8),
}

pub fn device_identification(this: &mut InquiryCommand) -> crate::Result<DeviceIdentification> {
    this.page_code(Some(PAGE_CODE));

    let result: FlexibleStruct<PageHeader, u8> = this.issue_flex(0)?;
    let remaining = result.get_body().page_length();
    let result = if remaining == 0 {
        result
    } else {
        this.issue_flex(remaining as usize)?
    };

    let mut bytes = unsafe { result.elements_as_slice() };
    let mut descriptors = vec![];

    while !bytes.is_empty() {
        let descriptor;
        (descriptor, bytes) = IdentificationDescriptor::from_bytes(bytes);
        descriptors.push(descriptor);
    }

    Ok(DeviceIdentification { descriptors })
}

impl IdentificationDescriptor {
    fn from_bytes(bytes: &[u8]) -> (Self, &[u8]) {
        let (array, bytes) = get_array(bytes);
        let descriptor_header = DescriptorHeader::from_bytes(array);

        let protocol_identifier = if descriptor_header.protocol_identifier_valid() == 0
            || (descriptor_header.association() != 1 && descriptor_header.association() != 2)
        {
            ProtocolIdentifier::None
        } else {
            match descriptor_header.protocol_identifier() {
                0x0 => ProtocolIdentifier::FibreChannel,
                0x2 => ProtocolIdentifier::Ssa,
                0x3 => ProtocolIdentifier::IEEE1394,
                0x4 => ProtocolIdentifier::RemoteDirectMemoryAccess,
                0x5 => ProtocolIdentifier::InternetScsi,
                0x6 => ProtocolIdentifier::SasSerialScsiProtocol,
                other => ProtocolIdentifier::Other(other),
            }
        };

        let association = match descriptor_header.association() {
            0x0 => Association::AddressedPhysicalOrLogicalDevice,
            0x1 => Association::PortThatReceivedTheRequest,
            0x2 => Association::ScsiTargetDeviceThatContainsTheAddressedLogicalUnit,
            other => Association::Other(other),
        };

        let (identifier_bytes, bytes) = bytes.split_at(usize::min(
            descriptor_header.identifier_length() as usize,
            bytes.len(),
        ));

        let identifier = match descriptor_header.code_set() {
            0x1 => Identifier::Binary(identifier_bytes.to_owned()),
            0x2 => Identifier::Ascii(String::from_utf8_lossy(identifier_bytes).to_string()),
            _ => Identifier::Unknown(identifier_bytes.to_owned()),
        };

        (
            IdentificationDescriptor {
                protocol_identifier,
                association,
                identifier_type: descriptor_header.identifier_type(),
                identifier,
            },
            bytes,
        )
    }
}

const PAGE_CODE: u8 = 0x83;

#[bitfield]
#[derive(Clone, Copy, Debug)]
struct PageHeader {
    peripheral_qualifier: B3,
    peripheral_device_type: B5,
    page_code: B8,
    page_length: B16,
}

#[bitfield]
#[derive(Clone, Copy, Debug)]
struct DescriptorHeader {
    protocol_identifier: B4,
    code_set: B4,
    protocol_identifier_valid: B1,
    reserved_0: B1,
    association: B2,
    identifier_type: B4,
    reserved_1: B8,
    identifier_length: B8,
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::mem::size_of;

    const PAGE_HEADER_LENGTH: usize = 4;
    const DESCRIPTOR_HEADER_LENGTH: usize = 4;

    #[test]
    fn layout_test() {
        assert_eq!(
            size_of::<PageHeader>(),
            PAGE_HEADER_LENGTH,
            concat!("Size of: ", stringify!(PageHeader))
        );

        assert_eq!(
            size_of::<DescriptorHeader>(),
            DESCRIPTOR_HEADER_LENGTH,
            concat!("Size of: ", stringify!(DescriptorHeader))
        );
    }
}