diff --git a/cache.go b/cache.go index 8b4d463..ea0f39e 100644 --- a/cache.go +++ b/cache.go @@ -276,7 +276,8 @@ func getEncodingStructType(t reflect.Type) (*encodingStructType, error) { copy(flds[i].cborNameByteString, flds[i].cborName) // Reset encoded CBOR type to byte string, preserving the "additional // information" bits: - flds[i].cborNameByteString[0] = byte(cborTypeByteString) | (flds[i].cborNameByteString[0] & 0x1f) + flds[i].cborNameByteString[0] = byte(cborTypeByteString) | + getAdditionalInformation(flds[i].cborNameByteString[0]) hasKeyAsStr = true } diff --git a/decode.go b/decode.go index 96003cb..4760d69 100644 --- a/decode.go +++ b/decode.go @@ -1311,6 +1311,39 @@ func (t cborType) String() string { } } +const ( + // From RFC 8949.3: + // The initial byte of each encoded data item contains both information about the major type + // (the high-order 3 bits, described in Section 3.1) and additional information + // (the low-order 5 bits). + + // typeMask is used to extract major type in initial byte of encoded data item. + typeMask = 0xe0 + + // additionalInformationMask is used to extract additional information in initial byte of encoded data item. + additionalInformationMask = 0x1f +) + +func getType(b byte) cborType { + return cborType(b & typeMask) +} + +func getAdditionalInformation(b byte) byte { + return b & additionalInformationMask +} + +func parseInitialByte(b byte) (t cborType, ai byte) { + return getType(b), getAdditionalInformation(b) +} + +const ( + maxAdditionalInformationWithoutArgument = 23 + additionalInformationWith1ByteArgument = 24 + additionalInformationWith2ByteArgument = 25 + additionalInformationWith4ByteArgument = 26 + additionalInformationWith8ByteArgument = 27 +) + const ( selfDescribedCBORTagNum = 55799 expectedLaterEncodingBase64URLTagNum = 21 @@ -2791,32 +2824,38 @@ func (d *decoder) skip() { // getHead assumes data is well-formed, and does not perform bounds checking. func (d *decoder) getHead() (t cborType, ai byte, val uint64) { - t = cborType(d.data[d.off] & 0xe0) - ai = d.data[d.off] & 0x1f + t, ai = parseInitialByte(d.data[d.off]) val = uint64(ai) d.off++ - if ai < 24 { + if ai <= maxAdditionalInformationWithoutArgument { return } - if ai == 24 { + + if ai == additionalInformationWith1ByteArgument { val = uint64(d.data[d.off]) d.off++ return } - if ai == 25 { - val = uint64(binary.BigEndian.Uint16(d.data[d.off : d.off+2])) - d.off += 2 + + if ai == additionalInformationWith2ByteArgument { + const argumentSize = 2 + val = uint64(binary.BigEndian.Uint16(d.data[d.off : d.off+argumentSize])) + d.off += argumentSize return } - if ai == 26 { - val = uint64(binary.BigEndian.Uint32(d.data[d.off : d.off+4])) - d.off += 4 + + if ai == additionalInformationWith4ByteArgument { + const argumentSize = 4 + val = uint64(binary.BigEndian.Uint32(d.data[d.off : d.off+argumentSize])) + d.off += argumentSize return } - if ai == 27 { - val = binary.BigEndian.Uint64(d.data[d.off : d.off+8]) - d.off += 8 + + if ai == additionalInformationWith8ByteArgument { + const argumentSize = 8 + val = binary.BigEndian.Uint64(d.data[d.off : d.off+argumentSize]) + d.off += argumentSize return } return @@ -2849,7 +2888,7 @@ func (d *decoder) reset(data []byte) { } func (d *decoder) nextCBORType() cborType { - return cborType(d.data[d.off] & 0xe0) + return getType(d.data[d.off]) } func (d *decoder) nextCBORNil() bool { @@ -3069,7 +3108,7 @@ func convertByteSliceToByteString(v interface{}) (interface{}, bool) { // validBuiltinTag checks that supported built-in tag numbers are followed by expected content types. func validBuiltinTag(tagNum uint64, contentHead byte) error { - t := cborType(contentHead & 0xe0) + t := getType(contentHead) switch tagNum { case 0: // Tag content (date/time text string in RFC 3339 format) must be string type. diff --git a/encode.go b/encode.go index 5440300..f1e720a 100644 --- a/encode.go +++ b/encode.go @@ -1642,34 +1642,46 @@ func encodeTag(e *bytes.Buffer, em *encMode, v reflect.Value) error { // encodeHead writes CBOR head of specified type t and returns number of bytes written. func encodeHead(e *bytes.Buffer, t byte, n uint64) int { - if n <= 23 { + if n <= maxAdditionalInformationWithoutArgument { + const headSize = 1 e.WriteByte(t | byte(n)) - return 1 + return headSize } + if n <= math.MaxUint8 { - scratch := [2]byte{t | byte(24), byte(n)} - e.Write(scratch[:2]) - return 2 + const headSize = 2 + scratch := [headSize]byte{ + t | byte(additionalInformationWith1ByteArgument), + byte(n), + } + e.Write(scratch[:]) + return headSize } + if n <= math.MaxUint16 { - var scratch [3]byte - scratch[0] = t | byte(25) + const headSize = 3 + var scratch [headSize]byte + scratch[0] = t | byte(additionalInformationWith2ByteArgument) binary.BigEndian.PutUint16(scratch[1:], uint16(n)) - e.Write(scratch[:3]) - return 3 + e.Write(scratch[:]) + return headSize } + if n <= math.MaxUint32 { - var scratch [5]byte - scratch[0] = t | byte(26) + const headSize = 5 + var scratch [headSize]byte + scratch[0] = t | byte(additionalInformationWith4ByteArgument) binary.BigEndian.PutUint32(scratch[1:], uint32(n)) - e.Write(scratch[:5]) - return 5 + e.Write(scratch[:]) + return headSize } - var scratch [9]byte - scratch[0] = t | byte(27) + + const headSize = 9 + var scratch [headSize]byte + scratch[0] = t | byte(additionalInformationWith8ByteArgument) binary.BigEndian.PutUint64(scratch[1:], n) - e.Write(scratch[:9]) - return 9 + e.Write(scratch[:]) + return headSize } var ( diff --git a/tag_test.go b/tag_test.go index dec3181..fd9246d 100644 --- a/tag_test.go +++ b/tag_test.go @@ -1388,7 +1388,7 @@ func (n *number3) UnmarshalCBOR(data []byte) (err error) { return fmt.Errorf("wrong tag number %d, want %d", rawTag.Number, 100) } - if rawTag.Content[0]&0xe0 != 0xa0 { + if getType(rawTag.Content[0]) != cborTypeMap { return fmt.Errorf("wrong tag content type, want map") } diff --git a/valid.go b/valid.go index 2fa5497..a60f613 100644 --- a/valid.go +++ b/valid.go @@ -99,7 +99,7 @@ func (d *decoder) wellformed(allowExtraData bool, checkBuiltinTags bool) error { } // wellformedInternal checks data's well-formedness and returns max depth and error. -func (d *decoder) wellformedInternal(depth int, checkBuiltinTags bool) (int, error) { // nolint:gocyclo +func (d *decoder) wellformedInternal(depth int, checkBuiltinTags bool) (int, error) { //nolint:gocyclo t, ai, val, err := d.wellformedHead() if err != nil { return 0, err @@ -194,7 +194,7 @@ func (d *decoder) wellformedInternal(depth int, checkBuiltinTags bool) (int, err Message: "bignum", } } - if cborType(d.data[d.off]&0xe0) != cborTypeTag { + if getType(d.data[d.off]) != cborTypeTag { break } if _, _, tagNum, err = d.wellformedHead(); err != nil { @@ -224,11 +224,11 @@ func (d *decoder) wellformedIndefiniteString(t cborType, depth int, checkBuiltin break } // Peek ahead to get next type and indefinite length status. - nt := cborType(d.data[d.off] & 0xe0) + nt, ai := parseInitialByte(d.data[d.off]) if t != nt { return 0, &SyntaxError{"cbor: wrong element type " + nt.String() + " for indefinite-length " + t.String()} } - if (d.data[d.off] & 0x1f) == 31 { + if ai == 31 { return 0, &SyntaxError{"cbor: indefinite-length " + t.String() + " chunk is not definite-length"} } if depth, err = d.wellformedInternal(depth, checkBuiltinTags); err != nil { @@ -281,16 +281,18 @@ func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error) return 0, 0, 0, io.ErrUnexpectedEOF } - t = cborType(d.data[d.off] & 0xe0) - ai = d.data[d.off] & 0x1f + t, ai = parseInitialByte(d.data[d.off]) val = uint64(ai) d.off++ + dataLen-- - if ai < 24 { + if ai <= maxAdditionalInformationWithoutArgument { return t, ai, val, nil } - if ai == 24 { - if dataLen < 2 { + + if ai == additionalInformationWith1ByteArgument { + const argumentSize = 1 + if dataLen < argumentSize { return 0, 0, 0, io.ErrUnexpectedEOF } val = uint64(d.data[d.off]) @@ -300,12 +302,14 @@ func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error) } return t, ai, val, nil } - if ai == 25 { - if dataLen < 3 { + + if ai == additionalInformationWith2ByteArgument { + const argumentSize = 2 + if dataLen < argumentSize { return 0, 0, 0, io.ErrUnexpectedEOF } - val = uint64(binary.BigEndian.Uint16(d.data[d.off : d.off+2])) - d.off += 2 + val = uint64(binary.BigEndian.Uint16(d.data[d.off : d.off+argumentSize])) + d.off += argumentSize if t == cborTypePrimitives { if err := d.acceptableFloat(float64(float16.Frombits(uint16(val)).Float32())); err != nil { return 0, 0, 0, err @@ -313,12 +317,14 @@ func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error) } return t, ai, val, nil } - if ai == 26 { - if dataLen < 5 { + + if ai == additionalInformationWith4ByteArgument { + const argumentSize = 4 + if dataLen < argumentSize { return 0, 0, 0, io.ErrUnexpectedEOF } - val = uint64(binary.BigEndian.Uint32(d.data[d.off : d.off+4])) - d.off += 4 + val = uint64(binary.BigEndian.Uint32(d.data[d.off : d.off+argumentSize])) + d.off += argumentSize if t == cborTypePrimitives { if err := d.acceptableFloat(float64(math.Float32frombits(uint32(val)))); err != nil { return 0, 0, 0, err @@ -326,12 +332,14 @@ func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error) } return t, ai, val, nil } - if ai == 27 { - if dataLen < 9 { + + if ai == additionalInformationWith8ByteArgument { + const argumentSize = 8 + if dataLen < argumentSize { return 0, 0, 0, io.ErrUnexpectedEOF } - val = binary.BigEndian.Uint64(d.data[d.off : d.off+8]) - d.off += 8 + val = binary.BigEndian.Uint64(d.data[d.off : d.off+argumentSize]) + d.off += argumentSize if t == cborTypePrimitives { if err := d.acceptableFloat(math.Float64frombits(val)); err != nil { return 0, 0, 0, err @@ -339,6 +347,7 @@ func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error) } return t, ai, val, nil } + if ai == 31 { switch t { case cborTypePositiveInt, cborTypeNegativeInt, cborTypeTag: @@ -348,6 +357,7 @@ func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error) } return t, ai, val, nil } + // ai == 28, 29, 30 return 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()} }