kubernetes/vendor/github.com/fxamacker/cbor/v2/diagnose.go
2024-02-14 10:09:15 -05:00

742 lines
18 KiB
Go

// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
package cbor
import (
"bytes"
"encoding/base32"
"encoding/base64"
"encoding/hex"
"errors"
"io"
"math"
"math/big"
"strconv"
"unicode/utf16"
"unicode/utf8"
"github.com/x448/float16"
)
// DiagMode is the main interface for CBOR diagnostic notation.
type DiagMode interface {
// Diagnose returns extended diagnostic notation (EDN) of CBOR data items using this DiagMode.
Diagnose([]byte) (string, error)
// DiagnoseFirst returns extended diagnostic notation (EDN) of the first CBOR data item using the DiagMode. Any remaining bytes are returned in rest.
DiagnoseFirst([]byte) (string, []byte, error)
// DiagOptions returns user specified options used to create this DiagMode.
DiagOptions() DiagOptions
}
// ByteStringEncoding specifies the base encoding that byte strings are notated.
type ByteStringEncoding uint8
const (
// ByteStringBase16Encoding encodes byte strings in base16, without padding.
ByteStringBase16Encoding ByteStringEncoding = iota
// ByteStringBase32Encoding encodes byte strings in base32, without padding.
ByteStringBase32Encoding
// ByteStringBase32HexEncoding encodes byte strings in base32hex, without padding.
ByteStringBase32HexEncoding
// ByteStringBase64Encoding encodes byte strings in base64url, without padding.
ByteStringBase64Encoding
maxByteStringEncoding
)
func (bse ByteStringEncoding) valid() error {
if bse >= maxByteStringEncoding {
return errors.New("cbor: invalid ByteStringEncoding " + strconv.Itoa(int(bse)))
}
return nil
}
// DiagOptions specifies Diag options.
type DiagOptions struct {
// ByteStringEncoding specifies the base encoding that byte strings are notated.
// Default is ByteStringBase16Encoding.
ByteStringEncoding ByteStringEncoding
// ByteStringHexWhitespace specifies notating with whitespace in byte string
// when ByteStringEncoding is ByteStringBase16Encoding.
ByteStringHexWhitespace bool
// ByteStringText specifies notating with text in byte string
// if it is a valid UTF-8 text.
ByteStringText bool
// ByteStringEmbeddedCBOR specifies notating embedded CBOR in byte string
// if it is a valid CBOR bytes.
ByteStringEmbeddedCBOR bool
// CBORSequence specifies notating CBOR sequences.
// otherwise, it returns an error if there are more bytes after the first CBOR.
CBORSequence bool
// FloatPrecisionIndicator specifies appending a suffix to indicate float precision.
// Refer to https://www.rfc-editor.org/rfc/rfc8949.html#name-encoding-indicators.
FloatPrecisionIndicator bool
// MaxNestedLevels specifies the max nested levels allowed for any combination of CBOR array, maps, and tags.
// Default is 32 levels and it can be set to [4, 65535]. Note that higher maximum levels of nesting can
// require larger amounts of stack to deserialize. Don't increase this higher than you require.
MaxNestedLevels int
// MaxArrayElements specifies the max number of elements for CBOR arrays.
// Default is 128*1024=131072 and it can be set to [16, 2147483647]
MaxArrayElements int
// MaxMapPairs specifies the max number of key-value pairs for CBOR maps.
// Default is 128*1024=131072 and it can be set to [16, 2147483647]
MaxMapPairs int
}
// DiagMode returns a DiagMode with immutable options.
func (opts DiagOptions) DiagMode() (DiagMode, error) {
return opts.diagMode()
}
func (opts DiagOptions) diagMode() (*diagMode, error) {
if err := opts.ByteStringEncoding.valid(); err != nil {
return nil, err
}
decMode, err := DecOptions{
MaxNestedLevels: opts.MaxNestedLevels,
MaxArrayElements: opts.MaxArrayElements,
MaxMapPairs: opts.MaxMapPairs,
}.decMode()
if err != nil {
return nil, err
}
return &diagMode{
byteStringEncoding: opts.ByteStringEncoding,
byteStringHexWhitespace: opts.ByteStringHexWhitespace,
byteStringText: opts.ByteStringText,
byteStringEmbeddedCBOR: opts.ByteStringEmbeddedCBOR,
cborSequence: opts.CBORSequence,
floatPrecisionIndicator: opts.FloatPrecisionIndicator,
decMode: decMode,
}, nil
}
type diagMode struct {
byteStringEncoding ByteStringEncoding
byteStringHexWhitespace bool
byteStringText bool
byteStringEmbeddedCBOR bool
cborSequence bool
floatPrecisionIndicator bool
decMode *decMode
}
// DiagOptions returns user specified options used to create this DiagMode.
func (dm *diagMode) DiagOptions() DiagOptions {
return DiagOptions{
ByteStringEncoding: dm.byteStringEncoding,
ByteStringHexWhitespace: dm.byteStringHexWhitespace,
ByteStringText: dm.byteStringText,
ByteStringEmbeddedCBOR: dm.byteStringEmbeddedCBOR,
CBORSequence: dm.cborSequence,
FloatPrecisionIndicator: dm.floatPrecisionIndicator,
MaxNestedLevels: dm.decMode.maxNestedLevels,
MaxArrayElements: dm.decMode.maxArrayElements,
MaxMapPairs: dm.decMode.maxMapPairs,
}
}
// Diagnose returns extended diagnostic notation (EDN) of CBOR data items using the DiagMode.
func (dm *diagMode) Diagnose(data []byte) (string, error) {
return newDiagnose(data, dm.decMode, dm).diag(dm.cborSequence)
}
// DiagnoseFirst returns extended diagnostic notation (EDN) of the first CBOR data item using the DiagMode. Any remaining bytes are returned in rest.
func (dm *diagMode) DiagnoseFirst(data []byte) (string, []byte, error) {
return newDiagnose(data, dm.decMode, dm).diagFirst()
}
var defaultDiagMode, _ = DiagOptions{}.diagMode()
// Diagnose returns extended diagnostic notation (EDN) of CBOR data items
// using the default diagnostic mode.
//
// Refer to https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation.
func Diagnose(data []byte) (string, error) {
return defaultDiagMode.Diagnose(data)
}
// Diagnose returns extended diagnostic notation (EDN) of the first CBOR data item using the DiagMode. Any remaining bytes are returned in rest.
func DiagnoseFirst(data []byte) (string, []byte, error) {
return defaultDiagMode.DiagnoseFirst(data)
}
type diagnose struct {
dm *diagMode
d *decoder
w *bytes.Buffer
}
func newDiagnose(data []byte, decm *decMode, diagm *diagMode) *diagnose {
return &diagnose{
dm: diagm,
d: &decoder{data: data, dm: decm},
w: &bytes.Buffer{},
}
}
func (di *diagnose) diag(cborSequence bool) (string, error) {
// CBOR Sequence
firstItem := true
for {
switch err := di.wellformed(cborSequence); err {
case nil:
if !firstItem {
if err = di.writeString(", "); err != nil {
return di.w.String(), err
}
}
firstItem = false
if err = di.item(); err != nil {
return di.w.String(), err
}
case io.EOF:
if firstItem {
return di.w.String(), err
}
return di.w.String(), nil
default:
return di.w.String(), err
}
}
}
func (di *diagnose) diagFirst() (string, []byte, error) {
err := di.wellformed(true)
if err == nil {
err = di.item()
}
if err == nil {
// Return EDN and the rest of the data slice (which might be len 0)
return di.w.String(), di.d.data[di.d.off:], nil
}
return di.w.String(), nil, err
}
func (di *diagnose) wellformed(allowExtraData bool) error {
off := di.d.off
err := di.d.wellformed(allowExtraData)
di.d.off = off
return err
}
func (di *diagnose) item() error { //nolint:gocyclo
initialByte := di.d.data[di.d.off]
switch initialByte {
case 0x5f, 0x7f: // indefinite-length byte/text string
di.d.off++
if di.d.data[di.d.off] == 0xff {
di.d.off++
switch initialByte {
case 0x5f:
// indefinite-length bytes with no chunks.
return di.writeString(`''_`)
case 0x7f:
// indefinite-length text with no chunks.
return di.writeString(`""_`)
}
}
if err := di.writeString("(_ "); err != nil {
return err
}
i := 0
for !di.d.foundBreak() {
if i > 0 {
if err := di.writeString(", "); err != nil {
return err
}
}
i++
// wellformedIndefiniteString() already checked that the next item is a byte/text string.
if err := di.item(); err != nil {
return err
}
}
return di.writeByte(')')
case 0x9f: // indefinite-length array
di.d.off++
if err := di.writeString("[_ "); err != nil {
return err
}
i := 0
for !di.d.foundBreak() {
if i > 0 {
if err := di.writeString(", "); err != nil {
return err
}
}
i++
if err := di.item(); err != nil {
return err
}
}
return di.writeByte(']')
case 0xbf: // indefinite-length map
di.d.off++
if err := di.writeString("{_ "); err != nil {
return err
}
i := 0
for !di.d.foundBreak() {
if i > 0 {
if err := di.writeString(", "); err != nil {
return err
}
}
i++
// key
if err := di.item(); err != nil {
return err
}
if err := di.writeString(": "); err != nil {
return err
}
// value
if err := di.item(); err != nil {
return err
}
}
return di.writeByte('}')
}
t := di.d.nextCBORType()
switch t {
case cborTypePositiveInt:
_, _, val := di.d.getHead()
return di.writeString(strconv.FormatUint(val, 10))
case cborTypeNegativeInt:
_, _, val := di.d.getHead()
if val > math.MaxInt64 {
// CBOR negative integer overflows int64, use big.Int to store value.
bi := new(big.Int)
bi.SetUint64(val)
bi.Add(bi, big.NewInt(1))
bi.Neg(bi)
return di.writeString(bi.String())
}
nValue := int64(-1) ^ int64(val)
return di.writeString(strconv.FormatInt(nValue, 10))
case cborTypeByteString:
b, _ := di.d.parseByteString()
return di.encodeByteString(b)
case cborTypeTextString:
b, err := di.d.parseTextString()
if err != nil {
return err
}
return di.encodeTextString(string(b), '"')
case cborTypeArray:
_, _, val := di.d.getHead()
count := int(val)
if err := di.writeByte('['); err != nil {
return err
}
for i := 0; i < count; i++ {
if i > 0 {
if err := di.writeString(", "); err != nil {
return err
}
}
if err := di.item(); err != nil {
return err
}
}
return di.writeByte(']')
case cborTypeMap:
_, _, val := di.d.getHead()
count := int(val)
if err := di.writeByte('{'); err != nil {
return err
}
for i := 0; i < count; i++ {
if i > 0 {
if err := di.writeString(", "); err != nil {
return err
}
}
// key
if err := di.item(); err != nil {
return err
}
if err := di.writeString(": "); err != nil {
return err
}
// value
if err := di.item(); err != nil {
return err
}
}
return di.writeByte('}')
case cborTypeTag:
_, _, tagNum := di.d.getHead()
switch tagNum {
case 2:
if nt := di.d.nextCBORType(); nt != cborTypeByteString {
return errors.New("cbor: tag number 2 must be followed by byte string, got " + nt.String())
}
b, _ := di.d.parseByteString()
bi := new(big.Int).SetBytes(b)
return di.writeString(bi.String())
case 3:
if nt := di.d.nextCBORType(); nt != cborTypeByteString {
return errors.New("cbor: tag number 3 must be followed by byte string, got " + nt.String())
}
b, _ := di.d.parseByteString()
bi := new(big.Int).SetBytes(b)
bi.Add(bi, big.NewInt(1))
bi.Neg(bi)
return di.writeString(bi.String())
default:
if err := di.writeString(strconv.FormatUint(tagNum, 10)); err != nil {
return err
}
if err := di.writeByte('('); err != nil {
return err
}
if err := di.item(); err != nil {
return err
}
return di.writeByte(')')
}
case cborTypePrimitives:
_, ai, val := di.d.getHead()
switch ai {
case 20:
return di.writeString("false")
case 21:
return di.writeString("true")
case 22:
return di.writeString("null")
case 23:
return di.writeString("undefined")
case 25, 26, 27:
return di.encodeFloat(ai, val)
default:
if err := di.writeString("simple("); err != nil {
return err
}
if err := di.writeString(strconv.FormatUint(val, 10)); err != nil {
return err
}
return di.writeByte(')')
}
}
return nil
}
func (di *diagnose) writeByte(val byte) error {
return di.w.WriteByte(val)
}
func (di *diagnose) writeString(val string) error {
_, err := di.w.WriteString(val)
return err
}
// writeU16 format a rune as "\uxxxx"
func (di *diagnose) writeU16(val rune) error {
if err := di.writeString("\\u"); err != nil {
return err
}
b := make([]byte, 2)
b[0] = byte(val >> 8)
b[1] = byte(val)
return di.writeString(hex.EncodeToString(b))
}
var rawBase32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
var rawBase32HexEncoding = base32.HexEncoding.WithPadding(base32.NoPadding)
func (di *diagnose) encodeByteString(val []byte) error {
if len(val) > 0 {
if di.dm.byteStringText && utf8.Valid(val) {
return di.encodeTextString(string(val), '\'')
}
if di.dm.byteStringEmbeddedCBOR {
di2 := newDiagnose(val, di.dm.decMode, di.dm)
// should always notating embedded CBOR sequence.
if str, err := di2.diag(true); err == nil {
if err := di.writeString("<<"); err != nil {
return err
}
if err := di.writeString(str); err != nil {
return err
}
return di.writeString(">>")
}
}
}
switch di.dm.byteStringEncoding {
case ByteStringBase16Encoding:
if err := di.writeString("h'"); err != nil {
return err
}
encoder := hex.NewEncoder(di.w)
if di.dm.byteStringHexWhitespace {
for i, b := range val {
if i > 0 {
if err := di.writeByte(' '); err != nil {
return err
}
}
if _, err := encoder.Write([]byte{b}); err != nil {
return err
}
}
} else {
if _, err := encoder.Write(val); err != nil {
return err
}
}
return di.writeByte('\'')
case ByteStringBase32Encoding:
if err := di.writeString("b32'"); err != nil {
return err
}
encoder := base32.NewEncoder(rawBase32Encoding, di.w)
if _, err := encoder.Write(val); err != nil {
return err
}
encoder.Close()
return di.writeByte('\'')
case ByteStringBase32HexEncoding:
if err := di.writeString("h32'"); err != nil {
return err
}
encoder := base32.NewEncoder(rawBase32HexEncoding, di.w)
if _, err := encoder.Write(val); err != nil {
return err
}
encoder.Close()
return di.writeByte('\'')
case ByteStringBase64Encoding:
if err := di.writeString("b64'"); err != nil {
return err
}
encoder := base64.NewEncoder(base64.RawURLEncoding, di.w)
if _, err := encoder.Write(val); err != nil {
return err
}
encoder.Close()
return di.writeByte('\'')
default:
return di.dm.byteStringEncoding.valid()
}
}
var utf16SurrSelf = rune(0x10000)
// quote should be either `'` or `"`
func (di *diagnose) encodeTextString(val string, quote byte) error {
if err := di.writeByte(quote); err != nil {
return err
}
for i := 0; i < len(val); {
if b := val[i]; b < utf8.RuneSelf {
switch {
case b == '\t', b == '\n', b == '\r', b == '\\', b == quote:
if err := di.writeByte('\\'); err != nil {
return err
}
switch b {
case '\t':
b = 't'
case '\n':
b = 'n'
case '\r':
b = 'r'
}
if err := di.writeByte(b); err != nil {
return err
}
case b >= ' ' && b <= '~':
if err := di.writeByte(b); err != nil {
return err
}
default:
if err := di.writeU16(rune(b)); err != nil {
return err
}
}
i++
continue
}
c, size := utf8.DecodeRuneInString(val[i:])
switch {
case c == utf8.RuneError:
// if err := di.writeU16(rune(val[i])); err != nil {
// return err
// }
return &SemanticError{"cbor: invalid UTF-8 string"}
case c < utf16SurrSelf:
if err := di.writeU16(c); err != nil {
return err
}
default:
c1, c2 := utf16.EncodeRune(c)
if err := di.writeU16(c1); err != nil {
return err
}
if err := di.writeU16(c2); err != nil {
return err
}
}
i += size
}
return di.writeByte(quote)
}
func (di *diagnose) encodeFloat(ai byte, val uint64) error {
f64 := float64(0)
switch ai {
case 25:
f16 := float16.Frombits(uint16(val))
switch {
case f16.IsNaN():
return di.writeString("NaN")
case f16.IsInf(1):
return di.writeString("Infinity")
case f16.IsInf(-1):
return di.writeString("-Infinity")
default:
f64 = float64(f16.Float32())
}
case 26:
f32 := math.Float32frombits(uint32(val))
switch {
case f32 != f32:
return di.writeString("NaN")
case f32 > math.MaxFloat32:
return di.writeString("Infinity")
case f32 < -math.MaxFloat32:
return di.writeString("-Infinity")
default:
f64 = float64(f32)
}
case 27:
f64 = math.Float64frombits(val)
switch {
case f64 != f64:
return di.writeString("NaN")
case f64 > math.MaxFloat64:
return di.writeString("Infinity")
case f64 < -math.MaxFloat64:
return di.writeString("-Infinity")
}
}
// Use ES6 number to string conversion which should match most JSON generators.
// Inspired by https://github.com/golang/go/blob/4df10fba1687a6d4f51d7238a403f8f2298f6a16/src/encoding/json/encode.go#L585
b := make([]byte, 0, 32)
if abs := math.Abs(f64); abs != 0 && (abs < 1e-6 || abs >= 1e21) {
b = strconv.AppendFloat(b, f64, 'e', -1, 64)
// clean up e-09 to e-9
n := len(b)
if n >= 4 && string(b[n-4:n-1]) == "e-0" {
b = append(b[:n-2], b[n-1])
}
} else {
b = strconv.AppendFloat(b, f64, 'f', -1, 64)
}
// add decimal point and trailing zero if needed
if bytes.IndexByte(b, '.') < 0 {
if i := bytes.IndexByte(b, 'e'); i < 0 {
b = append(b, '.', '0')
} else {
b = append(b[:i+2], b[i:]...)
b[i] = '.'
b[i+1] = '0'
}
}
if err := di.writeString(string(b)); err != nil {
return err
}
if di.dm.floatPrecisionIndicator {
switch ai {
case 25:
return di.writeString("_1")
case 26:
return di.writeString("_2")
case 27:
return di.writeString("_3")
}
}
return nil
}