Skip to content

Commit 90a2bf0

Browse files
authored
microcbor: add #[ereport(...)] attribute (#2397)
This commit adds a new attribute to `microcbor`, intended to make defining ereport types more convenient. Presently, we tend to define ereports by using an enum to represent all possible ereport classes that a task may report, with `microcbor`'s `#[cbor(variant_id = "...")]` attribute on the enum definition and `#[cbor(rename = "...")]` on the variants. Then, we define a struct which contains the class enum along with a version, and either an enum or generic field to represent the ereport body. For an example of this usage, consider the `cosmo_seq` task: https://github.com/oxidecomputer/hubris/blob/aa843e7b937e5a7d8bb21298919440689657ee29/drv/cosmo-seq-server/src/main.rs#L458-L481 This pattern has some disadvantages. In particular, it makes it very difficult for multiple tasks to share the definitions of some ereport types in a shared crate, which is useful in some situations. In particular, as we add ereports for sequencer events (see #2242), we would like to be able to share some ereport messages between the Cosmo and Gimlet sequencer tasks (and perhaps also the Tofino and PSC sequencers, in some cases). The current pattern makes this difficult. While we could use `#[derive(EncodeFields)]` to define common types and then embed them in an enum of "all ereport types in this task" in a task crate, the definition of the ereport message's class and version would be in the task rather than where the message is defined, meaning they are duplicated. This would be sad: since the class is important to how upstack software interprets the ereport, ensuring that both tasks emit the same class and version fields is a big chunk of why we would even want shared definitions. Also, the enum-based approach has some other disadvantages. When we define separate enums for the class and for message bodies, it is possible to accidentally use the wrong class for a given message body --- nothing ensures that these match. And, using enums for everything means that the size of the message that has to be constructed on the stack is the size of the _largest variant_, which makes stack usage worse when a particular code path always reports a smaller variant. This branch introduces a new API for defining ereport types as a `struct` for each individual class of ereport message. This is done using a new attribute which can be added to types that `#[derive(microcbor_derive::Encode)]`. The new attribute, `#[ereport(...)]`, takes `class = "a sting literal"` and `version = <an int literal>"` arguments, and, if present, changes the generated `Encode` implementation to output the `"k" = <class>` and `"v" = <version>` pairs when encoding the type. The maximum CBOR length value is also adjusted to include the length of the additional K/V pairs. Theusage of the new attribute is discussed in greater detail in the RustDoc. Now, we can define individual ereport messages as their own top-level Rust types, and those types will always be serialized with the correct class and version values. Multiple tasks can share these types, and can still use the automatic buffer size calculation by passing _multiple types_ to the `microcbor::max_cbor_len_for!` macro, which is how that API was really intended to be used in the first place. For example, we might imagine something like: ```rust #[derive(Encode)] #[ereport(class = "hw.discovery.ae35.fault", version = 0)] struct Ae35UnitEreport { critical_in_hrs: u32, detected_by: fixedstr::FixedStr<'static, 8>, } #[derive(Encode)] #[ereport(class = "hw.apollo.undervolt", version = 13)] #[cbor(variant_id = "bus")] enum UndervoltEreport { MainBusA { volts: f32 }, MainBusB { volts: f32 }, // "Houston, we've got a main bus B undervolt!" } use some_other_crate_that_defines_ereports; const EREPORT_BUF_SIZE: usize = microcbor::max_cbor_len_for![ Ae32UnitEreport, UndervoltEreport, some_other_crate_that_defines_ereports::SomeOtherEreport, ]; ``` and that will all just work. As an aside, I *did* consider the fact that this *could* be an API to add any arbitrary compile-time fields when encoding. I decided *not* to do that, as the goal here was specifically to help with ereports, and I felt like there was some value in having the attribute also enforce the names and types of the conventional ereport fields. That way, you are expressing the intent to say that "this is an ereport message", and the proc-macro ensures you have included the requisite fields and that they have the requisite types. We may consider adding a general-purpose "additional fields with compile time values" attribute in the future if such a thing seems useful, and if we do, the `#[ereport(...)]` attribute could be reimplemented using that internally.
1 parent e8b3d33 commit 90a2bf0

File tree

5 files changed

+466
-30
lines changed

5 files changed

+466
-30
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/microcbor-derive/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ syn = { version = "2.0.106", features = ["extra-traits", "parsing"] }
1313

1414
[dev-dependencies]
1515
microcbor = { path = "../microcbor" }
16+
fixedstr = { path = "../fixedstr", features = ["microcbor"]}
1617

1718
[lints]
1819
workspace = true

0 commit comments

Comments
 (0)