package ttlv import ( "fmt" "strconv" "strings" "github.com/ansel1/merry" "github.com/gemalto/kmip-go/internal/kmiputil" ) // FormatType formats a byte as a KMIP Type string, // as described in the KMIP Profiles spec. If the value is registered, // the normalized name of the value will be returned. // // Otherwise, a 1 byte hex string is returned, but this is not // technically a valid encoding for types in the JSON and XML encoding // specs. Hex values Should only be used for debugging. Examples: // // - Integer // - 0x42 func FormatType(b byte, enumMap EnumMap) string { if enumMap != nil { if s, ok := enumMap.Name(uint32(b)); ok { return s } } return fmt.Sprintf("%#02x", b) } // FormatTag formats an uint32 as a KMIP Tag string, // as described in the KMIP Profiles spec. If the value is registered, // the normalized name of the value will be returned. Otherwise, a // 3 byte hex string is returned. Examples: // // - ActivationDate // - 0x420001 func FormatTag(v uint32, enumMap EnumMap) string { if enumMap != nil { if s, ok := enumMap.Name(v); ok { return s } } return fmt.Sprintf("%#06x", v) } // FormatTagCanonical formats an uint32 as a canonical Tag name // from the KMIP spec. If the value is registered, // the canonical name of the value will be returned. Otherwise, a // 3 byte hex string is returned. Examples: // // - Activation Date // - 0x420001 // // Canonical tag names are used in the AttributeName of Attribute structs. func FormatTagCanonical(v uint32, enumMap EnumMap) string { if enumMap != nil { if s, ok := enumMap.CanonicalName(v); ok { return s } } return fmt.Sprintf("%#06x", v) } // FormatEnum formats an uint32 as a KMIP Enumeration string, // as described in the KMIP Profiles spec. If the value is registered, // the normalized name of the value will be returned. Otherwise, a // four byte hex string is returned. Examples: // // - SymmetricKey // - 0x00000002 func FormatEnum(v uint32, enumMap EnumMap) string { if enumMap != nil { if s, ok := enumMap.Name(v); ok { return s } } return fmt.Sprintf("%#08x", v) } // FormatInt formats an integer as a KMIP bitmask string, as // described in the KMIP Profiles spec for JSON under // the "Special case for Masks" section. Examples: // // - 0x0000100c // - Encrypt|Decrypt|CertificateSign // - CertificateSign|0x00000004|0x0000008 // - CertificateSign|0x0000000c func FormatInt(i int32, enumMap EnumMap) string { if enumMap == nil { return fmt.Sprintf("%#08x", i) } values := enumMap.Values() if len(values) == 0 { return fmt.Sprintf("%#08x", i) } v := uint32(i) // bitmask // decompose mask into the names of set flags, concatenated by pipe // if remaining value (minus registered flags) is not zero, append // the remaining value as hex. var sb strings.Builder for _, v1 := range values { if v1&v == v1 { if name, ok := enumMap.Name(v1); ok { if sb.Len() > 0 { sb.WriteString("|") } sb.WriteString(name) v ^= v1 } } if v == 0 { break } } if v != 0 { if sb.Len() > 0 { sb.WriteString("|") } _, _ = fmt.Fprintf(&sb, "%#08x", v) } return sb.String() } // ParseEnum parses a string into a uint32 according to the rules // in the KMIP Profiles regarding encoding enumeration values. // See FormatEnum for examples of the formats which can be parsed. // It will also parse numeric strings. Examples: // // ParseEnum("UnableToCancel", registry.EnumForTag(TagCancellationResult)) // ParseEnum("0x00000002") // ParseEnum("2") // // Returns ErrInvalidHexString if the string is invalid hex, or // if the hex value is less than 1 byte or more than 4 bytes (ignoring // leading zeroes). // // Returns ErrUnregisteredEnumName if string value is not a // registered enum value name. func ParseEnum(s string, enumMap EnumMap) (uint32, error) { u, err := strconv.ParseUint(s, 10, 32) if err == nil { // it was a raw number return uint32(u), nil } v, err := parseHexOrName(s, 4, enumMap) if err != nil { return 0, merry.Here(err) } return v, nil } // ParseInt parses a string into an int32 according the rules // in the KMIP Profiles regarding encoding integers, including // the special rules for bitmasks. See FormatInt for examples // of the formats which can be parsed. // // Returns ErrInvalidHexString if the string is invalid hex, or // if the hex value is less than 1 byte or more than 4 bytes (ignoring // leading zeroes). // // Returns ErrUnregisteredEnumName if string value is not a // registered enum value name. func ParseInt(s string, enumMap EnumMap) (int32, error) { i, err := strconv.ParseInt(s, 10, 32) if err == nil { // it was a raw number return int32(i), nil } if !strings.ContainsAny(s, "| ") { v, err := parseHexOrName(s, 4, enumMap) if err != nil { return 0, merry.Here(err) } return int32(v), nil } // split values, look up each, and recombine s = strings.ReplaceAll(s, "|", " ") parts := strings.Split(s, " ") var v uint32 for _, part := range parts { if len(part) == 0 { continue } i, err := parseHexOrName(part, 4, enumMap) if err != nil { return 0, merry.Here(err) } v |= i } return int32(v), nil } func parseHexOrName(s string, max int, enumMap EnumMap) (uint32, error) { b, err := kmiputil.ParseHexValue(s, max) if err != nil { return 0, err } if b != nil { return kmiputil.DecodeUint32(b), nil } if enumMap != nil { if v, ok := enumMap.Value(s); ok { return v, nil } } return 0, merry.Append(ErrUnregisteredEnumName, s) } // ParseTag parses a string into Tag according the rules // in the KMIP Profiles regarding encoding tag values. // See FormatTag for examples of the formats which can be parsed. // // Returns ErrInvalidHexString if the string is invalid hex, or // if the hex value is less than 1 byte or more than 3 bytes (ignoring // leading zeroes). // // Returns ErrUnregisteredEnumName if string value is not a // registered enum value name. func ParseTag(s string, enumMap EnumMap) (Tag, error) { v, err := parseHexOrName(s, 3, enumMap) if err != nil { return 0, merry.Here(err) } return Tag(v), nil } // ParseType parses a string into Type according the rules // in the KMIP Profiles regarding encoding type values. // See FormatType for examples of the formats which can be parsed. // This also supports parsing a hex string type (e.g. "0x01"), though // this is not technically a valid encoding of a type in the spec. // // Returns ErrInvalidHexString if the string is invalid hex, or // if the hex value is less than 1 byte or more than 3 bytes (ignoring // leading zeroes). // // Returns ErrUnregisteredEnumName if string value is not a // registered enum value name. func ParseType(s string, enumMap EnumMap) (Type, error) { b, err := kmiputil.ParseHexValue(s, 1) if err != nil { return 0, merry.Here(err) } if b != nil { return Type(b[0]), nil } if enumMap != nil { if v, ok := enumMap.Value(s); ok { return Type(v), nil } } return 0, merry.Here(ErrUnregisteredEnumName).Append(s) } // EnumMap defines a set of named enumeration values. Canonical names should // be the name from the spec. Names should be in the normalized format // described in the KMIP spec (see NormalizeName()). // // Value enumerations are used for encoding and decoding KMIP Enumeration values, // KMIP Integer bitmask values, Types, and Tags. type EnumMap interface { // Name returns the normalized name for a value, e.g. AttributeName. // If the name is not registered, it returns "", false. Name(v uint32) (string, bool) // CanonicalName returns the canonical name for the value from the spec, // e.g. Attribute Name. // If the name is not registered, it returns "", false CanonicalName(v uint32) (string, bool) // Value returns the value registered for the name argument. If there is // no name registered for this value, it returns 0, false. // The name argument may be the canonical name (e.g. "Cryptographic Algorithm") or // the normalized name (e.g. "CryptographicAlgorithm"). Value(name string) (uint32, bool) // Values returns the complete set of registered values. The order // they are returned in will be the order they are encoded in when // encoding bitmasks as strings. Values() []uint32 // Bitmask returns true if this is an enumeration of bitmask flags. Bitmask() bool }