...

Source file src/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/appendixa_test.go

Documentation: k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes

     1  /*
     2  Copyright 2024 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package modes_test
    18  
    19  import (
    20  	"encoding/hex"
    21  	"fmt"
    22  	"math"
    23  	"testing"
    24  
    25  	"k8s.io/apimachinery/pkg/conversion"
    26  	"k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  )
    30  
    31  // TestAppendixA roundtrips the examples of encoded CBOR data items in RFC 8949 Appendix A. For
    32  // completeness, all examples from the appendix are included, even those those that are rejected by
    33  // this decoder or are re-encoded to a different sequence of CBOR bytes (with explanation).
    34  func TestAppendixA(t *testing.T) {
    35  	hex := func(h string) []byte {
    36  		b, err := hex.DecodeString(h)
    37  		if err != nil {
    38  			t.Fatal(err)
    39  		}
    40  		return b
    41  	}
    42  
    43  	eq := conversion.EqualitiesOrDie(
    44  		// NaN float64 values are always inequal and have multiple representations. RFC 8949
    45  		// Section 4.2.2 recommends protocols not supporting NaN payloads or signaling NaNs
    46  		// choose a single representation for all NaN values. For the purposes of this test,
    47  		// all NaN representations are equivalent.
    48  		func(a float64, b float64) bool {
    49  			if math.IsNaN(a) && math.IsNaN(b) {
    50  				return true
    51  			}
    52  			return math.Float64bits(a) == math.Float64bits(b)
    53  		},
    54  	)
    55  
    56  	const (
    57  		reasonArrayFixedLength  = "indefinite-length arrays are re-encoded with fixed length"
    58  		reasonByteString        = "strings are encoded as the byte string major type"
    59  		reasonFloatPacked       = "floats are packed into the smallest value-preserving width"
    60  		reasonNaN               = "all NaN values are represented with a single encoding"
    61  		reasonMapFixedLength    = "indefinite-length maps are re-encoded with fixed length"
    62  		reasonMapSorted         = "map entries are sorted"
    63  		reasonStringFixedLength = "indefinite-length strings are re-encoded with fixed length"
    64  		reasonTagIgnored        = "unrecognized tag numbers are ignored"
    65  		reasonTimeToInterface   = "times decode to interface{} as RFC3339 timestamps for JSON interoperability"
    66  	)
    67  
    68  	for _, tc := range []struct {
    69  		example []byte // example data item
    70  		decoded interface{}
    71  		reject  string   // reason the decoder rejects the example
    72  		encoded []byte   // re-encoded object (only if different from example encoding)
    73  		reasons []string // reasons for re-encode difference
    74  
    75  		// TODO: The cases with nonempty fixme are known to be not working and fixing them
    76  		// is an alpha criteria. They're present and skipped for visibility.
    77  		fixme string
    78  	}{
    79  		{
    80  			example: hex("00"),
    81  			decoded: int64(0),
    82  		},
    83  		{
    84  			example: hex("01"),
    85  			decoded: int64(1),
    86  		},
    87  		{
    88  			example: hex("0a"),
    89  			decoded: int64(10),
    90  		},
    91  		{
    92  			example: hex("17"),
    93  			decoded: int64(23),
    94  		},
    95  		{
    96  			example: hex("1818"),
    97  			decoded: int64(24),
    98  		},
    99  		{
   100  			example: hex("1819"),
   101  			decoded: int64(25),
   102  		},
   103  		{
   104  			example: hex("1864"),
   105  			decoded: int64(100),
   106  		},
   107  		{
   108  			example: hex("1903e8"),
   109  			decoded: int64(1000),
   110  		},
   111  		{
   112  			example: hex("1a000f4240"),
   113  			decoded: int64(1000000),
   114  		},
   115  		{
   116  			example: hex("1b000000e8d4a51000"),
   117  			decoded: int64(1000000000000),
   118  		},
   119  		{
   120  			example: hex("1bffffffffffffffff"),
   121  			reject:  "2^64-1 overflows int64 and falling back to float64 (as with JSON) loses distinction between float and integer",
   122  		},
   123  		{
   124  			example: hex("c249010000000000000000"),
   125  			reject:  "decoding tagged positive bigint value to interface{} can't reproduce this value without losing distinction between float and integer",
   126  			fixme:   "decoding bigint to interface{} must not produce math/big.Int",
   127  		},
   128  		{
   129  			example: hex("3bffffffffffffffff"),
   130  			reject:  "-2^64-1 overflows int64 and falling back to float64 (as with JSON) loses distinction between float and integer",
   131  		},
   132  		{
   133  			example: hex("c349010000000000000000"),
   134  			reject:  "-18446744073709551617 overflows int64 and falling back to float64 (as with JSON) loses distinction between float and integer",
   135  			fixme:   "decoding negative bigint to interface{} must not produce math/big.Int",
   136  		},
   137  		{
   138  			example: hex("20"),
   139  			decoded: int64(-1),
   140  		},
   141  		{
   142  			example: hex("29"),
   143  			decoded: int64(-10),
   144  		},
   145  		{
   146  			example: hex("3863"),
   147  			decoded: int64(-100),
   148  		},
   149  		{
   150  			example: hex("3903e7"),
   151  			decoded: int64(-1000),
   152  		},
   153  		{
   154  			example: hex("f90000"),
   155  			decoded: 0.0,
   156  		},
   157  		{
   158  			example: hex("f98000"),
   159  			decoded: math.Copysign(0, -1),
   160  		},
   161  		{
   162  			example: hex("f93c00"),
   163  			decoded: 1.0,
   164  		},
   165  		{
   166  			example: hex("fb3ff199999999999a"),
   167  			decoded: 1.1,
   168  		},
   169  		{
   170  			example: hex("f93e00"),
   171  			decoded: 1.5,
   172  		},
   173  		{
   174  			example: hex("f97bff"),
   175  			decoded: 65504.0,
   176  		},
   177  		{
   178  			example: hex("fa47c35000"),
   179  			decoded: 100000.0,
   180  		},
   181  		{
   182  			example: hex("fa7f7fffff"),
   183  			decoded: 3.4028234663852886e+38,
   184  		},
   185  		{
   186  			example: hex("fb7e37e43c8800759c"),
   187  			decoded: 1.0e+300,
   188  		},
   189  		{
   190  			example: hex("f90001"),
   191  			decoded: 5.960464477539063e-8,
   192  		},
   193  		{
   194  			example: hex("f90400"),
   195  			decoded: 0.00006103515625,
   196  		},
   197  		{
   198  			example: hex("f9c400"),
   199  			decoded: -4.0,
   200  		},
   201  		{
   202  			example: hex("fbc010666666666666"),
   203  			decoded: -4.1,
   204  		},
   205  		// TODO: Should Inf/-Inf/NaN be supported? Current Protobuf will encode this, but
   206  		// JSON will produce an error.  This is less than ideal -- we can't transcode
   207  		// everything to JSON.
   208  		{
   209  			example: hex("f97c00"),
   210  			decoded: math.Inf(1),
   211  		},
   212  		{
   213  			example: hex("f97e00"),
   214  			decoded: math.Float64frombits(0x7ff8000000000000),
   215  		},
   216  		{
   217  			example: hex("f9fc00"),
   218  			decoded: math.Inf(-1),
   219  		},
   220  		{
   221  			example: hex("fa7f800000"),
   222  			decoded: math.Inf(1),
   223  			encoded: hex("f97c00"),
   224  			reasons: []string{
   225  				reasonFloatPacked,
   226  			},
   227  		},
   228  		{
   229  			example: hex("fa7fc00000"),
   230  			decoded: math.NaN(),
   231  			encoded: hex("f97e00"),
   232  			reasons: []string{
   233  				reasonNaN,
   234  			},
   235  		},
   236  		{
   237  			example: hex("faff800000"),
   238  			decoded: math.Inf(-1),
   239  			encoded: hex("f9fc00"),
   240  			reasons: []string{
   241  				reasonFloatPacked,
   242  			},
   243  		},
   244  		{
   245  			example: hex("fb7ff0000000000000"),
   246  			decoded: math.Inf(1),
   247  			encoded: hex("f97c00"),
   248  			reasons: []string{
   249  				reasonFloatPacked,
   250  			},
   251  		},
   252  		{
   253  			example: hex("fb7ff8000000000000"),
   254  			decoded: math.NaN(),
   255  			encoded: hex("f97e00"),
   256  			reasons: []string{
   257  				reasonNaN,
   258  			},
   259  		},
   260  		{
   261  			example: hex("fbfff0000000000000"),
   262  			decoded: math.Inf(-1),
   263  			encoded: hex("f9fc00"),
   264  			reasons: []string{
   265  				reasonFloatPacked,
   266  			},
   267  		},
   268  		{
   269  			example: hex("f4"),
   270  			decoded: false,
   271  		},
   272  		{
   273  			example: hex("f5"),
   274  			decoded: true,
   275  		},
   276  		{
   277  			example: hex("f6"),
   278  			decoded: nil,
   279  		},
   280  		{
   281  			example: hex("f7"),
   282  			reject:  "only simple values false, true, and null have a clear analog",
   283  			fixme:   "the undefined simple value should not successfully decode as nil",
   284  		},
   285  		{
   286  			example: hex("f0"),
   287  			reject:  "only simple values false, true, and null have a clear analog",
   288  			fixme:   "simple values other than false, true, and null should be rejected",
   289  		},
   290  		{
   291  			example: hex("f8ff"),
   292  			reject:  "only simple values false, true, and null have a clear analog",
   293  			fixme:   "simple values other than false, true, and null should be rejected",
   294  		},
   295  		{
   296  			example: hex("c074323031332d30332d32315432303a30343a30305a"),
   297  			decoded: "2013-03-21T20:04:00Z",
   298  			encoded: hex("54323031332d30332d32315432303a30343a30305a"),
   299  			reasons: []string{
   300  				reasonByteString,
   301  				reasonTimeToInterface,
   302  			},
   303  			fixme: "decoding of tagged time into interface{} must produce RFC3339 timestamp compatible with JSON, not time.Time",
   304  		},
   305  		{
   306  			example: hex("c11a514b67b0"),
   307  			decoded: "2013-03-21T16:04:00Z",
   308  			encoded: hex("54323031332d30332d32315431363a30343a30305a"),
   309  			reasons: []string{
   310  				reasonByteString,
   311  				reasonTimeToInterface,
   312  			},
   313  			fixme: "decoding of tagged time into interface{} must produce RFC3339 timestamp compatible with JSON, not time.Time",
   314  		},
   315  		{
   316  			example: hex("c1fb41d452d9ec200000"),
   317  			decoded: "2013-03-21T20:04:00.5Z",
   318  			encoded: hex("56323031332d30332d32315432303a30343a30302e355a"),
   319  			reasons: []string{
   320  				reasonByteString,
   321  				reasonTimeToInterface,
   322  			},
   323  			fixme: "decoding of tagged time into interface{} must produce RFC3339 timestamp compatible with JSON, not time.Time",
   324  		},
   325  		{
   326  			example: hex("d74401020304"),
   327  			decoded: "\x01\x02\x03\x04",
   328  			encoded: hex("4401020304"),
   329  			reasons: []string{
   330  				reasonTagIgnored,
   331  			},
   332  		},
   333  		{
   334  			example: hex("d818456449455446"),
   335  			decoded: "dIETF",
   336  			encoded: hex("456449455446"),
   337  			reasons: []string{
   338  				reasonTagIgnored,
   339  			},
   340  		},
   341  		{
   342  			example: hex("d82076687474703a2f2f7777772e6578616d706c652e636f6d"),
   343  			decoded: "http://www.example.com",
   344  			encoded: hex("56687474703a2f2f7777772e6578616d706c652e636f6d"),
   345  			reasons: []string{
   346  				reasonByteString,
   347  				reasonTagIgnored,
   348  			},
   349  		},
   350  		{
   351  			example: hex("40"),
   352  			decoded: "",
   353  		},
   354  		{
   355  			example: hex("4401020304"),
   356  			decoded: "\x01\x02\x03\x04",
   357  		},
   358  		{
   359  			example: hex("60"),
   360  			decoded: "",
   361  			encoded: hex("40"),
   362  			reasons: []string{
   363  				reasonByteString,
   364  			},
   365  		},
   366  		{
   367  			example: hex("6161"),
   368  			decoded: "a",
   369  			encoded: hex("4161"),
   370  			reasons: []string{
   371  				reasonByteString,
   372  			},
   373  		},
   374  		{
   375  			example: hex("6449455446"),
   376  			decoded: "IETF",
   377  			encoded: hex("4449455446"),
   378  			reasons: []string{
   379  				reasonByteString,
   380  			},
   381  		},
   382  		{
   383  			example: hex("62225c"),
   384  			decoded: "\"\\",
   385  			encoded: hex("42225c"),
   386  			reasons: []string{
   387  				reasonByteString,
   388  			},
   389  		},
   390  		{
   391  			example: hex("62c3bc"),
   392  			decoded: "ü",
   393  			encoded: hex("42c3bc"),
   394  			reasons: []string{
   395  				reasonByteString,
   396  			},
   397  		},
   398  		{
   399  			example: hex("63e6b0b4"),
   400  			decoded: "水",
   401  			encoded: hex("43e6b0b4"),
   402  			reasons: []string{
   403  				reasonByteString,
   404  			},
   405  		},
   406  		{
   407  			example: hex("64f0908591"),
   408  			decoded: "𐅑",
   409  			encoded: hex("44f0908591"),
   410  			reasons: []string{
   411  				reasonByteString,
   412  			},
   413  		},
   414  		{
   415  			example: hex("80"),
   416  			decoded: []interface{}{},
   417  		},
   418  		{
   419  			example: hex("83010203"),
   420  			decoded: []interface{}{int64(1), int64(2), int64(3)},
   421  		},
   422  		{
   423  			example: hex("8301820203820405"),
   424  			decoded: []interface{}{int64(1), []interface{}{int64(2), int64(3)}, []interface{}{int64(4), int64(5)}},
   425  		},
   426  		{
   427  			example: hex("98190102030405060708090a0b0c0d0e0f101112131415161718181819"),
   428  			decoded: []interface{}{int64(1), int64(2), int64(3), int64(4), int64(5), int64(6), int64(7), int64(8), int64(9), int64(10), int64(11), int64(12), int64(13), int64(14), int64(15), int64(16), int64(17), int64(18), int64(19), int64(20), int64(21), int64(22), int64(23), int64(24), int64(25)},
   429  		},
   430  		{
   431  			example: hex("a0"),
   432  			decoded: map[string]interface{}{},
   433  		},
   434  		{
   435  			example: hex("a201020304"),
   436  			reject:  "integer map keys don't correspond with field names or unstructured keys",
   437  		},
   438  		{
   439  			example: hex("a26161016162820203"),
   440  			decoded: map[string]interface{}{
   441  				"a": int64(1),
   442  				"b": []interface{}{int64(2), int64(3)},
   443  			},
   444  			encoded: hex("a24161014162820203"),
   445  			reasons: []string{
   446  				reasonByteString,
   447  			},
   448  		},
   449  		{
   450  			example: hex("826161a161626163"),
   451  			decoded: []interface{}{
   452  				"a",
   453  				map[string]interface{}{"b": "c"},
   454  			},
   455  			encoded: hex("824161a141624163"),
   456  			reasons: []string{
   457  				reasonByteString,
   458  			},
   459  		},
   460  		{
   461  			example: hex("a56161614161626142616361436164614461656145"),
   462  			decoded: map[string]interface{}{
   463  				"a": "A",
   464  				"b": "B",
   465  				"c": "C",
   466  				"d": "D",
   467  				"e": "E",
   468  			},
   469  			encoded: hex("a54161414141624142416341434164414441654145"),
   470  			reasons: []string{
   471  				reasonByteString,
   472  			},
   473  		},
   474  		{
   475  			example: hex("5f42010243030405ff"),
   476  			decoded: "\x01\x02\x03\x04\x05",
   477  			encoded: hex("450102030405"),
   478  			reasons: []string{
   479  				reasonStringFixedLength,
   480  			},
   481  		},
   482  		{
   483  			example: hex("7f657374726561646d696e67ff"),
   484  			decoded: "streaming",
   485  			encoded: hex("4973747265616d696e67"),
   486  			reasons: []string{
   487  				reasonByteString,
   488  				reasonStringFixedLength,
   489  			},
   490  		},
   491  		{
   492  			example: hex("9fff"),
   493  			decoded: []interface{}{},
   494  			encoded: hex("80"),
   495  			reasons: []string{
   496  				reasonArrayFixedLength,
   497  			},
   498  		},
   499  		{
   500  			example: hex("9f018202039f0405ffff"),
   501  			decoded: []interface{}{
   502  				int64(1),
   503  				[]interface{}{int64(2), int64(3)},
   504  				[]interface{}{int64(4), int64(5)},
   505  			},
   506  			encoded: hex("8301820203820405"),
   507  			reasons: []string{
   508  				reasonArrayFixedLength,
   509  			},
   510  		},
   511  		{
   512  			example: hex("9f01820203820405ff"),
   513  			decoded: []interface{}{
   514  				int64(1),
   515  				[]interface{}{int64(2), int64(3)},
   516  				[]interface{}{int64(4), int64(5)},
   517  			},
   518  			encoded: hex("8301820203820405"),
   519  			reasons: []string{
   520  				reasonArrayFixedLength,
   521  			},
   522  		},
   523  		{
   524  			example: hex("83018202039f0405ff"),
   525  			decoded: []interface{}{
   526  				int64(1),
   527  				[]interface{}{int64(2), int64(3)},
   528  				[]interface{}{int64(4), int64(5)},
   529  			},
   530  			encoded: hex("8301820203820405"),
   531  			reasons: []string{
   532  				reasonArrayFixedLength,
   533  			},
   534  		},
   535  		{
   536  			example: hex("83019f0203ff820405"),
   537  			decoded: []interface{}{
   538  				int64(1),
   539  				[]interface{}{int64(2), int64(3)},
   540  				[]interface{}{int64(4), int64(5)},
   541  			},
   542  			encoded: hex("8301820203820405"),
   543  			reasons: []string{
   544  				reasonArrayFixedLength,
   545  			},
   546  		},
   547  		{
   548  			example: hex("9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff"),
   549  			decoded: []interface{}{
   550  				int64(1), int64(2), int64(3), int64(4), int64(5),
   551  				int64(6), int64(7), int64(8), int64(9), int64(10),
   552  				int64(11), int64(12), int64(13), int64(14), int64(15),
   553  				int64(16), int64(17), int64(18), int64(19), int64(20),
   554  				int64(21), int64(22), int64(23), int64(24), int64(25),
   555  			},
   556  			encoded: hex("98190102030405060708090a0b0c0d0e0f101112131415161718181819"),
   557  			reasons: []string{
   558  				reasonArrayFixedLength,
   559  			},
   560  		},
   561  		{
   562  			example: hex("bf61610161629f0203ffff"),
   563  			decoded: map[string]interface{}{
   564  				"a": int64(1),
   565  				"b": []interface{}{int64(2), int64(3)},
   566  			},
   567  			encoded: hex("a24161014162820203"),
   568  			reasons: []string{
   569  				reasonArrayFixedLength,
   570  				reasonByteString,
   571  				reasonMapFixedLength,
   572  			},
   573  		},
   574  		{
   575  			example: hex("826161bf61626163ff"),
   576  			decoded: []interface{}{"a", map[string]interface{}{"b": "c"}},
   577  			encoded: hex("824161a141624163"),
   578  			reasons: []string{
   579  				reasonByteString,
   580  				reasonMapFixedLength,
   581  			},
   582  		},
   583  		{
   584  			example: hex("bf6346756ef563416d7421ff"),
   585  			decoded: map[string]interface{}{
   586  				"Amt": int64(-2),
   587  				"Fun": true,
   588  			},
   589  			encoded: hex("a243416d74214346756ef5"),
   590  			reasons: []string{
   591  				reasonByteString,
   592  				reasonMapFixedLength,
   593  				reasonMapSorted,
   594  			},
   595  		},
   596  	} {
   597  		t.Run(fmt.Sprintf("%x", tc.example), func(t *testing.T) {
   598  			if tc.fixme != "" {
   599  				t.Skip(tc.fixme) // TODO: Remove once all cases are fixed.
   600  			}
   601  
   602  			var decoded interface{}
   603  			err := modes.Decode.Unmarshal(tc.example, &decoded)
   604  			if err != nil {
   605  				if tc.reject != "" {
   606  					t.Logf("expected decode error (%s) occurred: %v", tc.reject, err)
   607  					return
   608  				}
   609  				t.Fatalf("unexpected decode error: %v", err)
   610  			} else if tc.reject != "" {
   611  				t.Fatalf("expected decode error (%v) did not occur", tc.reject)
   612  			}
   613  
   614  			if !eq.DeepEqual(tc.decoded, decoded) {
   615  				t.Fatal(cmp.Diff(tc.decoded, decoded))
   616  			}
   617  
   618  			actual, err := modes.Encode.Marshal(decoded)
   619  			if err != nil {
   620  				t.Fatal(err)
   621  			}
   622  
   623  			expected := tc.example
   624  			if tc.encoded != nil {
   625  				expected = tc.encoded
   626  				if len(tc.reasons) == 0 {
   627  					t.Fatal("invalid test case: missing reasons for difference between the example encoding and the actual encoding")
   628  				}
   629  				diff := cmp.Diff(tc.example, tc.encoded)
   630  				if diff == "" {
   631  					t.Fatal("invalid test case: no difference between the example encoding and the expected re-encoding")
   632  				}
   633  				t.Logf("expecting the following differences from the example encoding on re-encode:\n%s", diff)
   634  				t.Logf("reasons for encoding differences:")
   635  				for _, reason := range tc.reasons {
   636  					t.Logf("- %s", reason)
   637  				}
   638  
   639  			}
   640  
   641  			if diff := cmp.Diff(expected, actual); diff != "" {
   642  				t.Errorf("re-encoded object differs from expected:\n%s", diff)
   643  			}
   644  		})
   645  	}
   646  }
   647  

View as plain text