ceph-csi/vendor/github.com/gemalto/kmip-go/ttlv/encoder.go

963 lines
25 KiB
Go
Raw Normal View History

package ttlv
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"math/big"
"reflect"
"strings"
"time"
"github.com/ansel1/merry"
)
const structFieldTag = "ttlv"
var (
ErrIntOverflow = fmt.Errorf("value exceeds max int value %d", math.MaxInt32)
ErrUnsupportedEnumTypeError = errors.New("unsupported type for enums, must be string, or int types")
ErrUnsupportedTypeError = errors.New("marshaling/unmarshaling is not supported for this type")
ErrNoTag = errors.New("unable to determine tag for field")
ErrTagConflict = errors.New("tag conflict")
)
// Marshal encodes a golang value into a KMIP value.
//
// An error will be returned if v is an invalid pointer.
//
// Currently, Marshal does not support anonymous fields.
// Private fields are ignored.
//
// Marshal maps the golang value to a KMIP tag, type, and value
// encoding. To determine the KMIP tag, Marshal uses the same rules
// as Unmarshal.
//
// The appropriate type and encoding are inferred from the golang type
// and from the inferred KMIP tag, according to these rules:
//
// 1. If the value is a TTLV, it is copied byte for byte
// 2. If the value implements Marshaler, call that
// 3. If the struct field has an "omitempty" flag, and the value is
// zero, skip the field:
//
// type Foo struct {
// Comment string `ttlv:,omitempty`
// }
//
// 4. If the value is a slice (except []byte) or array, marshal all
// values concatenated
// 5. If a tag has not been inferred at this point, return *MarshalerError with
// cause ErrNoTag
// 6. If the Tag is registered as an enum, or has the "enum" struct tag flag, attempt
// to marshal as an Enumeration. int, int8, int16, int32, and their uint counterparts
// can be marshaled as an Enumeration. A string can be marshaled to an Enumeration
// if the string contains a number, a 4 byte (8 char) hex string with the prefix "0x",
// or the normalized name of an enum value registered to this tag. Examples:
//
// type Foo struct {
// CancellationResult string // will encode as an Enumeration because
// // the tag CancellationResult is registered
// // as an enum.
// C int `ttlv:"Comment,enum" // The tag Comment is not registered as an enum
// // but the enum flag will force this to encode
// // as an enumeration.
// }
//
// If the string can't be interpreted as an enum value, it will be encoded as a TextString. If
// the "enum" struct flag is set, the value *must* successfully encode to an Enumeration using
// above rules, or an error is returned.
// 7. If the Tag is registered as a bitmask, or has the "bitmask" struct tag flag, attempt
// to marshal to an Integer, following the same rules as for Enumerations. The ParseInt()
// function is used to parse string values.
// 9. time.Time marshals to DateTime. If the field has the "datetimeextended" struct flag,
// marshal as DateTimeExtended. Example:
//
// type Foo struct {
// ActivationDate time.Time `ttlv:",datetimeextended"`
// }
//
// 10. big.Int marshals to BigInteger
// 11. time.Duration marshals to Interval
// 12. string marshals to TextString
// 13. []byte marshals to ByteString
// 14. all int and uint variants except int64 and uint64 marshal to Integer. If the golang
// value overflows the KMIP value, *MarshalerError with cause ErrIntOverflow is returned
// 15. int64 and uint64 marshal to LongInteger
// 16. bool marshals to Boolean
// 17. structs marshal to Structure. Each field of the struct will be marshaled into the
// values of the Structure according to the above rules.
//
// Any other golang type will return *MarshalerError with cause ErrUnsupportedTypeError.
func Marshal(v interface{}) (TTLV, error) {
buf := bytes.NewBuffer(nil)
err := NewEncoder(buf).Encode(v)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// Marshaler knows how to encode itself to TTLV.
// The implementation should use the primitive methods of the encoder,
// such as EncodeInteger(), etc.
//
// The tag inferred by the Encoder from the field or type information is
// passed as an argument, but the implementation can choose to ignore it.
type Marshaler interface {
MarshalTTLV(e *Encoder, tag Tag) error
}
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: w}
}
// Encode a single value and flush to the writer. The tag will be inferred from
// the value. If no tag can be inferred, an error is returned.
// See Marshal for encoding rules.
func (e *Encoder) Encode(v interface{}) error {
return e.EncodeValue(TagNone, v)
}
// EncodeValue encodes a single value with the given tag and flushes it
// to the writer.
// See Marshal for encoding rules.
func (e *Encoder) EncodeValue(tag Tag, v interface{}) error {
err := e.encode(tag, reflect.ValueOf(v), nil)
if err != nil {
return err
}
return e.Flush()
}
// EncodeStructure encodes a Structure with the given tag to the writer.
// The function argument should encode the enclosed values inside the Structure.
// Call Flush() to write the data to the writer.
func (e *Encoder) EncodeStructure(tag Tag, f func(e *Encoder) error) error {
e.encodeDepth++
i := e.encBuf.begin(tag, TypeStructure)
err := f(e)
e.encBuf.end(i)
e.encodeDepth--
return err
}
// EncodeEnumeration, along with the other Encode<Type> methods, encodes a
// single KMIP value with the given tag to an internal buffer. These methods
// do not flush the data to the writer: call Flush() to flush the buffer.
func (e *Encoder) EncodeEnumeration(tag Tag, v uint32) {
e.encBuf.encodeEnum(tag, v)
}
func (e *Encoder) EncodeInteger(tag Tag, v int32) {
e.encBuf.encodeInt(tag, v)
}
func (e *Encoder) EncodeLongInteger(tag Tag, v int64) {
e.encBuf.encodeLongInt(tag, v)
}
func (e *Encoder) EncodeInterval(tag Tag, v time.Duration) {
e.encBuf.encodeInterval(tag, v)
}
func (e *Encoder) EncodeDateTime(tag Tag, v time.Time) {
e.encBuf.encodeDateTime(tag, v)
}
func (e *Encoder) EncodeDateTimeExtended(tag Tag, v time.Time) {
e.encBuf.encodeDateTimeExtended(tag, v)
}
func (e *Encoder) EncodeBigInteger(tag Tag, v *big.Int) {
e.encBuf.encodeBigInt(tag, v)
}
func (e *Encoder) EncodeBoolean(tag Tag, v bool) {
e.encBuf.encodeBool(tag, v)
}
func (e *Encoder) EncodeTextString(tag Tag, v string) {
e.encBuf.encodeTextString(tag, v)
}
func (e *Encoder) EncodeByteString(tag Tag, v []byte) {
e.encBuf.encodeByteString(tag, v)
}
// Flush flushes the internal encoding buffer to the writer.
func (e *Encoder) Flush() error {
if e.encodeDepth > 0 {
return nil
}
_, err := e.encBuf.WriteTo(e.w)
e.encBuf.Reset()
return err
}
type MarshalerError struct {
// Type is the golang type of the value being marshaled
Type reflect.Type
// Struct is the name of the enclosing struct if the marshaled value is a field.
Struct string
// Field is the name of the field being marshaled
Field string
Tag Tag
}
func (e *MarshalerError) Error() string {
msg := "kmip: error marshaling value"
if e.Type != nil {
msg += " of type " + e.Type.String()
}
if e.Struct != "" {
msg += " in struct field " + e.Struct + "." + e.Field
}
return msg
}
func (e *Encoder) marshalingError(tag Tag, t reflect.Type, cause error) merry.Error {
err := &MarshalerError{
Type: t,
Struct: e.currStruct,
Field: e.currField,
Tag: tag,
}
return merry.WrapSkipping(err, 1).WithCause(cause)
}
var (
byteType = reflect.TypeOf(byte(0))
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
bigIntPtrType = reflect.TypeOf((*big.Int)(nil))
bigIntType = bigIntPtrType.Elem()
durationType = reflect.TypeOf(time.Nanosecond)
ttlvType = reflect.TypeOf((*TTLV)(nil)).Elem()
tagType = reflect.TypeOf(Tag(0))
)
var invalidValue = reflect.Value{}
// indirect dives into interfaces values, and one level deep into pointers
// returns an invalid value if the resolved value is nil or invalid.
func indirect(v reflect.Value) reflect.Value {
if !v.IsValid() {
return v
}
if v.Kind() == reflect.Interface {
v = v.Elem()
}
if !v.IsValid() {
return v
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
switch v.Kind() {
case reflect.Func, reflect.Slice, reflect.Map, reflect.Chan, reflect.Ptr, reflect.Interface:
if v.IsNil() {
return invalidValue
}
default:
}
return v
}
var zeroBigInt = big.Int{}
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
default:
}
switch v.Type() {
case timeType:
return v.Interface().(time.Time).IsZero() //nolint:forcetypeassert
case bigIntType:
i := v.Interface().(big.Int) //nolint:forcetypeassert
return zeroBigInt.Cmp(&i) == 0
}
return false
}
func (e *Encoder) encode(tag Tag, v reflect.Value, fi *fieldInfo) error {
// if pointer or interface
v = indirect(v)
if !v.IsValid() {
return nil
}
typ := v.Type()
if typ == ttlvType {
// fast path: if the value is TTLV, we write it directly to the output buffer
_, err := e.encBuf.Write(v.Bytes())
return err
}
typeInfo, err := getTypeInfo(typ)
if err != nil {
return err
}
if tag == TagNone {
tag = tagForMarshal(v, typeInfo, fi)
}
var flags fieldFlags
if fi != nil {
flags = fi.flags
}
// check for Marshaler
switch {
case typ.Implements(marshalerType):
if flags.omitEmpty() && isEmptyValue(v) {
return nil
}
return v.Interface().(Marshaler).MarshalTTLV(e, tag) //nolint:forcetypeassert
case v.CanAddr():
pv := v.Addr()
pvtyp := pv.Type()
if pvtyp.Implements(marshalerType) {
if flags.omitEmpty() && isEmptyValue(v) {
return nil
}
return pv.Interface().(Marshaler).MarshalTTLV(e, tag) //nolint:forcetypeassert
}
}
// If the type doesn't implement Marshaler, then validate the value is a supported kind
switch v.Kind() {
case reflect.Chan, reflect.Map, reflect.Func, reflect.Ptr, reflect.UnsafePointer, reflect.Uintptr, reflect.Float32,
reflect.Float64,
reflect.Complex64,
reflect.Complex128,
reflect.Interface:
return e.marshalingError(tag, v.Type(), ErrUnsupportedTypeError)
default:
}
// skip if value is empty and tags include omitempty
if flags.omitEmpty() && isEmptyValue(v) {
return nil
}
// recurse to handle slices of values
switch v.Kind() {
case reflect.Slice:
if typ.Elem() == byteType {
// special case, encode as a ByteString, handled below
break
}
fallthrough
case reflect.Array:
for i := 0; i < v.Len(); i++ {
// turn off the omit empty flag. applies at the field level,
// not to each member of the slice
// TODO: is this true?
var fi2 *fieldInfo
if fi != nil {
fi2 = &fieldInfo{}
// make a copy.
*fi2 = *fi
fi2.flags &^= fOmitEmpty
}
err := e.encode(tag, v.Index(i), fi2)
if err != nil {
return err
}
}
return nil
default:
}
if tag == TagNone {
return e.marshalingError(tag, v.Type(), ErrNoTag)
}
// handle enums and bitmasks
//
// If the field has the "enum" or "bitmask" flag, or the tag is registered as an enum or bitmask,
// attempt to interpret the go value as such.
//
// If the field is explicitly flag, return an error if the value can't be interpreted. Otherwise
// ignore errors and let processing fallthrough to the type-based encoding.
enumMap := DefaultRegistry.EnumForTag(tag)
if flags.enum() || flags.bitmask() || enumMap != nil {
switch typ.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
i := v.Int()
if flags.bitmask() || (enumMap != nil && enumMap.Bitmask()) {
e.encBuf.encodeInt(tag, int32(i))
} else {
e.encBuf.encodeEnum(tag, uint32(i))
}
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
i := v.Uint()
if flags.bitmask() || (enumMap != nil && enumMap.Bitmask()) {
e.encBuf.encodeInt(tag, int32(i))
} else {
e.encBuf.encodeEnum(tag, uint32(i))
}
return nil
case reflect.String:
s := v.String()
if flags.bitmask() || (enumMap != nil && enumMap.Bitmask()) {
i, err := ParseInt(s, enumMap)
if err == nil {
e.encBuf.encodeInt(tag, i)
return nil
}
// only throw an error if the field is explicitly marked as a bitmask
// otherwise just ignore it, and let it encode as a string later on.
if flags.bitmask() {
// if we couldn't parse the string as an enum value
return e.marshalingError(tag, typ, err)
}
} else {
i, err := ParseEnum(s, enumMap)
if err == nil {
e.encBuf.encodeEnum(tag, i)
return nil
}
// only throw an error if the field is explicitly marked as an enum
// otherwise just ignore it, and let it encode as a string later on.
if flags.enum() {
// if we couldn't parse the string as an enum value
return e.marshalingError(tag, typ, err)
}
}
default:
if flags.enum() || flags.bitmask() {
return e.marshalingError(tag, typ, ErrUnsupportedEnumTypeError).Append(typ.String())
}
}
}
// handle special types
switch typ {
case timeType:
if flags.dateTimeExt() {
e.encBuf.encodeDateTimeExtended(tag, v.Interface().(time.Time)) //nolint:forcetypeassert
} else {
e.encBuf.encodeDateTime(tag, v.Interface().(time.Time)) //nolint:forcetypeassert
}
return nil
case bigIntType:
bi := v.Interface().(big.Int) //nolint:forcetypeassert
e.encBuf.encodeBigInt(tag, &bi)
return nil
case bigIntPtrType:
e.encBuf.encodeBigInt(tag, v.Interface().(*big.Int)) //nolint:forcetypeassert
return nil
case durationType:
e.encBuf.encodeInterval(tag, time.Duration(v.Int()))
return nil
}
// handle the rest of the kinds
switch typ.Kind() {
case reflect.Struct:
// push current struct onto stack
currStruct := e.currStruct
e.currStruct = typ.Name()
err = e.EncodeStructure(tag, func(e *Encoder) error {
for _, field := range typeInfo.valueFields {
fv := v.FieldByIndex(field.index)
// note: we're staying in reflection world here instead of
// converting back to an interface{} value and going through
// the non-reflection path again. Calling Interface()
// on the reflect value would make a potentially addressable value
// into an unaddressable value, reducing the chances we can coerce
// the value into a Marshalable.
//
// tl;dr
// Consider a type which implements Marshaler with
// a pointer receiver, and a struct with a non-pointer field of that type:
//
// type Wheel struct{}
// func (*Wheel) MarshalTTLV(...)
//
// type Car struct{
// Wheel Wheel
// }
//
// When traversing the Car struct, should the encoder invoke Wheel's
// Marshaler method, or not? Technically, the type `Wheel`
// doesn't implement the Marshaler interface. Only the type `*Wheel`
// implements it. However, the other encoders in the SDK, like JSON
// and XML, will try, if possible, to get a pointer to field values like this, in
// order to invoke the Marshaler interface anyway.
//
// Encoders can only get a pointer to field values if the field
// value is `addressable`. Addressability is explained in the docs for reflect.Value#CanAddr().
// Using reflection to turn a reflect.Value() back into an interface{}
// can make a potentially addressable value (like the field of an addressable struct)
// into an unaddressable value (reflect.Value#Interface{} always returns an unaddressable
// copy).
// push the currField
currField := e.currField
e.currField = field.name
err := e.encode(TagNone, fv, &field) //nolint:gosec,scopelint
// pop the currField
e.currField = currField
if err != nil {
return err
}
}
return nil
})
// pop current struct
e.currStruct = currStruct
return err
case reflect.String:
e.encBuf.encodeTextString(tag, v.String())
case reflect.Slice:
// special case, encode as a ByteString
// all slices which aren't []byte should have been handled above
// the call to v.Bytes() will panic if this assumption is wrong
e.encBuf.encodeByteString(tag, v.Bytes())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
i := v.Int()
if i > math.MaxInt32 {
return e.marshalingError(tag, typ, ErrIntOverflow)
}
e.encBuf.encodeInt(tag, int32(i))
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
u := v.Uint()
if u > math.MaxInt32 {
return e.marshalingError(tag, typ, ErrIntOverflow)
}
e.encBuf.encodeInt(tag, int32(u))
return nil
case reflect.Uint64:
u := v.Uint()
e.encBuf.encodeLongInt(tag, int64(u))
return nil
case reflect.Int64:
e.encBuf.encodeLongInt(tag, v.Int())
return nil
case reflect.Bool:
e.encBuf.encodeBool(tag, v.Bool())
default:
// all kinds should have been handled by now
panic(errors.New("should never get here"))
}
return nil
}
func tagForMarshal(v reflect.Value, ti typeInfo, fi *fieldInfo) Tag {
// the tag on the TTLVTag field
if ti.tagField != nil && ti.tagField.explicitTag != TagNone {
return ti.tagField.explicitTag
}
// the value of the TTLVTag field of type Tag
if v.IsValid() && ti.tagField != nil && ti.tagField.ti.typ == tagType {
tag := v.FieldByIndex(ti.tagField.index).Interface().(Tag) //nolint:forcetypeassert
if tag != TagNone {
return tag
}
}
// if value is in a struct field, infer the tag from the field
// else infer from the value's type name
if fi != nil {
return fi.tag
}
return ti.inferredTag
}
// encBuf encodes basic KMIP types into TTLV.
type encBuf struct {
bytes.Buffer
}
func (h *encBuf) begin(tag Tag, typ Type) int {
_ = h.WriteByte(byte(tag >> 16))
_ = h.WriteByte(byte(tag >> 8))
_ = h.WriteByte(byte(tag))
_ = h.WriteByte(byte(typ))
_, _ = h.Write(zeros[:4])
return h.Len()
}
func (h *encBuf) end(i int) {
n := h.Len() - i
if m := n % 8; m > 0 {
_, _ = h.Write(zeros[:8-m])
}
binary.BigEndian.PutUint32(h.Bytes()[i-4:], uint32(n))
}
func (h *encBuf) writeLongIntVal(tag Tag, typ Type, i int64) {
s := h.begin(tag, typ)
ll := h.Len()
_, _ = h.Write(zeros[:8])
binary.BigEndian.PutUint64(h.Bytes()[ll:], uint64(i))
h.end(s)
}
func (h *encBuf) writeIntVal(tag Tag, typ Type, val uint32) {
s := h.begin(tag, typ)
ll := h.Len()
_, _ = h.Write(zeros[:4])
binary.BigEndian.PutUint32(h.Bytes()[ll:], val)
h.end(s)
}
var (
ones = [8]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
zeros = [8]byte{}
)
func (h *encBuf) encodeBigInt(tag Tag, i *big.Int) {
if i == nil {
return
}
ii := h.begin(tag, TypeBigInteger)
switch i.Sign() {
case 0:
_, _ = h.Write(zeros[:8])
case 1:
b := i.Bytes()
l := len(b)
// if n is positive, but the first bit is a 1, it will look like
// a negative in 2's complement, so prepend zeroes in front
if b[0]&0x80 > 0 {
_ = h.WriteByte(byte(0))
l++
}
// pad front with zeros to multiple of 8
if m := l % 8; m > 0 {
_, _ = h.Write(zeros[:8-m])
}
_, _ = h.Write(b)
case -1:
length := uint(i.BitLen()/8+1) * 8
j := new(big.Int).Lsh(one, length)
b := j.Add(i, j).Bytes()
// When the most significant bit is on a byte
// boundary, we can get some extra significant
// bits, so strip them off when that happens.
if len(b) >= 2 && b[0] == 0xff && b[1]&0x80 != 0 {
b = b[1:]
}
l := len(b)
// pad front with ones to multiple of 8
if m := l % 8; m > 0 {
_, _ = h.Write(ones[:8-m])
}
_, _ = h.Write(b)
}
h.end(ii)
}
func (h *encBuf) encodeInt(tag Tag, i int32) {
h.writeIntVal(tag, TypeInteger, uint32(i))
}
func (h *encBuf) encodeBool(tag Tag, b bool) {
if b {
h.writeLongIntVal(tag, TypeBoolean, 1)
} else {
h.writeLongIntVal(tag, TypeBoolean, 0)
}
}
func (h *encBuf) encodeLongInt(tag Tag, i int64) {
h.writeLongIntVal(tag, TypeLongInteger, i)
}
func (h *encBuf) encodeDateTime(tag Tag, t time.Time) {
h.writeLongIntVal(tag, TypeDateTime, t.Unix())
}
func (h *encBuf) encodeDateTimeExtended(tag Tag, t time.Time) {
// take unix seconds, times a million, to get microseconds, then
// add nanoseconds remainder/1000
//
// this gives us a larger ranger of possible values than just t.UnixNano() / 1000.
// see UnixNano() docs for its limits.
//
// this is limited to max(int64) *microseconds* from epoch, rather than
// max(int64) nanoseconds like UnixNano().
m := (t.Unix() * 1000000) + int64(t.Nanosecond()/1000)
h.writeLongIntVal(tag, TypeDateTimeExtended, m)
}
func (h *encBuf) encodeInterval(tag Tag, d time.Duration) {
h.writeIntVal(tag, TypeInterval, uint32(d/time.Second))
}
func (h *encBuf) encodeEnum(tag Tag, i uint32) {
h.writeIntVal(tag, TypeEnumeration, i)
}
func (h *encBuf) encodeTextString(tag Tag, s string) {
i := h.begin(tag, TypeTextString)
_, _ = h.WriteString(s)
h.end(i)
}
func (h *encBuf) encodeByteString(tag Tag, b []byte) {
if b == nil {
return
}
i := h.begin(tag, TypeByteString)
_, _ = h.Write(b)
h.end(i)
}
func getTypeInfo(typ reflect.Type) (ti typeInfo, err error) {
ti.inferredTag, _ = DefaultRegistry.ParseTag(typ.Name())
ti.typ = typ
err = ti.getFieldsInfo()
return ti, err
}
var errSkip = errors.New("skip")
func getFieldInfo(typ reflect.Type, sf reflect.StructField) (fieldInfo, error) {
var fi fieldInfo
// skip anonymous and unexported fields
if sf.Anonymous || /*unexported:*/ sf.PkgPath != "" {
return fi, errSkip
}
fi.name = sf.Name
fi.structType = typ
fi.index = sf.Index
var anyField bool
// handle field tags
parts := strings.Split(sf.Tag.Get(structFieldTag), ",")
for i, value := range parts {
if i == 0 {
switch value {
case "-":
// skip
return fi, errSkip
case "":
default:
var err error
fi.explicitTag, err = DefaultRegistry.ParseTag(value)
if err != nil {
return fi, err
}
}
} else {
switch strings.ToLower(value) {
case "enum":
fi.flags |= fEnum
case "omitempty":
fi.flags |= fOmitEmpty
case "datetimeextended":
fi.flags |= fDateTimeExtended
case "bitmask":
fi.flags |= fBitBask
case "any":
anyField = true
fi.flags |= fAny
}
}
}
if anyField && fi.explicitTag != TagNone {
return fi, merry.Here(ErrTagConflict).Appendf(`field %s.%s may not specify a TTLV tag and the "any" flag`, fi.structType.Name(), fi.name)
}
// extract type info for the field. The KMIP tag
// for this field is derived from either the field name,
// the field tags, or the field type.
var err error
fi.ti, err = getTypeInfo(sf.Type)
if err != nil {
return fi, err
}
if fi.ti.tagField != nil && fi.ti.tagField.explicitTag != TagNone {
fi.tag = fi.ti.tagField.explicitTag
if fi.explicitTag != TagNone && fi.explicitTag != fi.tag {
// if there was a tag on the struct field containing this value, it must
// agree with the value's intrinsic tag
return fi, merry.Here(ErrTagConflict).Appendf(`TTLV tag "%s" in tag of %s.%s conflicts with TTLV tag "%s" in %s.%s`, fi.explicitTag, fi.structType.Name(), fi.name, fi.ti.tagField.explicitTag, fi.ti.typ.Name(), fi.ti.tagField.name)
}
}
// pre-calculate the tag for this field. This intentional duplicates
// some of tagForMarshaling(). The value is primarily used in unmarshaling
// where the dynamic value of the field is not needed.
if fi.tag == TagNone {
fi.tag = fi.explicitTag
}
if fi.tag == TagNone {
fi.tag, _ = DefaultRegistry.ParseTag(fi.name)
}
return fi, nil
}
func (ti *typeInfo) getFieldsInfo() error {
if ti.typ.Kind() != reflect.Struct {
return nil
}
for i := 0; i < ti.typ.NumField(); i++ {
fi, err := getFieldInfo(ti.typ, ti.typ.Field(i))
switch {
case err == errSkip: //nolint:errorlint
// skip
case err != nil:
return err
case fi.name == "TTLVTag":
ti.tagField = &fi
default:
ti.valueFields = append(ti.valueFields, fi)
}
}
// verify that multiple fields don't have the same tag
names := map[Tag]string{}
for _, f := range ti.valueFields {
if f.flags.any() {
// ignore any fields
continue
}
tag := f.tag
if tag != TagNone {
if fname, ok := names[tag]; ok {
return merry.Here(ErrTagConflict).Appendf("field resolves to the same tag (%s) as other field (%s)", tag, fname)
}
names[tag] = f.name
}
}
return nil
}
type typeInfo struct {
typ reflect.Type
inferredTag Tag
tagField *fieldInfo
valueFields []fieldInfo
}
const (
fOmitEmpty fieldFlags = 1 << iota
fEnum
fDateTimeExtended
fAny
fBitBask
)
type fieldFlags int
func (f fieldFlags) omitEmpty() bool {
return f&fOmitEmpty != 0
}
func (f fieldFlags) any() bool {
return f&fAny != 0
}
func (f fieldFlags) dateTimeExt() bool {
return f&fDateTimeExtended != 0
}
func (f fieldFlags) enum() bool {
return f&fEnum != 0
}
func (f fieldFlags) bitmask() bool {
return f&fBitBask != 0
}
type fieldInfo struct {
structType reflect.Type
explicitTag, tag Tag
name string
index []int
flags fieldFlags
ti typeInfo
}