Add CBOR Serializer implementation.

This commit is contained in:
Ben Luddy 2024-02-13 11:03:24 -05:00
parent d76d7a1e7a
commit 066421f108
No known key found for this signature in database
GPG Key ID: A6551E73A5974C30
7 changed files with 971 additions and 1 deletions

View File

@ -17,5 +17,228 @@ limitations under the License.
package cbor
import (
_ "github.com/fxamacker/cbor/v2"
"bytes"
"encoding/hex"
"errors"
"fmt"
"io"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes"
"k8s.io/apimachinery/pkg/runtime/serializer/recognizer"
util "k8s.io/apimachinery/pkg/util/runtime"
"github.com/fxamacker/cbor/v2"
)
type metaFactory interface {
// Interpret should return the version and kind of the wire-format of the object.
Interpret(data []byte) (*schema.GroupVersionKind, error)
}
type defaultMetaFactory struct{}
func (mf *defaultMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) {
var tm metav1.TypeMeta
// The input is expected to include additional map keys besides apiVersion and kind, so use
// lax mode for decoding into TypeMeta.
if err := modes.DecodeLax.Unmarshal(data, &tm); err != nil {
return nil, fmt.Errorf("unable to determine group/version/kind: %w", err)
}
actual := tm.GetObjectKind().GroupVersionKind()
return &actual, nil
}
type Serializer interface {
runtime.Serializer
recognizer.RecognizingDecoder
}
var _ Serializer = &serializer{}
type options struct {
strict bool
}
type Option func(*options)
func Strict(s bool) Option {
return func(opts *options) {
opts.strict = s
}
}
type serializer struct {
metaFactory metaFactory
creater runtime.ObjectCreater
typer runtime.ObjectTyper
options options
}
func NewSerializer(creater runtime.ObjectCreater, typer runtime.ObjectTyper, options ...Option) Serializer {
return newSerializer(&defaultMetaFactory{}, creater, typer, options...)
}
func newSerializer(metaFactory metaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, options ...Option) *serializer {
s := &serializer{
metaFactory: metaFactory,
creater: creater,
typer: typer,
}
for _, o := range options {
o(&s.options)
}
return s
}
func (s *serializer) Identifier() runtime.Identifier {
return "cbor"
}
func (s *serializer) Encode(obj runtime.Object, w io.Writer) error {
if _, err := w.Write(selfDescribedCBOR); err != nil {
return err
}
e := modes.Encode.NewEncoder(w)
if u, ok := obj.(runtime.Unstructured); ok {
return e.Encode(u.UnstructuredContent())
}
return e.Encode(obj)
}
// gvkWithDefaults returns group kind and version defaulting from provided default
func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind {
if len(actual.Kind) == 0 {
actual.Kind = defaultGVK.Kind
}
if len(actual.Version) == 0 && len(actual.Group) == 0 {
actual.Group = defaultGVK.Group
actual.Version = defaultGVK.Version
}
if len(actual.Version) == 0 && actual.Group == defaultGVK.Group {
actual.Version = defaultGVK.Version
}
return actual
}
// diagnose returns the diagnostic encoding of a well-formed CBOR data item.
func diagnose(data []byte) string {
diag, err := modes.Diagnostic.Diagnose(data)
if err != nil {
// Since the input must already be well-formed CBOR, converting it to diagnostic
// notation should not fail.
util.HandleError(err)
return hex.EncodeToString(data)
}
return diag
}
func (s *serializer) unmarshal(data []byte, into interface{}) (strict, lax error) {
if u, ok := into.(runtime.Unstructured); ok {
var content map[string]interface{}
defer func() {
// TODO: The UnstructuredList implementation of SetUnstructuredContent is
// not identical to what unstructuredJSONScheme does: (1) it retains the
// "items" key in its Object field, and (2) it does not infer a singular
// Kind from the list's Kind and populate omitted apiVersion/kind for all
// entries in Items.
u.SetUnstructuredContent(content)
}()
into = &content
}
if !s.options.strict {
return nil, modes.DecodeLax.Unmarshal(data, into)
}
err := modes.Decode.Unmarshal(data, into)
// TODO: UnknownFieldError is ambiguous. It only provides the index of the first problematic
// map entry encountered and does not indicate which map the index refers to.
var unknownField *cbor.UnknownFieldError
if errors.As(err, &unknownField) {
// Unlike JSON, there are no strict errors in CBOR for duplicate map keys. CBOR maps
// with duplicate keys are considered invalid according to the spec and are rejected
// entirely.
return runtime.NewStrictDecodingError([]error{unknownField}), modes.DecodeLax.Unmarshal(data, into)
}
return nil, err
}
func (s *serializer) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
// A preliminary pass over the input to obtain the actual GVK is redundant on a successful
// decode into Unstructured.
if _, ok := into.(runtime.Unstructured); ok {
if _, unmarshalErr := s.unmarshal(data, into); unmarshalErr != nil {
actual, interpretErr := s.metaFactory.Interpret(data)
if interpretErr != nil {
return nil, nil, interpretErr
}
if gvk != nil {
*actual = gvkWithDefaults(*actual, *gvk)
}
return nil, actual, unmarshalErr
}
actual := into.GetObjectKind().GroupVersionKind()
if len(actual.Kind) == 0 {
return nil, &actual, runtime.NewMissingKindErr(diagnose(data))
}
if len(actual.Version) == 0 {
return nil, &actual, runtime.NewMissingVersionErr(diagnose(data))
}
return into, &actual, nil
}
actual, err := s.metaFactory.Interpret(data)
if err != nil {
return nil, nil, err
}
if gvk != nil {
*actual = gvkWithDefaults(*actual, *gvk)
}
if into != nil {
types, _, err := s.typer.ObjectKinds(into)
if err != nil {
return nil, actual, err
}
*actual = gvkWithDefaults(*actual, types[0])
}
if len(actual.Kind) == 0 {
return nil, actual, runtime.NewMissingKindErr(diagnose(data))
}
if len(actual.Version) == 0 {
return nil, actual, runtime.NewMissingVersionErr(diagnose(data))
}
obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into)
if err != nil {
return nil, actual, err
}
strict, err := s.unmarshal(data, obj)
if err != nil {
return nil, actual, err
}
return obj, actual, strict
}
// selfDescribedCBOR is the CBOR encoding of the head of tag number 55799. This tag, specified in
// RFC 8949 Section 3.4.6 "Self-Described CBOR", encloses all output from the encoder, has no
// special semantics, and is used as a magic number to recognize CBOR-encoded data items.
//
// See https://www.rfc-editor.org/rfc/rfc8949.html#name-self-described-cbor.
var selfDescribedCBOR = []byte{0xd9, 0xd9, 0xf7}
func (s *serializer) RecognizesData(data []byte) (ok, unknown bool, err error) {
return bytes.HasPrefix(data, selfDescribedCBOR), false, nil
}

