encoding/cbor/coerce.go (171 lines of code) (raw):

package cbor import ( "fmt" "math/big" "time" ) func fmtNegint(v NegInt) string { if v == 0 { return "-2^64" } return fmt.Sprintf("-%d", v) } // AsInt8 coerces a Value to its int8 representation if possible. func AsInt8(v Value) (int8, error) { const max8 = 0x7f switch vv := v.(type) { case Uint: if vv > max8 { return 0, fmt.Errorf("cbor uint %d exceeds max int8 value", vv) } return int8(vv), nil case NegInt: if vv > max8+1 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min int8 value", fmtNegint(vv)) } return -int8(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsInt16 coerces a Value to its int16 representation if possible. func AsInt16(v Value) (int16, error) { const max16 = 0x7fff switch vv := v.(type) { case Uint: if vv > max16 { return 0, fmt.Errorf("cbor uint %d exceeds max int16 value", vv) } return int16(vv), nil case NegInt: if vv > max16+1 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min int16 value", fmtNegint(vv)) } return -int16(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsInt32 coerces a Value to its int32 representation if possible. func AsInt32(v Value) (int32, error) { const max32 = 0x7fffffff switch vv := v.(type) { case Uint: if vv > max32 { return 0, fmt.Errorf("cbor uint %d exceeds max int32 value", vv) } return int32(vv), nil case NegInt: if vv > max32+1 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min int32 value", fmtNegint(vv)) } return -int32(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsInt64 coerces a Value to its int64 representation if possible. func AsInt64(v Value) (int64, error) { const max64 = 0x7fffffff_ffffffff switch vv := v.(type) { case Uint: if vv > max64 { return 0, fmt.Errorf("cbor uint %d exceeds max int64 value", vv) } return int64(vv), nil case NegInt: if vv > max64+1 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min int64 value", fmtNegint(vv)) } return -int64(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsFloat32 coerces a Value to its float32 representation if possible. // // A float32 may be represented by any of the following alternatives: // - cbor uint (if within lossless range) // - cbor -int (if within lossless range) func AsFloat32(v Value) (float32, error) { const maxLosslessFloat32 = 1 << 24 switch vv := v.(type) { case Float32: return float32(vv), nil case Uint: if vv > maxLosslessFloat32 { return 0, fmt.Errorf("cbor uint %d exceeds max lossless float32 value", vv) } return float32(vv), nil case NegInt: if vv > maxLosslessFloat32 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min lossless float32 value", fmtNegint(vv)) } return -float32(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsFloat64 coerces a Value to its float64 representation if possible. // // A float64 may be represented by any of the following alternatives: // - float32 // - cbor uint (if within lossless range) // - cbor -int (if within lossless range) func AsFloat64(v Value) (float64, error) { const maxLosslessFloat64 = 1 << 54 switch vv := v.(type) { case Float64: return float64(vv), nil case Float32: return float64(vv), nil case Uint: if vv > maxLosslessFloat64 { return 0, fmt.Errorf("cbor uint %d exceeds max lossless float64 value", vv) } return float64(vv), nil case NegInt: if vv > maxLosslessFloat64 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min lossless float64 value", fmtNegint(vv)) } return -float64(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsTime coerces a Value to its time.Time representation if possible. // // This coercion will check that the given Value is a Tag with the registered // number (1) for epoch time. The value for time.Time within that tag may be // derived from any of the following: // - float32 // - float64 // - cbor uint (within int64 bounds) // - cbor -int (within int64 bounds) // // Tag number 0 (date-time RFC3339) is not supported. func AsTime(v Value) (time.Time, error) { const tagEpoch = 1 tag, ok := v.(*Tag) if !ok { return time.Time{}, fmt.Errorf("unexpected value type %T", v) } if tag.ID != tagEpoch { return time.Time{}, fmt.Errorf("unexpected tag ID %d", tag.ID) } switch vv := tag.Value.(type) { case Float32: return time.UnixMilli(int64(vv * 1e3)), nil case Float64: return time.UnixMilli(int64(vv * 1e3)), nil } as64, err := AsInt64(tag.Value) // will handle fail on non-int types if err != nil { return time.Time{}, fmt.Errorf("coerce tag value: %w", err) } return time.Unix(as64, 0), nil } // AsBigInt coerces a Value to its big.Int representation if possible. // // A BigInt may be represented by any of the following: // - Uint // - NegInt // - Tag (type 2/3, where tagged value is a Slice) // - Nil func AsBigInt(v Value) (*big.Int, error) { switch vv := v.(type) { case Uint: return new(big.Int).SetUint64(uint64(vv)), nil case NegInt: i := new(big.Int) if vv == 0 { i.SetBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0}) } else { i.SetUint64(uint64(vv)) } return i.Neg(i), nil case *Tag: return asBigIntFromTag(vv) case *Nil: return nil, nil default: return nil, fmt.Errorf("unexpected value type %T", v) } } func asBigIntFromTag(tv *Tag) (*big.Int, error) { const tagpos = 2 const tagneg = 3 if tv.ID != tagpos && tv.ID != tagneg { return nil, fmt.Errorf("unexpected tag ID %d", tv.ID) } bytes, ok := tv.Value.(Slice) if !ok { return nil, fmt.Errorf("unexpected tag value type %T", tv.Value) } i := new(big.Int).SetBytes([]byte(bytes)) if tv.ID == tagneg { i.Sub(big.NewInt(-1), i) } return i, nil }