protobuf-go/proto/size.go

92 lines
2.6 KiB
Go
Raw Normal View History

// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proto
import (
"fmt"
"github.com/golang/protobuf/v2/internal/encoding/wire"
"github.com/golang/protobuf/v2/reflect/protoreflect"
)
// Size returns the size in bytes of the wire-format encoding of m.
func Size(m Message) int {
proto: replace CachedSize fast-path method with UseCachedSize option Using an option instead of a separate method has several useful properties: It makes it explicit whether the fast-path AppendMarshal is expected to use cached sizes or not. It properly plumbs the decision to use cached sizes through the call stack. Consider the case where message A includes B includes C: If A and C support cached sizes but B does not, we would like to use the size cache in all messages which support it. Placing this decision in the options allows this to work properly with no additional effort. Placing this option in MarshalOptions permits users to request use of existing cached sizes. This is a two-edged sword: There are places where this ability can permit substantial efficiencies, but this is also an exceedingly sharp-edged API. I believe that on balance the benefits outweigh the risks, especially since the prerequisites for using cached sizes are intuitively obvious. (You must have called Size, and you must not have changed the message.) This CL adds a Size method to MarshalOptions, rather than adding a SizeOptions type. Future additions to MarshalOptions may affect the size of the encoded output (e.g., an option to skip encoding unknown fields) and using the same options for both Marshal and Size makes it easier to use a consistent configuration for each. Change-Id: I6adbb55b717dd03d39f067e1d0b7381945000976 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/171119 Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
2019-04-08 01:18:31 +00:00
return MarshalOptions{}.Size(m)
}
// Size returns the size in bytes of the wire-format encoding of m.
func (o MarshalOptions) Size(m Message) int {
if size, err := sizeMessageFast(m); err == nil {
return size
}
return sizeMessage(m.ProtoReflect())
}
func sizeMessageFast(m Message) (int, error) {
// TODO: Pass MarshalOptions to size to permit disabling fast path?
methods := protoMethods(m)
if methods == nil || methods.Size == nil {
return 0, errInternalNoFast
}
return methods.Size(m), nil
}
func sizeMessage(m protoreflect.Message) (size int) {
fieldDescs := m.Descriptor().Fields()
knownFields := m.KnownFields()
m.KnownFields().Range(func(num protoreflect.FieldNumber, value protoreflect.Value) bool {
field := fieldDescs.ByNumber(num)
if field == nil {
field = knownFields.ExtensionTypes().ByNumber(num).Descriptor()
if field == nil {
panic(fmt.Errorf("no descriptor for field %d in %q", num, m.Descriptor().FullName()))
}
}
size += sizeField(field, value)
return true
})
m.UnknownFields().Range(func(_ protoreflect.FieldNumber, raw protoreflect.RawFields) bool {
size += len(raw)
return true
})
return size
}
func sizeField(fd protoreflect.FieldDescriptor, value protoreflect.Value) (size int) {
num := fd.Number()
switch {
case fd.IsList():
return sizeList(num, fd, value.List())
case fd.IsMap():
return sizeMap(num, fd, value.Map())
default:
return wire.SizeTag(num) + sizeSingular(num, fd.Kind(), value)
}
}
func sizeList(num wire.Number, fd protoreflect.FieldDescriptor, list protoreflect.List) (size int) {
if fd.IsPacked() {
content := 0
for i, llen := 0, list.Len(); i < llen; i++ {
content += sizeSingular(num, fd.Kind(), list.Get(i))
}
return wire.SizeTag(num) + wire.SizeBytes(content)
}
for i, llen := 0, list.Len(); i < llen; i++ {
size += wire.SizeTag(num) + sizeSingular(num, fd.Kind(), list.Get(i))
}
return size
}
func sizeMap(num wire.Number, fd protoreflect.FieldDescriptor, mapv protoreflect.Map) (size int) {
mapv.Range(func(key protoreflect.MapKey, value protoreflect.Value) bool {
size += wire.SizeTag(num)
size += wire.SizeBytes(sizeField(fd.MapKey(), key.Value()) + sizeField(fd.MapValue(), value))
return true
})
return size
}