// 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" "errors" "reflect" "sort" "strconv" "strings" "sync" ) var ( decodingStructTypeCache sync.Map // map[reflect.Type]*decodingStructType encodingStructTypeCache sync.Map // map[reflect.Type]*encodingStructType encodeFuncCache sync.Map // map[reflect.Type]encodeFunc typeInfoCache sync.Map // map[reflect.Type]*typeInfo ) type specialType int const ( specialTypeNone specialType = iota specialTypeUnmarshalerIface specialTypeEmptyIface specialTypeTag specialTypeTime ) type typeInfo struct { elemTypeInfo *typeInfo keyTypeInfo *typeInfo typ reflect.Type kind reflect.Kind nonPtrType reflect.Type nonPtrKind reflect.Kind spclType specialType } func newTypeInfo(t reflect.Type) *typeInfo { tInfo := typeInfo{typ: t, kind: t.Kind()} for t.Kind() == reflect.Ptr { t = t.Elem() } k := t.Kind() tInfo.nonPtrType = t tInfo.nonPtrKind = k if k == reflect.Interface && t.NumMethod() == 0 { tInfo.spclType = specialTypeEmptyIface } else if t == typeTag { tInfo.spclType = specialTypeTag } else if t == typeTime { tInfo.spclType = specialTypeTime } else if reflect.PtrTo(t).Implements(typeUnmarshaler) { tInfo.spclType = specialTypeUnmarshalerIface } switch k { case reflect.Array, reflect.Slice: tInfo.elemTypeInfo = getTypeInfo(t.Elem()) case reflect.Map: tInfo.keyTypeInfo = getTypeInfo(t.Key()) tInfo.elemTypeInfo = getTypeInfo(t.Elem()) } return &tInfo } type decodingStructType struct { fields fields err error toArray bool } func getDecodingStructType(t reflect.Type) *decodingStructType { if v, _ := decodingStructTypeCache.Load(t); v != nil { return v.(*decodingStructType) } flds, structOptions := getFields(t) toArray := hasToArrayOption(structOptions) var err error for i := 0; i < len(flds); i++ { if flds[i].keyAsInt { nameAsInt, numErr := strconv.Atoi(flds[i].name) if numErr != nil { err = errors.New("cbor: failed to parse field name \"" + flds[i].name + "\" to int (" + numErr.Error() + ")") break } flds[i].nameAsInt = int64(nameAsInt) } flds[i].typInfo = getTypeInfo(flds[i].typ) } structType := &decodingStructType{fields: flds, err: err, toArray: toArray} decodingStructTypeCache.Store(t, structType) return structType } type encodingStructType struct { fields fields bytewiseFields fields lengthFirstFields fields err error toArray bool omitEmpty bool hasAnonymousField bool } func (st *encodingStructType) getFields(em *encMode) fields { if em.sort == SortNone { return st.fields } if em.sort == SortLengthFirst { return st.lengthFirstFields } return st.bytewiseFields } type bytewiseFieldSorter struct { fields fields } func (x *bytewiseFieldSorter) Len() int { return len(x.fields) } func (x *bytewiseFieldSorter) Swap(i, j int) { x.fields[i], x.fields[j] = x.fields[j], x.fields[i] } func (x *bytewiseFieldSorter) Less(i, j int) bool { return bytes.Compare(x.fields[i].cborName, x.fields[j].cborName) <= 0 } type lengthFirstFieldSorter struct { fields fields } func (x *lengthFirstFieldSorter) Len() int { return len(x.fields) } func (x *lengthFirstFieldSorter) Swap(i, j int) { x.fields[i], x.fields[j] = x.fields[j], x.fields[i] } func (x *lengthFirstFieldSorter) Less(i, j int) bool { if len(x.fields[i].cborName) != len(x.fields[j].cborName) { return len(x.fields[i].cborName) < len(x.fields[j].cborName) } return bytes.Compare(x.fields[i].cborName, x.fields[j].cborName) <= 0 } func getEncodingStructType(t reflect.Type) *encodingStructType { if v, _ := encodingStructTypeCache.Load(t); v != nil { return v.(*encodingStructType) } flds, structOptions := getFields(t) if hasToArrayOption(structOptions) { return getEncodingStructToArrayType(t, flds) } var err error var omitEmpty bool var hasAnonymousField bool var hasKeyAsInt bool var hasKeyAsStr bool e := getEncodeState() for i := 0; i < len(flds); i++ { // Get field's encodeFunc flds[i].ef = getEncodeFunc(flds[i].typ) if flds[i].ef == nil { err = &UnsupportedTypeError{t} break } // Encode field name if flds[i].keyAsInt { nameAsInt, numErr := strconv.Atoi(flds[i].name) if numErr != nil { err = errors.New("cbor: failed to parse field name \"" + flds[i].name + "\" to int (" + numErr.Error() + ")") break } flds[i].nameAsInt = int64(nameAsInt) if nameAsInt >= 0 { encodeHead(e, byte(cborTypePositiveInt), uint64(nameAsInt)) } else { n := nameAsInt*(-1) - 1 encodeHead(e, byte(cborTypeNegativeInt), uint64(n)) } flds[i].cborName = make([]byte, e.Len()) copy(flds[i].cborName, e.Bytes()) e.Reset() hasKeyAsInt = true } else { encodeHead(e, byte(cborTypeTextString), uint64(len(flds[i].name))) flds[i].cborName = make([]byte, e.Len()+len(flds[i].name)) n := copy(flds[i].cborName, e.Bytes()) copy(flds[i].cborName[n:], flds[i].name) e.Reset() hasKeyAsStr = true } // Check if field is from embedded struct if len(flds[i].idx) > 1 { hasAnonymousField = true } // Check if field can be omitted when empty if flds[i].omitEmpty { omitEmpty = true } } putEncodeState(e) if err != nil { structType := &encodingStructType{err: err} encodingStructTypeCache.Store(t, structType) return structType } // Sort fields by canonical order bytewiseFields := make(fields, len(flds)) copy(bytewiseFields, flds) sort.Sort(&bytewiseFieldSorter{bytewiseFields}) lengthFirstFields := bytewiseFields if hasKeyAsInt && hasKeyAsStr { lengthFirstFields = make(fields, len(flds)) copy(lengthFirstFields, flds) sort.Sort(&lengthFirstFieldSorter{lengthFirstFields}) } structType := &encodingStructType{ fields: flds, bytewiseFields: bytewiseFields, lengthFirstFields: lengthFirstFields, omitEmpty: omitEmpty, hasAnonymousField: hasAnonymousField, } encodingStructTypeCache.Store(t, structType) return structType } func getEncodingStructToArrayType(t reflect.Type, flds fields) *encodingStructType { var hasAnonymousField bool for i := 0; i < len(flds); i++ { // Get field's encodeFunc flds[i].ef = getEncodeFunc(flds[i].typ) if flds[i].ef == nil { structType := &encodingStructType{err: &UnsupportedTypeError{t}} encodingStructTypeCache.Store(t, structType) return structType } // Check if field is from embedded struct if len(flds[i].idx) > 1 { hasAnonymousField = true } } structType := &encodingStructType{ fields: flds, toArray: true, hasAnonymousField: hasAnonymousField, } encodingStructTypeCache.Store(t, structType) return structType } func getEncodeFunc(t reflect.Type) encodeFunc { if v, _ := encodeFuncCache.Load(t); v != nil { return v.(encodeFunc) } f := getEncodeFuncInternal(t) encodeFuncCache.Store(t, f) return f } func getTypeInfo(t reflect.Type) *typeInfo { if v, _ := typeInfoCache.Load(t); v != nil { return v.(*typeInfo) } tInfo := newTypeInfo(t) typeInfoCache.Store(t, tInfo) return tInfo } func hasToArrayOption(tag string) bool { s := ",toarray" idx := strings.Index(tag, s) return idx >= 0 && (len(tag) == idx+len(s) || tag[idx+len(s)] == ',') }