build source

This commit is contained in:
build 2026-04-16 04:16:36 +00:00
commit ee1fec43ed
4171 changed files with 1351288 additions and 0 deletions

View file

@ -0,0 +1,383 @@
/*
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 cbor
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"
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"
"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
runtime.NondeterministicEncoder
recognizer.RecognizingDecoder
// NewSerializer returns a value of this interface type rather than exporting the serializer
// type and returning one of those because the zero value of serializer isn't ready to
// use. Users aren't intended to implement cbor.Serializer themselves, and this unexported
// interface method is here to prevent that (https://go.dev/blog/module-compatibility).
private()
}
var _ Serializer = &serializer{}
type options struct {
strict bool
transcode bool
}
type Option func(*options)
// Strict configures a serializer to return a strict decoding error when it encounters map keys that
// do not correspond to a field in the target object of a decode operation. This option is disabled
// by default.
func Strict(s bool) Option {
return func(opts *options) {
opts.strict = s
}
}
// Transcode configures a serializer to transcode the "raw" bytes of a decoded runtime.RawExtension
// or metav1.FieldsV1 object to JSON. This is enabled by default to support existing programs that
// depend on the assumption that objects of either type contain valid JSON.
func Transcode(s bool) Option {
return func(opts *options) {
opts.transcode = s
}
}
type serializer struct {
metaFactory metaFactory
creater runtime.ObjectCreater
typer runtime.ObjectTyper
options options
}
func (serializer) private() {}
// NewSerializer creates and returns a serializer configured with the provided options. The default
// options are equivalent to explicitly passing Strict(false) and Transcode(true).
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,
}
s.options.transcode = true
for _, o := range options {
o(&s.options)
}
return s
}
func (s *serializer) Identifier() runtime.Identifier {
return "cbor"
}
// Encode writes a CBOR representation of the given object.
//
// Because the CBOR data item written by a call to Encode is always enclosed in the "self-described
// CBOR" tag, its encoded form always has the prefix 0xd9d9f7. This prefix is suitable for use as a
// "magic number" for distinguishing encoded CBOR from other protocols.
//
// The default serialization behavior for any given object replicates the behavior of the JSON
// serializer as far as it is necessary to allow the CBOR serializer to be used as a drop-in
// replacement for the JSON serializer, with limited exceptions. For example, the distinction
// between integers and floating-point numbers is preserved in CBOR due to its distinct
// representations for each type.
//
// Objects implementing runtime.Unstructured will have their unstructured content encoded rather
// than following the default behavior for their dynamic type.
func (s *serializer) Encode(obj runtime.Object, w io.Writer) error {
return s.encode(modes.Encode, obj, w)
}
func (s *serializer) EncodeNondeterministic(obj runtime.Object, w io.Writer) error {
return s.encode(modes.EncodeNondeterministic, obj, w)
}
func (s *serializer) encode(mode modes.EncMode, obj runtime.Object, w io.Writer) error {
var v interface{} = obj
if u, ok := obj.(runtime.Unstructured); ok {
v = u.UnstructuredContent()
}
if _, err := w.Write(selfDescribedCBOR); err != nil {
return err
}
return mode.MarshalTo(v, w)
}
// 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
}
// unmarshal unmarshals CBOR data from the provided byte slice into a Go object. If the decoder is
// configured to report strict errors, the first error return value may be a non-nil strict decoding
// error. If the last error return value is non-nil, then the unmarshal failed entirely and the
// state of the destination object should not be relied on.
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() {
switch u := u.(type) {
case *unstructured.UnstructuredList:
// UnstructuredList's implementation of SetUnstructuredContent
// produces different objects than those produced by a decode using
// UnstructuredJSONScheme:
//
// 1. SetUnstructuredContent retains the "items" key in the list's
// Object field. It is omitted from Object when decoding with
// UnstructuredJSONScheme.
// 2. SetUnstructuredContent does not populate "apiVersion" and
// "kind" on each entry of its Items
// field. UnstructuredJSONScheme does, inferring the singular
// Kind from the list Kind.
// 3. SetUnstructuredContent ignores entries of "items" that are
// not JSON objects or are objects without
// "kind". UnstructuredJSONScheme returns an error in either
// case.
//
// UnstructuredJSONScheme's behavior is replicated here.
var items []interface{}
if uncast, present := content["items"]; present {
var cast bool
items, cast = uncast.([]interface{})
if !cast {
strict, lax = nil, fmt.Errorf("items field of UnstructuredList must be encoded as an array or null if present")
return
}
}
apiVersion, _ := content["apiVersion"].(string)
kind, _ := content["kind"].(string)
kind = strings.TrimSuffix(kind, "List")
var unstructureds []unstructured.Unstructured
if len(items) > 0 {
unstructureds = make([]unstructured.Unstructured, len(items))
}
for i := range items {
object, cast := items[i].(map[string]interface{})
if !cast {
strict, lax = nil, fmt.Errorf("elements of the items field of UnstructuredList must be encoded as a map")
return
}
// As in UnstructuredJSONScheme, only set the heuristic
// singular GVK when both "apiVersion" and "kind" are either
// missing, non-string, or empty.
object["apiVersion"], _ = object["apiVersion"].(string)
object["kind"], _ = object["kind"].(string)
if object["apiVersion"] == "" && object["kind"] == "" {
object["apiVersion"] = apiVersion
object["kind"] = kind
}
if object["kind"] == "" {
strict, lax = nil, runtime.NewMissingKindErr(diagnose(data))
return
}
if object["apiVersion"] == "" {
strict, lax = nil, runtime.NewMissingVersionErr(diagnose(data))
return
}
unstructureds[i].Object = object
}
delete(content, "items")
u.Object = content
u.Items = unstructureds
default:
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
}
if s.options.transcode {
if err := transcodeRawTypes(obj); 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
}
// NewSerializerInfo returns a default SerializerInfo for CBOR using the given creater and typer.
func NewSerializerInfo(creater runtime.ObjectCreater, typer runtime.ObjectTyper) runtime.SerializerInfo {
return runtime.SerializerInfo{
MediaType: "application/cbor",
MediaTypeType: "application",
MediaTypeSubType: "cbor",
Serializer: NewSerializer(creater, typer),
StrictSerializer: NewSerializer(creater, typer, Strict(true)),
StreamSerializer: &runtime.StreamSerializerInfo{
Framer: NewFramer(),
Serializer: NewSerializer(creater, typer, Transcode(false)),
},
}
}

View file

@ -0,0 +1,43 @@
/*
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"
)
// Marshal serializes a value to CBOR. If there is more than one way to encode the value, it will
// make the same choice as the CBOR implementation of runtime.Serializer.
func Marshal(src any) ([]byte, error) {
return modes.Encode.Marshal(src)
}
// Unmarshal deserializes from CBOR into an addressable value. If there is more than one way to
// unmarshal a value, it will make the same choice as the CBOR implementation of runtime.Serializer.
func Unmarshal(src []byte, dst any) error {
return modes.Decode.Unmarshal(src, dst)
}
// Diagnose accepts well-formed CBOR bytes and returns a string representing the same data item in
// human-readable diagnostic notation (RFC 8949 Section 8). The diagnostic notation is not meant to
// be parsed.
func Diagnose(src []byte) (string, error) {
return modes.Diagnostic.Diagnose(src)
}

View file

@ -0,0 +1,90 @@
/*
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 cbor
import (
"io"
"k8s.io/apimachinery/pkg/runtime"
"github.com/fxamacker/cbor/v2"
)
// NewFramer returns a runtime.Framer based on RFC 8742 CBOR Sequences. Each frame contains exactly
// one encoded CBOR data item.
func NewFramer() runtime.Framer {
return framer{}
}
var _ runtime.Framer = framer{}
type framer struct{}
func (framer) NewFrameReader(rc io.ReadCloser) io.ReadCloser {
return &frameReader{
decoder: cbor.NewDecoder(rc),
closer: rc,
}
}
func (framer) NewFrameWriter(w io.Writer) io.Writer {
// Each data item in a CBOR sequence is self-delimiting (like JSON objects).
return w
}
type frameReader struct {
decoder *cbor.Decoder
closer io.Closer
overflow []byte
}
func (fr *frameReader) Read(dst []byte) (int, error) {
if len(fr.overflow) > 0 {
// We read a frame that was too large for the destination slice in a previous call
// to Read and have bytes left over.
n := copy(dst, fr.overflow)
if n < len(fr.overflow) {
fr.overflow = fr.overflow[n:]
return n, io.ErrShortBuffer
}
fr.overflow = nil
return n, nil
}
// The Reader contract allows implementations to use all of dst[0:len(dst)] as scratch
// space, even if n < len(dst), but it does not allow implementations to use
// dst[len(dst):cap(dst)]. Slicing it up-front allows us to append to it without worrying
// about overwriting dst[len(dst):cap(dst)].
m := cbor.RawMessage(dst[0:0:len(dst)])
if err := fr.decoder.Decode(&m); err != nil {
return 0, err
}
if len(m) > len(dst) {
// The frame was too big, m has a newly-allocated underlying array to accommodate
// it.
fr.overflow = m[len(dst):]
return copy(dst, m), io.ErrShortBuffer
}
return len(m), nil
}
func (fr *frameReader) Close() error {
return fr.closer.Close()
}

View file

@ -0,0 +1,65 @@
/*
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 (
"bytes"
"sync"
)
var buffers = BufferProvider{p: new(sync.Pool)}
type buffer struct {
bytes.Buffer
}
type pool interface {
Get() interface{}
Put(interface{})
}
type BufferProvider struct {
p pool
}
func (b *BufferProvider) Get() *buffer {
if buf, ok := b.p.Get().(*buffer); ok {
return buf
}
return &buffer{}
}
func (b *BufferProvider) Put(buf *buffer) {
if buf.Cap() > 3*1024*1024 /* Default MaxRequestBodyBytes */ {
// Objects in a sync.Pool are assumed to be fungible. This is not a good assumption
// for pools of *bytes.Buffer because a *bytes.Buffer's underlying array grows as
// needed to accommodate writes. In Kubernetes, apiservers tend to encode "small"
// objects very frequently and much larger objects (especially large lists) only
// occasionally. Under steady load, pooled buffers tend to be borrowed frequently
// enough to prevent them from being released. Over time, each buffer is used to
// encode a large object and its capacity increases accordingly. The result is that
// practically all buffers in the pool retain much more capacity than needed to
// encode most objects.
// As a basic mitigation for the worst case, buffers with more capacity than the
// default max request body size are never returned to the pool.
// TODO: Optimize for higher buffer utilization.
return
}
buf.Reset()
b.p.Put(buf)
}

