Files
s390-tools/rust/pvapconfig/src/config.rs
Steffen Eiden d30d272523 rust: Use Self wherever possible
Use Self instead of the struct name whenever possible.
Automagically replace struct name with Self:
`cargo clippy --fix -- -W clippy::use_self`

This streamlines the code.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-10-25 11:57:44 +02:00

393 lines
13 KiB
Rust

// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! Functions around handling the pvapconfig configuration file
//
use openssl::sha::sha256;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_yaml::{self};
use std::fs::File;
use std::slice::Iter;
pub const STR_MODE_EP11: &str = "ep11";
pub const STR_MODE_ACCEL: &str = "accel";
const RE_EP11_MKVP_32: &str = r"^(0x)?([[:xdigit:]]{32})$";
const RE_EP11_MKVP_64: &str = r"^(0x)?([[:xdigit:]]{64})$";
const RE_SERIALNR: &str = r"^(\S{16})$";
const RE_EP11_GEN: &str = r"^cex(8)$";
const RE_ACCEL_GEN: &str = r"^cex([4-8])$";
const RE_SECRETID: &str = r"^(0x)?([[:xdigit:]]{64})$";
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[serde(default, deny_unknown_fields)]
pub struct ApConfigEntry {
pub name: String, // name and description are unmodified from the config file
pub description: String, // accel after validation ep11 after validation
pub mode: String, // "accel" "ep11"
pub mkvp: String, // empty 32 hex lowercase characters
pub serialnr: String, // empty empty or 16 non-whitespace characters
pub mingen: String, // empty or "cex4"..."cex8" empty or "cex8"
pub secretid: String, // empty 64 hex lowercase characters
}
impl ApConfigEntry {
fn validate_secretid(&mut self) -> Result<(), String> {
// either secret id or name may be given
if self.secretid.is_empty() && self.name.is_empty() {
return Err("Neither secretid nor name given.".to_string());
}
// if name is given, calculate sha256 digest for this name
// test for the hash calculated here can be done with openssl:
// echo -n "Hello" >in.bin; openssl dgst -sha256 -binary -out out.bin in.bin; hexdump -C
// out.bin
if self.name.is_empty() {
return Ok(());
}
let hash = sha256(self.name.as_bytes());
let hashstr = crate::helper::u8_to_hexstring(&hash);
// if there is a secretid given, this must match to the hash
if !self.secretid.is_empty() {
if self.secretid != hashstr {
return Err("Mismatch between sha256(name) and secretid.".to_string());
}
} else {
self.secretid = hashstr;
}
Ok(())
}
/// # Panics
/// Panics if the compilation of a static regular expression fails.
fn validate_ep11_entry(&mut self) -> Result<(), String> {
// mkvp is required
let mut mkvp = self.mkvp.trim().to_lowercase();
if mkvp.is_empty() {
return Err("Mkvp value missing.".to_string());
}
// either 64 hex or 32 hex
if Regex::new(RE_EP11_MKVP_64).unwrap().is_match(&mkvp) {
// need to cut away the last 32 hex characters
mkvp = String::from(&mkvp[..mkvp.len() - 32])
} else if Regex::new(RE_EP11_MKVP_32).unwrap().is_match(&mkvp) {
// nothing to do here
} else {
return Err(format!("Mkvp value '{}' is not valid.", &self.mkvp));
}
self.mkvp = match mkvp.strip_prefix("0x") {
Some(rest) => String::from(rest),
None => mkvp,
};
// serialnr is optional
let serialnr = self.serialnr.trim().to_string();
if !serialnr.is_empty() && !Regex::new(RE_SERIALNR).unwrap().is_match(&serialnr) {
return Err(format!("Serialnr value '{}' is not valid.", &self.serialnr));
}
self.serialnr = serialnr;
// mingen is optional, but if given only CEX8 is valid
let mingen = self.mingen.trim().to_lowercase();
if !mingen.is_empty() && !Regex::new(RE_EP11_GEN).unwrap().is_match(&mingen) {
return Err(format!("Mingen value '{}' is not valid.", &self.mingen));
}
self.mingen = mingen;
// secretid or name is required
let secretid = self.secretid.trim().to_lowercase();
if !secretid.is_empty() && !Regex::new(RE_SECRETID).unwrap().is_match(&secretid) {
return Err(format!("Secretid value '{}' is not valid.", &self.secretid));
}
self.secretid = match secretid.strip_prefix("0x") {
Some(rest) => String::from(rest),
None => secretid,
};
// name is optional, ignored here
// description is optional, ignored here
// but the secretid needs some more validation
self.validate_secretid()
}
/// # Panics
/// Panics if the compilation of a static regular expression fails.
fn validate_accel_entry(&mut self) -> Result<(), String> {
// mkvp is ignored
self.mkvp.clear();
// serialnr is ignored
self.serialnr.clear();
// mingen is optional, but if given must match to CEX4..CEX8
let mingen = self.mingen.trim().to_lowercase();
if !mingen.is_empty() && !Regex::new(RE_ACCEL_GEN).unwrap().is_match(&mingen) {
return Err(format!("Mingen value '{}' is not valid.", &self.mingen));
}
self.mingen = mingen;
// secretid is ignored
self.secretid.clear();
// name is optional, ignored here
// description is optional, ignored here
Ok(())
}
fn validate(&mut self) -> Result<(), String> {
// trim name
self.name = self.name.trim().to_string();
// mode is always required
let mode = self.mode.trim().to_lowercase();
match mode.as_str() {
STR_MODE_EP11 => {
self.mode = mode;
self.validate_ep11_entry()?;
}
STR_MODE_ACCEL => {
self.mode = mode;
self.validate_accel_entry()?;
}
_ => return Err(format!("Unknown or invalid mode '{}'.", mode)),
}
Ok(())
}
}
/// Wrapper object around Vector of ApConfigEntry
pub struct ApConfigList(Vec<ApConfigEntry>);
impl ApConfigList {
#[cfg(test)] // only used in test code
pub fn from_apconfigentry_vec(apconfigs: Vec<ApConfigEntry>) -> Self {
Self(apconfigs)
}
pub fn iter(&self) -> Iter<'_, ApConfigEntry> {
self.0.iter()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn read_yaml_file(fname: &str) -> Result<Vec<ApConfigEntry>, String> {
let file = match File::open(fname) {
Ok(f) => f,
Err(err) => {
return Err(format!(
"Failure to open AP config file {}: {:?}",
fname, err
))
}
};
match serde_yaml::from_reader(file) {
Ok(cfg) => Ok(cfg),
Err(err) => Err(format!(
"Failure parsing AP config file {}: {:?}",
fname, err
)),
}
}
fn validate(config: &mut [ApConfigEntry]) -> Result<(), String> {
for (i, entry) in config.iter_mut().enumerate() {
let ename = if !entry.name.trim().is_empty() {
format!("AP config entry {} '{}'", i, entry.name.trim())
} else {
format!("AP config entry {}", i)
};
if let Err(err) = &entry.validate() {
return Err(format!("{}: {}", ename, err));
}
}
Ok(())
}
/// Read in and validate the yaml configuration from a file.
/// Returns a Result with Ok(ApConfigList) on success
/// or an Err(errorstring) on failure.
pub fn read_and_validate_yaml_file(fname: &str) -> Result<Self, String> {
let mut apconfig = Self::read_yaml_file(fname)?;
Self::validate(&mut apconfig)?;
Ok(Self(apconfig))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::fs;
use std::io::Write;
const GOOD_CONFIGS: [&str; 8] = [
"# good test 1
- name: my Accelerator
mode: AcCel
mingen: Cex7\n",
"# good test 2
- name: my Accelerator 2
description: Accelerator entry with description
mode: Accel\n",
"# good test 3
- name: my EP11 APQN 1
mode: Ep11
mkvp: 0xDB3C3B3C3F097DD55EC7EB0E7FDBCB93
serialnr: 93AADFK719460083
secretid: 0xBC9d46c052BC3574454C5715757274629a283767ed237922cfb8651c0e77320A\n",
"# good test 4
- name: my EP11 APQN 2
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
serialnr: 93aaDHzu42082261
secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n",
"# good test 5
- name: my EP11 APQN 3
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93db3c3b3c3f097dd55ec7eb0e7fdbcb93
serialnr: 93aaDHzu42082261
secretid: 0xd146c9ae77cdff25fa87a5b3487587dc29a4e391b315c98570e8fa2e2ec91454\n",
"# no name but secretid given
- mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
"# no secretid but name given
- mode: EP11
name: My-EP11-AP-config
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93\n",
"# secretid and name given
- mode: EP11
name: My-EP11-AP-config
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
];
const BAD_CONFIGS: [&str; 12] = [
"# mode missing
- name: bad test 1
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
"# invalid mode
- name: bad test 2
mode: CCA
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
"# Accelerator with wrong CEX3
- name: bad test 3
mode: Accel
mingen: Cex3\n",
"# Accelerator with wrong CEX9
- name: bad test 4
mode: Accel
mingen: CEX9\n",
"# EP11 with mkvp missing
- name: bad test 5
mode: EP11
serialnr: 93AADHZU42082261\n",
"# EP11 with non hex mkvp
- name: bad test 6
mode: EP11
mkvp: 0xabcdefghijklmnopqqponmlkjihgfedcba
serialnr: 93AADHZU42082261\n",
"# EP11 with mkvp too big
- name: bad test 7
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93aa
serialnr: 93AADHZU42082261\n",
"# EP11 with mkvp too small
- name: bad test 8
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb
serialnr: 93AADHZU42082261\n",
"# EP11 with invalid CEXx
- name: bad test 9
mode: EP11
mingen: CEX7
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: 93AADHZU42082261\n",
"# EP11 with invalid Serialnr
- name: bad test 10
mode: EP11
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: 93AADHZU4208226\n",
"# EP11 with invalid Serialnr
- name: bad test 11
mode: EP11
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: 93AAD ZU42082261\n",
"# EP11 with sha256(name) != secretid
- name: bad test 12
mode: EP11
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: AABBCCDDEEFFGGHH
secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n",
];
const BAD_DESERIALIZE: [&str; 2] = [
"/*\ntotal nonsense\n */\n",
"# wrong/unknown field
- name: de-serialize failure 1
type: EP11\n",
];
fn write_yaml_config_to_temp_file(content: &str) -> Result<String, String> {
let dir = env::temp_dir();
let rnd = rand::random::<u32>();
let fname = format!("{}/config-test-{}.yaml", dir.to_str().unwrap(), rnd);
let mut f = match File::create(&fname) {
Ok(f) => f,
Err(_) => return Err(format!("Failure creating temp file '{fname}'.")),
};
match f.write_all(content.as_bytes()) {
Ok(_) => Ok(fname),
Err(_) => {
fs::remove_file(&fname).ok();
Err(format!("Failure writing to temp file '{fname}'."))
}
}
}
#[test]
fn test_good_yaml() {
for yaml in GOOD_CONFIGS {
let f = write_yaml_config_to_temp_file(yaml).unwrap();
let config = ApConfigList::read_and_validate_yaml_file(&f).unwrap();
assert!(!config.is_empty());
fs::remove_file(&f).ok();
}
}
#[test]
fn test_bad_yaml() {
for yaml in BAD_CONFIGS {
let f = write_yaml_config_to_temp_file(yaml).unwrap();
let r = ApConfigList::read_and_validate_yaml_file(&f);
assert!(r.is_err());
fs::remove_file(&f).ok();
}
}
#[test]
fn test_invalid_deserizalize() {
for yaml in BAD_DESERIALIZE {
let f = write_yaml_config_to_temp_file(yaml).unwrap();
let r = ApConfigList::read_and_validate_yaml_file(&f);
assert!(r.is_err());
fs::remove_file(&f).ok();
}
}
#[test]
fn test_sha256() {
assert!(
crate::helper::u8_to_hexstring(&sha256("Hello".as_bytes()))
== "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"
);
assert!(
crate::helper::u8_to_hexstring(&sha256("SECRET1".as_bytes()))
== "03153249db7ce46b0330ffb1a760b59710531af08ec4d7f8424a6870fae49360"
);
assert!(
crate::helper::u8_to_hexstring(&sha256("SECRET2".as_bytes()))
== "258499e710e0bd3bb878d6bac7e478b30f3f3e72566989f638c4143d14f6c0b6"
);
}
}