View File

@ -0,0 +1,475 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// The tests in this package focus on the correctness of its implementation of
// runtime.Serializer. The specific behavior of marshaling Go values to CBOR bytes and back is
// tested in the ./internal/modes package, which is used both by the Serializer implementation and
// the package-scoped Marshal/Unmarshal functions in the ./direct package.
package cbor
import (
"bytes"
"encoding/hex"
"errors"
"io"
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes"
"github.com/google/go-cmp/cmp"
)
func TestRecognizesData(t *testing.T) {
for _, tc := range []struct {
in []byte
recognizes bool
}{
{
in: nil,
recognizes: false,
},
{
in: []byte{},
recognizes: false,
},
{
in: []byte{0xd9},
recognizes: false,
},
{
in: []byte{0xd9, 0xd9},
recognizes: false,
},
{
in: []byte{0xd9, 0xd9, 0xf7},
recognizes: true,
},
{
in: []byte{0xff, 0xff, 0xff},
recognizes: false,
},
{
in: []byte{0xd9, 0xd9, 0xf7, 0x01, 0x02, 0x03},
recognizes: true,
},
{
in: []byte{0xff, 0xff, 0xff, 0x01, 0x02, 0x03},
recognizes: false,
},
} {
t.Run(hex.EncodeToString(tc.in), func(t *testing.T) {
s := NewSerializer(nil, nil)
recognizes, unknown, err := s.RecognizesData(tc.in)
if recognizes != tc.recognizes {
t.Errorf("expected recognized to be %t, got %t", tc.recognizes, recognizes)
}
if unknown {
t.Error("expected unknown to be false, got true")
}
if err != nil {
t.Errorf("expected nil error, got: %v", err)
}
})
}
}
type stubWriter struct {
n int
err error
}
func (w stubWriter) Write([]byte) (int, error) {
return w.n, w.err
}
// anyObject wraps arbitrary concrete values to be encoded or decoded.
type anyObject struct {
Value interface{}
}
func (p anyObject) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
func (anyObject) DeepCopyObject() runtime.Object {
panic("unimplemented")
}
func (p anyObject) MarshalCBOR() ([]byte, error) {
return modes.Encode.Marshal(p.Value)
}
func (p *anyObject) UnmarshalCBOR(in []byte) error {
return modes.Decode.Unmarshal(in, &p.Value)
}
func TestEncode(t *testing.T) {
for _, tc := range []struct {
name string
in runtime.Object
assertOnWriter func() (io.Writer, func(*testing.T))
assertOnError func(*testing.T, error)
}{
{
name: "io error writing self described cbor tag",
assertOnWriter: func() (io.Writer, func(*testing.T)) {
return stubWriter{err: io.ErrShortWrite}, func(*testing.T) {}
},
assertOnError: func(t *testing.T, err error) {
if !errors.Is(err, io.ErrShortWrite) {
t.Errorf("expected io.ErrShortWrite, got: %v", err)
}
},
},
{
name: "output enclosed by self-described CBOR tag",
in: anyObject{},
assertOnWriter: func() (io.Writer, func(*testing.T)) {
var b bytes.Buffer
return &b, func(t *testing.T) {
if !bytes.HasPrefix(b.Bytes(), []byte{0xd9, 0xd9, 0xf7}) {
t.Errorf("expected output to have prefix 0xd9d9f7: 0x%x", b.Bytes())
}
}
},
assertOnError: func(t *testing.T, err error) {
if err != nil {
t.Errorf("expected nil error, got: %v", err)
}
},
},
} {
t.Run(tc.name, func(t *testing.T) {
s := NewSerializer(nil, nil)
w, assertOnWriter := tc.assertOnWriter()
err := s.Encode(tc.in, w)
tc.assertOnError(t, err)
assertOnWriter(t)
})
}
}
func TestDecode(t *testing.T) {
for _, tc := range []struct {
name string
options []Option
data []byte
gvk *schema.GroupVersionKind
metaFactory metaFactory
typer runtime.ObjectTyper
creater runtime.ObjectCreater
into runtime.Object
expectedObj runtime.Object
expectedGVK *schema.GroupVersionKind
assertOnError func(*testing.T, error)
}{
{
name: "error determining gvk",
metaFactory: stubMetaFactory{err: errors.New("test")},
assertOnError: func(t *testing.T, err error) {
if err == nil || err.Error() != "test" {
t.Errorf("expected error \"test\", got: %v", err)
}
},
},
{
name: "typer does not recognize into",
gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
typer: notRegisteredTyper{},
into: &anyObject{},
expectedObj: nil,
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
assertOnError: func(t *testing.T, err error) {
if !runtime.IsNotRegisteredError(err) {
t.Errorf("expected NotRegisteredError, got: %v", err)
}
},
},
{
name: "gvk from type of into",
data: []byte{0xf6},
gvk: &schema.GroupVersionKind{},
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}},
into: &anyObject{},
expectedObj: &anyObject{},
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
assertOnError: func(t *testing.T, err error) {
if err != nil {
t.Errorf("expected nil error, got: %v", err)
}
},
},
{
name: "strict mode strict error",
options: []Option{Strict(true)},
data: []byte{0xa1, 0x61, 'z', 0x01}, // {'z': 1}
gvk: &schema.GroupVersionKind{},
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}},
into: &metav1.PartialObjectMetadata{},
expectedObj: &metav1.PartialObjectMetadata{},
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
assertOnError: func(t *testing.T, err error) {
if !runtime.IsStrictDecodingError(err) {
t.Errorf("expected StrictDecodingError, got: %v", err)
}
},
},
{
name: "no strict mode no strict error",
data: []byte{0xa1, 0x61, 'z', 0x01}, // {'z': 1}
gvk: &schema.GroupVersionKind{},
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}},
into: &metav1.PartialObjectMetadata{},
expectedObj: &metav1.PartialObjectMetadata{},
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
assertOnError: func(t *testing.T, err error) {
if err != nil {
t.Errorf("expected nil error, got: %v", err)
}
},
},
{
name: "unknown error from typer on into",
gvk: &schema.GroupVersionKind{},
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
typer: stubTyper{err: errors.New("test")},
into: &anyObject{},
expectedObj: nil,
expectedGVK: &schema.GroupVersionKind{},
assertOnError: func(t *testing.T, err error) {
if err == nil || err.Error() != "test" {
t.Errorf("expected error \"test\", got: %v", err)
}
},
},
{
name: "missing kind",
gvk: &schema.GroupVersionKind{Version: "v"},
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
expectedObj: nil,
expectedGVK: &schema.GroupVersionKind{Version: "v"},
assertOnError: func(t *testing.T, err error) {
if !runtime.IsMissingKind(err) {
t.Errorf("expected MissingKind, got: %v", err)
}
},
},
{
name: "missing version",
gvk: &schema.GroupVersionKind{Kind: "k"},
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
expectedObj: nil,
expectedGVK: &schema.GroupVersionKind{Kind: "k"},
assertOnError: func(t *testing.T, err error) {
if !runtime.IsMissingVersion(err) {
t.Errorf("expected MissingVersion, got: %v", err)
}
},
},
{
name: "creater error",
gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
creater: stubCreater{err: errors.New("test")},
expectedObj: nil,
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
assertOnError: func(t *testing.T, err error) {
if err == nil || err.Error() != "test" {
t.Errorf("expected error \"test\", got: %v", err)
}
},
},
{
name: "unmarshal error",
data: nil, // EOF
gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
creater: stubCreater{obj: &anyObject{}},
expectedObj: nil,
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
assertOnError: func(t *testing.T, err error) {
if !errors.Is(err, io.EOF) {
t.Errorf("expected EOF, got: %v", err)
}
},
},
{
name: "strict mode unmarshal error",
options: []Option{Strict(true)},
data: nil, // EOF
gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
creater: stubCreater{obj: &anyObject{}},
expectedObj: nil,
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
assertOnError: func(t *testing.T, err error) {
if !errors.Is(err, io.EOF) {
t.Errorf("expected EOF, got: %v", err)
}
},
},
{
name: "into unstructured unmarshal error",
data: nil, // EOF
gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}},
into: &unstructured.Unstructured{},
expectedObj: nil,
expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"},
assertOnError: func(t *testing.T, err error) {
if !errors.Is(err, io.EOF) {
t.Errorf("expected EOF, got: %v", err)
}
},
},
{
name: "into unstructured missing kind",
data: []byte("\xa1\x6aapiVersion\x61v"),
into: &unstructured.Unstructured{},
expectedObj: nil,
expectedGVK: &schema.GroupVersionKind{Version: "v"},
assertOnError: func(t *testing.T, err error) {
if !runtime.IsMissingKind(err) {
t.Errorf("expected MissingKind, got: %v", err)
}
},
},
{
name: "into unstructured missing version",
data: []byte("\xa1\x64kind\x61k"),
into: &unstructured.Unstructured{},
expectedObj: nil,
expectedGVK: &schema.GroupVersionKind{Kind: "k"},
assertOnError: func(t *testing.T, err error) {
if !runtime.IsMissingVersion(err) {
t.Errorf("expected MissingVersion, got: %v", err)
}
},
},
{
name: "into unstructured",
data: []byte("\xa2\x6aapiVersion\x61v\x64kind\x61k"),
into: &unstructured.Unstructured{},
expectedObj: &unstructured.Unstructured{Object: map[string]interface{}{
"apiVersion": "v",
"kind": "k",
}},
expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "k"},
assertOnError: func(t *testing.T, err error) {
if err != nil {
t.Errorf("expected nil error, got: %v", err)
}
},
},
{
name: "using unstructured creater",
data: []byte("\xa2\x6aapiVersion\x61v\x64kind\x61k"),
metaFactory: &defaultMetaFactory{},
creater: stubCreater{obj: &unstructured.Unstructured{}},
expectedObj: &unstructured.Unstructured{Object: map[string]interface{}{
"apiVersion": "v",
"kind": "k",
}},
expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "k"},
assertOnError: func(t *testing.T, err error) {
if err != nil {
t.Errorf("expected nil error, got: %v", err)
}
},
},
} {
t.Run(tc.name, func(t *testing.T) {
s := newSerializer(tc.metaFactory, tc.creater, tc.typer, tc.options...)
actualObj, actualGVK, err := s.Decode(tc.data, tc.gvk, tc.into)
tc.assertOnError(t, err)
if !reflect.DeepEqual(tc.expectedObj, actualObj) {
t.Error(cmp.Diff(tc.expectedObj, actualObj))
}
if diff := cmp.Diff(tc.expectedGVK, actualGVK); diff != "" {
t.Error(diff)
}
})
}
}
func TestMetaFactoryInterpret(t *testing.T) {
mf := &defaultMetaFactory{}
_, err := mf.Interpret(nil)
if err == nil {
t.Error("expected non-nil error")
}
gvk, err := mf.Interpret([]byte("\xa2\x6aapiVersion\x63a/b\x64kind\x61c"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if diff := cmp.Diff(&schema.GroupVersionKind{Group: "a", Version: "b", Kind: "c"}, gvk); diff != "" {
t.Error(diff)
}
}
type stubTyper struct {
gvks []schema.GroupVersionKind
unversioned bool
err error
}
func (t stubTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
return t.gvks, t.unversioned, t.err
}
func (stubTyper) Recognizes(schema.GroupVersionKind) bool {
return false
}
type stubCreater struct {
obj runtime.Object
err error
}
func (c stubCreater) New(gvk schema.GroupVersionKind) (runtime.Object, error) {
return c.obj, c.err
}
type notRegisteredTyper struct{}
func (notRegisteredTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
return nil, false, runtime.NewNotRegisteredErrForType("test", reflect.TypeOf(obj))
}
func (notRegisteredTyper) Recognizes(schema.GroupVersionKind) bool {
return false
}
type stubMetaFactory struct {
gvk *schema.GroupVersionKind
err error
}
func (mf stubMetaFactory) Interpret([]byte) (*schema.GroupVersionKind, error) {
return mf.gvk, mf.err
}

View File

@ -0,0 +1,36 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package direct provides functions for marshaling and unmarshaling between arbitrary Go values and
// CBOR data, with behavior that is compatible with that of the CBOR serializer. In particular,
// types that implement cbor.Marshaler and cbor.Unmarshaler should use these functions.
package direct
import (
"k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes"
)
func Marshal(src interface{}) ([]byte, error) {
return modes.Encode.Marshal(src)
}
func Unmarshal(src []byte, dst interface{}) error {
return modes.Decode.Unmarshal(src, dst)
}
func Diagnose(src []byte) (string, error) {
return modes.Diagnostic.Diagnose(src)
}

View File

@ -0,0 +1,103 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package modes
import (
"reflect"
"github.com/fxamacker/cbor/v2"
)
var Decode cbor.DecMode = func() cbor.DecMode {
decode, err := cbor.DecOptions{
// Maps with duplicate keys are well-formed but invalid according to the CBOR spec
// and never acceptable. Unlike the JSON serializer, inputs containing duplicate map
// keys are rejected outright and not surfaced as a strict decoding error.
DupMapKey: cbor.DupMapKeyEnforcedAPF,
// For JSON parity, decoding an RFC3339 string into time.Time needs to be accepted
// with or without tagging. If a tag number is present, it must be valid.
TimeTag: cbor.DecTagOptional,
// Observed depth up to 16 in fuzzed batch/v1 CronJobList. JSON implementation limit
// is 10000.
MaxNestedLevels: 64,
MaxArrayElements: 1024,
MaxMapPairs: 1024,
// Indefinite-length sequences aren't produced by this serializer, but other
// implementations can.
IndefLength: cbor.IndefLengthAllowed,
// Accept inputs that contain CBOR tags.
TagsMd: cbor.TagsAllowed,
// Decode type 0 (unsigned integer) as int64.
// TODO: IntDecConvertSignedOrFail errors on overflow, JSON will try to fall back to float64.
IntDec: cbor.IntDecConvertSignedOrFail,
// Disable producing map[cbor.ByteString]interface{}, which is not acceptable for
// decodes into interface{}.
MapKeyByteString: cbor.MapKeyByteStringForbidden,
// Error on map keys that don't map to a field in the destination struct.
ExtraReturnErrors: cbor.ExtraDecErrorUnknownField,
// Decode maps into concrete type map[string]interface{} when the destination is an
// interface{}.
DefaultMapType: reflect.TypeOf(map[string]interface{}(nil)),
// A CBOR text string whose content is not a valid UTF-8 sequence is well-formed but
// invalid according to the CBOR spec. Reject invalid inputs. Encoders are
// responsible for ensuring that all text strings they produce contain valid UTF-8
// sequences and may use the byte string major type to encode strings that have not
// been validated.
UTF8: cbor.UTF8RejectInvalid,
// Never make a case-insensitive match between a map key and a struct field.
FieldNameMatching: cbor.FieldNameMatchingCaseSensitive,
// Produce string concrete values when decoding a CBOR byte string into interface{}.
DefaultByteStringType: reflect.TypeOf(""),
// Allow CBOR byte strings to be decoded into string destination values.
ByteStringToString: cbor.ByteStringToStringAllowed,
// Allow CBOR byte strings to match struct fields when appearing as a map key.
FieldNameByteString: cbor.FieldNameByteStringAllowed,
// When decoding an unrecognized tag to interface{}, return the decoded tag content
// instead of the default, a cbor.Tag representing a (number, content) pair.
UnrecognizedTagToAny: cbor.UnrecognizedTagContentToAny,
}.DecMode()
if err != nil {
panic(err)
}
return decode
}()
// DecodeLax is derived from Decode, but does not complain about unknown fields in the input.
var DecodeLax cbor.DecMode = func() cbor.DecMode {
opts := Decode.DecOptions()
opts.ExtraReturnErrors &^= cbor.ExtraDecErrorUnknownField // clear bit
dm, err := opts.DecMode()
if err != nil {
panic(err)
}
return dm
}()

View File

@ -0,0 +1,36 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package modes
import (
"github.com/fxamacker/cbor/v2"
)
var Diagnostic cbor.DiagMode = func() cbor.DiagMode {
opts := Decode.DecOptions()
diagnostic, err := cbor.DiagOptions{
ByteStringText: true,
MaxNestedLevels: opts.MaxNestedLevels,
MaxArrayElements: opts.MaxArrayElements,
MaxMapPairs: opts.MaxMapPairs,
}.DiagMode()
if err != nil {
panic(err)
}
return diagnostic
}()

View File

@ -0,0 +1,96 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package modes
import (
"github.com/fxamacker/cbor/v2"
)
var Encode cbor.EncMode = func() cbor.EncMode {
encode, err := cbor.EncOptions{
// Map keys need to be sorted to have deterministic output, and this is the order
// defined in RFC 8949 4.2.1 "Core Deterministic Encoding Requirements".
Sort: cbor.SortBytewiseLexical,
// CBOR supports distinct types for IEEE-754 float16, float32, and float64. Store
// floats in the smallest width that preserves value so that equivalent float32 and
// float64 values encode to identical bytes, as they do in a JSON
// encoding. Satisfies one of the "Core Deterministic Encoding Requirements".
ShortestFloat: cbor.ShortestFloat16,
// ShortestFloat doesn't apply to NaN or Inf values. Inf values are losslessly
// encoded to float16. RFC 8949 recommends choosing a single representation of NaN
// in applications that do not smuggle additional information inside NaN values, we
// use 0x7e00.
NaNConvert: cbor.NaNConvert7e00,
InfConvert: cbor.InfConvertFloat16,
// Prefer encoding math/big.Int to one of the 64-bit integer types if it fits. When
// later decoded into Unstructured, the set of allowable concrete numeric types is
// limited to int64 and float64, so the distinction between big integer and integer
// can't be preserved.
BigIntConvert: cbor.BigIntConvertShortest,
// MarshalJSON for time.Time writes RFC3339 with nanos.
Time: cbor.TimeRFC3339Nano,
// The decoder must be able to accept RFC3339 strings with or without tag 0 (e.g. by
// the end of time.Time -> JSON -> Unstructured -> CBOR, the CBOR encoder has no
// reliable way of knowing that a particular string originated from serializing a
// time.Time), so producing tag 0 has little use.
TimeTag: cbor.EncTagNone,
// Indefinite-length items have multiple encodings and aren't being used anyway, so
// disable to avoid an opportunity for nondeterminism.
IndefLength: cbor.IndefLengthForbidden,
// Preserve distinction between nil and empty for slices and maps.
NilContainers: cbor.NilContainerAsNull,
// OK to produce tags.
TagsMd: cbor.TagsAllowed,
// Use the same definition of "empty" as encoding/json.
OmitEmpty: cbor.OmitEmptyGoValue,
// The CBOR types text string and byte string are structurally equivalent, with the
// semantic difference that a text string whose content is an invalid UTF-8 sequence
// is itself invalid. We reject all invalid text strings at decode time and do not
// validate or sanitize all Go strings at encode time. Encoding Go strings to the
// byte string type is comparable to the existing Protobuf behavior and cheaply
// ensures that the output is valid CBOR.
String: cbor.StringToByteString,
// Encode struct field names to the byte string type rather than the text string
// type.
FieldName: cbor.FieldNameToByteString,
}.EncMode()
if err != nil {
panic(err)
}
return encode
}()
var EncodeNondeterministic cbor.EncMode = func() cbor.EncMode {
opts := Encode.EncOptions()
opts.Sort = cbor.SortNone
em, err := opts.EncMode()
if err != nil {
panic(err)
}
return em
}()

1
vendor/modules.txt vendored
View File

@ -1313,6 +1313,7 @@ k8s.io/apimachinery/pkg/runtime
k8s.io/apimachinery/pkg/runtime/schema
k8s.io/apimachinery/pkg/runtime/serializer
k8s.io/apimachinery/pkg/runtime/serializer/cbor
k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes
k8s.io/apimachinery/pkg/runtime/serializer/json
k8s.io/apimachinery/pkg/runtime/serializer/protobuf
k8s.io/apimachinery/pkg/runtime/serializer/recognizer