cryptobyte: add (*Builder).Unwrite and (*Builder).SetError

Unwrite allows programs to rollback builders more reliably and
efficiently than by copying a Builder (which might waste an allocation
and depends on internal behavior). This is useful for example to remove
a length-prefixed field if it ends up being empty.

SetError allows simple Builder extensions to set errors without making
MarshalingValue wrappers.

Based on the experience of CL 144115.

Change-Id: I9a785b81b51b15af49418b5bdb71c4ef222ccc46
Reviewed-on: https://go-review.googlesource.com/c/145317
Reviewed-by: Adam Langley <agl@golang.org>
This commit is contained in:
Filippo Valsorda
2018-10-29 21:22:43 +00:00
parent 7e6ffbd038
commit b078efbc80
2 changed files with 93 additions and 6 deletions
+27 -1
View File
@@ -50,6 +50,12 @@ func NewFixedBuilder(buffer []byte) *Builder {
}
}
// SetError sets the value to be returned as the error from Bytes. Writes
// performed after calling SetError are ignored.
func (b *Builder) SetError(err error) {
b.err = err
}
// Bytes returns the bytes written by the builder or an error if one has
// occurred during building.
func (b *Builder) Bytes() ([]byte, error) {
@@ -94,7 +100,7 @@ func (b *Builder) AddBytes(v []byte) {
b.add(v...)
}
// BuilderContinuation is continuation-passing interface for building
// BuilderContinuation is a continuation-passing interface for building
// length-prefixed byte sequences. Builder methods for length-prefixed
// sequences (AddUint8LengthPrefixed etc) will invoke the BuilderContinuation
// supplied to them. The child builder passed to the continuation can be used
@@ -290,6 +296,26 @@ func (b *Builder) add(bytes ...byte) {
b.result = append(b.result, bytes...)
}
// Unwrite rolls back n bytes written directly to the Builder. An attempt by a
// child builder passed to a continuation to unwrite bytes from its parent will
// panic.
func (b *Builder) Unwrite(n int) {
if b.err != nil {
return
}
if b.child != nil {
panic("attempted unwrite while child is pending")
}
length := len(b.result) - b.pendingLenLen - b.offset
if length < 0 {
panic("cryptobyte: internal error")
}
if n > length {
panic("cryptobyte: attempted to unwrite more than was written")
}
b.result = b.result[:len(b.result)-n]
}
// A MarshalingValue marshals itself into a Builder.
type MarshalingValue interface {
// Marshal is called by Builder.AddValue. It receives a pointer to a builder