From bb3f93171fa5c9dc478145cf155e664defd91997 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 3 Sep 2025 11:00:16 -0700 Subject: [PATCH 01/13] [wip] start on FRUID component details --- gateway-messages/src/sp_to_mgs.rs | 25 +++++- gateway-messages/src/sp_to_mgs/vpd.rs | 120 ++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 gateway-messages/src/sp_to_mgs/vpd.rs diff --git a/gateway-messages/src/sp_to_mgs.rs b/gateway-messages/src/sp_to_mgs.rs index 17f007fe..a23d7d33 100644 --- a/gateway-messages/src/sp_to_mgs.rs +++ b/gateway-messages/src/sp_to_mgs.rs @@ -25,6 +25,7 @@ use serde_repr::Serialize_repr; pub mod ignition; pub mod measurement; pub mod monorail_port_status; +pub mod vpd; pub use ignition::IgnitionState; pub use measurement::Measurement; @@ -711,20 +712,22 @@ pub struct TlvPage { /// possible types contained in a component details message. Each TLV-encoded /// struct corresponds to one of these cases. #[derive(Debug, Clone)] -pub enum ComponentDetails { +pub enum ComponentDetails<'a> { PortStatus(Result), Measurement(Measurement), + Vpd(vpd::Vpd<'a>), } -impl ComponentDetails { +impl ComponentDetails<'_> { pub fn tag(&self) -> tlv::Tag { match self { ComponentDetails::PortStatus(_) => PortStatus::TAG, ComponentDetails::Measurement(_) => MeasurementHeader::TAG, + ComponentDetails::Vpd(vpd) => vpd.tag(), } } - pub fn serialize(&self, buf: &mut [u8]) -> hubpack::error::Result { + pub fn serialize(&self, buf: &mut [u8]) -> hubpack::Result { match self { ComponentDetails::PortStatus(p) => hubpack::serialize(buf, p), ComponentDetails::Measurement(m) => { @@ -742,6 +745,18 @@ impl ComponentDetails { Ok(n + m.name.len()) } } + ComponentDetails::Vpd(vpd) => tlv::encode(buf, vpd.tag(), |buf| { + let bytes = vpd.value_bytes(); + if bytes.len() > buf.len() { + return Err(hubpack::Error::Overrun); + } + buf[..bytes.len()].copy_from_slice(bytes); + Ok(bytes.len()) + }) + .map_err(|e| match e { + tlv::EncodeError::BufferTooSmall => hubpack::Error::Overrun, + tlv::EncodeError::Custom(e) => e, + }), } } } @@ -868,6 +883,10 @@ bitflags! { const HAS_MEASUREMENT_CHANNELS = 1 << 1; const HAS_SERIAL_CONSOLE = 1 << 2; const IS_LED = 1 << 3; + /// Indicates that this device has its own vital product data (e.g. part + /// number/serial number) which can be read by requesting the device's + /// details. + const HAS_VPD = 1 << 4; // MGS has a placeholder API for powering off an individual component; // do we want to keep that? If so, add a bit for "can be powered on and // off". diff --git a/gateway-messages/src/sp_to_mgs/vpd.rs b/gateway-messages/src/sp_to_mgs/vpd.rs new file mode 100644 index 00000000..79d85fc8 --- /dev/null +++ b/gateway-messages/src/sp_to_mgs/vpd.rs @@ -0,0 +1,120 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crate::tlv; +use core::fmt; +use core::str; +use zerocopy::{little_endian, IntoBytes}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Vpd<'buf> { + Cpn(&'buf str), + Serial(&'buf str), + OxideRev(little_endian::U32), + Mpn(&'buf str), + MfgName(&'buf str), + MfgRev(&'buf str), +} + +impl Vpd<'_> { + // TLV tags for FRUID VPD. + // See: https://rfd.shared.oxide.computer/rfd/308#_fruid_data + + pub const SERIAL_TAG: tlv::Tag = tlv::Tag(*b"SER0"); + /// Tag for Oxide part numbers. The value of this tag is a UTF-8-encoded string. + pub const CPN_TAG: tlv::Tag = tlv::Tag(*b"CPN0"); + /// Tag for Oxide revisions. The value of this tag is always a little-endian `u32`. + pub const OXIDE_REV_TAG: tlv::Tag = tlv::Tag(*b"REV0"); + + /// Tag for manufacturer names. The value of this tag is a UTF-8-encoded string. + pub const MFG_TAG: tlv::Tag = tlv::Tag(*b"MFG0"); + /// Tag for manufacturer part numbers. The value of this tag is a UTF-8-encoded string. + pub const MPN_TAG: tlv::Tag = tlv::Tag(*b"MPN0"); + /// Manufacturer revision tag. Unlike `OXIDE_REV_TAG`, this is a byte array rather than a `u32`. + pub const MFG_REV_TAG: tlv::Tag = tlv::Tag(*b"MRV0"); + + pub fn tag(&self) -> tlv::Tag { + match self { + Self::Cpn(_) => Self::CPN_TAG, + Self::Serial(_) => Self::SERIAL_TAG, + Self::OxideRev(_) => Self::OXIDE_REV_TAG, + Self::Mpn(_) => Self::MPN_TAG, + Self::MfgName(_) => Self::MFG_TAG, + Self::MfgRev(_) => Self::MFG_REV_TAG, + } + } + + pub fn value_bytes(&self) -> &[u8] { + match self { + Self::Cpn(cpn) => cpn.as_bytes(), + Self::Serial(serial) => serial.as_bytes(), + Self::OxideRev(rev) => rev.as_bytes(), + Self::Mpn(mpn) => mpn.as_bytes(), + Self::MfgName(name) => name.as_bytes(), + Self::MfgRev(rev) => rev.as_bytes(), + } + } +} + +/// Intended for use with [`tlv::decode_iter`] and friends. +impl<'buf> TryFrom<(tlv::Tag, &'buf [u8])> for Vpd<'buf> { + type Error = DecodeError; + fn try_from( + (tag, value): (tlv::Tag, &'buf [u8]), + ) -> Result { + match tag { + Self::CPN_TAG => std::str::from_utf8(value) + .map_err(DecodeError::invalid_str(tag)) + .map(Self::Cpn), + Self::SERIAL_TAG => std::str::from_utf8(value) + .map_err(DecodeError::invalid_str(tag)) + .map(Self::Serial), + Self::OXIDE_REV_TAG => { + let bytes: [u8; 4] = value + .try_into() + .map_err(|_| DecodeError::InvalidU32(tag))?; + Ok(Self::OxideRev(u32::from_le_bytes(bytes).into())) + } + Self::MPN_TAG => std::str::from_utf8(value) + .map_err(DecodeError::invalid_str(tag)) + .map(Self::Mpn), + Self::MFG_TAG => std::str::from_utf8(value) + .map_err(DecodeError::invalid_str(tag)) + .map(Self::MfgName), + Self::MFG_REV_TAG => std::str::from_utf8(value) + .map_err(DecodeError::invalid_str(tag)) + .map(Self::MfgRev), + _ => Err(DecodeError::UnexpectedTag(tag)), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DecodeError { + UnexpectedTag(tlv::Tag), + InvalidUtf8(tlv::Tag, str::Utf8Error), + InvalidU32(tlv::Tag), +} + +impl DecodeError { + fn invalid_str(tag: tlv::Tag) -> impl Fn(str::Utf8Error) -> Self { + move |err| DecodeError::InvalidUtf8(tag, err) + } +} + +impl fmt::Display for DecodeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DecodeError::UnexpectedTag(tag) => { + write!(f, "unexpected TLV tag {tag:?}") + } + DecodeError::InvalidUtf8(tag, err) => { + write!(f, "value for tag {tag:?} was not UTF-8: {err}") + } + DecodeError::InvalidU32(tag) => { + write!(f, "value for tag {tag:?} was not a u32") + } + } + } +} From b25554b283a49a44da36aab8d45cfa4a5bec5340 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 4 Sep 2025 15:24:24 -0700 Subject: [PATCH 02/13] wip --- gateway-messages/src/sp_impl.rs | 63 ++++++++++++++++++++------- gateway-messages/src/sp_to_mgs.rs | 23 ++++++++++ gateway-messages/src/sp_to_mgs/vpd.rs | 23 ++++++++++ 3 files changed, 93 insertions(+), 16 deletions(-) diff --git a/gateway-messages/src/sp_impl.rs b/gateway-messages/src/sp_impl.rs index 9bd5e76b..12ed5440 100644 --- a/gateway-messages/src/sp_impl.rs +++ b/gateway-messages/src/sp_impl.rs @@ -51,6 +51,7 @@ use crate::UpdateId; use crate::UpdateStatus; use crate::HF_PAGE_SIZE; use crate::ROT_PAGE_SIZE; +use core::ops::ControlFlow; use hubpack::error::Error as HubpackError; use hubpack::error::Result as HubpackResult; @@ -282,7 +283,7 @@ pub trait SpHandler { &mut self, component: SpComponent, index: BoundsChecked, - ) -> ComponentDetails; + ) -> ComponentDetails<'_>; fn component_clear_status( &mut self, @@ -510,14 +511,20 @@ pub fn handle_message( component, offset, total, - }) => encode_tlv_structs( - &mut out[n..], - (offset..total).map(|i| { + }) => { + let mut encoder = TlvEncoder::new(&mut out[n..]); + for i in offset..total { let details = handler.component_details(component, BoundsChecked(i)); - (details.tag(), move |buf: &mut [u8]| details.serialize(buf)) - }), - ), + if encoder + .encode(details.tag(), move |buf| details.serialize(buf)) + .is_break() + { + break; + } + } + encoder.total_tlv_len() + } Some(OutgoingTrailingData::BulkIgnitionState(iter)) => { encode_tlv_structs( &mut out[n..], @@ -554,32 +561,56 @@ pub fn handle_message( /// many TLV triples from `iter` as we can into `out`. /// /// Returns the total number of bytes written into `out`. -fn encode_tlv_structs(mut out: &mut [u8], iter: I) -> usize +fn encode_tlv_structs(out: &mut [u8], iter: I) -> usize where I: Iterator, F: FnOnce(&mut [u8]) -> HubpackResult, { - let mut total_tlv_len = 0; - + let mut encoder = TlvEncoder::new(out); for (tag, encode) in iter { - match tlv::encode(out, tag, encode) { + if encoder.encode(tag, encode).is_break() { + break; + } + } + + encoder.total_tlv_len() +} + +struct TlvEncoder<'out> { + out: &'out mut [u8], + total_tlv_len: usize, +} + +impl<'out> TlvEncoder<'out> { + fn new(out: &'out mut [u8]) -> Self { + Self { out, total_tlv_len: 0 } + } + + fn total_tlv_len(&self) -> usize { + self.total_tlv_len + } + + fn encode( + &mut self, + tag: tlv::Tag, + encode: impl FnOnce(&mut [u8]) -> Result, + ) -> core::ops::ControlFlow<()> { + match tlv::encode(&mut self.out[self.total_tlv_len..], tag, encode) { Ok(n) => { - total_tlv_len += n; - out = &mut out[n..]; + self.total_tlv_len += n; + ControlFlow::Continue(()) } // If either the `encode` closure or the TLV header doesn't fit, // we've packed as much as we can into `out` and we're done. Err(tlv::EncodeError::Custom(HubpackError::Overrun)) - | Err(tlv::EncodeError::BufferTooSmall) => break, + | Err(tlv::EncodeError::BufferTooSmall) => ControlFlow::Break(()), // Other hubpack errors are impossible with all serialization types // we use. Err(tlv::EncodeError::Custom(_)) => panic!(), } } - - total_tlv_len } // We could use a combination of Option/Result/tuples to represent the results diff --git a/gateway-messages/src/sp_to_mgs.rs b/gateway-messages/src/sp_to_mgs.rs index a23d7d33..e6de43ca 100644 --- a/gateway-messages/src/sp_to_mgs.rs +++ b/gateway-messages/src/sp_to_mgs.rs @@ -718,6 +718,14 @@ pub enum ComponentDetails<'a> { Vpd(vpd::Vpd<'a>), } +#[derive(Debug, Clone)] +#[cfg(feature = "std")] +pub enum OwnedComponentDetails { + PortStatus(Result), + Measurement(Measurement), + Vpd(vpd::OwnedVpd), +} + impl ComponentDetails<'_> { pub fn tag(&self) -> tlv::Tag { match self { @@ -759,6 +767,21 @@ impl ComponentDetails<'_> { }), } } + + #[cfg(feature = "std")] + pub fn into_owned(self) -> OwnedComponentDetails { + match self { + ComponentDetails::PortStatus(status) => { + OwnedComponentDetails::PortStatus(status) + } + ComponentDetails::Measurement(measurement) => { + OwnedComponentDetails::Measurement(measurement) + } + ComponentDetails::Vpd(vpd) => { + OwnedComponentDetails::Vpd(vpd.into_owned()) + } + } + } } #[derive( diff --git a/gateway-messages/src/sp_to_mgs/vpd.rs b/gateway-messages/src/sp_to_mgs/vpd.rs index 79d85fc8..e4041274 100644 --- a/gateway-messages/src/sp_to_mgs/vpd.rs +++ b/gateway-messages/src/sp_to_mgs/vpd.rs @@ -17,6 +17,17 @@ pub enum Vpd<'buf> { MfgRev(&'buf str), } +#[cfg(feature = "std")] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum OwnedVpd { + Cpn(String), + Serial(String), + OxideRev(u32), + Mpn(String), + MfgName(String), + MfgRev(String), +} + impl Vpd<'_> { // TLV tags for FRUID VPD. // See: https://rfd.shared.oxide.computer/rfd/308#_fruid_data @@ -55,6 +66,18 @@ impl Vpd<'_> { Self::MfgRev(rev) => rev.as_bytes(), } } + + #[cfg(feature = "std")] + pub fn into_owned(self) -> OwnedVpd { + match self { + Self::Cpn(cpn) => OwnedVpd::Cpn(cpn.to_string()), + Self::Serial(serial) => OwnedVpd::Serial(serial.to_string()), + Self::OxideRev(rev) => OwnedVpd::OxideRev(rev.get()), + Self::Mpn(mpn) => OwnedVpd::Mpn(mpn.to_string()), + Self::MfgName(mfg_name) => OwnedVpd::MfgName(mfg_name.to_string()), + Self::MfgRev(mfg_rev) => OwnedVpd::MfgRev(mfg_rev.to_string()), + } + } } /// Intended for use with [`tlv::decode_iter`] and friends. From ae8ceecc1a879bfec922ea09ad35184bd29d8a21 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 5 Sep 2025 09:31:06 -0700 Subject: [PATCH 03/13] wip --- gateway-sp-comms/src/single_sp.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gateway-sp-comms/src/single_sp.rs b/gateway-sp-comms/src/single_sp.rs index 8ca55737..b26c24e0 100644 --- a/gateway-sp-comms/src/single_sp.rs +++ b/gateway-sp-comms/src/single_sp.rs @@ -28,7 +28,6 @@ use gateway_messages::BadRequestReason; use gateway_messages::CfpaPage; use gateway_messages::ComponentAction; use gateway_messages::ComponentActionResponse; -use gateway_messages::ComponentDetails; use gateway_messages::DeviceCapabilities; use gateway_messages::DeviceDescriptionHeader; use gateway_messages::DevicePresence; @@ -42,6 +41,7 @@ use gateway_messages::Message; use gateway_messages::MessageKind; use gateway_messages::MgsRequest; use gateway_messages::MonorailError; +use gateway_messages::OwnedComponentDetails; use gateway_messages::PowerState; use gateway_messages::PowerStateTransition; use gateway_messages::RotBootInfo; @@ -142,7 +142,7 @@ pub struct SpDevice { #[derive(Debug, Clone)] pub struct SpComponentDetails { - pub entries: Vec, + pub entries: Vec, } #[derive(Debug, Clone, Copy)] @@ -1550,7 +1550,7 @@ struct ComponentDetailsTlvRpc<'a> { } impl TlvRpc for ComponentDetailsTlvRpc<'_> { - type Item = ComponentDetails; + type Item = OwnedComponentDetails; const LOG_NAME: &'static str = "component details"; From 63b2debd90a0de90d337e5145efa85d8c2a576dd Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 5 Sep 2025 12:00:56 -0700 Subject: [PATCH 04/13] wip --- gateway-messages/src/sp_to_mgs.rs | 58 +++--- gateway-messages/src/sp_to_mgs/vpd.rs | 286 +++++++++++++++++++------- gateway-sp-comms/src/error.rs | 2 + gateway-sp-comms/src/single_sp.rs | 13 +- 4 files changed, 245 insertions(+), 114 deletions(-) diff --git a/gateway-messages/src/sp_to_mgs.rs b/gateway-messages/src/sp_to_mgs.rs index e6de43ca..c46a895d 100644 --- a/gateway-messages/src/sp_to_mgs.rs +++ b/gateway-messages/src/sp_to_mgs.rs @@ -29,6 +29,7 @@ pub mod vpd; pub use ignition::IgnitionState; pub use measurement::Measurement; +pub use vpd::Vpd; use ignition::IgnitionError; use measurement::MeasurementHeader; @@ -718,20 +719,20 @@ pub enum ComponentDetails<'a> { Vpd(vpd::Vpd<'a>), } -#[derive(Debug, Clone)] -#[cfg(feature = "std")] -pub enum OwnedComponentDetails { - PortStatus(Result), - Measurement(Measurement), - Vpd(vpd::OwnedVpd), -} +// #[derive(Debug, Clone)] +// #[cfg(feature = "std")] +// pub enum OwnedComponentDetails { +// PortStatus(Result), +// Measurement(Measurement), +// Vpd(vpd::OwnedVpd), +// } impl ComponentDetails<'_> { pub fn tag(&self) -> tlv::Tag { match self { ComponentDetails::PortStatus(_) => PortStatus::TAG, ComponentDetails::Measurement(_) => MeasurementHeader::TAG, - ComponentDetails::Vpd(vpd) => vpd.tag(), + ComponentDetails::Vpd(_) => Vpd::TAG, } } @@ -753,35 +754,24 @@ impl ComponentDetails<'_> { Ok(n + m.name.len()) } } - ComponentDetails::Vpd(vpd) => tlv::encode(buf, vpd.tag(), |buf| { - let bytes = vpd.value_bytes(); - if bytes.len() > buf.len() { - return Err(hubpack::Error::Overrun); - } - buf[..bytes.len()].copy_from_slice(bytes); - Ok(bytes.len()) - }) - .map_err(|e| match e { - tlv::EncodeError::BufferTooSmall => hubpack::Error::Overrun, - tlv::EncodeError::Custom(e) => e, - }), + ComponentDetails::Vpd(vpd) => vpd.encode(buf), } } - #[cfg(feature = "std")] - pub fn into_owned(self) -> OwnedComponentDetails { - match self { - ComponentDetails::PortStatus(status) => { - OwnedComponentDetails::PortStatus(status) - } - ComponentDetails::Measurement(measurement) => { - OwnedComponentDetails::Measurement(measurement) - } - ComponentDetails::Vpd(vpd) => { - OwnedComponentDetails::Vpd(vpd.into_owned()) - } - } - } + // #[cfg(feature = "std")] + // pub fn into_owned(self) -> OwnedComponentDetails { + // match self { + // ComponentDetails::PortStatus(status) => { + // OwnedComponentDetails::PortStatus(status) + // } + // ComponentDetails::Measurement(measurement) => { + // OwnedComponentDetails::Measurement(measurement) + // } + // ComponentDetails::Vpd(vpd) => { + // OwnedComponentDetails::Vpd(vpd.into_owned()) + // } + // } + // } } #[derive( diff --git a/gateway-messages/src/sp_to_mgs/vpd.rs b/gateway-messages/src/sp_to_mgs/vpd.rs index e4041274..e5163c20 100644 --- a/gateway-messages/src/sp_to_mgs/vpd.rs +++ b/gateway-messages/src/sp_to_mgs/vpd.rs @@ -5,119 +5,236 @@ use crate::tlv; use core::fmt; use core::str; -use zerocopy::{little_endian, IntoBytes}; +#[cfg(feature = "std")] +use std::borrow::Borrow; + +pub const MAX_STR_LEN: usize = 32; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Vpd<'buf> { - Cpn(&'buf str), - Serial(&'buf str), - OxideRev(little_endian::U32), - Mpn(&'buf str), - MfgName(&'buf str), - MfgRev(&'buf str), + Oxide(OxideVpd<'buf>), + Mfg(MfgVpd<'buf>), +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct OxideVpd<'buf> { + pub serial: &'buf str, + pub rev: u32, + pub part_number: &'buf str, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct MfgVpd<'buf> { + pub mfg: &'buf str, + pub serial: &'buf str, + pub mfg_rev: &'buf str, + pub mpn: &'buf str, +} + +#[cfg(feature = "std")] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct OwnedOxideVpd { + pub serial: String, + pub rev: u32, + pub part_number: String, +} + +#[cfg(feature = "std")] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct OwnedMfgVpd { + pub mfg: String, + pub serial: String, + pub mfg_rev: String, + pub mpn: String, } #[cfg(feature = "std")] #[derive(Clone, Debug, Eq, PartialEq)] pub enum OwnedVpd { - Cpn(String), - Serial(String), - OxideRev(u32), - Mpn(String), - MfgName(String), - MfgRev(String), + Oxide(OwnedOxideVpd), + Mfg(OwnedMfgVpd), } -impl Vpd<'_> { - // TLV tags for FRUID VPD. - // See: https://rfd.shared.oxide.computer/rfd/308#_fruid_data +// TLV tags for FRUID VPD. +// See: https://rfd.shared.oxide.computer/rfd/308#_fruid_data + +const SERIAL_TAG: tlv::Tag = tlv::Tag(*b"SER0"); +/// Tag for Oxide part numbers. The value of this tag is a UTF-8-encoded string. +const CPN_TAG: tlv::Tag = tlv::Tag(*b"CPN0"); +/// Tag for Oxide revisions. The value of this tag is always a little-endian `u32`. +const OXIDE_REV_TAG: tlv::Tag = tlv::Tag(*b"REV0"); - pub const SERIAL_TAG: tlv::Tag = tlv::Tag(*b"SER0"); - /// Tag for Oxide part numbers. The value of this tag is a UTF-8-encoded string. - pub const CPN_TAG: tlv::Tag = tlv::Tag(*b"CPN0"); - /// Tag for Oxide revisions. The value of this tag is always a little-endian `u32`. - pub const OXIDE_REV_TAG: tlv::Tag = tlv::Tag(*b"REV0"); +/// Tag for manufacturer names. The value of this tag is a UTF-8-encoded string. +const MFG_TAG: tlv::Tag = tlv::Tag(*b"MFG0"); +/// Tag for manufacturer part numbers. The value of this tag is a UTF-8-encoded string. +const MPN_TAG: tlv::Tag = tlv::Tag(*b"MPN0"); +/// Manufacturer revision tag. Unlike `OXIDE_REV_TAG`, this is a byte array rather than a `u32`. +const MFG_REV_TAG: tlv::Tag = tlv::Tag(*b"MRV0"); - /// Tag for manufacturer names. The value of this tag is a UTF-8-encoded string. - pub const MFG_TAG: tlv::Tag = tlv::Tag(*b"MFG0"); - /// Tag for manufacturer part numbers. The value of this tag is a UTF-8-encoded string. - pub const MPN_TAG: tlv::Tag = tlv::Tag(*b"MPN0"); - /// Manufacturer revision tag. Unlike `OXIDE_REV_TAG`, this is a byte array rather than a `u32`. - pub const MFG_REV_TAG: tlv::Tag = tlv::Tag(*b"MRV0"); +impl<'buf> Vpd<'buf> { + pub const TAG: tlv::Tag = tlv::Tag(*b"FRU0"); - pub fn tag(&self) -> tlv::Tag { + pub fn tlv_len(&self) -> usize { match self { - Self::Cpn(_) => Self::CPN_TAG, - Self::Serial(_) => Self::SERIAL_TAG, - Self::OxideRev(_) => Self::OXIDE_REV_TAG, - Self::Mpn(_) => Self::MPN_TAG, - Self::MfgName(_) => Self::MFG_TAG, - Self::MfgRev(_) => Self::MFG_REV_TAG, + Vpd::Oxide(vpd) => tlv::tlv_len(vpd.tlv_len()), + Vpd::Mfg(vpd) => tlv::tlv_len(vpd.tlv_len()), } } - pub fn value_bytes(&self) -> &[u8] { + pub fn encode(&self, out: &mut [u8]) -> Result { + if out.len() < self.tlv_len() { + return Err(hubpack::Error::Overrun); + } match self { - Self::Cpn(cpn) => cpn.as_bytes(), - Self::Serial(serial) => serial.as_bytes(), - Self::OxideRev(rev) => rev.as_bytes(), - Self::Mpn(mpn) => mpn.as_bytes(), - Self::MfgName(name) => name.as_bytes(), - Self::MfgRev(rev) => rev.as_bytes(), + Vpd::Oxide(vpd) => vpd.encode(out), + Vpd::Mfg(vpd) => vpd.encode(out), + } + .map_err(|e| match e { + tlv::EncodeError::BufferTooSmall => hubpack::Error::Overrun, + tlv::EncodeError::Custom(e) => e, + }) + } + + pub fn decode_body(buf: &'buf [u8]) -> Result { + let mut tags = tlv::decode_iter(buf); + + fn expect_tag<'a, T>( + tags: &mut impl Iterator< + Item = Result<(tlv::Tag, &'a [u8]), tlv::DecodeError>, + >, + expected_tag: tlv::Tag, + decode: impl Fn(&'a [u8]) -> Result, + ) -> Result { + match tags.next() { + Some(Ok((tag, value))) if tag == expected_tag => decode(value), + Some(Ok((tag, _))) => Err(DecodeError::UnexpectedTag(tag)), + Some(Err(err)) => Err(DecodeError::Tlv(expected_tag, err)), + None => Err(DecodeError::MissingTag(expected_tag)), + } + } + + fn expect_str_tag<'a>( + tags: &mut impl Iterator< + Item = Result<(tlv::Tag, &'a [u8]), tlv::DecodeError>, + >, + expected_tag: tlv::Tag, + ) -> Result<&'a str, DecodeError> { + expect_tag(tags, expected_tag, |value| { + core::str::from_utf8(value) + .map_err(DecodeError::invalid_str(expected_tag)) + }) + } + + let serial = expect_str_tag(&mut tags, SERIAL_TAG)?; + match tags.next() { + Some(Ok((MFG_TAG, mfg))) => { + let mfg = core::str::from_utf8(mfg) + .map_err(DecodeError::invalid_str(MFG_TAG))?; + let mpn = expect_str_tag(&mut tags, MPN_TAG)?; + let mfg_rev = expect_str_tag(&mut tags, MFG_REV_TAG)?; + Ok(Self::Mfg(MfgVpd { mfg, mpn, mfg_rev, serial })) + } + Some(Ok((CPN_TAG, cpn))) => { + let part_number = core::str::from_utf8(cpn) + .map_err(DecodeError::invalid_str(MFG_TAG))?; + let rev = expect_tag(&mut tags, OXIDE_REV_TAG, |value| { + let rev_bytes: [u8; 4] = value + .try_into() + .map_err(|_| DecodeError::InvalidU32(OXIDE_REV_TAG))?; + Ok(u32::from_le_bytes(rev_bytes)) + })?; + Ok(Self::Oxide(OxideVpd { part_number, rev, serial })) + } + Some(Ok((tag, _))) => Err(DecodeError::UnexpectedTag(tag)), + Some(Err(e)) => Err(DecodeError::TlvUntyped(e)), + None => Err(DecodeError::UnexpectedEnd), } } #[cfg(feature = "std")] pub fn into_owned(self) -> OwnedVpd { match self { - Self::Cpn(cpn) => OwnedVpd::Cpn(cpn.to_string()), - Self::Serial(serial) => OwnedVpd::Serial(serial.to_string()), - Self::OxideRev(rev) => OwnedVpd::OxideRev(rev.get()), - Self::Mpn(mpn) => OwnedVpd::Mpn(mpn.to_string()), - Self::MfgName(mfg_name) => OwnedVpd::MfgName(mfg_name.to_string()), - Self::MfgRev(mfg_rev) => OwnedVpd::MfgRev(mfg_rev.to_string()), + Self::Oxide(vpd) => OwnedVpd::Oxide(vpd.into_owned()), + Self::Mfg(vpd) => OwnedVpd::Mfg(vpd.into_owned()), } } } -/// Intended for use with [`tlv::decode_iter`] and friends. -impl<'buf> TryFrom<(tlv::Tag, &'buf [u8])> for Vpd<'buf> { - type Error = DecodeError; - fn try_from( - (tag, value): (tlv::Tag, &'buf [u8]), - ) -> Result { - match tag { - Self::CPN_TAG => std::str::from_utf8(value) - .map_err(DecodeError::invalid_str(tag)) - .map(Self::Cpn), - Self::SERIAL_TAG => std::str::from_utf8(value) - .map_err(DecodeError::invalid_str(tag)) - .map(Self::Serial), - Self::OXIDE_REV_TAG => { - let bytes: [u8; 4] = value - .try_into() - .map_err(|_| DecodeError::InvalidU32(tag))?; - Ok(Self::OxideRev(u32::from_le_bytes(bytes).into())) +impl OxideVpd<'_> { + pub fn tlv_len(&self) -> usize { + tlv::tlv_len(self.part_number.len()) + + tlv::tlv_len(self.serial.len()) + + tlv::tlv_len(4) // revision number (u32) + } + + pub fn encode( + &self, + out: &mut [u8], + ) -> Result> { + if out.len() < self.tlv_len() { + return Err(tlv::EncodeError::Custom(hubpack::Error::Overrun)); + } + let mut total = 0; + total += encode_str(&mut out[total..], SERIAL_TAG, self.serial)?; + total += encode_str(&mut out[total..], CPN_TAG, self.part_number)?; + total += tlv::encode(&mut out[total..], OXIDE_REV_TAG, |out| { + if out.len() < 4 { + return Err(hubpack::Error::Overrun); } - Self::MPN_TAG => std::str::from_utf8(value) - .map_err(DecodeError::invalid_str(tag)) - .map(Self::Mpn), - Self::MFG_TAG => std::str::from_utf8(value) - .map_err(DecodeError::invalid_str(tag)) - .map(Self::MfgName), - Self::MFG_REV_TAG => std::str::from_utf8(value) - .map_err(DecodeError::invalid_str(tag)) - .map(Self::MfgRev), - _ => Err(DecodeError::UnexpectedTag(tag)), + out.copy_from_slice(&self.rev.to_le_bytes()[..]); + Ok(4) + })?; + Ok(total) + } +} + +impl MfgVpd<'_> { + pub fn tlv_len(&self) -> usize { + tlv::tlv_len(self.mfg.len()) + + tlv::tlv_len(self.mpn.len()) + + tlv::tlv_len(self.mfg_rev.len()) + + tlv::tlv_len(self.serial.len()) + } + + pub fn encode( + &self, + out: &mut [u8], + ) -> Result> { + if out.len() < self.tlv_len() { + return Err(tlv::EncodeError::Custom(hubpack::Error::Overrun)); } + let mut total = 0; + total += encode_str(&mut out[total..], SERIAL_TAG, self.serial)?; + total += encode_str(&mut out[total..], MFG_TAG, self.mfg)?; + total += encode_str(&mut out[total..], MPN_TAG, self.mpn)?; + total += encode_str(&mut out[total..], MFG_REV_TAG, self.mfg_rev)?; + Ok(total) } } +fn encode_str( + out: &mut [u8], + tag: tlv::Tag, + value: &str, +) -> Result> { + tlv::encode(out, tag, |out| { + if out.len() < value.len() { + return Err(hubpack::Error::Overrun); + } + out[..value.len()].copy_from_slice(value.as_bytes()); + Ok(value.len()) + }) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DecodeError { + UnexpectedEnd, + MissingTag(tlv::Tag), UnexpectedTag(tlv::Tag), InvalidUtf8(tlv::Tag, str::Utf8Error), InvalidU32(tlv::Tag), + Tlv(tlv::Tag, tlv::DecodeError), + TlvUntyped(tlv::DecodeError), } impl DecodeError { @@ -129,15 +246,28 @@ impl DecodeError { impl fmt::Display for DecodeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - DecodeError::UnexpectedTag(tag) => { + Self::MissingTag(tag) => write!( + f, + "unexpected end of input while expecting TLV tag {tag:?}" + ), + Self::UnexpectedTag(tag) => { write!(f, "unexpected TLV tag {tag:?}") } - DecodeError::InvalidUtf8(tag, err) => { + Self::InvalidUtf8(tag, err) => { write!(f, "value for tag {tag:?} was not UTF-8: {err}") } - DecodeError::InvalidU32(tag) => { + Self::InvalidU32(tag) => { write!(f, "value for tag {tag:?} was not a u32") } + Self::Tlv(tag, error) => { + write!(f, "TLV decode error while decoding {tag:?}: {error}") + } + Self::TlvUntyped(error) => { + write!(f, "TLV decode error while expecting {MFG_TAG:?} or {CPN_TAG:?}: {error}") + } + Self::UnexpectedEnd => { + write!(f, "unexpected end of input") + } } } } diff --git a/gateway-sp-comms/src/error.rs b/gateway-sp-comms/src/error.rs index 8c45978e..bdae9938 100644 --- a/gateway-sp-comms/src/error.rs +++ b/gateway-sp-comms/src/error.rs @@ -55,6 +55,8 @@ pub enum CommunicationError { VersionMismatch { sp: u32, mgs: u32 }, #[error("failed to deserialize TLV value for tag {tag:?}: {err}")] TlvDeserialize { tag: tlv::Tag, err: gateway_messages::HubpackError }, + #[error("failed to deserialize TLV-encoded VPD: {err}")] + VpdDeserialize { err: gateway_messages::vpd::DecodeError }, #[error("failed to decode TLV triple")] TlvDecode(#[from] tlv::DecodeError), #[error("invalid pagination: {reason}")] diff --git a/gateway-sp-comms/src/single_sp.rs b/gateway-sp-comms/src/single_sp.rs index b26c24e0..28de0205 100644 --- a/gateway-sp-comms/src/single_sp.rs +++ b/gateway-sp-comms/src/single_sp.rs @@ -1576,6 +1576,7 @@ impl TlvRpc for ComponentDetailsTlvRpc<'_> { use gateway_messages::measurement::MeasurementHeader; use gateway_messages::monorail_port_status::PortStatus; use gateway_messages::monorail_port_status::PortStatusError; + use gateway_messages::vpd::Vpd; match tag { PortStatus::TAG => { @@ -1594,7 +1595,7 @@ impl TlvRpc for ComponentDetailsTlvRpc<'_> { ); } - Ok(Some(ComponentDetails::PortStatus(result))) + Ok(Some(OwnedComponentDetails::PortStatus(result))) } MeasurementHeader::TAG => { let (header, leftover) = @@ -1616,12 +1617,20 @@ impl TlvRpc for ComponentDetailsTlvRpc<'_> { } })?; - Ok(Some(ComponentDetails::Measurement(Measurement { + Ok(Some(OwnedComponentDetails::Measurement(Measurement { name: name.to_string(), kind: header.kind, value: header.value, }))) } + Vpd::TAG => { + let (vpd, leftover) = gateway_messages::Vpd::decode(value) + .map_err(|err| CommunicationError::VpdDeserialize { + err, + })?; + + Ok(Some(OwnedComponentDetails::Vpd(vpd))) + } _ => { info!( self.log, From 38237b8ef6cc6aac5acdf9c251cb6888bc5b4478 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 5 Sep 2025 12:17:36 -0700 Subject: [PATCH 05/13] okay i think this seems nice but we'll see what it's like in hubris... --- faux-mgs/src/main.rs | 50 +++++++++----- gateway-messages/src/sp_impl.rs | 12 ++-- gateway-messages/src/sp_to_mgs.rs | 34 ++-------- gateway-messages/src/sp_to_mgs/vpd.rs | 95 ++++++++++++++++++--------- gateway-sp-comms/src/single_sp.rs | 21 +++--- 5 files changed, 120 insertions(+), 92 deletions(-) diff --git a/faux-mgs/src/main.rs b/faux-mgs/src/main.rs index 48950ec8..97cd0863 100644 --- a/faux-mgs/src/main.rs +++ b/faux-mgs/src/main.rs @@ -2231,6 +2231,7 @@ enum Output { fn component_details_to_json(details: SpComponentDetails) -> serde_json::Value { use gateway_messages::measurement::{MeasurementError, MeasurementKind}; use gateway_messages::monorail_port_status::{PortStatus, PortStatusError}; + use gateway_messages::vpd::{MfgVpd, OxideVpd, Vpd}; // SpComponentDetails and Measurement from gateway_messages intentionally do // not derive `Serialize` to avoid accidental misuse in MGS / the SP, so we @@ -2240,6 +2241,8 @@ fn component_details_to_json(details: SpComponentDetails) -> serde_json::Value { enum ComponentDetails { PortStatus(Result), Measurement(Measurement), + OxideVpd { serial: String, part_number: String, rev: u32 }, + MfgVpd { mfg: String, part_number: String, rev: String, serial: String }, } #[derive(serde::Serialize)] @@ -2249,22 +2252,37 @@ fn component_details_to_json(details: SpComponentDetails) -> serde_json::Value { pub value: Result, } - let entries = details - .entries - .into_iter() - .map(|d| match d { - gateway_messages::ComponentDetails::PortStatus(r) => { - ComponentDetails::PortStatus(r) - } - gateway_messages::ComponentDetails::Measurement(m) => { - ComponentDetails::Measurement(Measurement { - name: m.name, - kind: m.kind, - value: m.value, - }) - } - }) - .collect::>(); + let entries = + details + .entries + .into_iter() + .map(|d| match d { + gateway_messages::ComponentDetails::PortStatus(r) => { + ComponentDetails::PortStatus(r) + } + gateway_messages::ComponentDetails::Measurement(m) => { + ComponentDetails::Measurement(Measurement { + name: m.name, + kind: m.kind, + value: m.value, + }) + } + gateway_messages::ComponentDetails::Vpd(Vpd::Oxide( + OxideVpd { serial, part_number, rev }, + )) => ComponentDetails::OxideVpd { serial, part_number, rev }, + gateway_messages::ComponentDetails::Vpd(Vpd::Mfg(MfgVpd { + mfg, + mpn, + mfg_rev, + serial, + })) => ComponentDetails::MfgVpd { + mfg, + part_number: mpn, + rev: mfg_rev, + serial, + }, + }) + .collect::>(); json!({ "entries": entries }) } diff --git a/gateway-messages/src/sp_impl.rs b/gateway-messages/src/sp_impl.rs index 12ed5440..09cc5bb3 100644 --- a/gateway-messages/src/sp_impl.rs +++ b/gateway-messages/src/sp_impl.rs @@ -279,11 +279,11 @@ pub trait SpHandler { /// Implementors are allowed to panic if `index` is not in range (i.e., is /// greater than or equal to the value returned by `num_component_details()` /// for this component). - fn component_details( - &mut self, + fn component_details<'a>( + &'a mut self, component: SpComponent, index: BoundsChecked, - ) -> ComponentDetails<'_>; + ) -> ComponentDetails<&'a str>; fn component_clear_status( &mut self, @@ -1292,11 +1292,11 @@ mod tests { unimplemented!() } - fn component_details( - &mut self, + fn component_details<'a>( + &'a mut self, _component: SpComponent, _index: BoundsChecked, - ) -> ComponentDetails { + ) -> ComponentDetails<&'a str> { unimplemented!() } diff --git a/gateway-messages/src/sp_to_mgs.rs b/gateway-messages/src/sp_to_mgs.rs index c46a895d..b73abf9a 100644 --- a/gateway-messages/src/sp_to_mgs.rs +++ b/gateway-messages/src/sp_to_mgs.rs @@ -713,26 +713,21 @@ pub struct TlvPage { /// possible types contained in a component details message. Each TLV-encoded /// struct corresponds to one of these cases. #[derive(Debug, Clone)] -pub enum ComponentDetails<'a> { +pub enum ComponentDetails { PortStatus(Result), Measurement(Measurement), - Vpd(vpd::Vpd<'a>), + Vpd(vpd::Vpd), } -// #[derive(Debug, Clone)] -// #[cfg(feature = "std")] -// pub enum OwnedComponentDetails { -// PortStatus(Result), -// Measurement(Measurement), -// Vpd(vpd::OwnedVpd), -// } - -impl ComponentDetails<'_> { +impl ComponentDetails +where + S: AsRef, +{ pub fn tag(&self) -> tlv::Tag { match self { ComponentDetails::PortStatus(_) => PortStatus::TAG, ComponentDetails::Measurement(_) => MeasurementHeader::TAG, - ComponentDetails::Vpd(_) => Vpd::TAG, + ComponentDetails::Vpd(_) => Vpd::::TAG, } } @@ -757,21 +752,6 @@ impl ComponentDetails<'_> { ComponentDetails::Vpd(vpd) => vpd.encode(buf), } } - - // #[cfg(feature = "std")] - // pub fn into_owned(self) -> OwnedComponentDetails { - // match self { - // ComponentDetails::PortStatus(status) => { - // OwnedComponentDetails::PortStatus(status) - // } - // ComponentDetails::Measurement(measurement) => { - // OwnedComponentDetails::Measurement(measurement) - // } - // ComponentDetails::Vpd(vpd) => { - // OwnedComponentDetails::Vpd(vpd.into_owned()) - // } - // } - // } } #[derive( diff --git a/gateway-messages/src/sp_to_mgs/vpd.rs b/gateway-messages/src/sp_to_mgs/vpd.rs index e5163c20..46ff6ae7 100644 --- a/gateway-messages/src/sp_to_mgs/vpd.rs +++ b/gateway-messages/src/sp_to_mgs/vpd.rs @@ -5,30 +5,28 @@ use crate::tlv; use core::fmt; use core::str; -#[cfg(feature = "std")] -use std::borrow::Borrow; pub const MAX_STR_LEN: usize = 32; #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum Vpd<'buf> { - Oxide(OxideVpd<'buf>), - Mfg(MfgVpd<'buf>), +pub enum Vpd { + Oxide(OxideVpd), + Mfg(MfgVpd), } #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct OxideVpd<'buf> { - pub serial: &'buf str, +pub struct OxideVpd { + pub serial: S, pub rev: u32, - pub part_number: &'buf str, + pub part_number: S, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct MfgVpd<'buf> { - pub mfg: &'buf str, - pub serial: &'buf str, - pub mfg_rev: &'buf str, - pub mpn: &'buf str, +pub struct MfgVpd { + pub mfg: S, + pub serial: S, + pub mfg_rev: S, + pub mpn: S, } #[cfg(feature = "std")] @@ -71,7 +69,10 @@ const MPN_TAG: tlv::Tag = tlv::Tag(*b"MPN0"); /// Manufacturer revision tag. Unlike `OXIDE_REV_TAG`, this is a byte array rather than a `u32`. const MFG_REV_TAG: tlv::Tag = tlv::Tag(*b"MRV0"); -impl<'buf> Vpd<'buf> { +impl Vpd +where + S: AsRef, +{ pub const TAG: tlv::Tag = tlv::Tag(*b"FRU0"); pub fn tlv_len(&self) -> usize { @@ -94,7 +95,9 @@ impl<'buf> Vpd<'buf> { tlv::EncodeError::Custom(e) => e, }) } +} +impl<'buf> Vpd<&'buf str> { pub fn decode_body(buf: &'buf [u8]) -> Result { let mut tags = tlv::decode_iter(buf); @@ -152,18 +155,21 @@ impl<'buf> Vpd<'buf> { } #[cfg(feature = "std")] - pub fn into_owned(self) -> OwnedVpd { + pub fn into_owned(self) -> Vpd { match self { - Self::Oxide(vpd) => OwnedVpd::Oxide(vpd.into_owned()), - Self::Mfg(vpd) => OwnedVpd::Mfg(vpd.into_owned()), + Self::Oxide(vpd) => Vpd::Oxide(vpd.into_owned()), + Self::Mfg(vpd) => Vpd::Mfg(vpd.into_owned()), } } } -impl OxideVpd<'_> { +impl OxideVpd +where + S: AsRef, +{ pub fn tlv_len(&self) -> usize { - tlv::tlv_len(self.part_number.len()) - + tlv::tlv_len(self.serial.len()) + tlv::tlv_len(self.part_number.as_ref().len()) + + tlv::tlv_len(self.serial.as_ref().len()) + tlv::tlv_len(4) // revision number (u32) } @@ -175,8 +181,8 @@ impl OxideVpd<'_> { return Err(tlv::EncodeError::Custom(hubpack::Error::Overrun)); } let mut total = 0; - total += encode_str(&mut out[total..], SERIAL_TAG, self.serial)?; - total += encode_str(&mut out[total..], CPN_TAG, self.part_number)?; + total += encode_str(&mut out[total..], SERIAL_TAG, &self.serial)?; + total += encode_str(&mut out[total..], CPN_TAG, &self.part_number)?; total += tlv::encode(&mut out[total..], OXIDE_REV_TAG, |out| { if out.len() < 4 { return Err(hubpack::Error::Overrun); @@ -186,14 +192,27 @@ impl OxideVpd<'_> { })?; Ok(total) } + + #[cfg(feature = "std")] + pub fn into_owned(self) -> OxideVpd { + let Self { serial, rev, part_number } = self; + OxideVpd { + serial: serial.as_ref().to_owned(), + rev, + part_number: part_number.as_ref().to_owned(), + } + } } -impl MfgVpd<'_> { +impl MfgVpd +where + S: AsRef, +{ pub fn tlv_len(&self) -> usize { - tlv::tlv_len(self.mfg.len()) - + tlv::tlv_len(self.mpn.len()) - + tlv::tlv_len(self.mfg_rev.len()) - + tlv::tlv_len(self.serial.len()) + tlv::tlv_len(self.mfg.as_ref().len()) + + tlv::tlv_len(self.mpn.as_ref().len()) + + tlv::tlv_len(self.mfg_rev.as_ref().len()) + + tlv::tlv_len(self.serial.as_ref().len()) } pub fn encode( @@ -204,19 +223,31 @@ impl MfgVpd<'_> { return Err(tlv::EncodeError::Custom(hubpack::Error::Overrun)); } let mut total = 0; - total += encode_str(&mut out[total..], SERIAL_TAG, self.serial)?; - total += encode_str(&mut out[total..], MFG_TAG, self.mfg)?; - total += encode_str(&mut out[total..], MPN_TAG, self.mpn)?; - total += encode_str(&mut out[total..], MFG_REV_TAG, self.mfg_rev)?; + total += encode_str(&mut out[total..], SERIAL_TAG, &self.serial)?; + total += encode_str(&mut out[total..], MFG_TAG, &self.mfg)?; + total += encode_str(&mut out[total..], MPN_TAG, &self.mpn)?; + total += encode_str(&mut out[total..], MFG_REV_TAG, &self.mfg_rev)?; Ok(total) } + + #[cfg(feature = "std")] + pub fn into_owned(self) -> MfgVpd { + let Self { serial, mfg_rev, mpn, mfg } = self; + MfgVpd { + serial: serial.as_ref().to_owned(), + mfg_rev: mfg_rev.as_ref().to_owned(), + mpn: mpn.as_ref().to_owned(), + mfg: mfg.as_ref().to_owned(), + } + } } fn encode_str( out: &mut [u8], tag: tlv::Tag, - value: &str, + value: &impl AsRef, ) -> Result> { + let value = value.as_ref(); tlv::encode(out, tag, |out| { if out.len() < value.len() { return Err(hubpack::Error::Overrun); diff --git a/gateway-sp-comms/src/single_sp.rs b/gateway-sp-comms/src/single_sp.rs index 28de0205..d233f0a1 100644 --- a/gateway-sp-comms/src/single_sp.rs +++ b/gateway-sp-comms/src/single_sp.rs @@ -28,6 +28,7 @@ use gateway_messages::BadRequestReason; use gateway_messages::CfpaPage; use gateway_messages::ComponentAction; use gateway_messages::ComponentActionResponse; +use gateway_messages::ComponentDetails; use gateway_messages::DeviceCapabilities; use gateway_messages::DeviceDescriptionHeader; use gateway_messages::DevicePresence; @@ -41,7 +42,6 @@ use gateway_messages::Message; use gateway_messages::MessageKind; use gateway_messages::MgsRequest; use gateway_messages::MonorailError; -use gateway_messages::OwnedComponentDetails; use gateway_messages::PowerState; use gateway_messages::PowerStateTransition; use gateway_messages::RotBootInfo; @@ -142,7 +142,7 @@ pub struct SpDevice { #[derive(Debug, Clone)] pub struct SpComponentDetails { - pub entries: Vec, + pub entries: Vec>, } #[derive(Debug, Clone, Copy)] @@ -1550,7 +1550,7 @@ struct ComponentDetailsTlvRpc<'a> { } impl TlvRpc for ComponentDetailsTlvRpc<'_> { - type Item = OwnedComponentDetails; + type Item = ComponentDetails; const LOG_NAME: &'static str = "component details"; @@ -1595,7 +1595,7 @@ impl TlvRpc for ComponentDetailsTlvRpc<'_> { ); } - Ok(Some(OwnedComponentDetails::PortStatus(result))) + Ok(Some(ComponentDetails::PortStatus(result))) } MeasurementHeader::TAG => { let (header, leftover) = @@ -1617,19 +1617,18 @@ impl TlvRpc for ComponentDetailsTlvRpc<'_> { } })?; - Ok(Some(OwnedComponentDetails::Measurement(Measurement { + Ok(Some(ComponentDetails::Measurement(Measurement { name: name.to_string(), kind: header.kind, value: header.value, }))) } - Vpd::TAG => { - let (vpd, leftover) = gateway_messages::Vpd::decode(value) - .map_err(|err| CommunicationError::VpdDeserialize { - err, - })?; + Vpd::::TAG => { + let vpd = gateway_messages::Vpd::decode_body(value).map_err( + |err| CommunicationError::VpdDeserialize { err }, + )?; - Ok(Some(OwnedComponentDetails::Vpd(vpd))) + Ok(Some(ComponentDetails::Vpd(vpd.into_owned()))) } _ => { info!( From dc297c9a94674d13db3f2faf7536af70a8671e8c Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 5 Sep 2025 12:19:49 -0700 Subject: [PATCH 06/13] clippy :/ --- gateway-messages/src/sp_impl.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gateway-messages/src/sp_impl.rs b/gateway-messages/src/sp_impl.rs index 09cc5bb3..78ee62cf 100644 --- a/gateway-messages/src/sp_impl.rs +++ b/gateway-messages/src/sp_impl.rs @@ -279,11 +279,11 @@ pub trait SpHandler { /// Implementors are allowed to panic if `index` is not in range (i.e., is /// greater than or equal to the value returned by `num_component_details()` /// for this component). - fn component_details<'a>( - &'a mut self, + fn component_details( + &mut self, component: SpComponent, index: BoundsChecked, - ) -> ComponentDetails<&'a str>; + ) -> ComponentDetails<&'_ str>; fn component_clear_status( &mut self, From cc46871fae37f5abb2abea2c3b4a6a8913948b75 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 9 Sep 2025 13:35:01 -0700 Subject: [PATCH 07/13] use fixed strs in oxide vpd --- gateway-messages/src/sp_to_mgs/vpd.rs | 74 ++++++++++++++------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/gateway-messages/src/sp_to_mgs/vpd.rs b/gateway-messages/src/sp_to_mgs/vpd.rs index 46ff6ae7..d0884cf8 100644 --- a/gateway-messages/src/sp_to_mgs/vpd.rs +++ b/gateway-messages/src/sp_to_mgs/vpd.rs @@ -10,15 +10,15 @@ pub const MAX_STR_LEN: usize = 32; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Vpd { - Oxide(OxideVpd), + Oxide(OxideVpd), Mfg(MfgVpd), } #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct OxideVpd { - pub serial: S, +pub struct OxideVpd { + pub serial: [u8; 11], pub rev: u32, - pub part_number: S, + pub part_number: [u8; 11], } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -128,18 +128,25 @@ impl<'buf> Vpd<&'buf str> { }) } - let serial = expect_str_tag(&mut tags, SERIAL_TAG)?; match tags.next() { Some(Ok((MFG_TAG, mfg))) => { let mfg = core::str::from_utf8(mfg) .map_err(DecodeError::invalid_str(MFG_TAG))?; let mpn = expect_str_tag(&mut tags, MPN_TAG)?; + let serial = expect_str_tag(&mut tags, SERIAL_TAG)?; let mfg_rev = expect_str_tag(&mut tags, MFG_REV_TAG)?; Ok(Self::Mfg(MfgVpd { mfg, mpn, mfg_rev, serial })) } Some(Ok((CPN_TAG, cpn))) => { - let part_number = core::str::from_utf8(cpn) - .map_err(DecodeError::invalid_str(MFG_TAG))?; + let part_number: [u8; 11] = cpn + .try_into() + .map_err(|_| DecodeError::BadLength(CPN_TAG, cpn.len()))?; + let serial: [u8; 11] = + expect_tag(&mut tags, SERIAL_TAG, |val| { + val.try_into().map_err(|_| { + DecodeError::BadLength(CPN_TAG, cpn.len()) + }) + })?; let rev = expect_tag(&mut tags, OXIDE_REV_TAG, |value| { let rev_bytes: [u8; 4] = value .try_into() @@ -157,19 +164,16 @@ impl<'buf> Vpd<&'buf str> { #[cfg(feature = "std")] pub fn into_owned(self) -> Vpd { match self { - Self::Oxide(vpd) => Vpd::Oxide(vpd.into_owned()), + Self::Oxide(vpd) => Vpd::Oxide(vpd), Self::Mfg(vpd) => Vpd::Mfg(vpd.into_owned()), } } } -impl OxideVpd -where - S: AsRef, -{ +impl OxideVpd { pub fn tlv_len(&self) -> usize { - tlv::tlv_len(self.part_number.as_ref().len()) - + tlv::tlv_len(self.serial.as_ref().len()) + tlv::tlv_len(self.part_number.len()) + + tlv::tlv_len(self.serial.len()) + tlv::tlv_len(4) // revision number (u32) } @@ -181,27 +185,15 @@ where return Err(tlv::EncodeError::Custom(hubpack::Error::Overrun)); } let mut total = 0; - total += encode_str(&mut out[total..], SERIAL_TAG, &self.serial)?; - total += encode_str(&mut out[total..], CPN_TAG, &self.part_number)?; - total += tlv::encode(&mut out[total..], OXIDE_REV_TAG, |out| { - if out.len() < 4 { - return Err(hubpack::Error::Overrun); - } - out.copy_from_slice(&self.rev.to_le_bytes()[..]); - Ok(4) - })?; + total += encode_bytes(&mut out[total..], CPN_TAG, &self.part_number)?; + total += encode_bytes(&mut out[total..], SERIAL_TAG, &self.serial)?; + total += encode_bytes( + &mut out[total..], + OXIDE_REV_TAG, + &self.rev.to_le_bytes()[..], + )?; Ok(total) } - - #[cfg(feature = "std")] - pub fn into_owned(self) -> OxideVpd { - let Self { serial, rev, part_number } = self; - OxideVpd { - serial: serial.as_ref().to_owned(), - rev, - part_number: part_number.as_ref().to_owned(), - } - } } impl MfgVpd @@ -223,9 +215,9 @@ where return Err(tlv::EncodeError::Custom(hubpack::Error::Overrun)); } let mut total = 0; - total += encode_str(&mut out[total..], SERIAL_TAG, &self.serial)?; total += encode_str(&mut out[total..], MFG_TAG, &self.mfg)?; total += encode_str(&mut out[total..], MPN_TAG, &self.mpn)?; + total += encode_str(&mut out[total..], SERIAL_TAG, &self.serial)?; total += encode_str(&mut out[total..], MFG_REV_TAG, &self.mfg_rev)?; Ok(total) } @@ -246,13 +238,21 @@ fn encode_str( out: &mut [u8], tag: tlv::Tag, value: &impl AsRef, +) -> Result> { + encode_bytes(out, tag, value.as_ref().as_bytes()) +} + +fn encode_bytes( + out: &mut [u8], + tag: tlv::Tag, + value: &[u8], ) -> Result> { let value = value.as_ref(); tlv::encode(out, tag, |out| { if out.len() < value.len() { return Err(hubpack::Error::Overrun); } - out[..value.len()].copy_from_slice(value.as_bytes()); + out[..value.len()].copy_from_slice(value); Ok(value.len()) }) } @@ -266,6 +266,7 @@ pub enum DecodeError { InvalidU32(tlv::Tag), Tlv(tlv::Tag, tlv::DecodeError), TlvUntyped(tlv::DecodeError), + BadLength(tlv::Tag, usize), } impl DecodeError { @@ -299,6 +300,9 @@ impl fmt::Display for DecodeError { Self::UnexpectedEnd => { write!(f, "unexpected end of input") } + Self::BadLength(tag, size) => { + write!(f, "expected value for tag {tag:?} to be 11 bytes, but got {size} bytes") + } } } } From 5a4e13597a548345d369e131e6a680a0853cdfae Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Tue, 9 Sep 2025 13:43:28 -0700 Subject: [PATCH 08/13] stop needlessly asrefing --- gateway-messages/src/sp_to_mgs/vpd.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/gateway-messages/src/sp_to_mgs/vpd.rs b/gateway-messages/src/sp_to_mgs/vpd.rs index d0884cf8..b0e8c3b9 100644 --- a/gateway-messages/src/sp_to_mgs/vpd.rs +++ b/gateway-messages/src/sp_to_mgs/vpd.rs @@ -247,7 +247,6 @@ fn encode_bytes( tag: tlv::Tag, value: &[u8], ) -> Result> { - let value = value.as_ref(); tlv::encode(out, tag, |out| { if out.len() < value.len() { return Err(hubpack::Error::Overrun); From 4f5a721d43610fe8c637c2f45ff58ebbff2235ff Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 11 Sep 2025 09:48:01 -0700 Subject: [PATCH 09/13] fix --- faux-mgs/src/main.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/faux-mgs/src/main.rs b/faux-mgs/src/main.rs index 97cd0863..5c3ad3e6 100644 --- a/faux-mgs/src/main.rs +++ b/faux-mgs/src/main.rs @@ -2269,7 +2269,23 @@ fn component_details_to_json(details: SpComponentDetails) -> serde_json::Value { } gateway_messages::ComponentDetails::Vpd(Vpd::Oxide( OxideVpd { serial, part_number, rev }, - )) => ComponentDetails::OxideVpd { serial, part_number, rev }, + )) => { + let serial = str::from_utf8(&serial) + .map(str::to_owned) + .unwrap_or_else(|e| { + format!( + "error: serial number {serial:?} not UTF-8: {e}" + ) + }); + let part_number = str::from_utf8(&part_number) + .map(str::to_owned) + .unwrap_or_else(|e| { + format!( + "error: part number {part_number:?} not UTF-8: {e}" + ) + }); + ComponentDetails::OxideVpd { serial, part_number, rev } + } gateway_messages::ComponentDetails::Vpd(Vpd::Mfg(MfgVpd { mfg, mpn, From 206fd606bcea437f9b6b243788d2d17677746748 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Thu, 11 Sep 2025 11:16:45 -0700 Subject: [PATCH 10/13] add TMP117 VPD --- gateway-messages/src/sp_to_mgs/vpd.rs | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/gateway-messages/src/sp_to_mgs/vpd.rs b/gateway-messages/src/sp_to_mgs/vpd.rs index b0e8c3b9..6c887eb9 100644 --- a/gateway-messages/src/sp_to_mgs/vpd.rs +++ b/gateway-messages/src/sp_to_mgs/vpd.rs @@ -5,6 +5,9 @@ use crate::tlv; use core::fmt; use core::str; +use hubpack::SerializedSize; +use serde::Deserialize; +use serde::Serialize; pub const MAX_STR_LEN: usize = 32; @@ -12,6 +15,7 @@ pub const MAX_STR_LEN: usize = 32; pub enum Vpd { Oxide(OxideVpd), Mfg(MfgVpd), + Tmp117(Tmp117Vpd), } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -29,6 +33,18 @@ pub struct MfgVpd { pub mpn: S, } +#[derive( + Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, SerializedSize, +)] +pub struct Tmp117Vpd { + /// Device ID (register 0x0F) + pub id: u16, + /// 48-bit NIST traceability data + pub eeprom1: u16, + pub eeprom2: u16, + pub eeprom3: u16, +} + #[cfg(feature = "std")] #[derive(Clone, Debug, Eq, PartialEq)] pub struct OwnedOxideVpd { @@ -79,6 +95,7 @@ where match self { Vpd::Oxide(vpd) => tlv::tlv_len(vpd.tlv_len()), Vpd::Mfg(vpd) => tlv::tlv_len(vpd.tlv_len()), + Vpd::Tmp117(_) => tlv::tlv_len(Tmp117Vpd::MAX_SIZE), } } @@ -89,6 +106,7 @@ where match self { Vpd::Oxide(vpd) => vpd.encode(out), Vpd::Mfg(vpd) => vpd.encode(out), + Vpd::Tmp117(vpd) => vpd.encode(out), } .map_err(|e| match e { tlv::EncodeError::BufferTooSmall => hubpack::Error::Overrun, @@ -129,6 +147,13 @@ impl<'buf> Vpd<&'buf str> { } match tags.next() { + Some(Ok((Tmp117Vpd::TAG, value))) => { + let (vpd, _rest) = + hubpack::deserialize(value).map_err(|error| { + DecodeError::Tmp117(Tmp117Vpd::TAG, error) + })?; + Ok(Self::Tmp117(vpd)) + } Some(Ok((MFG_TAG, mfg))) => { let mfg = core::str::from_utf8(mfg) .map_err(DecodeError::invalid_str(MFG_TAG))?; @@ -165,6 +190,7 @@ impl<'buf> Vpd<&'buf str> { pub fn into_owned(self) -> Vpd { match self { Self::Oxide(vpd) => Vpd::Oxide(vpd), + Self::Tmp117(vpd) => Vpd::Tmp117(vpd), Self::Mfg(vpd) => Vpd::Mfg(vpd.into_owned()), } } @@ -234,6 +260,18 @@ where } } +impl Tmp117Vpd { + pub const TAG: tlv::Tag = tlv::Tag(*b"TMP1"); + pub const TLV_LEN: usize = tlv::tlv_len(Self::MAX_SIZE); + + pub fn encode( + &self, + out: &mut [u8], + ) -> Result> { + tlv::encode(out, Self::TAG, |out| hubpack::serialize(out, self)) + } +} + fn encode_str( out: &mut [u8], tag: tlv::Tag, @@ -266,6 +304,7 @@ pub enum DecodeError { Tlv(tlv::Tag, tlv::DecodeError), TlvUntyped(tlv::DecodeError), BadLength(tlv::Tag, usize), + Tmp117(tlv::Tag, hubpack::Error), } impl DecodeError { @@ -302,6 +341,9 @@ impl fmt::Display for DecodeError { Self::BadLength(tag, size) => { write!(f, "expected value for tag {tag:?} to be 11 bytes, but got {size} bytes") } + Self::Tmp117(tag, error) => { + write!(f, "failed to decode TMP117 VPD tag {tag:?}: {error}") + } } } } From c7049dc8a3c32949438f1ac91420e42c1a3d9273 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 12 Sep 2025 12:18:50 -0700 Subject: [PATCH 11/13] allow returning errors --- faux-mgs/src/main.rs | 59 +++++++++++++++++++-------- gateway-messages/src/sp_to_mgs/vpd.rs | 55 ++++++++++++++++++++++--- 2 files changed, 90 insertions(+), 24 deletions(-) diff --git a/faux-mgs/src/main.rs b/faux-mgs/src/main.rs index 5c3ad3e6..85bc803b 100644 --- a/faux-mgs/src/main.rs +++ b/faux-mgs/src/main.rs @@ -2231,7 +2231,7 @@ enum Output { fn component_details_to_json(details: SpComponentDetails) -> serde_json::Value { use gateway_messages::measurement::{MeasurementError, MeasurementKind}; use gateway_messages::monorail_port_status::{PortStatus, PortStatusError}; - use gateway_messages::vpd::{MfgVpd, OxideVpd, Vpd}; + use gateway_messages::vpd::{self, MfgVpd, OxideVpd, Vpd, VpdReadError}; // SpComponentDetails and Measurement from gateway_messages intentionally do // not derive `Serialize` to avoid accidental misuse in MGS / the SP, so we @@ -2241,8 +2241,22 @@ fn component_details_to_json(details: SpComponentDetails) -> serde_json::Value { enum ComponentDetails { PortStatus(Result), Measurement(Measurement), - OxideVpd { serial: String, part_number: String, rev: u32 }, - MfgVpd { mfg: String, part_number: String, rev: String, serial: String }, + Vpd(Result), + } + + #[derive(serde::Serialize)] + #[serde(tag = "kind")] + enum VpdDetails { + Oxide { serial: String, part_number: String, rev: u32 }, + Mfg { mfg: String, part_number: String, rev: String, serial: String }, + Tmp117(vpd::Tmp117Vpd), + } + + #[derive(serde::Serialize)] + #[serde(tag = "kind", rename = "snake_case")] + enum VpdError { + Read(VpdReadError), + InvalidString(String), } #[derive(serde::Serialize)] @@ -2270,32 +2284,41 @@ fn component_details_to_json(details: SpComponentDetails) -> serde_json::Value { gateway_messages::ComponentDetails::Vpd(Vpd::Oxide( OxideVpd { serial, part_number, rev }, )) => { - let serial = str::from_utf8(&serial) - .map(str::to_owned) - .unwrap_or_else(|e| { - format!( - "error: serial number {serial:?} not UTF-8: {e}" - ) - }); - let part_number = str::from_utf8(&part_number) - .map(str::to_owned) - .unwrap_or_else(|e| { - format!( - "error: part number {part_number:?} not UTF-8: {e}" - ) + + let res = str::from_utf8(&serial) + .map(str::to_owned).map_err(|e| { + VpdError::InvalidString( + format!("serial number {serial:?} not UTF-8: {e}") + ) + }).and_then(|serial| { + let part_number = str::from_utf8(&part_number) + .map(str::to_owned) + .map_err(|e| { + VpdError::InvalidString( + format!("part number {part_number:?} not UTF-8: {e}") + ) + })?; + Ok(VpdDetails::Oxide { serial, part_number, rev }) + }); - ComponentDetails::OxideVpd { serial, part_number, rev } + ComponentDetails::Vpd(res) } gateway_messages::ComponentDetails::Vpd(Vpd::Mfg(MfgVpd { mfg, mpn, mfg_rev, serial, - })) => ComponentDetails::MfgVpd { + })) => ComponentDetails::Vpd(Ok(VpdDetails::Mfg { mfg, part_number: mpn, rev: mfg_rev, serial, + })), + gateway_messages::ComponentDetails::Vpd(Vpd::Tmp117(vpd)) => { + ComponentDetails::Vpd(Ok(VpdDetails::Tmp117(vpd))) + }, + gateway_messages::ComponentDetails::Vpd(Vpd::Err(err)) => { + ComponentDetails::Vpd(Err(VpdError::Read(err))) }, }) .collect::>(); diff --git a/gateway-messages/src/sp_to_mgs/vpd.rs b/gateway-messages/src/sp_to_mgs/vpd.rs index 6c887eb9..37e4f7e0 100644 --- a/gateway-messages/src/sp_to_mgs/vpd.rs +++ b/gateway-messages/src/sp_to_mgs/vpd.rs @@ -16,6 +16,7 @@ pub enum Vpd { Oxide(OxideVpd), Mfg(MfgVpd), Tmp117(Tmp117Vpd), + Err(VpdReadError), } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -45,6 +46,15 @@ pub struct Tmp117Vpd { pub eeprom3: u16, } +#[derive( + Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, SerializedSize, +)] +pub enum VpdReadError { + DeviceNotPresent, + DeviceOff, + InvalidContents, +} + #[cfg(feature = "std")] #[derive(Clone, Debug, Eq, PartialEq)] pub struct OwnedOxideVpd { @@ -96,6 +106,7 @@ where Vpd::Oxide(vpd) => tlv::tlv_len(vpd.tlv_len()), Vpd::Mfg(vpd) => tlv::tlv_len(vpd.tlv_len()), Vpd::Tmp117(_) => tlv::tlv_len(Tmp117Vpd::MAX_SIZE), + Vpd::Err(_) => tlv::tlv_len(VpdReadError::MAX_SIZE), } } @@ -107,6 +118,7 @@ where Vpd::Oxide(vpd) => vpd.encode(out), Vpd::Mfg(vpd) => vpd.encode(out), Vpd::Tmp117(vpd) => vpd.encode(out), + Vpd::Err(err) => err.encode(out), } .map_err(|e| match e { tlv::EncodeError::BufferTooSmall => hubpack::Error::Overrun, @@ -147,10 +159,17 @@ impl<'buf> Vpd<&'buf str> { } match tags.next() { + Some(Ok((VpdReadError::TAG, value))) => { + let (vpd, _rest) = + hubpack::deserialize(value).map_err(|error| { + DecodeError::Hubpack(VpdReadError::TAG, error) + })?; + Ok(Self::Err(vpd)) + } Some(Ok((Tmp117Vpd::TAG, value))) => { let (vpd, _rest) = hubpack::deserialize(value).map_err(|error| { - DecodeError::Tmp117(Tmp117Vpd::TAG, error) + DecodeError::Hubpack(Tmp117Vpd::TAG, error) })?; Ok(Self::Tmp117(vpd)) } @@ -192,6 +211,7 @@ impl<'buf> Vpd<&'buf str> { Self::Oxide(vpd) => Vpd::Oxide(vpd), Self::Tmp117(vpd) => Vpd::Tmp117(vpd), Self::Mfg(vpd) => Vpd::Mfg(vpd.into_owned()), + Self::Err(err) => Vpd::Err(err), } } } @@ -272,6 +292,18 @@ impl Tmp117Vpd { } } +impl VpdReadError { + pub const TAG: tlv::Tag = tlv::Tag(*b"ERR0"); + pub const TLV_LEN: usize = tlv::tlv_len(Self::MAX_SIZE); + + pub fn encode( + &self, + out: &mut [u8], + ) -> Result> { + tlv::encode(out, Self::TAG, |out| hubpack::serialize(out, self)) + } +} + fn encode_str( out: &mut [u8], tag: tlv::Tag, @@ -304,7 +336,7 @@ pub enum DecodeError { Tlv(tlv::Tag, tlv::DecodeError), TlvUntyped(tlv::DecodeError), BadLength(tlv::Tag, usize), - Tmp117(tlv::Tag, hubpack::Error), + Hubpack(tlv::Tag, hubpack::Error), } impl DecodeError { @@ -333,16 +365,27 @@ impl fmt::Display for DecodeError { write!(f, "TLV decode error while decoding {tag:?}: {error}") } Self::TlvUntyped(error) => { - write!(f, "TLV decode error while expecting {MFG_TAG:?} or {CPN_TAG:?}: {error}") + write!( + f, + "TLV decode error while expecting {MFG_TAG:?} or \ + {CPN_TAG:?}: {error}" + ) } Self::UnexpectedEnd => { write!(f, "unexpected end of input") } Self::BadLength(tag, size) => { - write!(f, "expected value for tag {tag:?} to be 11 bytes, but got {size} bytes") + write!( + f, + "expected value for tag {tag:?} to be 11 bytes, but got \ + {size} bytes" + ) } - Self::Tmp117(tag, error) => { - write!(f, "failed to decode TMP117 VPD tag {tag:?}: {error}") + Self::Hubpack(tag, error) => { + write!( + f, + "failed to decode hubpack-encoded VPD tag {tag:?}: {error}" + ) } } } From b2b0ef3abb2ebeb89e04e17286a1fe51d8a15a06 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 12 Sep 2025 12:22:25 -0700 Subject: [PATCH 12/13] change variants to I2cError --- gateway-messages/src/sp_to_mgs/vpd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway-messages/src/sp_to_mgs/vpd.rs b/gateway-messages/src/sp_to_mgs/vpd.rs index 37e4f7e0..85e69724 100644 --- a/gateway-messages/src/sp_to_mgs/vpd.rs +++ b/gateway-messages/src/sp_to_mgs/vpd.rs @@ -51,7 +51,7 @@ pub struct Tmp117Vpd { )] pub enum VpdReadError { DeviceNotPresent, - DeviceOff, + I2cError, InvalidContents, } From 0017c81be552980b9efbd5041ac72ce3dd061019 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 12 Sep 2025 12:31:38 -0700 Subject: [PATCH 13/13] more errors --- gateway-messages/src/sp_to_mgs/vpd.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/gateway-messages/src/sp_to_mgs/vpd.rs b/gateway-messages/src/sp_to_mgs/vpd.rs index 85e69724..016664e9 100644 --- a/gateway-messages/src/sp_to_mgs/vpd.rs +++ b/gateway-messages/src/sp_to_mgs/vpd.rs @@ -53,6 +53,7 @@ pub enum VpdReadError { DeviceNotPresent, I2cError, InvalidContents, + BadRead, } #[cfg(feature = "std")]