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

1253 lines
30 KiB
Go
Raw Normal View History

package ttlv
import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"math/big"
"strconv"
"strings"
"time"
"github.com/ansel1/merry"
"github.com/gemalto/kmip-go/internal/kmiputil"
)
//nolint:deadcode,varcheck
const (
lenTag = 3
lenLen = 4
lenInt = 4
lenLongInt = 8
lenDateTime = 8
lenInterval = 4
lenEnumeration = 4
lenBool = 8
lenHeader = lenTag + 1 + lenLen // tag + type + len
)
var (
ErrValueTruncated = errors.New("value truncated")
ErrHeaderTruncated = errors.New("header truncated")
ErrInvalidLen = errors.New("invalid length")
ErrInvalidType = errors.New("invalid KMIP type")
ErrInvalidTag = errors.New("invalid tag")
)
// TTLV is a byte slice that begins with a TTLV encoded block. The methods of TTLV operate on the
// TTLV value located at the beginning of the slice. Any bytes in the slice after
// the end of the TTLV are ignored. Use TTLV.Next() to return a new slice starting
// after the current value.
//
// TTLV knows how to marshal/unmarshal to XML and JSON, following the KMIP specification
// for those encodings. Wherever possible, normalized names for tags, enum values, and mask
// values will be used, if those values have been registered. Otherwise, compliant
// hex value encoding is used.
type TTLV []byte
// Tag returns the KMIP Tag encoded in the TTLV header.
// Returns empty value if TTLV header is truncated.
func (t TTLV) Tag() Tag {
// don't panic if header is truncated
if len(t) < 3 {
return Tag(0)
}
return Tag(uint32(t[2]) | uint32(t[1])<<8 | uint32(t[0])<<16)
}
// Type returns the KMIP Type encoded in the TTLV header.
// Returns empty value if TTLV header is truncated.
func (t TTLV) Type() Type {
// don't panic if header is truncated
if len(t) < 4 {
return 0
}
return Type(t[3])
}
// Len returns the length encoded in the TTLV header.
// Note: The value segment of the TTLV may be longer since
// some value types encode with padding.
// See FullLen()
//
// Len only reads the header, so it does not validate if the value
// segment's length matches the length in the header. See Valid().
//
// Returns empty value if TTLV header is truncated.
func (t TTLV) Len() int {
// don't panic if header is truncated
if len(t) < lenHeader {
return 0
}
return int(binary.BigEndian.Uint32(t[4:8]))
}
// FullLen returns the expected length of the entire TTLV block (header + value), based
// on the type and len encoded in the header.
//
// Does not check whether the actual value segment matches
// the expected length. See Valid().
//
// panics if type encoded in header is invalid or unrecognized.
func (t TTLV) FullLen() int {
switch t.Type() {
case TypeInterval, TypeDateTime, TypeDateTimeExtended, TypeBoolean, TypeEnumeration, TypeLongInteger, TypeInteger:
return lenHeader + 8
case TypeByteString, TypeTextString:
l := t.Len() + lenHeader
if m := l % 8; m > 0 {
return l + (8 - m)
}
return l
case TypeBigInteger, TypeStructure:
return t.Len() + lenHeader
}
panic(fmt.Sprintf("invalid type: %x", byte(t.Type())))
}
// ValueRaw returns the raw bytes of the value segment of the TTLV.
// It relies on the length segment of the TTLV to know how many bytes
// to read. If the length segment's value is greater than the length of
// the TTLV slice, all the remaining bytes in the slice will be returned,
// but it will not panic.
func (t TTLV) ValueRaw() []byte {
// don't panic if the value is truncated
l := t.Len()
if l == 0 {
return nil
}
if len(t) < lenHeader+l {
return t[lenHeader:]
}
return t[lenHeader : lenHeader+l]
}
// Value returns the value of the TTLV, converted to an idiomatic
// go type.
func (t TTLV) Value() interface{} {
switch t.Type() {
case TypeInterval:
return t.ValueInterval()
case TypeDateTime:
return t.ValueDateTime()
case TypeDateTimeExtended:
return t.ValueDateTimeExtended()
case TypeByteString:
return t.ValueByteString()
case TypeTextString:
return t.ValueTextString()
case TypeBoolean:
return t.ValueBoolean()
case TypeEnumeration:
return t.ValueEnumeration()
case TypeBigInteger:
return t.ValueBigInteger()
case TypeLongInteger:
return t.ValueLongInteger()
case TypeInteger:
return t.ValueInteger()
case TypeStructure:
return t.ValueStructure()
}
panic(fmt.Sprintf("invalid type: %x", byte(t.Type())))
}
// ValueInteger, and the other Value<Type>() variants attempt to decode
// the value segment of the TTLV into a golang value. These methods do
// not check the type of the TTLV. If the value in the TTLV isn't actually
// encoded as expected, the result is undetermined, and it may panic.
func (t TTLV) ValueInteger() int32 {
return int32(binary.BigEndian.Uint32(t.ValueRaw()))
}
func (t TTLV) ValueLongInteger() int64 {
return int64(binary.BigEndian.Uint64(t.ValueRaw()))
}
func (t TTLV) ValueBigInteger() *big.Int {
i := new(big.Int)
unmarshalBigInt(i, unpadBigInt(t.ValueRaw()))
return i
}
func (t TTLV) ValueEnumeration() EnumValue {
return EnumValue(binary.BigEndian.Uint32(t.ValueRaw()))
}
func (t TTLV) ValueBoolean() bool {
return t.ValueRaw()[7] != 0
}
func (t TTLV) ValueTextString() string {
// conveniently, KMIP strings are UTF-8 encoded, as are
// golang strings
return string(t.ValueRaw())
}
func (t TTLV) ValueByteString() []byte {
return t.ValueRaw()
}
func (t TTLV) ValueDateTime() time.Time {
i := t.ValueLongInteger()
if t.Type() == TypeDateTimeExtended {
return time.Unix(0, i*1000).UTC()
}
return time.Unix(i, 0).UTC()
}
func (t TTLV) ValueDateTimeExtended() DateTimeExtended {
i := t.ValueLongInteger()
if t.Type() == TypeDateTimeExtended {
return DateTimeExtended{Time: time.Unix(0, i*1000).UTC()}
}
return DateTimeExtended{Time: time.Unix(i, 0).UTC()}
}
func (t TTLV) ValueInterval() time.Duration {
return time.Duration(binary.BigEndian.Uint32(t.ValueRaw())) * time.Second
}
// ValueStructure returns the raw bytes of the value segment of the TTLV
// as a new TTLV. The value segment of a TTLV Structure is just a concatenation
// of more TTLV values.
func (t TTLV) ValueStructure() TTLV {
return t.ValueRaw()
}
// Valid checks whether a TTLV value is valid. It checks whether the value segment
// is long enough to hold the encoded type. If the type is Structure, it recursively
// checks all the enclosed TTLV values.
//
// Returns nil if valid.
func (t TTLV) Valid() error {
if err := t.ValidHeader(); err != nil {
return err
}
if len(t) < t.FullLen() {
return ErrValueTruncated
}
if t.Type() == TypeStructure {
inner := t.ValueStructure()
for {
if len(inner) == 0 {
break
}
if err := inner.Valid(); err != nil {
return merry.Prepend(err, t.Tag().String())
}
inner = inner.Next()
}
}
return nil
}
func (t TTLV) validTag() bool {
switch t[0] {
case 0x42, 0x54: // valid
return true
}
return false
}
// ValidHeader checks whether the header is valid. It ensures the
// value is long enough to hold a full header, whether the tag
// value is within valid ranges, whether the type is recognized,
// and whether the encoded length is valid for the encoded type.
//
// Returns nil if valid.
func (t TTLV) ValidHeader() error {
if l := len(t); l < lenHeader {
return ErrHeaderTruncated
}
if !t.validTag() {
return ErrInvalidTag
}
switch t.Type() {
case TypeStructure, TypeTextString, TypeByteString:
// any length is valid
case TypeInteger, TypeEnumeration, TypeInterval:
if t.Len() != lenInt {
return ErrInvalidLen
}
case TypeLongInteger, TypeBoolean, TypeDateTime, TypeDateTimeExtended:
if t.Len() != lenLongInt {
return ErrInvalidLen
}
case TypeBigInteger:
if (t.Len() % 8) != 0 {
return ErrInvalidLen
}
default:
return ErrInvalidType
}
return nil
}
func (t TTLV) Next() TTLV {
if t.Valid() != nil {
return nil
}
n := t[t.FullLen():]
if len(n) == 0 {
return nil
}
return n
}
// String renders the TTLV in a human-friendly format using Print().
func (t TTLV) String() string {
var sb strings.Builder
_ = Print(&sb, "", " ", t)
return sb.String()
}
func (t TTLV) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
if len(t) == 0 {
return nil
}
out := struct {
XMLName xml.Name
Tag string `xml:"tag,omitempty,attr"`
Type string `xml:"type,attr,omitempty"`
Value string `xml:"value,attr,omitempty"`
Children []TTLV
Inner []byte `xml:",innerxml"`
}{}
if tagS := t.Tag().String(); strings.HasPrefix(tagS, "0x") {
out.XMLName.Local = "TTLV"
out.Tag = tagS
} else {
out.XMLName.Local = tagS
}
if t.Type() != TypeStructure {
out.Type = t.Type().String()
}
switch t.Type() {
case TypeStructure:
se := xml.StartElement{Name: out.XMLName}
if out.Type != "" {
se.Attr = append(se.Attr, xml.Attr{Name: xml.Name{Local: "type"}, Value: out.Type})
}
err := e.EncodeToken(se)
if err != nil {
return err
}
var attrTag Tag
n := t.ValueStructure()
for len(n) > 0 {
// if the struct contains an attribute name, followed by an
// attribute value, use the name to try and map enumeration values
// to their string variants
if n.Tag() == tagAttributeName {
// try to map the attribute name to a tag
attrTag, _ = DefaultRegistry.ParseTag(kmiputil.NormalizeName(n.ValueTextString()))
}
if n.Tag() == tagAttributeValue && (n.Type() == TypeEnumeration || n.Type() == TypeInteger) {
valAttr := xml.Attr{
Name: xml.Name{Local: "value"},
}
if n.Type() == TypeEnumeration {
valAttr.Value = DefaultRegistry.FormatEnum(attrTag, uint32(n.ValueEnumeration()))
} else {
valAttr.Value = DefaultRegistry.FormatInt(attrTag, n.ValueInteger())
}
err := e.EncodeToken(xml.StartElement{
Name: xml.Name{Local: tagAttributeValue.String()},
Attr: []xml.Attr{
{
Name: xml.Name{Local: "type"},
Value: n.Type().String(),
},
valAttr,
},
})
if err != nil {
return err
}
if err := e.EncodeToken(xml.EndElement{Name: xml.Name{Local: "AttributeValue"}}); err != nil {
return err
}
} else if err := e.Encode(n); err != nil {
return err
}
n = n.Next()
}
return e.EncodeToken(xml.EndElement{Name: out.XMLName})
case TypeInteger:
if enum := DefaultRegistry.EnumForTag(t.Tag()); enum != nil {
out.Value = strings.ReplaceAll(FormatInt(t.ValueInteger(), enum), "|", " ")
} else {
out.Value = strconv.Itoa(int(t.ValueInteger()))
}
case TypeBoolean:
out.Value = strconv.FormatBool(t.ValueBoolean())
case TypeLongInteger:
out.Value = strconv.FormatInt(t.ValueLongInteger(), 10)
case TypeBigInteger:
out.Value = hex.EncodeToString(t.ValueRaw())
case TypeEnumeration:
out.Value = DefaultRegistry.FormatEnum(t.Tag(), uint32(t.ValueEnumeration()))
case TypeTextString:
out.Value = t.ValueTextString()
case TypeByteString:
out.Value = hex.EncodeToString(t.ValueByteString())
case TypeDateTime, TypeDateTimeExtended:
out.Value = t.ValueDateTime().Format(time.RFC3339Nano)
case TypeInterval:
out.Value = strconv.FormatUint(uint64(t.ValueInterval()/time.Second), 10)
}
return e.Encode(&out)
}
type xmltval struct {
XMLName xml.Name
Tag string `xml:"tag,omitempty,attr"`
Type string `xml:"type,attr,omitempty"`
Value string `xml:"value,attr,omitempty"`
Children []*xmltval `xml:",any"`
}
func syntaxError(tag Tag, tp Type, err error) error {
return merry.Prependf(err, "%s: invalid %s", tag.String(), tp.String())
}
func unmarshalXMLTval(buf *encBuf, tval *xmltval, attrTag Tag) error {
if tval.Tag == "" {
tval.Tag = tval.XMLName.Local
}
tag, err := DefaultRegistry.ParseTag(tval.Tag)
if err != nil {
return merry.Prepend(err, "invalid tag")
}
var tp Type
if tval.Type == "" {
tp = TypeStructure
} else {
tp, err = DefaultRegistry.ParseType(tval.Type)
if err != nil {
return merry.Prepend(err, "invalid type")
}
}
syntaxError := func(err error) error {
return syntaxError(tag, tp, merry.HereSkipping(err, 1))
}
switch tp {
case TypeBoolean:
b, err := strconv.ParseBool(tval.Value)
if err != nil {
return syntaxError(merry.Prepend(err, "must be 0, 1, true, or false"))
}
buf.encodeBool(tag, b)
case TypeTextString:
buf.encodeTextString(tag, tval.Value)
case TypeByteString:
// TODO: consider allowing this, just strip off the 0x prefix
// it's not to spec, but its a simple accommodation
if strings.HasPrefix(tval.Value, "0x") {
return syntaxError(errors.New("should not have 0x prefix"))
}
b, err := hex.DecodeString(tval.Value)
if err != nil {
return syntaxError(err)
}
buf.encodeByteString(tag, b)
case TypeInterval:
u, err := strconv.ParseUint(tval.Value, 10, 64)
if err != nil {
return syntaxError(merry.Prepend(err, "must be a number"))
}
buf.encodeInterval(tag, time.Duration(u)*time.Second)
case TypeDateTime, TypeDateTimeExtended:
d, err := time.Parse(time.RFC3339Nano, tval.Value)
if err != nil {
return syntaxError(merry.Prepend(err, "must be ISO8601 format"))
}
if tp == TypeDateTime {
buf.encodeDateTime(tag, d)
} else {
buf.encodeDateTimeExtended(tag, d)
}
case TypeInteger:
enumTag := tag
if tag == tagAttributeValue && attrTag != TagNone {
enumTag = attrTag
}
i, err := DefaultRegistry.ParseInt(enumTag, strings.ReplaceAll(tval.Value, " ", "|"))
if err != nil {
return syntaxError(err)
}
buf.encodeInt(tag, i)
case TypeLongInteger:
i, err := strconv.ParseInt(tval.Value, 10, 64)
if err != nil {
return syntaxError(merry.Prepend(err, "must be number"))
}
buf.encodeLongInt(tag, i)
case TypeBigInteger:
// TODO: consider allowing this, just strip off the 0x prefix
// it's not to spec, but its a simple accommodation
if strings.HasPrefix(tval.Value, "0x") {
return syntaxError(merry.New("should not have 0x prefix"))
}
b, err := hex.DecodeString(tval.Value)
if err != nil {
return syntaxError(err)
}
if len(b)%8 != 0 {
return syntaxError(errors.New("must be multiple of 8 bytes"))
}
n := &big.Int{}
unmarshalBigInt(n, b)
buf.encodeBigInt(tag, n)
case TypeEnumeration:
enumTag := tag
if tag == tagAttributeValue && attrTag != TagNone {
enumTag = attrTag
}
e, err := DefaultRegistry.ParseEnum(enumTag, tval.Value)
if err != nil {
return syntaxError(err)
}
buf.encodeEnum(tag, e)
case TypeStructure:
i := buf.begin(tag, TypeStructure)
var attrTag Tag
for _, c := range tval.Children {
offset := buf.Len()
err := unmarshalXMLTval(buf, c, attrTag)
if err != nil {
return err
}
// check whether the TTLV we just unmarshaled is an AttributeName
ttlv := TTLV(buf.Bytes()[offset:])
if ttlv.Tag() == tagAttributeName {
// try to parse the value as a tag name, which may be used later
// when unmarshaling the AttributeValue
attrTag, _ = DefaultRegistry.ParseTag(kmiputil.NormalizeName(ttlv.ValueTextString()))
}
}
buf.end(i)
}
return nil
}
func (t *TTLV) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var out xmltval
err := d.DecodeElement(&out, &start)
if err != nil {
return err
}
var buf encBuf
err = unmarshalXMLTval(&buf, &out, TagNone)
if err != nil {
return err
}
*t = buf.Bytes()
return nil
}
var (
maxJSONInt = int64(1) << 52
maxJSONBigInt = big.NewInt(maxJSONInt)
)
func (t *TTLV) UnmarshalJSON(b []byte) error {
return t.unmarshalJSON(b, TagNone)
}
func (t *TTLV) unmarshalJSON(b []byte, attrTag Tag) error {
if len(b) == 0 {
return nil
}
type tval struct {
Tag string `json:"tag"`
Type string `json:"type,omitempty"`
Value json.RawMessage `json:"value"`
}
var ttl tval
err := json.Unmarshal(b, &ttl)
if err != nil {
return err
}
tag, err := DefaultRegistry.ParseTag(ttl.Tag)
if err != nil {
return merry.Prepend(err, "invalid tag")
}
var tp Type
var v interface{}
if ttl.Type == "" {
tp = TypeStructure
} else {
tp, err = DefaultRegistry.ParseType(ttl.Type)
if err != nil {
return merry.Prepend(err, "invalid type")
}
// for all types besides Structure, unmarshal
// value into interface{}
err = json.Unmarshal(ttl.Value, &v)
if err != nil {
return err
}
}
syntaxError := func(err error) error {
return syntaxError(tag, tp, merry.HereSkipping(err, 1))
}
// performance note: for some types, like int, long int, and interval,
// we are essentially decoding from binary into a go native type
// then re-encoding to binary. I benchmarked skipping this step
// and transferring the bytes directly from the decoded hex strings
// to the binary TTLV. It turned out not be faster, and added an
// additional set of paths to test. Wasn't worth it.
enc := encBuf{}
switch tp {
case TypeBoolean:
switch tv := v.(type) {
default:
return syntaxError(errors.New("must be boolean or hex string"))
case bool:
enc.encodeBool(tag, tv)
case string:
switch tv {
default:
return syntaxError(errors.New("hex string for Boolean value must be either 0x0000000000000001 (true) or 0x0000000000000000 (false)"))
case "0x0000000000000001":
enc.encodeBool(tag, true)
case "0x0000000000000000":
enc.encodeBool(tag, false)
}
}
case TypeTextString:
switch tv := v.(type) {
default:
return syntaxError(errors.New("must be string"))
case string:
enc.encodeTextString(tag, tv)
}
case TypeByteString:
switch tv := v.(type) {
default:
return syntaxError(errors.New("must be hex string"))
case string:
// TODO: consider allowing this, just strip off the 0x prefix
// it's not to spec, but its a simple accommodation
if strings.HasPrefix(tv, "0x") {
return syntaxError(errors.New("should not have 0x prefix"))
}
b, err := hex.DecodeString(tv)
if err != nil {
return syntaxError(err)
}
enc.encodeByteString(tag, b)
}
case TypeInterval:
switch tv := v.(type) {
default:
return syntaxError(errors.New("must be number or hex string"))
case string:
b, err := kmiputil.ParseHexValue(tv, 4)
if err != nil {
return syntaxError(err)
}
if b == nil {
return syntaxError(errors.New("hex value must start with 0x"))
}
enc.encodeInterval(tag, time.Duration(kmiputil.DecodeUint32(b))*time.Second)
case float64:
enc.encodeInterval(tag, time.Duration(tv)*time.Second)
}
case TypeDateTime, TypeDateTimeExtended:
switch tv := v.(type) {
default:
return syntaxError(errors.New("must be string"))
case string:
var tm time.Time
b, err := kmiputil.ParseHexValue(tv, 8)
if err != nil {
return syntaxError(err)
}
if b != nil {
u := kmiputil.DecodeUint64(b)
if tp == TypeDateTime {
tm = time.Unix(int64(u), 0)
} else {
tm = tm.Add(time.Duration(u) * time.Microsecond)
}
} else {
var err error
tm, err = time.Parse(time.RFC3339Nano, tv)
if err != nil {
return syntaxError(merry.Prepend(err, "must be ISO8601 format"))
}
}
if tp == TypeDateTime {
enc.encodeDateTime(tag, tm)
} else {
enc.encodeDateTimeExtended(tag, tm)
}
}
case TypeInteger:
switch tv := v.(type) {
default:
return syntaxError(errors.New("must be number, hex string, or mask value name"))
case string:
enumTag := tag
if tag == tagAttributeValue && attrTag != TagNone {
enumTag = attrTag
}
i, err := DefaultRegistry.ParseInt(enumTag, tv)
if err != nil {
return syntaxError(err)
}
enc.encodeInt(tag, i)
case float64:
enc.encodeInt(tag, int32(tv))
}
case TypeLongInteger:
switch tv := v.(type) {
default:
return syntaxError(errors.New("must be number or hex string"))
case string:
b, err := kmiputil.ParseHexValue(tv, 8)
if err != nil {
return syntaxError(err)
}
if b == nil {
return syntaxError(errors.New("hex value must start with 0x"))
}
enc.encodeLongInt(tag, int64(kmiputil.DecodeUint64(b)))
case float64:
enc.encodeLongInt(tag, int64(tv))
}
case TypeBigInteger:
switch tv := v.(type) {
default:
return syntaxError(errors.New("must be number or hex string"))
case string:
if !strings.HasPrefix(tv, "0x") {
return syntaxError(errors.New("hex value must start with 0x"))
}
b, err := hex.DecodeString(tv[2:])
if err != nil {
return syntaxError(err)
}
if len(b)%8 != 0 {
return syntaxError(errors.New("must be multiple of 8 bytes (16 hex characters)"))
}
i := &big.Int{}
unmarshalBigInt(i, unpadBigInt(b))
enc.encodeBigInt(tag, i)
case float64:
enc.encodeBigInt(tag, big.NewInt(int64(tv)))
}
case TypeEnumeration:
switch tv := v.(type) {
default:
return syntaxError(errors.New("must be number or string"))
case string:
enumTag := tag
if tag == tagAttributeValue && attrTag != TagNone {
enumTag = attrTag
}
u, err := DefaultRegistry.ParseEnum(enumTag, tv)
if err != nil {
return syntaxError(err)
}
enc.encodeEnum(tag, u)
case float64:
enc.encodeEnum(tag, uint32(tv))
}
case TypeStructure:
// unmarshal each sub value
var children []json.RawMessage
err := json.Unmarshal(ttl.Value, &children)
if err != nil {
return syntaxError(err)
}
var scratch TTLV
s := enc.begin(tag, TypeStructure)
var attrTag Tag
for _, c := range children {
err := (&scratch).unmarshalJSON(c, attrTag)
if err != nil {
return syntaxError(err)
}
if tagAttributeName == scratch.Tag() {
attrTag, _ = DefaultRegistry.ParseTag(kmiputil.NormalizeName(scratch.ValueTextString()))
}
_, _ = enc.Write(scratch)
}
enc.end(s)
}
*t = enc.Bytes()
return nil
}
func (t TTLV) MarshalJSON() ([]byte, error) {
if len(t) == 0 {
return []byte("null"), nil
}
if err := t.Valid(); err != nil {
return nil, err
}
var sb strings.Builder
sb.WriteString(`{"tag":"`)
sb.WriteString(t.Tag().String())
if t.Type() != TypeStructure {
sb.WriteString(`","type":"`)
sb.WriteString(t.Type().String())
}
sb.WriteString(`","value":`)
switch t.Type() {
case TypeBoolean:
if t.ValueBoolean() {
sb.WriteString("true")
} else {
sb.WriteString("false")
}
case TypeEnumeration:
sb.WriteString(`"`)
sb.WriteString(DefaultRegistry.FormatEnum(t.Tag(), uint32(t.ValueEnumeration())))
sb.WriteString(`"`)
case TypeInteger:
if enum := DefaultRegistry.EnumForTag(t.Tag()); enum != nil {
sb.WriteString(`"`)
sb.WriteString(FormatInt(t.ValueInteger(), enum))
sb.WriteString(`"`)
} else {
sb.WriteString(strconv.Itoa(int(t.ValueInteger())))
}
case TypeLongInteger:
v := t.ValueLongInteger()
if v <= -maxJSONInt || v >= maxJSONInt {
sb.WriteString(`"0x`)
sb.WriteString(hex.EncodeToString(t.ValueRaw()))
sb.WriteString(`"`)
} else {
sb.WriteString(strconv.FormatInt(v, 10))
}
case TypeBigInteger:
v := t.ValueBigInteger()
if v.IsInt64() && v.CmpAbs(maxJSONBigInt) < 0 {
val, err := v.MarshalJSON()
if err != nil {
return nil, err
}
sb.Write(val)
} else {
sb.WriteString(`"0x`)
sb.WriteString(hex.EncodeToString(t.ValueRaw()))
sb.WriteString(`"`)
}
case TypeTextString:
val, err := json.Marshal(t.ValueTextString())
if err != nil {
return nil, err
}
sb.Write(val)
case TypeByteString:
sb.WriteString(`"`)
sb.WriteString(hex.EncodeToString(t.ValueRaw()))
sb.WriteString(`"`)
case TypeStructure:
sb.WriteString("[")
c := t.ValueStructure()
var attrTag Tag
for len(c) > 0 {
// if the struct contains an attribute name, followed by an
// attribute value, use the name to try and map enumeration values
// to their string variants
if c.Tag() == tagAttributeName {
// try to map the attribute name to a tag
attrTag, _ = DefaultRegistry.ParseTag(kmiputil.NormalizeName(c.ValueTextString()))
}
switch {
case c.Tag() == tagAttributeValue && c.Type() == TypeEnumeration:
sb.WriteString(`{"tag":"AttributeValue","type":"Enumeration","value":"`)
sb.WriteString(DefaultRegistry.FormatEnum(attrTag, uint32(c.ValueEnumeration())))
sb.WriteString(`"}`)
case c.Tag() == tagAttributeValue && c.Type() == TypeInteger:
sb.WriteString(`{"tag":"AttributeValue","type":"Integer","value":`)
if enum := DefaultRegistry.EnumForTag(attrTag); enum != nil {
sb.WriteString(`"`)
sb.WriteString(FormatInt(c.ValueInteger(), enum))
sb.WriteString(`"`)
} else {
sb.WriteString(strconv.Itoa(int(c.ValueInteger())))
}
sb.WriteString(`}`)
default:
v, err := c.MarshalJSON()
if err != nil {
return nil, err
}
sb.Write(v)
}
c = c.Next()
if len(c) > 0 {
sb.WriteString(",")
}
}
sb.WriteString("]")
case TypeDateTime, TypeDateTimeExtended:
val, err := t.ValueDateTime().MarshalJSON()
if err != nil {
return nil, err
}
sb.Write(val)
case TypeInterval:
sb.WriteString(strconv.FormatUint(uint64(binary.BigEndian.Uint32(t.ValueRaw())), 10))
}
sb.WriteString(`}`)
return []byte(sb.String()), nil
}
// UnmarshalTTLV implements ttlv.Unmarshaler. Unmarshaling a TTLV
// into another TTLV will allocate a new slice, and copy the bytes
// from the source TTLV into the new slice.
func (t *TTLV) UnmarshalTTLV(_ *Decoder, ttlv TTLV) error {
if ttlv == nil {
*t = nil
return nil
}
if l := len(ttlv); len(*t) < l {
*t = make([]byte, l)
} else {
*t = (*t)[:l]
}
copy(*t, ttlv)
return nil
}
// Print pretty prints the TTLV value in a human-readable format. This
// format cannot be parsed back into TTLV.
//
// Print is safe to call on any TTLV value, even one which is valid,
// not correctly encoded, or not actually TTLV bytes. Print will
// try and print as much of the value as it can decode, and return
// a parsing error.
func Print(w io.Writer, prefix, indent string, t TTLV) error {
currIndent := prefix
tag := t.Tag()
typ := t.Type()
l := t.Len()
if _, err := fmt.Fprintf(w, "%s%v (%s/%d):", currIndent, tag, typ.String(), l); err != nil {
return err
}
if verr := t.Valid(); verr != nil {
if _, err := fmt.Fprintf(w, " (%s)", verr.Error()); err != nil {
return err
}
if errors.Is(verr, ErrHeaderTruncated) {
// print the err, and as much of the truncated header as we have
if _, err := fmt.Fprintf(w, " %#x", []byte(t)); err != nil {
return err
}
} else {
// Something is wrong with the value. Print the error, and the value
if _, err := fmt.Fprintf(w, " %#x", t.ValueRaw()); err != nil {
return err
}
}
return verr
}
switch typ {
case TypeByteString:
if _, err := fmt.Fprintf(w, " %#x", t.ValueByteString()); err != nil {
return err
}
case TypeStructure:
currIndent += indent
s := t.ValueStructure()
for s != nil {
if _, err := fmt.Fprint(w, "\n"); err != nil {
return err
}
if err := Print(w, currIndent, indent, s); err != nil {
// an error means we've hit invalid bytes in the stream
// there are no markers to pick back up again, so we have to give up
return err
}
s = s.Next()
}
case TypeEnumeration:
if _, err := fmt.Fprint(w, " ", DefaultRegistry.FormatEnum(tag, uint32(t.ValueEnumeration()))); err != nil {
return err
}
case TypeInteger:
if enum := DefaultRegistry.EnumForTag(tag); enum != nil {
if _, err := fmt.Fprint(w, " ", FormatInt(t.ValueInteger(), enum)); err != nil {
return err
}
} else {
if _, err := fmt.Fprintf(w, " %v", t.Value()); err != nil {
return err
}
}
default:
if _, err := fmt.Fprintf(w, " %v", t.Value()); err != nil {
return err
}
}
return nil
}
// PrintPrettyHex pretty prints the TTLV value as hex values, with spacers between
// the segments of the TTLV. Like Print, this is safe to call even on invalid TTLV
// values. An error will only be returned if there is a problem with the writer.
func PrintPrettyHex(w io.Writer, prefix, indent string, t TTLV) error {
currIndent := prefix
b := []byte(t)
if t.ValidHeader() != nil {
// print the entire value as hex, un-indented
_, err := fmt.Fprint(w, hex.EncodeToString(t))
return err
}
if t.Valid() != nil {
// print the header, then dump the rest of the value on the next line
_, err := fmt.Fprintf(w, "%s%x | %x | %x\n%x", currIndent, b[0:3], b[3:4], b[4:8], b[8:])
return err
}
if t.Type() != TypeStructure {
// print the entire value
_, err := fmt.Fprintf(w, "%s%x | %x | %x | %x", currIndent, b[0:3], b[3:4], b[4:8], b[lenHeader:t.FullLen()])
return err
}
// for structures, print the header, then print the body
// indented
if _, err := fmt.Fprintf(w, "%s%x | %x | %x", currIndent, b[0:3], b[3:4], b[4:8]); err != nil {
return err
}
currIndent += indent
s := t.ValueStructure()
for s != nil {
if _, werr := fmt.Fprint(w, "\n"); werr != nil {
return werr
}
if err := PrintPrettyHex(w, currIndent, indent, s); err != nil {
// an error means we've hit invalid bytes in the stream
// there are no markers to pick back up again, so we have to give up
return err
}
s = s.Next()
}
return nil
}
var one = big.NewInt(1)
func unpadBigInt(data []byte) []byte {
if len(data) < 2 {
return data
}
i := 0
for ; (i + 1) < len(data); i++ {
switch {
// first two cases keep looping, skipping pad bytes
// pad bytes are all the same bit as
// the first bit of the next byte
case data[i] == 0xFF && data[i+1]&0x80 > 1:
case data[i] == 0x00 && data[i+1]&0x80 == 0:
default:
// we've hit a byte that doesn't match the pad pattern
return data[i:]
}
}
// we've reached the last byte
return data[i:]
}
// unmarshalBigInt sets the value of n to the big-endian two's complement
// value stored in the given data. If data[0]&80 != 0, the number
// is negative. If data is empty, the result will be 0.
func unmarshalBigInt(n *big.Int, data []byte) {
n.SetBytes(data)
if len(data) > 0 && data[0]&0x80 > 0 {
// first byte is 1, so number is negative.
// left shifting 1 by the length in bits of the data
// then subtracting the value from that gives us the
// twos complement.
// e.g. if the value is 111111111, then 1 << 8 gives us
// 1000000000
// and 100000000 - 11111111 = 00000001
n.Sub(n, new(big.Int).Lsh(one, uint(len(data))*8))
}
}
// Hex2bytes converts hex string to bytes. Any non-hex characters in the string are stripped first.
// panics on error
func Hex2bytes(s string) []byte {
// strip non hex bytes
s = strings.Map(func(r rune) rune {
switch {
case r >= '0' && r <= '9':
case r >= 'A' && r <= 'F':
case r >= 'a' && r <= 'f':
default:
return -1 // drop
}
return r
}, s)
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}