Skip to content

Compute and emit the device.yml digest used by INTERFACE_HASH #142

Description

@glopesdev

R_VERSION carries an INTERFACE_HASH field, defined as the SHA-1 digest of the device interface schema. Footnote 2 of harp-tech/protocol#68 assigned the computation to this generator, so that a header could be emitted and embedded in firmware. That header was never written, and no SHA-1 computation exists anywhere in harp-tech today. Every device therefore reports zeros, and the field is unusable at both ends.

The specification names a file rather than a byte sequence, so the same committed device.yml gives a different digest depending on how it was checked out. The input is pinned by harp-tech/protocol#234. The digest covers the schema file encoded as UTF-8, with no byte order mark, and with LF line terminators. Everything the author wrote stays significant, so comments, key order and the use of anchors all still change the value. This issue covers the implementation, which is worth prototyping alongside that proposal, because the generator is the only place that can make the two ends agree.

Why the generator is the right place

The Device and the Controller must derive the same value from the same file. A Controller that consumed the interface as a generated package holds no YAML to hash, so it cannot compute the digest itself. Emitting the value into both the firmware and the client output makes the comparison possible and removes any need to run a hash by hand.

The obstacle

The source bytes are discarded before any template runs.

TemplateHelper.ReadDeviceMetadata opens a StreamReader, wraps it in a MergingParser, and hands it to the deserializer. GeneratorHelper.ReadDeviceMetadata in harp-tech/toolkit does the same six lines, because TemplateHelper is internal. Neither retains the text. InterfaceGenerator and FirmwareGenerator are then constructed from a DeviceMetadata alone.

So the digest has to be computed where the file is opened and carried into the templates. Two shapes are available:

  • Compute it in the read path and expose the hashed value in DeviceMetadata, which makes it available to every target with no signature changes.
  • Return the hash alongside the model and pass it into each generator, which keeps DeviceMetadata a faithful projection of the file contents.

The first is less invasive. The second avoids putting a derived value on a type whose other members all map to YAML keys. I lean to the second, but either works, and the choice is worth settling before anyone writes the templates.

Whichever is chosen, the duplicate read path should be resolved first, or the digest will be computed in two places that can disagree.

Normalization

The digest must be taken over the raw file bytes, not over the parsed model and not over a re-serialization of it. MergingParser resolves merge keys, so the model is already post-merge, while two files differing only in their use of anchors are intended to produce different digests.

A StreamReader already strips a byte order mark when decoding, so reading the bytes and normalizing the line endings explicitly is clearer than reading text and re-encoding it.

What to emit

Three targets, and the third is the one footnote 2 promised:

  • C#, a constant on the generated device class, so Bonsai.Harp and the toolkit can compare it against R_VERSION.
  • Python, the same constant in the generated module.
  • Firmware, a header constant the application passes to the core. core.pico already accepts a 20-byte buffer through HarpCApp::init and defaults it to zero, so the firmware side needs only the value. The ATxmega target emits app.h, app_funcs.h and app_ios_and_regs.h, so there is an existing home for it.

The natural shape is the 20 raw bytes, since that is what the register carries. A hex string is friendlier to read in a diff but forces every consumer to parse it.

Relationship to the two open header issues

#138 adds an auto-generated header to the C# output and #121 asks for provenance in the Python output. The digest is provenance too, and it would be reasonable for it to appear in that header comment as well as in a constant. The two are not substitutes: a comment identifies the file for a human reading it, a constant is what a Controller compares at runtime. Worth deciding whether the header carries it.

Drawbacks

The generator gains a responsibility that is not code generation. It would be computing a value about its own input and embedding it, which is a different kind of operation from projecting a model into a template.

A device is also free to supply the value by other means, so the emitted constant is a convenience unless the standard says otherwise. That question is open in harp-tech/protocol#234.

Implementation

Small, if the read path is settled first. The digest computation is a few lines. Each target gains one emitted constant, and the two C# and one Python reference outputs under tests/ExpectedOutput gain a line each. A test asserting that a known file produces a known digest is what pins the normalization, and is the part most worth writing first.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions