Files
s390-tools/rust/pvattest/src/exchange.rs
Timo Keller 9dca2d3181 pvattest: Use hybrid keys
Allow the creation of Attestation requests using hybrid (=quantum safe)
keys. This results in using the headers in version 2 (0x200).

Co-developed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00

930 lines
27 KiB
Rust

// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
#![allow(unused)]
use std::io::{ErrorKind, Read, Seek, SeekFrom, Write};
use std::mem::size_of;
use anyhow::{anyhow, bail, Error, Result};
use pv::assert_size;
use pv::request::MagicValue;
use pv::uv::{AttestationCmd, ConfigUid};
use zerocopy::{BigEndian, ByteOrder, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
use crate::additional;
const INV_EXCHANGE_FMT_ERROR_TEXT: &str = "The input has not the correct format:";
#[repr(C)]
#[derive(Debug, IntoBytes, PartialEq, Eq, Default, FromBytes, Immutable, KnownLayout)]
struct Entry {
size: U32<BigEndian>,
offset: U32<BigEndian>,
}
assert_size!(Entry, 8);
/// If size == 0 the offset is ignored. (entry does not exist)
/// If offset >0 and <0x40 -> invalid format
/// If offset == 0 and size > 0 no data saved, however the request will need this amount of memory
/// to succeed. Only makes sense for measurement and additional data. This however, is not
/// enforced.
impl Entry {
fn new(size: u32, offset: u32) -> Self {
Self {
size: size.into(),
offset: offset.into(),
}
}
/// # Panic
///
/// panics if `val` is larger than `max_size` bytes
fn from_slice(val: Option<&[u8]>, max_size: u32, offset: &mut u32) -> Self {
val.map_or_else(Self::default, |val| {
assert!(val.len() <= max_size as usize);
let size = val.len() as u32;
let res = Self::new(size, *offset);
*offset += size;
res
})
}
/// # Panic
///
/// panics if `val` is larger than `max_size` bytes
fn from_exp(val: Option<u32>) -> Self {
val.map_or_else(Self::default, |val| Self::new(val, 0))
}
fn from_none() -> Self {
Self::default()
}
/// Reads data from stream if required
fn read<R>(&self, reader: &mut R) -> Result<ExpOrData>
where
R: Read + Seek,
{
match self {
Self { size, .. } if size.get() == 0 => Ok(ExpOrData::None),
Self { size, offset } if offset.get() == 0 => Ok(ExpOrData::Exp(size.get())),
Self { size, offset } => {
reader.seek(SeekFrom::Start(offset.get() as u64))?;
let mut buf = vec![0; size.get() as usize];
reader.read_exact(&mut buf)?;
Ok(ExpOrData::Data(buf))
}
}
}
}
#[repr(C)]
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
struct ExchangeFormatV1Hdr {
magic: U64<BigEndian>,
version: U32<BigEndian>,
size: U32<BigEndian>,
reserved: U64<BigEndian>,
/// v1 specific
arcb: Entry,
measurement: Entry,
additional: Entry,
user: Entry,
config_uid: Entry,
}
assert_size!(ExchangeFormatV1Hdr, 0x40);
impl ExchangeFormatV1Hdr {
fn new_request(arcb: &[u8], measurement: u32, additional: u32) -> Result<Self> {
let mut offset: u32 = size_of::<Self>() as u32;
let arcb_entry = Entry::from_slice(Some(arcb), AttestationCmd::ARCB_MAX_SIZE, &mut offset);
let measurement_entry = Entry::from_exp(Some(measurement));
let exp_add = match additional {
0 => None,
size => {
if size > AttestationCmd::ADDITIONAL_MAX_SIZE {
bail!(
"Additional data size ({}) exceeds maximum allowed size ({})",
size,
AttestationCmd::ADDITIONAL_MAX_SIZE
);
}
Some(size)
}
};
let additional_entry = Entry::from_exp(exp_add);
let user_entry = Entry::from_none();
let cuid_entry = Entry::from_none();
Ok(Self {
magic: U64::from_bytes(ExchangeMagic::MAGIC),
version: ExchangeFormatVersion::One.into(),
size: offset.into(),
reserved: 0.into(),
arcb: arcb_entry,
measurement: measurement_entry,
additional: additional_entry,
user: user_entry,
config_uid: cuid_entry,
})
}
fn new_response(
arcb: &[u8],
measurement: &[u8],
additional: Option<&[u8]>,
user: Option<&[u8]>,
config_uid: &[u8],
) -> Result<Self> {
let mut offset: u32 = size_of::<Self>() as u32;
let arcb_entry = Entry::from_slice(Some(arcb), AttestationCmd::ARCB_MAX_SIZE, &mut offset);
let measurement_entry = Entry::from_slice(
Some(measurement),
AttestationCmd::MEASUREMENT_MAX_SIZE,
&mut offset,
);
let additional_entry =
Entry::from_slice(additional, AttestationCmd::ADDITIONAL_MAX_SIZE, &mut offset);
let user_entry = Entry::from_slice(user, AttestationCmd::USER_MAX_SIZE, &mut offset);
let cuid_entry = Entry::from_slice(Some(config_uid), 0x10, &mut offset);
Ok(Self {
magic: U64::from_bytes(ExchangeMagic::MAGIC),
version: ExchangeFormatVersion::One.into(),
size: offset.into(),
reserved: 0.into(),
arcb: arcb_entry,
measurement: measurement_entry,
additional: additional_entry,
user: user_entry,
config_uid: cuid_entry,
})
}
}
/// The magic value used to identify an [`ExchangeFormatRequest`]
///
/// The magic value is ASCII:
/// ```rust
/// # use s390_pv_core::attest::ExchangeMagic;
/// # use s390_pv_core::request::MagicValue;
/// # fn main() {
/// # let magic =
/// b"pvattest"
/// # ;
/// # assert!(ExchangeMagic::starts_with_magic(magic));
/// # }
/// ```
pub struct ExchangeMagic;
impl MagicValue<8> for ExchangeMagic {
const MAGIC: [u8; 8] = [0x70, 0x76, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74];
}
/// Version identifier for an [`ExchangeFormatRequest`]
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExchangeFormatVersion {
/// Version 1 (= 0x0100)
One = 0x0100,
}
impl<E: ByteOrder> TryFrom<U32<E>> for ExchangeFormatVersion {
type Error = Error;
fn try_from(value: U32<E>) -> Result<Self, Self::Error> {
if value.get() == Self::One as u32 {
Ok(Self::One)
} else {
bail!(
"{INV_EXCHANGE_FMT_ERROR_TEXT} Unsupported version: ({})",
value.get()
);
}
}
}
impl<E: ByteOrder> From<ExchangeFormatVersion> for U32<E> {
fn from(value: ExchangeFormatVersion) -> Self {
(value as u32).into()
}
}
/// A parsed exchange entry value
///
/// An entry can be all zero(None), just a size (Exp) or a offset+size to some data (Data)
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum ExpOrData {
Exp(u32),
Data(Vec<u8>),
None,
}
impl ExpOrData {
/// calculates the (expected or real) size
fn size(&self) -> u32 {
match self {
Self::Exp(s) => *s,
// size is max u32 large as read in before
Self::Data(v) => v.len() as u32,
Self::None => 0,
}
}
/// Returns data if self is [`ExpOrData::Data`]
///
/// Consumes itself
fn data(self) -> Option<Vec<u8>> {
match self {
Self::Data(v) => Some(v),
_ => None,
}
}
}
impl From<Option<u32>> for ExpOrData {
fn from(value: Option<u32>) -> Self {
value.map_or(Self::None, Self::Exp)
}
}
impl From<&ExpOrData> for Option<u32> {
fn from(value: &ExpOrData) -> Self {
match value {
ExpOrData::Exp(v) => Some(*v),
_ => None,
}
}
}
impl From<ExpOrData> for Option<Vec<u8>> {
fn from(value: ExpOrData) -> Self {
match value {
ExpOrData::Exp(s) => Some(vec![0; s as usize]),
ExpOrData::Data(d) => Some(d),
ExpOrData::None => None,
}
}
}
/// The _exchange format_ is a simple file format to send labeled binary blobs between
/// pvattest instances on different machines.
#[derive(Debug, PartialEq, Eq)]
pub struct ExchangeFormatRequest {
// all sizes are guaranteed to fit in the exchange format/UV Call at any time
// pub to allow deconstruction of this struct
pub arcb: Vec<u8>,
pub exp_measurement: u32,
pub exp_additional: u32,
}
/// The _exchange format_ is a simple file format to send labeled binary blobs between
/// pvattest instances on different machines.
#[derive(Debug, PartialEq, Eq)]
pub struct ExchangeFormatResponse {
// all sizes are guaranteed to fit in the exchange format/UV Call at any time
// pub to allow deconstruction of this struct
pub arcb: Vec<u8>,
pub measurement: Vec<u8>,
pub additional: Option<Vec<u8>>,
pub user: Option<Vec<u8>>,
pub config_uid: ConfigUid,
}
impl ExchangeFormatRequest {
/// Creates a new exchange context, with an attestation request, expected measurement and
/// optional an additional data size. Useful for creating a attestation request.
pub fn new(arcb: Vec<u8>, exp_measurement: u32, exp_additional: u32) -> Result<Self> {
verify_size(
exp_measurement,
1,
AttestationCmd::MEASUREMENT_MAX_SIZE,
"Expected measurement size",
)?;
verify_size(
exp_additional,
0,
AttestationCmd::ADDITIONAL_MAX_SIZE,
"Expected additional data size",
)?;
verify_slice(&arcb, AttestationCmd::ARCB_MAX_SIZE, "Attestation request")?;
Ok(Self {
arcb,
exp_measurement,
exp_additional,
})
}
fn write_v1<W>(&self, writer: &mut W) -> Result<()>
where
W: Write,
{
let hdr = ExchangeFormatV1Hdr::new_request(
self.arcb.as_slice(),
self.exp_measurement,
self.exp_additional,
)?;
writer.write_all(hdr.as_bytes())?;
writer.write_all(&self.arcb)?;
Ok(())
}
/// Serializes the encapsulated data into the provides stream in the provided format
pub fn write<W>(&self, writer: &mut W, version: ExchangeFormatVersion) -> Result<()>
where
W: Write,
{
match version {
ExchangeFormatVersion::One => self.write_v1(writer),
}
}
/// Reads and deserializes the exchange file in the provided stream
///
/// # Errors
///
/// Returns an error if the stream does not contain data in exchange format, CUID or user data
/// do not fit, or any IO error that can appear during reading streams.
pub fn read<R>(reader: &mut R) -> Result<Self>
where
R: Read + Seek,
{
let mut buf = vec![0; size_of::<ExchangeFormatV1Hdr>()];
match reader.read_exact(&mut buf) {
Ok(it) => it,
// report hdr file to small for header
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} Invalid Header.");
}
Err(err) => return Err(err.into()),
};
if !ExchangeMagic::starts_with_magic(&buf) {
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} Does not start with the magic value.",);
}
let hdr = ExchangeFormatV1Hdr::ref_from_bytes(buf.as_slice())
.map_err(|_| anyhow!("{INV_EXCHANGE_FMT_ERROR_TEXT} Invalid Header."))?;
match TryInto::<ExchangeFormatVersion>::try_into(hdr.version)? {
ExchangeFormatVersion::One => (),
}
if stream_len(reader)? < hdr.size.get() as u64 {
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} File size too small");
}
let arcb = hdr.arcb.read(reader)?.data().ok_or(anyhow!(
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation request.",
))?;
let measurement = hdr.measurement.read(reader)?.size();
let additional = hdr.additional.read(reader)?.size();
Self::new(arcb, measurement, additional)
}
}
// Seek::stream_is unstable
// not expose to API users
// taken from rust std::io::seek;
fn stream_len<S>(seek: &mut S) -> Result<u64>
where
S: Seek,
{
let old_pos = seek.stream_position()?;
let len = seek.seek(SeekFrom::End(0))?;
// Avoid seeking a third time when we were already at the end of the
// stream. The branch is usually way cheaper than a seek operation.
if old_pos != len {
seek.seek(SeekFrom::Start(old_pos))?;
}
Ok(len)
}
fn verify_size(size: u32, min_size: u32, max_size: u32, field: &'static str) -> Result<()> {
if size < min_size {
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} The {field} field is too small ({size})");
}
if size > max_size {
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} The {field} field is too large ({size})");
}
Ok(())
}
/// check that a slice has at max `max_size` amount of bytes
fn verify_slice(val: &[u8], max_size: u32, field: &'static str) -> Result<()> {
if val.len() > max_size as usize {
bail!(
"{INV_EXCHANGE_FMT_ERROR_TEXT} The {field} field is too large ({})",
val.len()
);
}
Ok(())
}
impl ExchangeFormatResponse {
/// Creates a new exchange context, with an attestation request, measurement and
/// cuid.
pub fn new(
arcb: Vec<u8>,
measurement: Vec<u8>,
additional: Option<Vec<u8>>,
user: Option<Vec<u8>>,
config_uid: ConfigUid,
) -> Result<Self> {
// should not fail; Already checked during import.
verify_slice(
&arcb,
AttestationCmd::ARCB_MAX_SIZE,
"Attestation request data",
)?;
verify_slice(
&measurement,
AttestationCmd::MEASUREMENT_MAX_SIZE,
"Attestation Measurement",
)?;
if let Some(additional) = &additional {
verify_slice(
additional,
AttestationCmd::ADDITIONAL_MAX_SIZE,
"Additional data",
)?;
}
if let Some(user) = &user {
verify_slice(user, AttestationCmd::USER_MAX_SIZE, "User data")?;
}
Ok(Self {
arcb,
measurement,
additional,
user,
config_uid,
})
}
fn write_v1<W>(&self, writer: &mut W) -> Result<()>
where
W: Write,
{
let hdr = ExchangeFormatV1Hdr::new_response(
self.arcb.as_slice(),
&self.measurement,
self.additional.as_deref(),
self.user.as_deref(),
&self.config_uid,
)?;
writer.write_all(hdr.as_bytes())?;
writer.write_all(&self.arcb)?;
writer.write_all(&self.measurement)?;
if let Some(data) = &self.additional {
writer.write_all(data)?;
}
if let Some(data) = &self.user {
writer.write_all(data)?;
}
writer.write_all(&self.config_uid)?;
Ok(())
}
/// Serializes the encapsulated data into the provides stream in the provided format
pub fn write<W>(&self, writer: &mut W, version: ExchangeFormatVersion) -> Result<()>
where
W: Write,
{
match version {
ExchangeFormatVersion::One => self.write_v1(writer),
}
}
/// Reads and deserializes the exchange file in the provided stream
///
/// # Errors
///
/// Returns an error if the stream does not contain data in exchange format, CUID or user data
/// do not fit, or any IO error that can appear during reading streams.
pub fn read<R>(reader: &mut R) -> Result<Self>
where
R: Read + Seek,
{
let mut buf = vec![0; size_of::<ExchangeFormatV1Hdr>()];
match reader.read_exact(&mut buf) {
Ok(it) => it,
// report hdr file to small for header
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} Invalid Header.");
}
Err(err) => return Err(err.into()),
};
if !ExchangeMagic::starts_with_magic(&buf) {
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} Does not start with the magic value.");
}
let hdr = ExchangeFormatV1Hdr::ref_from_bytes(buf.as_slice())
.map_err(|_| anyhow!("{INV_EXCHANGE_FMT_ERROR_TEXT} Invalid Header."))?;
match TryInto::<ExchangeFormatVersion>::try_into(hdr.version)? {
ExchangeFormatVersion::One => (),
}
if stream_len(reader)? < hdr.size.get() as u64 {
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} File size too small");
}
let arcb = hdr.arcb.read(reader)?.data().ok_or(anyhow!(
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation request.",
))?;
let measurement = hdr.measurement.read(reader)?.data().ok_or(anyhow!(
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation response (Measurement missing).",
))?;
let additional = hdr.additional.read(reader)?.data();
let user = hdr.user.read(reader)?.data();
let config_uid: ConfigUid = match hdr.config_uid.read(reader)?.data() {
Some(v) => v.try_into().map_err(|_| {
anyhow!(
"{INV_EXCHANGE_FMT_ERROR_TEXT} Configuration UID has an invalid size. Expected size 16, is {}",hdr.config_uid.size.get()
)
})?,
None =>
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation response (CUID missing).")
,
};
Self::new(arcb, measurement, additional, user, config_uid)
}
/// Returns the measurement of this [`ExchangeFormatRequest`].
pub fn measurement(&self) -> &[u8] {
&self.measurement
}
/// Returns the additional data of this [`ExchangeFormatRequest`].
pub fn additional(&self) -> Option<&[u8]> {
self.additional.as_deref()
}
/// Returns the user data of this [`ExchangeFormatRequest`].
pub fn user(&self) -> Option<&[u8]> {
self.user.as_deref()
}
/// Returns the config UID of this [`ExchangeFormatRequest`].
///
/// # Error
/// Returns an error if the [`ExchangeFormatRequest`] contains no CUID,
pub const fn config_uid(&self) -> &ConfigUid {
&self.config_uid
}
/// Returns a reference to the attestation request of this [`ExchangeFormatRequest`].
pub fn arcb(&self) -> &[u8] {
self.arcb.as_ref()
}
}
#[cfg(test)]
mod test {
use std::io::Cursor;
use pv::misc::read_file;
use super::*;
#[test]
fn exchange_from_slice() {
let val = &[0; 17];
let mut offset = 18;
let entry = Entry::from_slice(Some(val), 20, &mut offset);
assert_eq!(
entry,
Entry {
size: 17.into(),
offset: 18.into(),
}
);
assert_eq!(offset, 18 + 17);
}
static ARCB: [u8; 16] = [0x11; 16];
static MEASUREMENT: [u8; 64] = [0x12; 64];
static ADDITIONAL: [u8; 32] = [0x13; 32];
static CUID: [u8; 16] = [0x14; 16];
static USER: [u8; 256] = [0x15; 256];
fn test_read_write_request(
path: &'static str,
arcb: Vec<u8>,
measurement: usize,
additional: usize,
) {
// TODO as 32 checks
let ctx_write = ExchangeFormatRequest::new(arcb, measurement as u32, additional as u32)
.expect("exchange fmt creation");
// let mut out = create_file(path).unwrap();
let mut out = vec![];
ctx_write
.write(&mut out, ExchangeFormatVersion::One)
.unwrap();
let buf = read_file(path, "test read exchange").unwrap();
assert_eq!(out, buf);
let ctx_read = ExchangeFormatRequest::read(&mut Cursor::new(&mut &buf)).unwrap();
assert_eq!(ctx_read, ctx_write);
}
fn test_read_write_response(
path: &'static str,
arcb: Vec<u8>,
measurement: Vec<u8>,
additional: Option<Vec<u8>>,
user: Option<Vec<u8>>,
cuid: ConfigUid,
) {
let ctx_write = ExchangeFormatResponse::new(arcb, measurement, additional, user, cuid)
.expect("exchange fmt creation");
// let mut out = create_file(path).unwrap();
let mut out = vec![];
ctx_write
.write(&mut out, ExchangeFormatVersion::One)
.unwrap();
let buf = read_file(path, "test read exchange").unwrap();
assert_eq!(out, buf);
let ctx_read = ExchangeFormatResponse::read(&mut Cursor::new(&mut &buf)).unwrap();
assert_eq!(ctx_read, ctx_write);
}
#[test]
fn full_req() {
test_read_write_request(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/full_req.bin"
),
ARCB.to_vec(),
MEASUREMENT.len(),
ADDITIONAL.len(),
);
}
#[test]
fn add_req() {
test_read_write_request(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/add_req.bin"
),
ARCB.to_vec(),
MEASUREMENT.len(),
ADDITIONAL.len(),
);
}
#[test]
fn invalid_req() {
ExchangeFormatRequest::new(ARCB.to_vec(), 0, ADDITIONAL.len() as u32).unwrap_err();
}
#[test]
fn test_additional_data_size_validation() {
// Test for TODO 1 fix: Additional data size validation
let arcb = ARCB.to_vec();
// Test with valid size at maximum
let result = ExchangeFormatV1Hdr::new_request(
&arcb,
MEASUREMENT.len() as u32,
AttestationCmd::ADDITIONAL_MAX_SIZE,
);
assert!(
result.is_ok(),
"Maximum additional data size should be accepted"
);
// Test with size exceeding maximum
let result = ExchangeFormatV1Hdr::new_request(
&arcb,
MEASUREMENT.len() as u32,
AttestationCmd::ADDITIONAL_MAX_SIZE + 1,
);
assert!(
result.is_err(),
"Additional data size exceeding maximum should fail"
);
if let Err(e) = result {
let error_msg = e.to_string();
assert!(
error_msg.contains("exceeds maximum"),
"Error should mention exceeding maximum: {}",
error_msg
);
}
// Test with zero size (no additional data)
let result = ExchangeFormatV1Hdr::new_request(&arcb, MEASUREMENT.len() as u32, 0);
assert!(
result.is_ok(),
"Zero additional data size should be accepted"
);
}
#[test]
fn min_req() {
test_read_write_request(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/min_req.bin"
),
ARCB.to_vec(),
MEASUREMENT.len(),
0,
);
}
#[test]
fn full_resp() {
test_read_write_response(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/full_resp.bin"
),
ARCB.to_vec(),
MEASUREMENT.to_vec(),
ADDITIONAL.to_vec().into(),
USER.to_vec().into(),
CUID,
);
}
#[test]
fn add_resp() {
test_read_write_response(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/add_resp.bin"
),
ARCB.to_vec(),
MEASUREMENT.to_vec(),
ADDITIONAL.to_vec().into(),
None,
CUID,
);
}
#[test]
fn user_resp() {
test_read_write_response(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/user_resp.bin"
),
ARCB.to_vec(),
MEASUREMENT.to_vec(),
None,
USER.to_vec().into(),
CUID,
);
}
#[test]
fn min_resp() {
test_read_write_response(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/min_resp.bin"
),
ARCB.to_vec(),
MEASUREMENT.to_vec(),
None,
None,
CUID,
)
}
#[test]
fn resp_no_cuid() {
let buf = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/min_req.bin"
));
let _ctx_read = ExchangeFormatResponse::read(&mut Cursor::new(&mut &buf)).unwrap_err();
}
#[test]
fn resp_inv_magic() {
let mut buf = read_file(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/min_req.bin"
),
"test resp inv magic",
)
.unwrap();
// tamper with the magic
buf[0] = !buf[0];
let _ctx_read = ExchangeFormatResponse::read(&mut Cursor::new(&mut &buf)).unwrap_err();
}
#[test]
fn no_arcb() {
let mut buf = read_file(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/min_req.bin"
),
"test resp inv magic",
)
.unwrap();
// delete the arcb entry
buf[0x18..0x20].copy_from_slice(&[0; 8]);
let _ctx_read = ExchangeFormatRequest::read(&mut Cursor::new(&mut &buf)).unwrap_err();
}
#[test]
fn small() {
let mut buf = read_file(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/min_req.bin"
),
"test resp inv magic",
)
.unwrap();
buf.pop();
let _ctx_read = ExchangeFormatRequest::read(&mut Cursor::new(&mut &buf)).unwrap_err();
}
#[test]
fn hdr() {
// buffer smaller than the header but containing the magic
let buf = [
0x70, 0x76, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x1, 0x2, 0x3, 0x4,
];
let _ctx_read = ExchangeFormatRequest::read(&mut Cursor::new(&mut &buf)).unwrap_err();
}
#[test]
fn version() {
let mut buf = read_file(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/min_req.bin"
),
"test resp inv magic",
)
.unwrap();
// tamper with the version
buf[0x8] = 0xff;
let _ctx_read = ExchangeFormatRequest::read(&mut Cursor::new(&mut &buf)).unwrap_err();
}
#[test]
fn cuid_size() {
let mut buf = read_file(
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/assets/",
"exp/exchange/min_resp.bin"
),
"test resp inv magic",
)
.unwrap();
// tamper with the cuid size
buf[0x3b] = 0xf;
let _ctx_read = ExchangeFormatResponse::read(&mut Cursor::new(&mut &buf)).unwrap_err();
}
}