Files
s390-tools/rust/pvattest/src/exchange.rs
T
Steffen Eiden c88e0276c1 pvattest: Improve UX on non-s390 systems
If pvattest perform is executed with options on a non-s390 system the
user gets misleading error messages as the options are not defined.

> pvattest perform -i attestation_request.bin  -o attresp.bin
  error: unexpected argument '-i' found

This may lead the user to think wrong arguments where chosen, which is
not entirely true as they are valid on s390. The more helpful error
message would be
error: Command only available on s390x

Which is already the case if no arguments are given.
Solve this by allowing non-s390 systems to parse the options:

> pvattest perform -i attestation_request.bin  -o attresp.bin
  error: Command only available on s390x

While at it ignore some unused code warnings in the exchange format code
that appear on non-s390 systems as not all code is used.

Reported-by: Carlo Della Giusta <carlo.dellagiusta@suse.com>
Fixes: 16610a211f ("rust: pvattest-Rust")
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>
2026-04-30 08:44:02 +02:00

876 lines
25 KiB
Rust

// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
#![allow(unused)]
use anyhow::{anyhow, bail, Error, Result};
use pv::{assert_size, request::MagicValue, uv::AttestationCmd, uv::ConfigUid};
use std::{
io::{ErrorKind, Read, Seek, SeekFrom, Write},
mem::size_of,
};
use zerocopy::ByteOrder;
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
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 => Some(size),
};
// TODO min and max size check?
let additional_entry = Entry::from_exp(exp_add); //, AttestationCmd::ADDITIONAL_MAX_SIZE, &mut offset);
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.",
))?;
// TODO remove unwrap
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 super::*;
use pv::misc::read_file;
#[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 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();
}
}