Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 73 additions & 16 deletions faux-mgs/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{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
Expand All @@ -2240,6 +2241,22 @@ fn component_details_to_json(details: SpComponentDetails) -> serde_json::Value {
enum ComponentDetails {
PortStatus(Result<PortStatus, PortStatusError>),
Measurement(Measurement),
Vpd(Result<VpdDetails, VpdError>),
}

#[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)]
Expand All @@ -2249,22 +2266,62 @@ fn component_details_to_json(details: SpComponentDetails) -> serde_json::Value {
pub value: Result<f32, MeasurementError>,
}

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::<Vec<_>>();
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 },
)) => {

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::Vpd(res)
}
gateway_messages::ComponentDetails::Vpd(Vpd::Mfg(MfgVpd {
mfg,
mpn,
mfg_rev,
serial,
})) => 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::<Vec<_>>();

json!({ "entries": entries })
}
69 changes: 50 additions & 19 deletions gateway-messages/src/sp_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -282,7 +283,7 @@ pub trait SpHandler {
&mut self,
component: SpComponent,
index: BoundsChecked,
) -> ComponentDetails;
) -> ComponentDetails<&'_ str>;

fn component_clear_status(
&mut self,
Expand Down Expand Up @@ -510,14 +511,20 @@ pub fn handle_message<H: SpHandler>(
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..],
Expand Down Expand Up @@ -554,32 +561,56 @@ pub fn handle_message<H: SpHandler>(
/// many TLV triples from `iter` as we can into `out`.
///
/// Returns the total number of bytes written into `out`.
fn encode_tlv_structs<I, F>(mut out: &mut [u8], iter: I) -> usize
fn encode_tlv_structs<I, F>(out: &mut [u8], iter: I) -> usize
where
I: Iterator<Item = (tlv::Tag, F)>,
F: FnOnce(&mut [u8]) -> HubpackResult<usize>,
{
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<usize, HubpackError>,
) -> 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
Expand Down Expand Up @@ -1261,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!()
}

Expand Down
18 changes: 15 additions & 3 deletions gateway-messages/src/sp_to_mgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ 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;
pub use vpd::Vpd;

use ignition::IgnitionError;
use measurement::MeasurementHeader;
Expand Down Expand Up @@ -711,20 +713,25 @@ 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<S> {
PortStatus(Result<PortStatus, PortStatusError>),
Measurement(Measurement),
Vpd(vpd::Vpd<S>),
}

impl ComponentDetails {
impl<S> ComponentDetails<S>
where
S: AsRef<str>,
{
pub fn tag(&self) -> tlv::Tag {
match self {
ComponentDetails::PortStatus(_) => PortStatus::TAG,
ComponentDetails::Measurement(_) => MeasurementHeader::TAG,
ComponentDetails::Vpd(_) => Vpd::<S>::TAG,
}
}

pub fn serialize(&self, buf: &mut [u8]) -> hubpack::error::Result<usize> {
pub fn serialize(&self, buf: &mut [u8]) -> hubpack::Result<usize> {
match self {
ComponentDetails::PortStatus(p) => hubpack::serialize(buf, p),
ComponentDetails::Measurement(m) => {
Expand All @@ -742,6 +749,7 @@ impl ComponentDetails {
Ok(n + m.name.len())
}
}
ComponentDetails::Vpd(vpd) => vpd.encode(buf),
}
}
}
Expand Down Expand Up @@ -868,6 +876,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".
Expand Down
Loading
Loading