use anyhow::{Result, bail};
pub const INQUIRY_OPCODE: u8 = 0x12;
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum VpdPage {
SupportedPages = 0x00,
UnitSerial = 0x80,
DeviceId = 0x83,
ExtendedInquiry = 0x86,
BlockLimits = 0xB0, BlockDeviceCharacteristics = 0xB1, LbProvisioning = 0xB2, }
impl From<VpdPage> for u8 {
#[inline]
fn from(p: VpdPage) -> u8 {
p as u8
}
}
impl TryFrom<u8> for VpdPage {
type Error = anyhow::Error;
#[inline]
fn try_from(v: u8) -> Result<Self> {
use VpdPage::*;
Ok(match v {
0x00 => SupportedPages,
0x80 => UnitSerial,
0x83 => DeviceId,
0x86 => ExtendedInquiry,
0xB0 => BlockLimits,
0xB1 => BlockDeviceCharacteristics,
0xB2 => LbProvisioning,
_ => bail!("invalid vpd page: {v}"),
})
}
}
#[inline]
pub fn fill_inquiry_standard(cdb: &mut [u8; 16], allocation_len: u8, control: u8) {
cdb.fill(0);
cdb[0] = INQUIRY_OPCODE;
cdb[1] = 0x00; cdb[2] = 0x00; cdb[3] = 0x00;
cdb[4] = allocation_len;
cdb[5] = control;
}
#[inline]
pub fn fill_inquiry_standard_simple(cdb: &mut [u8; 16], allocation_len: u8) {
fill_inquiry_standard(cdb, allocation_len, 0x00)
}
#[inline]
pub fn fill_inquiry_vpd(
cdb: &mut [u8; 16],
page: VpdPage,
allocation_len: u8,
control: u8,
) {
fill_inquiry_vpd_with_subpage(cdb, page, 0x00, allocation_len, control)
}
#[inline]
pub fn fill_inquiry_vpd_with_subpage(
cdb: &mut [u8; 16],
page: VpdPage,
subpage_code: u8,
allocation_len: u8,
control: u8,
) {
cdb.fill(0);
cdb[0] = INQUIRY_OPCODE;
cdb[1] = 0x01; cdb[2] = page.into();
cdb[3] = subpage_code;
cdb[4] = allocation_len;
cdb[5] = control;
}
#[inline]
pub fn fill_inquiry_vpd_simple(
cdb: &mut [u8; 16],
page_code: VpdPage,
allocation_len: u8,
) {
fill_inquiry_vpd(cdb, page_code, allocation_len, 0x00)
}
#[derive(Debug, Clone)]
pub struct InquiryStandard {
pub peripheral_qualifier: u8, pub device_type: u8, pub rmb: bool, pub version: u8, pub response_data_format: u8, pub additional_length: u8, pub vendor_id: String, pub product_id: String, pub product_rev: String, }
impl InquiryStandard {
pub fn device_type_str(&self) -> &'static str {
match self.device_type {
0x00 => "Direct-access block (disk)",
0x01 => "Sequential-access (tape)",
0x02 => "Printer (obsolete)",
0x03 => "Processor",
0x04 => "WORM",
0x05 => "CD/DVD",
0x06 => "Scanner (obsolete)",
0x07 => "Optical memory",
0x08 => "Medium changer",
0x09 => "Communications",
0x0C => "Storage array controller",
0x0D => "Enclosure services",
0x0E => "RBC",
0x0F => "Optical card",
0x11 => "Object-based storage",
0x12 => "Automation/Drive Interface",
_ => "Unknown/Reserved",
}
}
}
pub fn parse_inquiry_standard(buf: &[u8]) -> Result<InquiryStandard> {
if buf.len() < 36 {
bail!("INQUIRY buffer too short: {}", buf.len());
}
let b0 = buf[0];
let b1 = buf[1];
let b3 = buf[3];
let peripheral_qualifier = (b0 >> 5) & 0x07;
let device_type = b0 & 0x1F;
let rmb = (b1 & 0x80) != 0;
let version = buf[2];
let response_data_format = b3 & 0x0F;
let additional_length = buf[4];
Ok(InquiryStandard {
peripheral_qualifier,
device_type,
rmb,
version,
response_data_format,
additional_length,
vendor_id: trim_ascii(&buf[8..16]),
product_id: trim_ascii(&buf[16..32]),
product_rev: trim_ascii(&buf[32..36]),
})
}
fn vpd_payload(buf: &[u8]) -> Result<(u8, &[u8])> {
if buf.len() < 4 {
bail!("VPD buffer too short: {}", buf.len());
}
let page_code = buf[1];
let len = u16::from_be_bytes([buf[2], buf[3]]) as usize;
if buf.len() < 4 + len {
bail!(
"VPD truncated: header says {} bytes, have {}",
len,
buf.len().saturating_sub(4)
);
}
Ok((page_code, &buf[4..4 + len]))
}
pub fn parse_vpd_supported_pages(buf: &[u8]) -> Result<Vec<u8>> {
let (pc, payload) = vpd_payload(buf)?;
if pc != 0x00 {
bail!("expected VPD page 0x00, got 0x{:02X}", pc);
}
Ok(payload.to_vec()) }
pub fn parse_vpd_unit_serial(buf: &[u8]) -> Result<String> {
let (pc, payload) = vpd_payload(buf)?;
if pc != 0x80 {
bail!("expected VPD page 0x80, got 0x{:02X}", pc);
}
Ok(trim_ascii(payload))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeviceIdDescriptor {
pub code_set: u8,
pub piv: bool,
pub association: u8,
pub id_type: u8,
pub identifier: String,
}
pub fn parse_vpd_device_id(buf: &[u8]) -> Result<Vec<DeviceIdDescriptor>> {
let (pc, p) = vpd_payload(buf)?;
if pc != 0x83 {
bail!("expected VPD page 0x83, got 0x{:02X}", pc);
}
let mut out = Vec::new();
let mut off = 0usize;
while off + 4 <= p.len() {
let b0 = p[off];
let b1 = p[off + 1];
let len = u16::from_be_bytes([p[off + 2], p[off + 3]]) as usize;
let start = off + 4;
let end = start.saturating_add(len);
if end > p.len() {
break;
}
let code_set = b0 & 0x0F;
let piv = (b1 & 0x80) != 0;
let association = (b1 >> 4) & 0x03;
let id_type = b1 & 0x0F;
let id_bytes = &p[start..end];
let identifier = match code_set {
0x02 => trim_ascii(id_bytes), 0x03 => String::from_utf8_lossy(id_bytes).trim().to_string(), _ => hex_bytes(id_bytes),
};
out.push(DeviceIdDescriptor {
code_set,
piv,
association,
id_type,
identifier,
});
off = end;
}
Ok(out)
}
fn trim_ascii(bytes: &[u8]) -> String {
let s: String = bytes
.iter()
.map(|&b| if b.is_ascii() { b as char } else { '?' })
.collect();
s.trim().to_string()
}
fn hex_bytes(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
use core::fmt::Write;
let _ = write!(&mut s, "{:02X}", b);
}
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_std_inquiry_min() {
let mut b = [0u8; 36];
b[0] = 0x00; b[1] = 0x00;
b[2] = 0x06; b[3] = 0x02; b[4] = 31; b[8..16].copy_from_slice(b"LIO-ORG ");
b[16..32].copy_from_slice(b"TCMU device ");
b[32..36].copy_from_slice(b"0020");
let s = parse_inquiry_standard(&b).expect("WTF");
assert_eq!(s.device_type, 0x00);
assert_eq!(s.vendor_id, "LIO-ORG");
assert_eq!(s.product_id, "TCMU device");
assert_eq!(s.product_rev, "0020");
}
#[test]
fn parse_vpd_supported() {
let b = [0x00, 0x00, 0x00, 0x03, 0x00, 0x80, 0x83];
let mut buf = Vec::new();
buf.extend_from_slice(&b);
let pages = parse_vpd_supported_pages(&buf).expect("WTF");
assert_eq!(pages, vec![0x00, 0x80, 0x83]);
}
#[test]
fn parse_vpd_device_id_basic() {
let mut payload = vec![0x02, 0x00, 0x00, 0x04];
payload.extend_from_slice(b"ABCD");
let mut buf = vec![0x00, 0x83, 0x00, payload.len() as u8];
buf.extend_from_slice(&payload);
let v = parse_vpd_device_id(&buf).expect("WTF");
assert_eq!(v.len(), 1);
assert_eq!(v[0].identifier, "ABCD");
assert_eq!(v[0].code_set, 0x02);
assert_eq!(v[0].id_type, 0x00);
}
}