-
Notifications
You must be signed in to change notification settings - Fork 159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use generics to simplify the TPMDirect interface #310
Conversation
@alexmwu @josephlr Hi all, sorry this sat for so long. I finally made some time to work on the feedback from our verbal review. I've deleted generics from where they weren't absolutely necessary (e.g., the old |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few questions:
- Why do we still have the
Startup
andShutdown
functions, can't we just use the direct API to call these functions? - Why does the
Response
need aTPMCC
as part of its interface? - Having the Command/Response structs contain private types makes it hard to figure out how to construct our use these types.
- Constructing is possible to figure out by searching the docs
- However, use of the private structs (say to read fields of a union) doesn't seem to show up in godoc.
- Would it be possible to have the types be public (
TPM2BAttest
) and have public constructors for these types (NewTPM2BAttest
)
// Returns an error if the value is not marshallable. | ||
marshal(buf *bytes.Buffer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to return an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I'll fix this.
To your other questions:
Why do we still have the Startup and Shutdown functions, can't we just use the direct API to call these functions?
As an adapter so that we can use https://github.com/google/go-tpm-tools/tree/master/simulator (which will call back to Startup and Shutdown as free functions in go-tpm) until there's a version of go-tpm-tools that calls TPMDirect startup and shutdown. I'll add a comment and TODO about this.
Why does the Response need a TPMCC as part of its interface?
This allows the internal consistency check in the execute
function to match the correct command and response together. I guess this could be done with generics instead now, WDYT?
Having the Command/Response structs contain private types makes it hard to figure out how to construct our use these types.
Yeah, this is a great point. I'll make e.g., the TPM2B
types themselves public and provide public constructors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was playing around a little and I think the following would work well:
Have a tpm2b
submodule:
// Package tpm2b ...
//
// Document here how the TPM2B types work and give examples of how to use them
package tpm2b
type TPM2B[T any] struct {
contents *T
buffer []byte
}
func New[T any](t T) TPM2B[T] {
return TPM2B[T]{contents: &t}
}
func FromBytes[T any](b []byte) TPM2B[T] {
return TPM2B[T]{buffer: b}
}
func (t *TPM2B[T]) Contents() (*T, error) {
if value.contents != nil {
return value.contents, nil
}
if value.buffer == nil {
panic("TPMB had no contents or buffer")
}
var result T
if err := unmarshal(bytes.NewBuffer(value.buffer), reflect.ValueOf(&result).Elem()); err != nil {
return nil, err
}
// Cache result
value.contents = &result
return value.contents, nil
}
func (t *TPM2B[T]) Bytes() []byte {
if value.buffer != nil {
return value.buffer
}
if value.contents == nil {
panic("TPMB had no contents or buffer")
}
var temp bytes.Buffer
marshal(&temp, reflect.ValueOf(value.contents))
// Cache Bytes
value.buffer = temp.Bytes()
return value.buffer
}
func (value *tpm2b[T]) marshal(buf *bytes.Buffer) {
b := value.Bytes()
binary.Write(buf, binary.BigEndian, uint16(len(b)))
buf.Write(b)
}
func (value *tpm2b[T]) unmarshal(buf *bytes.Buffer) error {
var size uint16
binary.Read(buf, binary.BigEndian, &size)
value.contents = nil
value.buffer = make([]byte, size)
_, err := io.ReadAtLeast(buf, value.buffer)
return err
}
Then in the main tpm2
module will have just the typedefs:
type TPM2BECCPoint = tpm2b.TPM2B[TPMSECCPoint]
Good catch. I'll fix this.
I think not having Marshall
/marshal
return an error makes the library easier to use.
For the commands and responses, is there a reason that the following doesn't work: We have a generic interface for commands: type Command[Response any] interface {
CommandCode() TPMCC
Execute(t transport.TPM, s ...Session) (Response, error)
} and we have value recievers for the Commands and return the responses by value: type CreateLoaded struct { /* ... */ }
type CreateLoadedResponse struct { /* ... */ }
func (CreateLoaded) CommandCode() TPMCC { return TPMCCCreateLoaded }
func (cmd CreateLoaded) Execute(t transport.TPM, s ...Session) (CreateLoadedResponse, error) {
var rsp CreateLoadedResponse
return rsp, execute(t, cmd, &rsp, s...)
} We have internal // Typechecks that we implemented the Command interface correctly
func execute[C Command[R], R any](t transport.TPM, cmd C, rsp *R, s ...Session) error {
return executeImpl(t, cmd.CommandCode(), cmd, rsp, s...)
}
func executeImpl(t transport.TPM, cc TPMCC, cmd any, rsp any, s ...Session) error {
// Actual implementation of command execution
} The reason I would prefer the Commands/Responses being passed by value is that them being pointer inputs/outputs was a little surprising when I was trying to write some code. For example, it would be nice if the following worked: rsp, err := tpm2.GetCapability{Capability: tpm2.TPMCapAlgs, PropertyCount: 0xFFFF}.Execute(s)
if err != nil {
log.Fatalf("Read Public: %v", err)
}
algs, err := rsp.CapabilityData.Data.Algorithms()
if err != nil {
log.Fatalf("Didn't get algs: %v", err)
}
for _, alg := range algs.AlgProperties {
fmt.Printf("0x%04X - %+v\n", alg.Alg, alg.AlgProperties)
} |
Another thing, for the type TPMLPCRSelection []TPMSPCRSelection and have the reflection code figure out that its a list? Or if that doesn't work, just tag it as a list in the parent structure: type TPMSQuoteInfo struct {
marshalByReflection
// information on algID, PCR selected and digest
PCRSelect TPMLPCRSelection `gotpm:"list"`
// digest of the selected PCR using the hash of the signing key
PCRDigest TPM2BDigest
} It seems like we could just determine what to do based on the type alone though (i.e. we shouldn't need the tags). As if the type to marshalled/unmarshalled is an array, either:
|
The above TPML change does cause one issue, it makes things like: type capabilitiesContents interface {
Marshallable
*TPMLAlgProperty | *TPMLHandle | *TPMLCCA | *TPMLCC | *TPMLPCRSelection | *TPMLTaggedTPMProperty |
*TPMLTaggedPCRProperty | *TPMLECCCurve | *TPMLTaggedPolicy | *TPMLACTData
} no longer work as something like The solution seems to be to remove the |
One final ergonomics issue around the Similar to the TPM2B types, the actual For exampe, if we have type TPMUFoo struct {
selector TPMAlgID
contents any
}
type FooContents interface {
TPMSBar | TPMSBaz
}
func NewTPMUFoo[C FooContents](contents C) TPMUFoo {
sel := /* ideally computed from C, if not possible, pass selector type in constructor */
return TPMUFoo{
selector: sel,
contents: &contents,
}
}
// Accessor methods (nil if unoccupied):
func (u *TPMUFoo) Bar() *TPMSBar {
// ...
}
func (u *TPMUFoo) Baz() *TPMSBaz {
// ...
} |
Great suggestion! This required making rsp, err := (tpm2.GetCapability{Capability: tpm2.TPMCapAlgs, PropertyCount: 0xFFFF}).Execute(s) now works (note you have to put parens around the |
I'm in the middle of incorporating these excellent ideas. ❤️ = done, 👍 = "I think I can do this" and 👀 = "I have to try it but I like it." I'll ping when ready for another round of review! |
This change incorporates some PR feedback that suggested making Command a generic interface and deleting the need for both command and response structures to have boilerplate methods that just return the command code. Now only commands have that. This had the side effect of making marshalling in general take inputs by-value instead of by-pointer, which is a little more clear anyway.
I'm confused why taking/returning the Command and Response structures by pointer or value affects the receiver types for
It initially seemed to me that return the zero value for |
The subtle problem:
I think this would harm users more than it helped them: maybe the user was expecting the zero value for the response. I think we should require the user to check the error (and crash obviously if they didn't). |
Unfortunately this generally means the TPML types can't be marshalled by users. I'd like users to be able to marshal any TPM type. I think the drawback to keeping it this way is just a bit more boilerplate in the structures code for TPML, so keeping it for now. WDYT? |
@josephlr Thanks for your patience and for the great feedback. I've incorporated your comments except for the idea about making BTW, the solution to TPM2B differs from yours in a couple key ways:
|
I'd like to mirror this into Google's internal repo as part of next steps for tpmdirect. Since this is a separate branch, it seems OK to me to go ahead and merge. Thanks for the great feedback so far! Let's discuss any other things that need to be fixed as part of the path to 1.0 release. |
That seems reasonable further changes (if any) will likely be small, and can have their own prs. I think the main test now is to actually use this library to implement some of the medium complexity TPM functionality, and see what works and what doesnt |
This is unfortunately a large change, but I think it does a lot for the ergonomics of the TPMDirect API.
This change uses the new Go 1.18 generics to solve a few problems:
[]byte
or actual structure when instantiating TPM2BsGenerics to the rescue. Here's what this PR does:
marshalling.go
that handles a lot of the high level marshalling work.reflect.go
is still the dirty reflection guts of the librarymarshalByReflection
into all the structs that can be marshalled by reflection, as a clear hint to the reflection libraryUnmarshallableWithHint
- most of the TPMU implement this, and the oldmarshalUnion
andunmarshalUnion
functions are gone nowtags
function was allocating several orders of magnitude more memory than the rest of the library, so I rewrote itFixes #307 and #292.