protobuf-go/internal/impl/legacy_unknown.go
Joe Tsai e2afdc27e7 internal/impl: support legacy unknown fields
Add wrapper data structures to get legacy XXX_unrecognized fields to support
the new protoreflect.UnknownFields interface. This is a challenge since the
field is a []byte, which does not give us much flexibility to work with
in terms of choice of data structures.

This implementation is relatively naive where every operation is O(n) since
it needs to strip through the entire []byte each time. The Range operation
operates slightly differently from ranging over Go maps since it presents a
stale version of RawFields should a mutation occur while ranging.
This distinction is unlikely to affect anyone in practice.

Change-Id: Ib3247cb827f9a0dd6c2192cd59830dca5eef8257
Reviewed-on: https://go-review.googlesource.com/c/144697
Reviewed-by: Damien Neil <dneil@google.com>
2018-10-26 19:55:21 +00:00

143 lines
3.8 KiB
Go

// Copyright 2018 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 impl
import (
"reflect"
protoV1 "github.com/golang/protobuf/proto"
"github.com/golang/protobuf/v2/internal/encoding/wire"
pref "github.com/golang/protobuf/v2/reflect/protoreflect"
)
var (
extTypeA = reflect.TypeOf(map[int32]protoV1.Extension(nil))
extTypeB = reflect.TypeOf(protoV1.XXX_InternalExtensions{})
)
func generateLegacyUnknownFieldFuncs(t reflect.Type, md pref.MessageDescriptor) func(p *messageDataType) pref.UnknownFields {
fu, ok := t.FieldByName("XXX_unrecognized")
if !ok || fu.Type != bytesType {
return nil
}
fx1, _ := t.FieldByName("XXX_extensions")
fx2, _ := t.FieldByName("XXX_InternalExtensions")
if fx1.Type == extTypeA || fx2.Type == extTypeB {
// TODO: In proto v1, the unknown fields are split between both
// XXX_unrecognized and XXX_InternalExtensions. If the message supports
// extensions, then we will need to create a wrapper data structure
// that presents unknown fields in both lists as a single ordered list.
panic("not implemented")
}
fieldOffset := offsetOf(fu)
return func(p *messageDataType) pref.UnknownFields {
rv := p.p.apply(fieldOffset).asType(bytesType)
return (*legacyUnknownBytes)(rv.Interface().(*[]byte))
}
}
// legacyUnknownBytes is a wrapper around XXX_unrecognized that implements
// the protoreflect.UnknownFields interface. This is challenging since we are
// limited to a []byte, so we do not have much flexibility in the choice
// of data structure that would have been ideal.
type legacyUnknownBytes []byte
func (fs *legacyUnknownBytes) Len() int {
// Runtime complexity: O(n)
b := *fs
m := map[pref.FieldNumber]bool{}
for len(b) > 0 {
num, _, n := wire.ConsumeField(b)
m[num] = true
b = b[n:]
}
return len(m)
}
func (fs *legacyUnknownBytes) Get(num pref.FieldNumber) (raw pref.RawFields) {
// Runtime complexity: O(n)
b := *fs
for len(b) > 0 {
num2, _, n := wire.ConsumeField(b)
if num == num2 {
raw = append(raw, b[:n]...)
}
b = b[n:]
}
return raw
}
func (fs *legacyUnknownBytes) Set(num pref.FieldNumber, raw pref.RawFields) {
num2, _, _ := wire.ConsumeTag(raw)
if len(raw) > 0 && (!raw.IsValid() || num != num2) {
panic("invalid raw fields")
}
// Remove all current fields of num.
// Runtime complexity: O(n)
b := *fs
out := (*fs)[:0]
for len(b) > 0 {
num2, _, n := wire.ConsumeField(b)
if num != num2 {
out = append(out, b[:n]...)
}
b = b[n:]
}
*fs = out
// Append new fields of num.
*fs = append(*fs, raw...)
}
func (fs *legacyUnknownBytes) Range(f func(pref.FieldNumber, pref.RawFields) bool) {
type entry struct {
num pref.FieldNumber
raw pref.RawFields
}
var xs []entry
// Collect up a list of all the raw fields.
// We preserve the order such that the latest encountered fields
// are presented at the end.
//
// Runtime complexity: O(n)
b := *fs
m := map[pref.FieldNumber]int{}
for len(b) > 0 {
num, _, n := wire.ConsumeField(b)
// Ensure the most recently updated entry is always at the end of xs.
x := entry{num: num}
if i, ok := m[num]; ok {
j := len(xs) - 1
xs[i], xs[j] = xs[j], xs[i] // swap current entry with last entry
m[xs[i].num] = i // update index of swapped entry
x = xs[j] // retrieve the last entry
xs = xs[:j] // truncate off the last entry
}
m[num] = len(xs)
x.raw = append(x.raw, b[:n]...)
xs = append(xs, x)
b = b[n:]
}
// Iterate over all the raw fields.
// This ranges over a snapshot of the current state such that mutations
// while ranging are not observable.
//
// Runtime complexity: O(n)
for _, x := range xs {
if !f(x.num, x.raw) {
return
}
}
}
func (fs *legacyUnknownBytes) IsSupported() bool {
return true
}