scsir 0.3.0

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

use std::vec;

use modular_bitfield_msb::prelude::*;

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

#[derive(Debug)]
pub struct ScsiPorts {
    pub scsi_port_designation_descriptors: Vec<ScsiPortDesignationDescriptor>,
}

#[derive(Debug)]
pub struct ScsiPortDesignationDescriptor {
    pub relative_port_identifier: u16,
    pub initiator_port_transportid: Vec<u8>,
    pub target_port_descriptors: Vec<TargetPortDescriptor>,
}

#[derive(Debug)]
pub struct TargetPortDescriptor {
    pub protocol_identifier: ProtocolIdentifier,
    pub designator_type: u8,
    pub designator: Designator,
}

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

pub fn scsi_ports(this: &mut InquiryCommand) -> crate::Result<ScsiPorts> {
    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) = ScsiPortDesignationDescriptor::from_bytes(bytes);

        descriptors.push(descriptor);
    }

    Ok(ScsiPorts {
        scsi_port_designation_descriptors: descriptors,
    })
}

impl ScsiPortDesignationDescriptor {
    fn from_bytes(bytes: &[u8]) -> (Self, &[u8]) {
        let (array, bytes) = get_array(bytes);
        let descriptor_header = ScsiPortDesignationDescriptorHeader::from_bytes(array);
        let initiator_port_transportid_length =
            descriptor_header.initiator_port_transportid_length() as usize;
        let (initiator_port_transportid_bytes, bytes) =
            bytes.split_at(usize::min(initiator_port_transportid_length, bytes.len()));
        let initiator_port_transportid = initiator_port_transportid_bytes.to_vec();
        let (_, bytes) = get_array::<2>(bytes);
        let (array, bytes) = get_array(bytes);
        let target_port_descriptors_length = u16::from_be_bytes(array) as usize;
        let (mut target_port_descriptors_bytes, bytes) =
            bytes.split_at(usize::min(target_port_descriptors_length, bytes.len()));
        let mut target_port_descriptors = vec![];
        while !target_port_descriptors_bytes.is_empty() {
            let descriptor;
            (descriptor, target_port_descriptors_bytes) =
                TargetPortDescriptor::from_bytes(target_port_descriptors_bytes);
            target_port_descriptors.push(descriptor);
        }

        (
            Self {
                relative_port_identifier: descriptor_header.relative_port_identifier(),
                initiator_port_transportid,
                target_port_descriptors,
            },
            bytes,
        )
    }
}

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

        let protocol_identifier = if descriptor_header.protocol_identifier_valid() != 1
            || (descriptor_header.association() != 1)
        {
            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 (designator_bytes, bytes) = bytes.split_at(usize::min(
            descriptor_header.designator_length() as usize,
            bytes.len(),
        ));

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

        (
            TargetPortDescriptor {
                protocol_identifier,
                designator_type: descriptor_header.designator_type(),
                designator,
            },
            bytes,
        )
    }
}

const PAGE_CODE: u8 = 0x88;

#[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)]
pub struct ScsiPortDesignationDescriptorHeader {
    reserved_0: B16,
    relative_port_identifier: B16,
    reserved_1: B16,
    initiator_port_transportid_length: B16,
}

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

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

    const PAGE_HEADER_LENGTH: usize = 4;
    const SCSI_PORT_DESIGNATION_DESCRIPTOR_HEADER_LENGTH: usize = 8;
    const TARGET_PORT_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::<ScsiPortDesignationDescriptorHeader>(),
            SCSI_PORT_DESIGNATION_DESCRIPTOR_HEADER_LENGTH,
            concat!("Size of: ", stringify!(ScsiPortDesignationDescriptorHeader))
        );

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