View file

@ -0,0 +1,178 @@
/*
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 simpleValues *cbor.SimpleValueRegistry = func() *cbor.SimpleValueRegistry {
var opts []func(*cbor.SimpleValueRegistry) error
for sv := 0; sv <= 255; sv++ {
// Reject simple values 0-19, 23, and 32-255. The simple values 24-31 are reserved
// and considered ill-formed by the CBOR specification. We only accept false (20),
// true (21), and null (22).
switch sv {
case 20: // false
case 21: // true
case 22: // null
case 24, 25, 26, 27, 28, 29, 30, 31: // reserved
default:
opts = append(opts, cbor.WithRejectedSimpleValue(cbor.SimpleValue(sv)))
}
}
simpleValues, err := cbor.NewSimpleValueRegistryFromDefaults(opts...)
if err != nil {
panic(err)
}
return simpleValues
}()
// decode is the basis for the Decode mode, with no JSONUnmarshalerTranscoder
// configured. TranscodeToJSON uses this directly rather than Decode to avoid an initialization
// cycle between the two. Everything else should use one of the exported DecModes.
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. If a byte
// string is enclosed in an "expected later encoding" tag
// (https://www.rfc-editor.org/rfc/rfc8949.html#section-3.4.5.2), then the text
// encoding indicated by that tag (e.g. base64) will be applied to the contents of
// the byte string.
ByteStringToString: cbor.ByteStringToStringAllowedWithExpectedLaterEncoding,
// 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,
// Decode time tags to interface{} as strings containing RFC 3339 timestamps.
TimeTagToAny: cbor.TimeTagToRFC3339Nano,
// For parity with JSON, strings can be decoded into time.Time if they are RFC 3339
// timestamps.
ByteStringToTime: cbor.ByteStringToTimeAllowed,
// Reject NaN and infinite floating-point values since they don't have a JSON
// representation (RFC 8259 Section 6).
NaN: cbor.NaNDecodeForbidden,
Inf: cbor.InfDecodeForbidden,
// When unmarshaling a byte string into a []byte, assume that the byte string
// contains base64-encoded bytes, unless explicitly counterindicated by an "expected
// later encoding" tag. This is consistent with the because of unmarshaling a JSON
// text into a []byte.
ByteStringExpectedFormat: cbor.ByteStringExpectedBase64,
// Reject the arbitrary-precision integer tags because they can't be faithfully
// roundtripped through the allowable Unstructured types.
BignumTag: cbor.BignumTagForbidden,
// Reject anything other than the simple values true, false, and null.
SimpleValues: simpleValues,
// Disable default recognition of types implementing encoding.BinaryUnmarshaler,
// which is not recognized for JSON decoding.
BinaryUnmarshaler: cbor.BinaryUnmarshalerNone,
// Marshal types that implement encoding.TextMarshaler by calling their MarshalText
// method and encoding the result to a CBOR text string.
TextUnmarshaler: cbor.TextUnmarshalerTextString,
}.DecMode()
if err != nil {
panic(err)
}
return decode
}()
var Decode cbor.DecMode = func() cbor.DecMode {
opts := decode.DecOptions()
// When decoding into a value of a type that implements json.Unmarshaler (and does not
// implement cbor.Unmarshaler), transcode the input to JSON and pass it to the value's
// UnmarshalJSON method.
opts.JSONUnmarshalerTranscoder = TranscodeFunc(TranscodeToJSON)
dm, err := opts.DecMode()
if err != nil {
panic(err)
}
return dm
}()
// 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,177 @@
/*
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 (
"io"
"github.com/fxamacker/cbor/v2"
)
// encode is the basis for the Encode mode, with no JSONMarshalerTranscoder
// configured. TranscodeFromJSON uses this directly rather than Encode to avoid an initialization
// cycle between the two. Everything else should use one of the exported EncModes.
var encode = EncMode{
delegate: func() cbor.UserBufferEncMode {
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,
// Error on attempt to encode NaN and infinite values. This is what the JSON
// serializer does.
NaNConvert: cbor.NaNConvertReject,
InfConvert: cbor.InfConvertReject,
// Error on attempt to encode math/big.Int values, which can't be faithfully
// roundtripped through Unstructured in general (the dynamic numeric types allowed
// in Unstructured are limited to float64 and int64).
BigIntConvert: cbor.BigIntConvertReject,
// 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,
// Marshal Go byte arrays to CBOR arrays of integers (as in JSON) instead of byte
// strings.
ByteArray: cbor.ByteArrayToArray,
// Marshal []byte to CBOR byte string enclosed in tag 22 (expected later base64
// encoding, https://www.rfc-editor.org/rfc/rfc8949.html#section-3.4.5.2), to
// interoperate with the existing JSON behavior. This indicates to the decoder that,
// when decoding into a string (or unstructured), the resulting value should be the
// base64 encoding of the original bytes. No base64 encoding or decoding needs to be
// performed for []byte-to-CBOR-to-[]byte roundtrips.
ByteSliceLaterFormat: cbor.ByteSliceLaterFormatBase64,
// Disable default recognition of types implementing encoding.BinaryMarshaler, which
// is not recognized for JSON encoding.
BinaryMarshaler: cbor.BinaryMarshalerNone,
// Unmarshal into types that implement encoding.TextUnmarshaler by passing
// the contents of a CBOR string to their UnmarshalText method.
TextMarshaler: cbor.TextMarshalerTextString,
}.UserBufferEncMode()
if err != nil {
panic(err)
}
return encode
}(),
}
var Encode = EncMode{
delegate: func() cbor.UserBufferEncMode {
opts := encode.options()
// To encode a value of a type that implements json.Marshaler (and does not
// implement cbor.Marshaler), transcode the result of calling its MarshalJSON method
// directly to CBOR.
opts.JSONMarshalerTranscoder = TranscodeFunc(TranscodeFromJSON)
em, err := opts.UserBufferEncMode()
if err != nil {
panic(err)
}
return em
}(),
}
var EncodeNondeterministic = EncMode{
delegate: func() cbor.UserBufferEncMode {
opts := Encode.options()
opts.Sort = cbor.SortFastShuffle
em, err := opts.UserBufferEncMode()
if err != nil {
panic(err)
}
return em
}(),
}
type EncMode struct {
delegate cbor.UserBufferEncMode
}
func (em EncMode) options() cbor.EncOptions {
return em.delegate.EncOptions()
}
func (em EncMode) MarshalTo(v interface{}, w io.Writer) error {
if buf, ok := w.(*buffer); ok {
return em.delegate.MarshalToBuffer(v, &buf.Buffer)
}
buf := buffers.Get()
defer buffers.Put(buf)
if err := em.delegate.MarshalToBuffer(v, &buf.Buffer); err != nil {
return err
}
if _, err := io.Copy(w, buf); err != nil {
return err
}
return nil
}
func (em EncMode) Marshal(v interface{}) ([]byte, error) {
buf := buffers.Get()
defer buffers.Put(buf)
if err := em.MarshalTo(v, &buf.Buffer); err != nil {
return nil, err
}
clone := make([]byte, buf.Len())
copy(clone, buf.Bytes())
return clone, nil
}

View file

@ -0,0 +1,108 @@
/*
Copyright 2025 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 (
"encoding/json"
"errors"
"io"
kjson "sigs.k8s.io/json"
)
type TranscodeFunc func(dst io.Writer, src io.Reader) error
func (f TranscodeFunc) Transcode(dst io.Writer, src io.Reader) error {
return f(dst, src)
}
func TranscodeFromJSON(dst io.Writer, src io.Reader) error {
var tmp any
dec := kjson.NewDecoderCaseSensitivePreserveInts(src)
if err := dec.Decode(&tmp); err != nil {
return err
}
if err := dec.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
return errors.New("extraneous data")
}
return encode.MarshalTo(tmp, dst)
}
func TranscodeToJSON(dst io.Writer, src io.Reader) error {
var tmp any
dec := decode.NewDecoder(src)
if err := dec.Decode(&tmp); err != nil {
return err
}
if err := dec.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
return errors.New("extraneous data")
}
// Use an Encoder to avoid the extra []byte allocated by Marshal. Encode, unlike Marshal,
// appends a trailing newline to separate consecutive encodings of JSON values that aren't
// self-delimiting, like numbers. Strip the newline to avoid the assumption that every
// json.Unmarshaler implementation will accept trailing whitespace.
enc := json.NewEncoder(&trailingLinefeedSuppressor{delegate: dst})
enc.SetIndent("", "")
return enc.Encode(tmp)
}
// trailingLinefeedSuppressor is an io.Writer that wraps another io.Writer, suppressing a single
// trailing linefeed if it is the last byte written by the latest call to Write.
type trailingLinefeedSuppressor struct {
lf bool
delegate io.Writer
}
func (w *trailingLinefeedSuppressor) Write(p []byte) (int, error) {
if len(p) == 0 {
// Avoid flushing a buffered linefeeds on an empty write.
return 0, nil
}
if w.lf {
// The previous write had a trailing linefeed that was buffered. That wasn't the
// last Write call, so flush the buffered linefeed before continuing.
n, err := w.delegate.Write([]byte{'\n'})
if n > 0 {
w.lf = false
}
if err != nil {
return 0, err
}
}
if p[len(p)-1] != '\n' {
return w.delegate.Write(p)
}
p = p[:len(p)-1]
if len(p) == 0 { // []byte{'\n'}
w.lf = true
return 1, nil
}
n, err := w.delegate.Write(p)
if n == len(p) {
// Everything up to the trailing linefeed has been flushed. Eat the linefeed.
w.lf = true
n++
}
return n, err
}

View file

@ -0,0 +1,236 @@
/*
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 cbor
import (
"fmt"
"reflect"
"sync"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
var sharedTranscoders transcoders
var rawTypeTranscodeFuncs = map[reflect.Type]func(reflect.Value) error{
reflect.TypeFor[runtime.RawExtension](): func(rv reflect.Value) error {
if !rv.CanAddr() {
return nil
}
re := rv.Addr().Interface().(*runtime.RawExtension)
if re.Raw == nil {
// When Raw is nil it encodes to null. Don't change nil Raw values during
// transcoding, they would have unmarshalled from JSON as nil too.
return nil
}
j, err := re.MarshalJSON()
if err != nil {
return fmt.Errorf("failed to transcode RawExtension to JSON: %w", err)
}
re.Raw = j
return nil
},
reflect.TypeFor[metav1.FieldsV1](): func(rv reflect.Value) error {
if !rv.CanAddr() {
return nil
}
fields := rv.Addr().Interface().(*metav1.FieldsV1)
if fields.Raw == nil {
// When Raw is nil it encodes to null. Don't change nil Raw values during
// transcoding, they would have unmarshalled from JSON as nil too.
return nil
}
j, err := fields.MarshalJSON()
if err != nil {
return fmt.Errorf("failed to transcode FieldsV1 to JSON: %w", err)
}
fields.Raw = j
return nil
},
}
func transcodeRawTypes(v interface{}) error {
if v == nil {
return nil
}
rv := reflect.ValueOf(v)
return sharedTranscoders.getTranscoder(rv.Type()).fn(rv)
}
type transcoder struct {
fn func(rv reflect.Value) error
}
var noop = transcoder{
fn: func(reflect.Value) error {
return nil
},
}
type transcoders struct {
lock sync.RWMutex
m map[reflect.Type]**transcoder
}
func (ts *transcoders) getTranscoder(rt reflect.Type) transcoder {
ts.lock.RLock()
tpp, ok := ts.m[rt]
ts.lock.RUnlock()
if ok {
return **tpp
}
ts.lock.Lock()
defer ts.lock.Unlock()
tp := ts.getTranscoderLocked(rt)
return *tp
}
func (ts *transcoders) getTranscoderLocked(rt reflect.Type) *transcoder {
if tpp, ok := ts.m[rt]; ok {
// A transcoder for this type was cached while waiting to acquire the lock.
return *tpp
}
// Cache the transcoder now, before populating fn, so that circular references between types
// don't overflow the call stack.
t := new(transcoder)
if ts.m == nil {
ts.m = make(map[reflect.Type]**transcoder)
}
ts.m[rt] = &t
for rawType, fn := range rawTypeTranscodeFuncs {
if rt == rawType {
t = &transcoder{fn: fn}
return t
}
}
switch rt.Kind() {
case reflect.Array:
te := ts.getTranscoderLocked(rt.Elem())
rtlen := rt.Len()
if rtlen == 0 || te == &noop {
t = &noop
break
}
t.fn = func(rv reflect.Value) error {
for i := 0; i < rtlen; i++ {
if err := te.fn(rv.Index(i)); err != nil {
return err
}
}
return nil
}
case reflect.Interface:
// Any interface value might have a dynamic type involving RawExtension. It needs to
// be checked.
t.fn = func(rv reflect.Value) error {
if rv.IsNil() {
return nil
}
rv = rv.Elem()
// The interface element's type is dynamic so its transcoder can't be
// determined statically.
return ts.getTranscoder(rv.Type()).fn(rv)
}
case reflect.Map:
rtk := rt.Key()
tk := ts.getTranscoderLocked(rtk)
rte := rt.Elem()
te := ts.getTranscoderLocked(rte)
if tk == &noop && te == &noop {
t = &noop
break
}
t.fn = func(rv reflect.Value) error {
iter := rv.MapRange()
rvk := reflect.New(rtk).Elem()
rve := reflect.New(rte).Elem()
for iter.Next() {
rvk.SetIterKey(iter)
if err := tk.fn(rvk); err != nil {
return err
}
rve.SetIterValue(iter)
if err := te.fn(rve); err != nil {
return err
}
}
return nil
}
case reflect.Pointer:
te := ts.getTranscoderLocked(rt.Elem())
if te == &noop {
t = &noop
break
}
t.fn = func(rv reflect.Value) error {
if rv.IsNil() {
return nil
}
return te.fn(rv.Elem())
}
case reflect.Slice:
te := ts.getTranscoderLocked(rt.Elem())
if te == &noop {
t = &noop
break
}
t.fn = func(rv reflect.Value) error {
for i := 0; i < rv.Len(); i++ {
if err := te.fn(rv.Index(i)); err != nil {
return err
}
}
return nil
}
case reflect.Struct:
type fieldTranscoder struct {
Index int
Transcoder *transcoder
}
var fieldTranscoders []fieldTranscoder
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
tf := ts.getTranscoderLocked(f.Type)
if tf == &noop {
continue
}
fieldTranscoders = append(fieldTranscoders, fieldTranscoder{Index: i, Transcoder: tf})
}
if len(fieldTranscoders) == 0 {
t = &noop
break
}
t.fn = func(rv reflect.Value) error {
for _, ft := range fieldTranscoders {
if err := ft.Transcoder.fn(rv.Field(ft.Index)); err != nil {
return err
}
}
return nil
}
default:
t = &noop
}
return t
}