From 4942504a9a2977d49989a5e5b7c1c8e07dc0fa41 Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Mon, 3 Feb 2025 13:16:49 +0100 Subject: [PATCH] rust/pv*: Split pvapconfig::ap to pv_core::apdevice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move appropriate parts into new pv_core::ap module. Signed-off-by: Jakob Naucke Reviewed-by: Steffen Eiden Signed-off-by: Jan Höppner --- rust/Cargo.lock | 1 + rust/pv_core/Cargo.toml | 1 + rust/pv_core/src/apdevice.rs | 468 ++++++++++++++++++++++++++++++++++ rust/pv_core/src/lib.rs | 25 ++ rust/pvapconfig/src/ap.rs | 482 +---------------------------------- rust/pvapconfig/src/main.rs | 69 ++--- 6 files changed, 544 insertions(+), 502 deletions(-) create mode 100644 rust/pv_core/src/apdevice.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 4b19cc50..609fa631 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -805,6 +805,7 @@ dependencies = [ "lazy_static", "libc", "log", + "regex", "serde", "serde_test", "thiserror", diff --git a/rust/pv_core/Cargo.toml b/rust/pv_core/Cargo.toml index 607b5b39..e9c0d103 100644 --- a/rust/pv_core/Cargo.toml +++ b/rust/pv_core/Cargo.toml @@ -22,6 +22,7 @@ thiserror = "2.0.11" zerocopy = {version = "0.7", features = ["derive"]} serde = { version = "1.0.217", features = ["derive"]} byteorder = "1.5" +regex = "1.10" [dev-dependencies] serde_test = "1.0.177" diff --git a/rust/pv_core/src/apdevice.rs b/rust/pv_core/src/apdevice.rs new file mode 100644 index 00000000..0c957db4 --- /dev/null +++ b/rust/pv_core/src/apdevice.rs @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 +// +//! AP support functions +// + +use crate::{ + utils::{pv_guest_bit_set, read_file_string, write_file}, + Error, Result, +}; +use regex::Regex; +use std::fmt; +use std::thread; +use std::time; + +const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap"; + +/// Regular expression for AP queue directories +pub const RE_QUEUE_DIR: &str = r"^([[:xdigit:]]{2})\.([[:xdigit:]]{4})$"; +const RE_CARD_TYPE: &str = r"^CEX([3-8])([ACP])$"; +const RE_EP11_MKVP: &str = r"WK\s+CUR:\s+(\S+)\s+(\S+)"; +const RE_CCA_AES_MKVP: &str = r"AES\s+CUR:\s+(\S+)\s+(\S+)"; +const RE_CCA_APKA_MKVP: &str = r"APKA\s+CUR:\s+(\S+)\s+(\S+)"; + +const SYS_BUS_AP_BIND_POLL_MS: u64 = 500; +const SYS_BUS_AP_BIND_TIMEOUT_MS: u64 = 10000; + +const SYS_BUS_AP_ASSOC_POLL_MS: u64 = 500; +const SYS_BUS_AP_ASSOC_TIMEOUT_MS: u64 = 10000; + +/// APQN mode +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ApqnMode { + /// Accelerator mode + Accel, + /// EP11 (Enterprise PKCS #11) coprocessor mode + Ep11, + /// Common Cryptographic Architecture (CCA) coprocessor mode + Cca, +} + +/// Info on an APQN configured for accelerator +#[derive(Debug, Clone)] +pub struct ApqnInfoAccel { + // empty +} + +/// Info on an APQN configured for EP11 coprocessor +#[derive(Debug, Clone)] +pub struct ApqnInfoEp11 { + /// Serial number of the Crypto Express adapter as a case-sensitive ASCII string + pub serialnr: String, + /// Master key verification pattern as hex string + pub mkvp: String, // may be an empty string if no WK set +} + +/// Info on an APQN configured for CCA coprocessor +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct ApqnInfoCca { + /// Serial number of the Crypto Express adapter as a case-sensitive ASCII string + pub serialnr: String, + /// Master key verification pattern as hex string for AES + pub mkvp_aes: String, // may be an empty string if no MK set + /// Master key verification pattern as hex string for asymmetric public key algorithms + pub mkvp_apka: String, // may be an empty string if no MK set +} + +/// Info for an APQN's mode +#[derive(Debug, Clone)] +pub enum ApqnInfo { + /// Info on an APQN configured for accelerator + Accel(ApqnInfoAccel), + /// Info on an APQN configured for EP11 coprocessor + Ep11(ApqnInfoEp11), + /// Info on an APQN configured for CCA coprocessor + #[allow(dead_code)] + Cca(ApqnInfoCca), +} + +macro_rules! parse_error { + ($subject:expr, $content:expr) => { + Error::ParseError { + subject: $subject, + content: $content, + } + }; +} + +impl ApqnInfo { + fn accel_info(_carddir: &str, _queuedir: &str) -> Result { + Ok(Self::Accel(ApqnInfoAccel {})) + } + + fn cca_info(carddir: &str, queuedir: &str) -> Result { + let serialnr_str = read_file_string(format!("{carddir}/serialnr"), "serialnr")?; + let serialnr = serialnr_str.trim().to_string(); + let mkvps = read_file_string(format!("{carddir}/{queuedir}/mkvps"), "mkvps")?; + let mut aes_mkvp = String::new(); + let re_cca_aes_mkvp = Regex::new(RE_CCA_AES_MKVP).unwrap(); + if !re_cca_aes_mkvp.is_match(&mkvps) { + return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps)); + } else { + let caps = re_cca_aes_mkvp.captures(&mkvps).unwrap(); + if caps.get(1).unwrap().as_str().to_lowercase() == "valid" { + aes_mkvp = caps.get(2).unwrap().as_str().to_lowercase(); + if aes_mkvp.starts_with("0x") { + aes_mkvp = String::from(&aes_mkvp[2..]); + } + } + } + let mut apka_mkvp = String::new(); + let re_cca_apka_mkvp = Regex::new(RE_CCA_APKA_MKVP).unwrap(); + if !re_cca_apka_mkvp.is_match(&mkvps) { + return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps)); + } else { + let caps = re_cca_apka_mkvp.captures(&mkvps).unwrap(); + if caps.get(1).unwrap().as_str().to_lowercase() == "valid" { + apka_mkvp = caps.get(2).unwrap().as_str().to_lowercase(); + if apka_mkvp.starts_with("0x") { + apka_mkvp = String::from(&apka_mkvp[2..]); + } + } + } + Ok(Self::Cca(ApqnInfoCca { + serialnr, + mkvp_aes: aes_mkvp, + mkvp_apka: apka_mkvp, + })) + } + + fn ep11_info(carddir: &str, queuedir: &str) -> Result { + let serialnr_str = read_file_string(format!("{carddir}/serialnr"), "serialnr")?; + let serialnr = serialnr_str.trim().to_string(); + let mkvps = read_file_string(format!("{carddir}/{queuedir}/mkvps"), "mkvps")?; + let mut mkvp = String::new(); + let re_ep11_mkvp = Regex::new(RE_EP11_MKVP).unwrap(); + if !re_ep11_mkvp.is_match(&mkvps) { + return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps)); + } else { + let caps = re_ep11_mkvp.captures(&mkvps).unwrap(); + if caps.get(1).unwrap().as_str().to_lowercase() == "valid" { + mkvp = caps.get(2).unwrap().as_str().to_lowercase(); + if mkvp.starts_with("0x") { + mkvp = String::from(&mkvp[2..]); + } + if mkvp.len() > 32 { + mkvp = String::from(&mkvp[..32]) + } + } + } + Ok(Self::Ep11(ApqnInfoEp11 { serialnr, mkvp })) + } + + /// Get mode-specific info + pub fn info(mode: &ApqnMode, carddir: &str, queuedir: &str) -> Result { + match mode { + ApqnMode::Accel => Self::accel_info(carddir, queuedir), + ApqnMode::Cca => Self::cca_info(carddir, queuedir), + ApqnMode::Ep11 => Self::ep11_info(carddir, queuedir), + } + } +} + +/// `Apqn` encodes an adjunct processor queue number. +#[derive(Debug, Clone)] +pub struct Apqn { + /// Name of the APQN + #[allow(dead_code)] + pub name: String, + /// Card number + pub card: u32, + /// Domain number + pub domain: u32, + /// CryptoExpress generation + pub gen: u32, + /// Mode that adapter is configured to use + pub mode: ApqnMode, + /// Mode-specific info + pub info: Option, +} + +impl TryFrom<&str> for Apqn { + type Error = Error; + + /// Create an `Apqn` struct from a CARD.DOMAIN-formatted APQN + /// string, such as `28.0014`. Will not populate `info` upon + /// failure to read it. Other failures to read required information + /// are treated as an Error. + /// # Panics + /// Panics if the compilation of a static regular expression fails + /// or a regex capture that is already format-checked does not + /// parse, e.g. when the capture `([[:xdigit:]]{2})` does not + /// parse as hex string. + fn try_from(name: &str) -> Result { + let re_card_type = Regex::new(RE_CARD_TYPE).unwrap(); + let re_queue_dir = Regex::new(RE_QUEUE_DIR).unwrap(); + + let caps = re_queue_dir + .captures(name) + .ok_or_else(|| parse_error!("queue".to_string(), name.to_string()))?; + let cardstr = caps.get(1).unwrap().as_str(); + let card = u32::from_str_radix(cardstr, 16).unwrap(); + let domstr = caps.get(2).unwrap().as_str(); + let domain = u32::from_str_radix(domstr, 16).unwrap(); + + let path = format!("{PATH_SYS_DEVICES_AP}/card{cardstr}"); + let card_type = + read_file_string(format!("{path}/type"), "card type").map(|s| s.trim().to_string())?; + let caps = re_card_type + .captures(&card_type) + .ok_or_else(|| parse_error!("card type".to_string(), card_type.to_string()))?; + let gen = caps.get(1).unwrap().as_str().parse::().unwrap(); + let mode = match caps.get(2).unwrap().as_str().parse::().unwrap() { + 'A' => ApqnMode::Accel, + 'C' => ApqnMode::Cca, + 'P' => ApqnMode::Ep11, + _ => unreachable!("Code inconsistency between regex RE_CARD_TYPE and evaluation code."), + }; + // the UV blocks requests to CCA cards within SE guest with AP + // pass-through support. However, filter out CCA cards as + // these cards cause hangs during information gathering. + if mode == ApqnMode::Cca && pv_guest_bit_set() { + return Err(Error::CcaSeIncompatible(card)); + } + + match read_file_string(format!("{path}/{name}/online"), "AP queue online status") + .map(|s| s.trim().parse::()) + { + Ok(Ok(1)) => {} + _ => return Err(Error::ApOffline { card, domain }), + } + // For the MKVP and serialnr to fetch from the APQN within a SE + // guest the APQN needs to be bound to the guest. So if the APQN + // is not bound, temporarily bind it here until the info has + // been retrieved. + let mut tempbound = false; + if pv_guest_bit_set() { + let cbs = get_apqn_bind_state(card, domain)?; + if cbs == BindState::Unbound { + set_apqn_bind_state(card, domain, BindState::Bound)?; + tempbound = true; + } + } + let info = ApqnInfo::info(&mode, &path, name).ok(); + if tempbound { + set_apqn_bind_state(card, domain, BindState::Unbound)?; + } + + Ok(Apqn { + name: name.to_string(), + card, + domain, + gen, + mode, + info, + }) + } +} + +impl fmt::Display for Apqn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({},{})", self.card, self.domain) + } +} + +impl Apqn { + /// Read bind state of the APQN. + pub fn bind_state(&self) -> Result { + get_apqn_bind_state(self.card, self.domain) + } + + /// Set bind state of the APQN. + pub fn set_bind_state(&self, state: BindState) -> Result<()> { + set_apqn_bind_state(self.card, self.domain, state) + } + + /// Read associate state of the APQN. + pub fn associate_state(&self) -> Result { + get_apqn_associate_state(self.card, self.domain) + } + + /// Set associate state of the APQN. + pub fn set_associate_state(&self, state: AssocState) -> Result<()> { + set_apqn_associate_state(self.card, self.domain, state) + } +} + +/// Bind state of an APQN +#[derive(Debug, PartialEq, Eq)] +pub enum BindState { + /// APQN is bound + Bound, + /// APQN is unbound + Unbound, + /// APQN does not support bind + NotSupported, +} + +/// Query bind state for this APQN. +/// +/// Returns a BindState enum as defined above or on failure +/// an error string. Does NOT print any error messages. +pub fn get_apqn_bind_state(card: u32, dom: u32) -> Result { + let path = format!( + "{}/card{:02x}/{:02x}.{:04x}/se_bind", + PATH_SYS_DEVICES_AP, card, card, dom + ); + let state_str = read_file_string(path, "se_bind attribute")?; + let state = state_str.trim(); + match state { + "bound" => Ok(BindState::Bound), + "unbound" => Ok(BindState::Unbound), + "-" => Ok(BindState::NotSupported), + _ => Err(Error::UnknownBindState(state.to_string())), + } +} + +/// Bind or unbind an APQN. +/// +/// The action is determined by the BindState given in. +/// But of course only Bound and Unbound is supported - otherwise +/// this function panics! +/// The function actively loops over the bind state until +/// the requested bind state is reached or a timeout has +/// occurred (SYS_BUS_AP_BIND_TIMEOUT_MS). +/// On success () is returned, on failure an error string +/// is returned. Does NOT print any error messages. +/// # Panics +/// Panics if a desired bind state other than Bound or Unbound is given. +pub fn set_apqn_bind_state(card: u32, dom: u32, state: BindState) -> Result<()> { + let ctx = "bind APQN"; + let path = format!( + "{}/card{:02x}/{:02x}.{:04x}/se_bind", + PATH_SYS_DEVICES_AP, card, card, dom + ); + match state { + BindState::Bound => write_file(path, 1.to_string(), ctx), + BindState::Unbound => write_file(path, 0.to_string(), ctx), + _ => panic!("set_apqn_bind_state called with invalid BindState."), + }?; + let mut ms: u64 = 0; + loop { + thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BIND_POLL_MS)); + ms += SYS_BUS_AP_BIND_POLL_MS; + if ms >= SYS_BUS_AP_BIND_TIMEOUT_MS { + break Err(Error::Timeout(format!( + "setting APQN({card},{dom}) bind state" + ))); + } + let newstate = get_apqn_bind_state(card, dom)?; + if newstate == state { + return Ok(()); + } + } +} + +/// Association state of an APQN +#[derive(Debug, PartialEq, Eq)] +pub enum AssocState { + /// Associated with index + Associated(u16), + /// Association pending + AssociationPending, + /// Not associated + Unassociated, + /// APQN does not support association + NotSupported, +} + +/// Query association state for this APQN. +/// +/// Returns an AssocState enum as defined above or on failure +/// an error string. Does NOT print any error messages. +pub fn get_apqn_associate_state(card: u32, dom: u32) -> Result { + let path = format!( + "{}/card{:02x}/{:02x}.{:04x}/se_associate", + PATH_SYS_DEVICES_AP, card, card, dom + ); + let state_str = read_file_string(path, "se_associate attribute")?; + let state = state_str.trim(); + match state.strip_prefix("associated ") { + Some(prefix) => Ok(AssocState::Associated(prefix.parse()?)), + _ => match state { + "association pending" => Ok(AssocState::AssociationPending), + "unassociated" => Ok(AssocState::Unassociated), + "-" => Ok(AssocState::NotSupported), + _ => Err(Error::UnknownAssocState(state.to_string())), + }, + } +} + +fn set_apqn_associate_state_associate(card: u32, dom: u32, idx: u16) -> Result<()> { + let path = format!( + "{}/card{:02x}/{:02x}.{:04x}/se_associate", + PATH_SYS_DEVICES_AP, card, card, dom + ); + write_file(path, idx.to_string(), "associate APQN")?; + let mut ms: u64 = 0; + loop { + thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS)); + ms += SYS_BUS_AP_ASSOC_POLL_MS; + if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS { + break Err(Error::Timeout(format!( + "setting APQN({card},{dom}) association index {idx} state", + ))); + } + match get_apqn_associate_state(card, dom)? { + AssocState::Associated(i) if i == idx => return Ok(()), + AssocState::Associated(i) => { + return Err(Error::WrongAssocState { + card, + domain: dom, + desired: idx, + actual: i, + }) + } + _ => {} + } + } +} + +fn set_apqn_associate_state_unbind(card: u32, dom: u32) -> Result<()> { + let bindpath = format!( + "{}/card{:02x}/{:02x}.{:04x}/se_bind", + PATH_SYS_DEVICES_AP, card, card, dom + ); + write_file(bindpath, 0.to_string(), "unbind APQN")?; + let mut ms: u64 = 0; + loop { + thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS)); + ms += SYS_BUS_AP_ASSOC_POLL_MS; + if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS { + break Err(Error::Timeout(format!( + "setting APQN({card},{dom}) association unbind state", + ))); + } + let newstate = get_apqn_associate_state(card, dom)?; + if newstate == AssocState::Unassociated { + return Ok(()); + } + } +} + +/// Associate or Unassociate an APQN. +/// +/// The action is determined by the AssocState given in. +/// But of course only Associated and Unassociated is supported +/// otherwise this function panics! +/// The function actively loops over the association state until +/// the requested state is reached or a timeout has +/// occurred (SYS_BUS_AP_ASSOC_TIMEOUT_MS). +/// The unassociate is in fact a unbind. So the code triggers +/// an unbind and then loops over the sysfs se_associate until +/// "unassociated" is reached. +/// On success () is returned, on failure an error string +/// is returned. Does NOT print any error messages. +/// # Panics +/// Panics if a desired bind state other than Associated or +/// Unassociated is given. +pub fn set_apqn_associate_state(card: u32, dom: u32, state: AssocState) -> Result<()> { + match state { + AssocState::Associated(idx) => set_apqn_associate_state_associate(card, dom, idx), + AssocState::Unassociated => set_apqn_associate_state_unbind(card, dom), + _ => panic!("set_apqn_associate_state called with invalid AssocState."), + } +} diff --git a/rust/pv_core/src/lib.rs b/rust/pv_core/src/lib.rs index 53978145..03360fbc 100644 --- a/rust/pv_core/src/lib.rs +++ b/rust/pv_core/src/lib.rs @@ -2,6 +2,7 @@ // // Copyright IBM Corp. 2023, 2024 #![doc = include_str!("../README.md")] +mod apdevice; mod confidential; mod error; mod macros; @@ -72,5 +73,29 @@ pub mod secret { pub use crate::uvsecret::UserDataType; } +/// Functionalities for the AP bus +pub mod ap { + pub use crate::apdevice::Apqn; + pub use crate::apdevice::RE_QUEUE_DIR; + pub use crate::apdevice::{get_apqn_bind_state, set_apqn_bind_state}; + /// AP modes + pub mod apqn_mode { + pub use crate::apdevice::ApqnMode::{self, *}; + } + /// AP info for each state + pub mod apqn_info { + pub use crate::apdevice::ApqnInfo::{self, *}; + pub use crate::apdevice::{ApqnInfoAccel, ApqnInfoCca, ApqnInfoEp11}; + } + /// AP bind states + pub mod bind_state { + pub use crate::apdevice::BindState::{self, *}; + } + /// AP association states + pub mod assoc_state { + pub use crate::apdevice::AssocState::{self, *}; + } +} + // Internal definitions/ imports const PAGESIZE: usize = 0x1000; diff --git a/rust/pvapconfig/src/ap.rs b/rust/pvapconfig/src/ap.rs index 50807c37..7880db32 100644 --- a/rust/pvapconfig/src/ap.rs +++ b/rust/pvapconfig/src/ap.rs @@ -6,38 +6,22 @@ // use crate::helper::*; -use pv_core::{ - misc::{read_file_string, write_file}, - Error, -}; -use regex::Regex; -use std::fmt; +use pv_core::ap::*; +use pv_core::misc::read_file_string; use std::path::Path; use std::slice::Iter; use std::thread; use std::time; +const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$"; + const PATH_SYS_BUS_AP: &str = "/sys/bus/ap"; const PATH_SYS_BUS_AP_FEATURES: &str = "/sys/bus/ap/features"; const PATH_SYS_BUS_AP_BINDINGS: &str = "/sys/bus/ap/bindings"; const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap"; -const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$"; -/// Regular expression for AP queue directories -pub const RE_QUEUE_DIR: &str = r"^([[:xdigit:]]{2})\.([[:xdigit:]]{4})$"; -const RE_CARD_TYPE: &str = r"^CEX([3-8])([ACP])$"; -const RE_EP11_MKVP: &str = r"WK\s+CUR:\s+(\S+)\s+(\S+)"; -const RE_CCA_AES_MKVP: &str = r"AES\s+CUR:\s+(\S+)\s+(\S+)"; -const RE_CCA_APKA_MKVP: &str = r"APKA\s+CUR:\s+(\S+)\s+(\S+)"; - const SYS_BUS_AP_BINDINGS_POLL_MS: u64 = 500; -const SYS_BUS_AP_BIND_POLL_MS: u64 = 500; -const SYS_BUS_AP_BIND_TIMEOUT_MS: u64 = 10000; - -const SYS_BUS_AP_ASSOC_POLL_MS: u64 = 500; -const SYS_BUS_AP_ASSOC_TIMEOUT_MS: u64 = 10000; - /// Check if AP bus support is available. /// Returns Result with Ok(()) or Err(failurestring). pub fn check_ap_bus_support() -> Result<(), String> { @@ -97,264 +81,6 @@ pub fn wait_for_ap_bus_bindings_complete() -> bool { } } -/// APQN mode -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ApqnMode { - /// Accelerator mode - Accel, - /// EP11 (Enterprise PKCS #11) coprocessor mode - Ep11, - /// Common Cryptographic Architecture (CCA) coprocessor mode - Cca, -} - -/// Info on an APQN configured for accelerator -#[derive(Debug, Clone)] -pub struct ApqnInfoAccel { - // empty -} - -/// Info on an APQN configured for EP11 coprocessor -#[derive(Debug, Clone)] -pub struct ApqnInfoEp11 { - /// Serial number of the Crypto Express adapter as a case-sensitive ASCII string - pub serialnr: String, - /// Master key verification pattern as hex string - pub mkvp: String, // may be an empty string if no WK set -} - -/// Info on an APQN configured for CCA coprocessor -#[derive(Debug, Clone)] -#[allow(dead_code)] -pub struct ApqnInfoCca { - /// Serial number of the Crypto Express adapter as a case-sensitive ASCII string - pub serialnr: String, - /// Master key verification pattern as hex string for AES - pub mkvp_aes: String, // may be an empty string if no MK set - /// Master key verification pattern as hex string for asymmetric public key algorithms - pub mkvp_apka: String, // may be an empty string if no MK set -} - -/// Info for an APQN's mode -#[derive(Debug, Clone)] -pub enum ApqnInfo { - /// Info on an APQN configured for accelerator - Accel(ApqnInfoAccel), - /// Info on an APQN configured for EP11 coprocessor - Ep11(ApqnInfoEp11), - /// Info on an APQN configured for CCA coprocessor - #[allow(dead_code)] - Cca(ApqnInfoCca), -} - -macro_rules! parse_error { - ($subject:expr, $content:expr) => { - Error::ParseError { - subject: $subject, - content: $content, - } - }; -} - -impl ApqnInfo { - fn accel_info(_carddir: &str, _queuedir: &str) -> pv_core::Result { - Ok(Self::Accel(ApqnInfoAccel {})) - } - - fn cca_info(carddir: &str, queuedir: &str) -> pv_core::Result { - let serialnr_str = read_file_string(format!("{carddir}/serialnr"), "serialnr")?; - let serialnr = serialnr_str.trim().to_string(); - let mkvps = read_file_string(format!("{carddir}/{queuedir}/mkvps"), "mkvps")?; - let mut aes_mkvp = String::new(); - let re_cca_aes_mkvp = Regex::new(RE_CCA_AES_MKVP).unwrap(); - if !re_cca_aes_mkvp.is_match(&mkvps) { - return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps)); - } else { - let caps = re_cca_aes_mkvp.captures(&mkvps).unwrap(); - if caps.get(1).unwrap().as_str().to_lowercase() == "valid" { - aes_mkvp = caps.get(2).unwrap().as_str().to_lowercase(); - if aes_mkvp.starts_with("0x") { - aes_mkvp = String::from(&aes_mkvp[2..]); - } - } - } - let mut apka_mkvp = String::new(); - let re_cca_apka_mkvp = Regex::new(RE_CCA_APKA_MKVP).unwrap(); - if !re_cca_apka_mkvp.is_match(&mkvps) { - return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps)); - } else { - let caps = re_cca_apka_mkvp.captures(&mkvps).unwrap(); - if caps.get(1).unwrap().as_str().to_lowercase() == "valid" { - apka_mkvp = caps.get(2).unwrap().as_str().to_lowercase(); - if apka_mkvp.starts_with("0x") { - apka_mkvp = String::from(&apka_mkvp[2..]); - } - } - } - Ok(Self::Cca(ApqnInfoCca { - serialnr, - mkvp_aes: aes_mkvp, - mkvp_apka: apka_mkvp, - })) - } - - fn ep11_info(carddir: &str, queuedir: &str) -> pv_core::Result { - let serialnr_str = read_file_string(format!("{carddir}/serialnr"), "serialnr")?; - let serialnr = serialnr_str.trim().to_string(); - let mkvps = read_file_string(format!("{carddir}/{queuedir}/mkvps"), "mkvps")?; - let mut mkvp = String::new(); - let re_ep11_mkvp = Regex::new(RE_EP11_MKVP).unwrap(); - if !re_ep11_mkvp.is_match(&mkvps) { - return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps)); - } else { - let caps = re_ep11_mkvp.captures(&mkvps).unwrap(); - if caps.get(1).unwrap().as_str().to_lowercase() == "valid" { - mkvp = caps.get(2).unwrap().as_str().to_lowercase(); - if mkvp.starts_with("0x") { - mkvp = String::from(&mkvp[2..]); - } - if mkvp.len() > 32 { - mkvp = String::from(&mkvp[..32]) - } - } - } - Ok(Self::Ep11(ApqnInfoEp11 { serialnr, mkvp })) - } - - /// Get mode-specific info - pub fn info(mode: &ApqnMode, carddir: &str, queuedir: &str) -> pv_core::Result { - match mode { - ApqnMode::Accel => Self::accel_info(carddir, queuedir), - ApqnMode::Cca => Self::cca_info(carddir, queuedir), - ApqnMode::Ep11 => Self::ep11_info(carddir, queuedir), - } - } -} - -/// `Apqn` encodes an adjunct processor queue number. -#[derive(Debug, Clone)] -pub struct Apqn { - /// Name of the APQN - #[allow(dead_code)] - pub name: String, - /// Card number - pub card: u32, - /// Domain number - pub domain: u32, - /// CryptoExpress generation - pub gen: u32, - /// Mode that adapter is configured to use - pub mode: ApqnMode, - /// Mode-specific info - pub info: Option, -} - -impl TryFrom<&str> for Apqn { - type Error = Error; - - /// Create an `Apqn` struct from a CARD.DOMAIN-formatted APQN - /// string, such as `28.0014`. Will not populate `info` upon - /// failure to read it. Other failures to read required information - /// are treated as an Error. - /// # Panics - /// Panics if the compilation of a static regular expression fails - /// or a regex capture that is already format-checked does not - /// parse, e.g. when the capture `([[:xdigit:]]{2})` does not - /// parse as hex string. - fn try_from(name: &str) -> pv_core::Result { - let re_card_type = Regex::new(RE_CARD_TYPE).unwrap(); - let re_queue_dir = Regex::new(RE_QUEUE_DIR).unwrap(); - - let caps = re_queue_dir - .captures(name) - .ok_or_else(|| parse_error!("queue".to_string(), name.to_string()))?; - let cardstr = caps.get(1).unwrap().as_str(); - let card = u32::from_str_radix(cardstr, 16).unwrap(); - let domstr = caps.get(2).unwrap().as_str(); - let domain = u32::from_str_radix(domstr, 16).unwrap(); - - let path = format!("{PATH_SYS_DEVICES_AP}/card{cardstr}"); - let card_type = - read_file_string(format!("{path}/type"), "card type").map(|s| s.trim().to_string())?; - let caps = re_card_type - .captures(&card_type) - .ok_or_else(|| parse_error!("card type".to_string(), card_type.to_string()))?; - let gen = caps.get(1).unwrap().as_str().parse::().unwrap(); - let mode = match caps.get(2).unwrap().as_str().parse::().unwrap() { - 'A' => ApqnMode::Accel, - 'C' => ApqnMode::Cca, - 'P' => ApqnMode::Ep11, - _ => unreachable!("Code inconsistency between regex RE_CARD_TYPE and evaluation code."), - }; - // the UV blocks requests to CCA cards within SE guest with AP - // pass-through support. However, filter out CCA cards as - // these cards cause hangs during information gathering. - if mode == ApqnMode::Cca && pv_core::misc::pv_guest_bit_set() { - return Err(Error::CcaSeIncompatible(card)); - } - - match read_file_string(format!("{path}/{name}/online"), "AP queue online status") - .map(|s| s.trim().parse::()) - { - Ok(Ok(1)) => {} - _ => return Err(Error::ApOffline { card, domain }), - } - // For the MKVP and serialnr to fetch from the APQN within a SE - // guest the APQN needs to be bound to the guest. So if the APQN - // is not bound, temporarily bind it here until the info has - // been retrieved. - let mut tempbound = false; - if pv_core::misc::pv_guest_bit_set() { - let cbs = get_apqn_bind_state(card, domain)?; - if cbs == BindState::Unbound { - set_apqn_bind_state(card, domain, BindState::Bound)?; - tempbound = true; - } - } - let info = ApqnInfo::info(&mode, &path, name).ok(); - if tempbound { - set_apqn_bind_state(card, domain, BindState::Unbound)?; - } - - Ok(Apqn { - name: name.to_string(), - card, - domain, - gen, - mode, - info, - }) - } -} - -impl fmt::Display for Apqn { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "({},{})", self.card, self.domain) - } -} - -impl Apqn { - /// Read bind state of the APQN. - pub fn bind_state(&self) -> pv_core::Result { - get_apqn_bind_state(self.card, self.domain) - } - - /// Set bind state of the APQN. - pub fn set_bind_state(&self, state: BindState) -> pv_core::Result<()> { - set_apqn_bind_state(self.card, self.domain, state) - } - - /// Read associate state of the APQN. - pub fn associate_state(&self) -> pv_core::Result { - get_apqn_associate_state(self.card, self.domain) - } - - /// Set associate state of the APQN. - pub fn set_associate_state(&self, state: AssocState) -> pv_core::Result<()> { - set_apqn_associate_state(self.card, self.domain, state) - } -} - /// Wrapper object around Vector of Apqns #[derive(Debug)] pub struct ApqnList(Vec); @@ -431,7 +157,7 @@ impl ApqnList { if apqn.info.is_none() { eprintln!("Warning: Failure gathering info for APQN {queue_dir}"); } - if let Some(ApqnInfo::Cca(ref cca_info)) = apqn.info { + if let Some(apqn_info::Cca(ref cca_info)) = apqn.info { if cca_info.mkvp_aes.is_empty() { eprintln!("Warning: APQN {queue_dir} has no valid AES master key set."); } @@ -439,7 +165,7 @@ impl ApqnList { eprintln!("Warning: APQN {queue_dir} has no valid APKA master key set."); } } - if let Some(ApqnInfo::Ep11(ref ep11_info)) = apqn.info { + if let Some(apqn_info::Ep11(ref ep11_info)) = apqn.info { if ep11_info.mkvp.is_empty() { eprintln!("Warning: APQN {queue_dir} has no valid wrapping key set."); } @@ -467,16 +193,16 @@ impl ApqnList { for a2 in self.0.iter() { if a1.card == a2.card && a1.domain < a2.domain - && a1.mode == ApqnMode::Ep11 + && a1.mode == apqn_mode::Ep11 && a1.info.is_some() && a2.info.is_some() { let i1 = match a1.info.as_ref().unwrap() { - ApqnInfo::Ep11(i) => i, + apqn_info::Ep11(i) => i, _ => continue, }; let i2 = match a2.info.as_ref().unwrap() { - ApqnInfo::Ep11(i) => i, + apqn_info::Ep11(i) => i, _ => continue, }; if i1.mkvp.is_empty() || i2.mkvp.is_empty() { @@ -493,186 +219,6 @@ impl ApqnList { } } -/// Bind state of an APQN -#[derive(Debug, PartialEq, Eq)] -pub enum BindState { - /// APQN is bound - Bound, - /// APQN is unbound - Unbound, - /// APQN does not support bind - NotSupported, -} - -/// Query bind state for this APQN. -/// -/// Returns a BindState enum as defined above or on failure -/// an error string. Does NOT print any error messages. -pub fn get_apqn_bind_state(card: u32, dom: u32) -> pv_core::Result { - let path = format!( - "{}/card{:02x}/{:02x}.{:04x}/se_bind", - PATH_SYS_DEVICES_AP, card, card, dom - ); - let state_str = read_file_string(path, "se_bind attribute")?; - let state = state_str.trim(); - match state { - "bound" => Ok(BindState::Bound), - "unbound" => Ok(BindState::Unbound), - "-" => Ok(BindState::NotSupported), - _ => Err(Error::UnknownBindState(state.to_string())), - } -} - -/// Bind or unbind an APQN. -/// -/// The action is determined by the BindState given in. -/// But of course only Bound and Unbound is supported - otherwise -/// this function panics! -/// The function actively loops over the bind state until -/// the requested bind state is reached or a timeout has -/// occurred (SYS_BUS_AP_BIND_TIMEOUT_MS). -/// On success () is returned, on failure an error string -/// is returned. Does NOT print any error messages. -/// # Panics -/// Panics if a desired bind state other than Bound or Unbound is given. -pub fn set_apqn_bind_state(card: u32, dom: u32, state: BindState) -> pv_core::Result<()> { - let ctx = "bind APQN"; - let path = format!( - "{}/card{:02x}/{:02x}.{:04x}/se_bind", - PATH_SYS_DEVICES_AP, card, card, dom - ); - match state { - BindState::Bound => write_file(path, 1.to_string(), ctx), - BindState::Unbound => write_file(path, 0.to_string(), ctx), - _ => panic!("set_apqn_bind_state called with invalid BindState."), - }?; - let mut ms: u64 = 0; - loop { - thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BIND_POLL_MS)); - ms += SYS_BUS_AP_BIND_POLL_MS; - if ms >= SYS_BUS_AP_BIND_TIMEOUT_MS { - break Err(Error::Timeout(format!( - "setting APQN({card},{dom}) bind state" - ))); - } - let newstate = get_apqn_bind_state(card, dom)?; - if newstate == state { - return Ok(()); - } - } -} - -/// Association state of an APQN -#[derive(Debug, PartialEq, Eq)] -pub enum AssocState { - /// Associated with index - Associated(u16), - /// Association pending - AssociationPending, - /// Not associated - Unassociated, - /// APQN does not support association - NotSupported, -} - -/// Query association state for this APQN. -/// -/// Returns an AssocState enum as defined above or on failure -/// an error string. Does NOT print any error messages. -pub fn get_apqn_associate_state(card: u32, dom: u32) -> pv_core::Result { - let path = format!( - "{}/card{:02x}/{:02x}.{:04x}/se_associate", - PATH_SYS_DEVICES_AP, card, card, dom - ); - let state_str = read_file_string(path, "se_associate attribute")?; - let state = state_str.trim(); - match state.strip_prefix("associated ") { - Some(prefix) => Ok(AssocState::Associated(prefix.parse()?)), - _ => match state { - "association pending" => Ok(AssocState::AssociationPending), - "unassociated" => Ok(AssocState::Unassociated), - "-" => Ok(AssocState::NotSupported), - _ => Err(Error::UnknownAssocState(state.to_string())), - }, - } -} - -fn set_apqn_associate_state_associate(card: u32, dom: u32, idx: u16) -> pv_core::Result<()> { - let path = format!( - "{}/card{:02x}/{:02x}.{:04x}/se_associate", - PATH_SYS_DEVICES_AP, card, card, dom - ); - write_file(path, idx.to_string(), "associate APQN")?; - let mut ms: u64 = 0; - loop { - thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS)); - ms += SYS_BUS_AP_ASSOC_POLL_MS; - if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS { - break Err(Error::Timeout(format!( - "setting APQN({card},{dom}) association index {idx} state", - ))); - } - match get_apqn_associate_state(card, dom)? { - AssocState::Associated(i) if i == idx => return Ok(()), - AssocState::Associated(i) => { - return Err(Error::WrongAssocState { - card, - domain: dom, - desired: idx, - actual: i, - }) - } - _ => {} - } - } -} - -fn set_apqn_associate_state_unbind(card: u32, dom: u32) -> pv_core::Result<()> { - let bindpath = format!( - "{}/card{:02x}/{:02x}.{:04x}/se_bind", - PATH_SYS_DEVICES_AP, card, card, dom - ); - write_file(bindpath, 0.to_string(), "unbind APQN")?; - let mut ms: u64 = 0; - loop { - thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS)); - ms += SYS_BUS_AP_ASSOC_POLL_MS; - if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS { - break Err(Error::Timeout(format!( - "setting APQN({card},{dom}) association unbind state", - ))); - } - let newstate = get_apqn_associate_state(card, dom)?; - if newstate == AssocState::Unassociated { - return Ok(()); - } - } -} - -/// Associate or Unassociate an APQN. -/// -/// The action is determined by the AssocState given in. -/// But of course only Associated and Unassociated is supported -/// otherwise this function panics! -/// The function actively loops over the association state until -/// the requested state is reached or a timeout has -/// occurred (SYS_BUS_AP_ASSOC_TIMEOUT_MS). -/// The unassociate is in fact a unbind. So the code triggers -/// an unbind and then loops over the sysfs se_associate until -/// "unassociated" is reached. -/// On success () is returned, on failure an error string -/// is returned. Does NOT print any error messages. -/// # Panics -/// Panics if a desired bind state other than Associated or -/// Unassociated is given. -pub fn set_apqn_associate_state(card: u32, dom: u32, state: AssocState) -> pv_core::Result<()> { - match state { - AssocState::Associated(idx) => set_apqn_associate_state_associate(card, dom, idx), - AssocState::Unassociated => set_apqn_associate_state_unbind(card, dom), - _ => panic!("set_apqn_associate_state called with invalid AssocState."), - } -} - #[cfg(test)] mod tests { @@ -724,16 +270,16 @@ mod tests { let v = l.to_apqn_vec(); for a in v { match a.mode { - ApqnMode::Accel => { + apqn_mode::Accel => { // fail if no ApqnInfo is attached assert!(a.info.is_some()); } - ApqnMode::Ep11 => { + apqn_mode::Ep11 => { // fail if no ApqnInfo is attached assert!(a.info.is_some()); let info = a.info.unwrap(); let i = match &info { - ApqnInfo::Ep11(i) => i, + apqn_info::Ep11(i) => i, _ => panic!("ApqnInfo attached onto Ep11 APQN is NOT ApqnInfoEp11 ?!?"), }; // fail if no serialnr @@ -741,12 +287,12 @@ mod tests { // mkvp is either empty (no WK set) or has exact 32 characters assert!(i.mkvp.is_empty() || i.mkvp.len() == 32); } - ApqnMode::Cca => { + apqn_mode::Cca => { // fail if no ApqnInfo is attached assert!(a.info.is_some()); let info = a.info.unwrap(); let i = match &info { - ApqnInfo::Cca(i) => i, + apqn_info::Cca(i) => i, _ => panic!("ApqnInfo attached onto Cca APQN is NOT ApqnInfoCca ?!?"), }; // fail if no serialnr diff --git a/rust/pvapconfig/src/main.rs b/rust/pvapconfig/src/main.rs index dd5cb96a..95649f86 100644 --- a/rust/pvapconfig/src/main.rs +++ b/rust/pvapconfig/src/main.rs @@ -12,10 +12,11 @@ mod config; mod helper; mod uv; -use ap::{Apqn, ApqnList}; +use ap::ApqnList; use cli::ARGS; use config::{ApConfigEntry, ApConfigList}; use helper::{LockFile, PATH_PVAPCONFIG_LOCK}; +use pv_core::ap::{self as pvap, Apqn}; use pv_core::misc::encode_hex; use pv_core::uv::{ListableSecretType, SecretList}; use std::process::ExitCode; @@ -225,14 +226,14 @@ fn do_ap_config( continue; } match apqn.mode { - ap::ApqnMode::Accel => { + pvap::apqn_mode::Accel => { // check bind state of this APQN let bind_state_ok = match apqn.bind_state() { Err(err) => { eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}"); false } - Ok(ap::BindState::Bound) => true, + Ok(pvap::bind_state::Bound) => true, Ok(_) => false, }; if !bind_state_ok { @@ -247,7 +248,7 @@ fn do_ap_config( resolved_entries += 1; break; } - ap::ApqnMode::Ep11 => { + pvap::apqn_mode::Ep11 => { // check association state of this APQN let (assoc_state_ok, assoc_idx) = match apqn.associate_state() { Err(err) => { @@ -256,7 +257,7 @@ fn do_ap_config( ); (false, 0) } - Ok(ap::AssocState::Associated(idx)) => (true, idx), + Ok(pvap::assoc_state::Associated(idx)) => (true, idx), Ok(_) => (false, 0), }; if !assoc_state_ok { @@ -298,10 +299,10 @@ fn do_ap_config( } match apqn.bind_state() { Err(err) => eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}"), - Ok(ap::BindState::Bound) => { + Ok(pvap::bind_state::Bound) => { info!("Unbind APQN {apqn} as this bind/associate does not match to any AP config entry.\n"); if !ARGS.dryrun() { - if let Err(err) = apqn.set_bind_state(ap::BindState::Unbound) { + if let Err(err) = apqn.set_bind_state(pvap::bind_state::Unbound) { return Err(format!("Failure unbinding APQN {apqn}: {err}")); } } @@ -331,15 +332,15 @@ fn do_ap_config( continue; } match apqn.mode { - ap::ApqnMode::Accel => { + pvap::apqn_mode::Accel => { // try to bind this accelerator APQN if ARGS.verbose() || fntest { println!("Bind APQN {apqn} to match to AP config entry {cistr}."); } if !(ARGS.dryrun() || fntest) { - if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) { + if let Err(err) = apqn.set_bind_state(pvap::bind_state::Bound) { // bind failed, unbind/reset this apqn, return with failure - let _ = apqn.set_bind_state(ap::BindState::Unbound); + let _ = apqn.set_bind_state(pvap::bind_state::Unbound); return Err(format!("Failure binding APQN {apqn}: {err}")); } } @@ -348,7 +349,7 @@ fn do_ap_config( resolved_entries += 1; break; } - ap::ApqnMode::Ep11 => { + pvap::apqn_mode::Ep11 => { // EP11 needs bind and associate, but before doing this let's // check out which secret index to use with the associate let se = match secrets.iter().find(|&se| { @@ -370,9 +371,9 @@ fn do_ap_config( ); } if !(ARGS.dryrun() || fntest) { - if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) { + if let Err(err) = apqn.set_bind_state(pvap::bind_state::Bound) { // bind failed, unbind/reset this apqn, return with failure - let _ = apqn.set_bind_state(ap::BindState::Unbound); + let _ = apqn.set_bind_state(pvap::bind_state::Unbound); return Err(format!("Failure binding APQN {}: {}", apqn, err)); } } @@ -384,7 +385,7 @@ fn do_ap_config( ); } if !(ARGS.dryrun() || fntest) { - let apas = ap::AssocState::Associated(se.index()); + let apas = pvap::assoc_state::Associated(se.index()); apqn.set_associate_state(apas) .map_err(|err| format!("Failure associating APQN {apqn}: {err}"))?; } @@ -409,7 +410,7 @@ fn do_ap_config( /// Please note this can not happen, as mingen is already checked via RE /// during storing the value into mingen. fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool { - if apc.mode == config::STR_MODE_ACCEL && apqn.mode == ap::ApqnMode::Accel { + if apc.mode == config::STR_MODE_ACCEL && apqn.mode == pvap::apqn_mode::Accel { // config and apqn are accelerators // maybe check mingen if !apc.mingen.is_empty() { @@ -419,10 +420,10 @@ fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool { } } return true; - } else if apc.mode == config::STR_MODE_EP11 && apqn.mode == ap::ApqnMode::Ep11 { + } else if apc.mode == config::STR_MODE_EP11 && apqn.mode == pvap::apqn_mode::Ep11 { // config and apqn are ep11 let info = match &apqn.info { - Some(ap::ApqnInfo::Ep11(i)) => i, + Some(pvap::apqn_info::Ep11(i)) => i, _ => return false, }; // maybe check mingen @@ -458,64 +459,64 @@ mod tests { fn make_test_apqns() -> Vec { vec![ - ap::Apqn { + pvap::Apqn { name: String::from("10.0007"), card: 16, domain: 7, gen: 8, - mode: ap::ApqnMode::Accel, - info: Option::Some(ap::ApqnInfo::Accel(ap::ApqnInfoAccel {})), + mode: pvap::apqn_mode::Accel, + info: Option::Some(pvap::apqn_info::Accel(pvap::apqn_info::ApqnInfoAccel {})), }, - ap::Apqn { + pvap::Apqn { name: String::from("11.0008"), card: 17, domain: 8, gen: 8, - mode: ap::ApqnMode::Ep11, - info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 { + mode: pvap::apqn_mode::Ep11, + info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 { serialnr: String::from("93AADFK719460083"), mkvp: String::from("db3c3b3c3f097dd55ec7eb0e7fdbcb93"), })), }, - ap::Apqn { + pvap::Apqn { name: String::from("12.0009"), card: 18, domain: 9, gen: 8, - mode: ap::ApqnMode::Ep11, - info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 { + mode: pvap::apqn_mode::Ep11, + info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 { serialnr: String::from("93AADHZU42082261"), mkvp: String::from("4a27bb66520ac85f6073a7f678d262c0"), })), }, - ap::Apqn { + pvap::Apqn { name: String::from("12.000a"), card: 18, domain: 10, gen: 8, - mode: ap::ApqnMode::Ep11, - info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 { + mode: pvap::apqn_mode::Ep11, + info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 { serialnr: String::from("93AADHZU42082261"), mkvp: String::from("383d2a9ab781f35343554c5b3d9337cd"), })), }, - ap::Apqn { + pvap::Apqn { name: String::from("13.000d"), card: 19, domain: 13, gen: 8, - mode: ap::ApqnMode::Ep11, - info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 { + mode: pvap::apqn_mode::Ep11, + info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 { serialnr: String::from("87HU397G150TZGR"), mkvp: String::new(), })), }, - ap::Apqn { + pvap::Apqn { name: String::from("13.000f"), card: 19, domain: 15, gen: 8, - mode: ap::ApqnMode::Ep11, + mode: pvap::apqn_mode::Ep11, info: Option::None, }, ]