| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Package tls implements functionality for dealing with TLS-encoded data, |
| // as defined in RFC 5246. This includes parsing and generation of TLS-encoded |
| // data, together with utility functions for dealing with the DigitallySigned |
| // TLS type. |
| package tls |
| |
| import ( |
| "bytes" |
| "encoding/binary" |
| "fmt" |
| "reflect" |
| "strconv" |
| "strings" |
| ) |
| |
| // This file holds utility functions for TLS encoding/decoding data |
| // as per RFC 5246 section 4. |
| |
| // A structuralError suggests that the TLS data is valid, but the Go type |
| // which is receiving it doesn't match. |
| type structuralError struct { |
| field string |
| msg string |
| } |
| |
| func (e structuralError) Error() string { |
| var prefix string |
| if e.field != "" { |
| prefix = e.field + ": " |
| } |
| return "tls: structure error: " + prefix + e.msg |
| } |
| |
| // A syntaxError suggests that the TLS data is invalid. |
| type syntaxError struct { |
| field string |
| msg string |
| } |
| |
| func (e syntaxError) Error() string { |
| var prefix string |
| if e.field != "" { |
| prefix = e.field + ": " |
| } |
| return "tls: syntax error: " + prefix + e.msg |
| } |
| |
| // Uint24 is an unsigned 3-byte integer. |
| type Uint24 uint32 |
| |
| // Enum is an unsigned integer. |
| type Enum uint64 |
| |
| var ( |
| uint8Type = reflect.TypeOf(uint8(0)) |
| uint16Type = reflect.TypeOf(uint16(0)) |
| uint24Type = reflect.TypeOf(Uint24(0)) |
| uint32Type = reflect.TypeOf(uint32(0)) |
| uint64Type = reflect.TypeOf(uint64(0)) |
| enumType = reflect.TypeOf(Enum(0)) |
| ) |
| |
| // Unmarshal parses the TLS-encoded data in b and uses the reflect package to |
| // fill in an arbitrary value pointed at by val. Because Unmarshal uses the |
| // reflect package, the structs being written to must use exported fields |
| // (upper case names). |
| // |
| // The mappings between TLS types and Go types is as follows; some fields |
| // must have tags (to indicate their encoded size). |
| // |
| // TLS Go Required Tags |
| // opaque byte / uint8 |
| // uint8 byte / uint8 |
| // uint16 uint16 |
| // uint24 tls.Uint24 |
| // uint32 uint32 |
| // uint64 uint64 |
| // enum tls.Enum size:S or maxval:N |
| // Type<N,M> []Type minlen:N,maxlen:M |
| // opaque[N] [N]byte / [N]uint8 |
| // uint8[N] [N]byte / [N]uint8 |
| // struct { } struct { } |
| // select(T) { |
| // case e1: Type *T selector:Field,val:e1 |
| // } |
| // |
| // TLS variants (RFC 5246 s4.6.1) are only supported when the value of the |
| // associated enumeration type is available earlier in the same enclosing |
| // struct, and each possible variant is marked with a selector tag (to |
| // indicate which field selects the variants) and a val tag (to indicate |
| // what value of the selector picks this particular field). |
| // |
| // For example, a TLS structure: |
| // |
| // enum { e1(1), e2(2) } EnumType; |
| // struct { |
| // EnumType sel; |
| // select(sel) { |
| // case e1: uint16 |
| // case e2: uint32 |
| // } data; |
| // } VariantItem; |
| // |
| // would have a corresponding Go type: |
| // |
| // type VariantItem struct { |
| // Sel tls.Enum `tls:"maxval:2"` |
| // Data16 *uint16 `tls:"selector:Sel,val:1"` |
| // Data32 *uint32 `tls:"selector:Sel,val:2"` |
| // } |
| // |
| // TLS fixed-length vectors of types other than opaque or uint8 are not supported. |
| // |
| // For TLS variable-length vectors that are themselves used in other vectors, |
| // create a single-field structure to represent the inner type. For example, for: |
| // |
| // opaque InnerType<1..65535>; |
| // struct { |
| // InnerType inners<1,65535>; |
| // } Something; |
| // |
| // convert to: |
| // |
| // type InnerType struct { |
| // Val []byte `tls:"minlen:1,maxlen:65535"` |
| // } |
| // type Something struct { |
| // Inners []InnerType `tls:"minlen:1,maxlen:65535"` |
| // } |
| // |
| // If the encoded value does not fit in the Go type, Unmarshal returns a parse error. |
| func Unmarshal(b []byte, val interface{}) ([]byte, error) { |
| return UnmarshalWithParams(b, val, "") |
| } |
| |
| // UnmarshalWithParams allows field parameters to be specified for the |
| // top-level element. The form of the params is the same as the field tags. |
| func UnmarshalWithParams(b []byte, val interface{}, params string) ([]byte, error) { |
| info, err := fieldTagToFieldInfo(params, "") |
| if err != nil { |
| return nil, err |
| } |
| // The passed in interface{} is a pointer (to allow the value to be written |
| // to); extract the pointed-to object as a reflect.Value, so parseField |
| // can do various introspection things. |
| v := reflect.ValueOf(val).Elem() |
| offset, err := parseField(v, b, 0, info) |
| if err != nil { |
| return nil, err |
| } |
| return b[offset:], nil |
| } |
| |
| // Return the number of bytes needed to encode values up to (and including) x. |
| func byteCount(x uint64) uint { |
| switch { |
| case x < 0x100: |
| return 1 |
| case x < 0x10000: |
| return 2 |
| case x < 0x1000000: |
| return 3 |
| case x < 0x100000000: |
| return 4 |
| case x < 0x10000000000: |
| return 5 |
| case x < 0x1000000000000: |
| return 6 |
| case x < 0x100000000000000: |
| return 7 |
| default: |
| return 8 |
| } |
| } |
| |
| type fieldInfo struct { |
| count uint // Number of bytes |
| countSet bool |
| minlen uint64 // Only relevant for slices |
| maxlen uint64 // Only relevant for slices |
| selector string // Only relevant for select sub-values |
| val uint64 // Only relevant for select sub-values |
| name string // Used for better error messages |
| } |
| |
| func (i *fieldInfo) fieldName() string { |
| if i == nil { |
| return "" |
| } |
| return i.name |
| } |
| |
| // Given a tag string, return a fieldInfo describing the field. |
| func fieldTagToFieldInfo(str string, name string) (*fieldInfo, error) { |
| var info *fieldInfo |
| // Iterate over clauses in the tag, ignoring any that don't parse properly. |
| for _, part := range strings.Split(str, ",") { |
| switch { |
| case strings.HasPrefix(part, "maxval:"): |
| if v, err := strconv.ParseUint(part[7:], 10, 64); err == nil { |
| info = &fieldInfo{count: byteCount(v), countSet: true} |
| } |
| case strings.HasPrefix(part, "size:"): |
| if sz, err := strconv.ParseUint(part[5:], 10, 32); err == nil { |
| info = &fieldInfo{count: uint(sz), countSet: true} |
| } |
| case strings.HasPrefix(part, "maxlen:"): |
| v, err := strconv.ParseUint(part[7:], 10, 64) |
| if err != nil { |
| continue |
| } |
| if info == nil { |
| info = &fieldInfo{} |
| } |
| info.count = byteCount(v) |
| info.countSet = true |
| info.maxlen = v |
| case strings.HasPrefix(part, "minlen:"): |
| v, err := strconv.ParseUint(part[7:], 10, 64) |
| if err != nil { |
| continue |
| } |
| if info == nil { |
| info = &fieldInfo{} |
| } |
| info.minlen = v |
| case strings.HasPrefix(part, "selector:"): |
| if info == nil { |
| info = &fieldInfo{} |
| } |
| info.selector = part[9:] |
| case strings.HasPrefix(part, "val:"): |
| v, err := strconv.ParseUint(part[4:], 10, 64) |
| if err != nil { |
| continue |
| } |
| if info == nil { |
| info = &fieldInfo{} |
| } |
| info.val = v |
| } |
| } |
| if info != nil { |
| info.name = name |
| if info.selector == "" { |
| if info.count < 1 { |
| return nil, structuralError{name, "field of unknown size in " + str} |
| } else if info.count > 8 { |
| return nil, structuralError{name, "specified size too large in " + str} |
| } else if info.minlen > info.maxlen { |
| return nil, structuralError{name, "specified length range inverted in " + str} |
| } else if info.val > 0 { |
| return nil, structuralError{name, "specified selector value but not field in " + str} |
| } |
| } |
| } else if name != "" { |
| info = &fieldInfo{name: name} |
| } |
| return info, nil |
| } |
| |
| // Check that a value fits into a field described by a fieldInfo structure. |
| func (i fieldInfo) check(val uint64, fldName string) error { |
| if val >= (1 << (8 * i.count)) { |
| return structuralError{fldName, fmt.Sprintf("value %d too large for size", val)} |
| } |
| if i.maxlen != 0 { |
| if val < i.minlen { |
| return structuralError{fldName, fmt.Sprintf("value %d too small for minimum %d", val, i.minlen)} |
| } |
| if val > i.maxlen { |
| return structuralError{fldName, fmt.Sprintf("value %d too large for maximum %d", val, i.maxlen)} |
| } |
| } |
| return nil |
| } |
| |
| // readVarUint reads an big-endian unsigned integer of the given size in |
| // bytes. |
| func readVarUint(data []byte, info *fieldInfo) (uint64, error) { |
| if info == nil || !info.countSet { |
| return 0, structuralError{info.fieldName(), "no field size information available"} |
| } |
| if len(data) < int(info.count) { |
| return 0, syntaxError{info.fieldName(), "truncated variable-length integer"} |
| } |
| var result uint64 |
| for i := uint(0); i < info.count; i++ { |
| result = (result << 8) | uint64(data[i]) |
| } |
| if err := info.check(result, info.name); err != nil { |
| return 0, err |
| } |
| return result, nil |
| } |
| |
| // parseField is the main parsing function. Given a byte slice and an offset |
| // (in bytes) into the data, it will try to parse a suitable ASN.1 value out |
| // and store it in the given Value. |
| func parseField(v reflect.Value, data []byte, initOffset int, info *fieldInfo) (int, error) { |
| offset := initOffset |
| rest := data[offset:] |
| |
| fieldType := v.Type() |
| // First look for known fixed types. |
| switch fieldType { |
| case uint8Type: |
| if len(rest) < 1 { |
| return offset, syntaxError{info.fieldName(), "truncated uint8"} |
| } |
| v.SetUint(uint64(rest[0])) |
| offset++ |
| return offset, nil |
| case uint16Type: |
| if len(rest) < 2 { |
| return offset, syntaxError{info.fieldName(), "truncated uint16"} |
| } |
| v.SetUint(uint64(binary.BigEndian.Uint16(rest))) |
| offset += 2 |
| return offset, nil |
| case uint24Type: |
| if len(rest) < 3 { |
| return offset, syntaxError{info.fieldName(), "truncated uint24"} |
| } |
| v.SetUint(uint64(data[0])<<16 | uint64(data[1])<<8 | uint64(data[2])) |
| offset += 3 |
| return offset, nil |
| case uint32Type: |
| if len(rest) < 4 { |
| return offset, syntaxError{info.fieldName(), "truncated uint32"} |
| } |
| v.SetUint(uint64(binary.BigEndian.Uint32(rest))) |
| offset += 4 |
| return offset, nil |
| case uint64Type: |
| if len(rest) < 8 { |
| return offset, syntaxError{info.fieldName(), "truncated uint64"} |
| } |
| v.SetUint(uint64(binary.BigEndian.Uint64(rest))) |
| offset += 8 |
| return offset, nil |
| } |
| |
| // Now deal with user-defined types. |
| switch v.Kind() { |
| case enumType.Kind(): |
| // Assume that anything of the same kind as Enum is an Enum, so that |
| // users can alias types of their own to Enum. |
| val, err := readVarUint(rest, info) |
| if err != nil { |
| return offset, err |
| } |
| v.SetUint(val) |
| offset += int(info.count) |
| return offset, nil |
| case reflect.Struct: |
| structType := fieldType |
| // TLS includes a select(Enum) {..} construct, where the value of an enum |
| // indicates which variant field is present (like a C union). We require |
| // that the enum value be an earlier field in the same structure (the selector), |
| // and that each of the possible variant destination fields be pointers. |
| // So the Go mapping looks like: |
| // type variantType struct { |
| // Which tls.Enum `tls:"size:1"` // this is the selector |
| // Val1 *type1 `tls:"selector:Which,val:1"` // this is a destination |
| // Val2 *type2 `tls:"selector:Which,val:1"` // this is a destination |
| // } |
| |
| // To deal with this, we track any enum-like fields and their values... |
| enums := make(map[string]uint64) |
| // .. and we track which selector names we've seen (in the destination field tags), |
| // and whether a destination for that selector has been chosen. |
| selectorSeen := make(map[string]bool) |
| for i := 0; i < structType.NumField(); i++ { |
| // Find information about this field. |
| tag := structType.Field(i).Tag.Get("tls") |
| fieldInfo, err := fieldTagToFieldInfo(tag, structType.Field(i).Name) |
| if err != nil { |
| return offset, err |
| } |
| |
| destination := v.Field(i) |
| if fieldInfo.selector != "" { |
| // This is a possible select(Enum) destination, so first check that the referenced |
| // selector field has already been seen earlier in the struct. |
| choice, ok := enums[fieldInfo.selector] |
| if !ok { |
| return offset, structuralError{fieldInfo.name, "selector not seen: " + fieldInfo.selector} |
| } |
| if structType.Field(i).Type.Kind() != reflect.Ptr { |
| return offset, structuralError{fieldInfo.name, "choice field not a pointer type"} |
| } |
| // Is this the first mention of the selector field name? If so, remember it. |
| seen, ok := selectorSeen[fieldInfo.selector] |
| if !ok { |
| selectorSeen[fieldInfo.selector] = false |
| } |
| if choice != fieldInfo.val { |
| // This destination field was not the chosen one, so make it nil (we checked |
| // it was a pointer above). |
| v.Field(i).Set(reflect.Zero(structType.Field(i).Type)) |
| continue |
| } |
| if seen { |
| // We already saw a different destination field receive the value for this |
| // selector value, which indicates a badly annotated structure. |
| return offset, structuralError{fieldInfo.name, "duplicate selector value for " + fieldInfo.selector} |
| } |
| selectorSeen[fieldInfo.selector] = true |
| // Make an object of the pointed-to type and parse into that. |
| v.Field(i).Set(reflect.New(structType.Field(i).Type.Elem())) |
| destination = v.Field(i).Elem() |
| } |
| offset, err = parseField(destination, data, offset, fieldInfo) |
| if err != nil { |
| return offset, err |
| } |
| |
| // Remember any possible tls.Enum values encountered in case they are selectors. |
| if structType.Field(i).Type.Kind() == enumType.Kind() { |
| enums[structType.Field(i).Name] = v.Field(i).Uint() |
| } |
| |
| } |
| |
| // Now we have seen all fields in the structure, check that all select(Enum) {..} selector |
| // fields found a destination to put their data in. |
| for selector, seen := range selectorSeen { |
| if !seen { |
| return offset, syntaxError{info.fieldName(), selector + ": unhandled value for selector"} |
| } |
| } |
| return offset, nil |
| case reflect.Array: |
| datalen := v.Len() |
| |
| if datalen > len(rest) { |
| return offset, syntaxError{info.fieldName(), "truncated array"} |
| } |
| inner := rest[:datalen] |
| offset += datalen |
| if fieldType.Elem().Kind() != reflect.Uint8 { |
| // Only byte/uint8 arrays are supported |
| return offset, structuralError{info.fieldName(), "unsupported array type: " + v.Type().String()} |
| } |
| reflect.Copy(v, reflect.ValueOf(inner)) |
| return offset, nil |
| |
| case reflect.Slice: |
| sliceType := fieldType |
| // Slices represent variable-length vectors, which are prefixed by a length field. |
| // The fieldInfo indicates the size of that length field. |
| varlen, err := readVarUint(rest, info) |
| if err != nil { |
| return offset, err |
| } |
| datalen := int(varlen) |
| offset += int(info.count) |
| rest = rest[info.count:] |
| |
| if datalen > len(rest) { |
| return offset, syntaxError{info.fieldName(), "truncated slice"} |
| } |
| inner := rest[:datalen] |
| offset += datalen |
| if fieldType.Elem().Kind() == reflect.Uint8 { |
| // Fast version for []byte |
| v.Set(reflect.MakeSlice(sliceType, datalen, datalen)) |
| reflect.Copy(v, reflect.ValueOf(inner)) |
| return offset, nil |
| } |
| |
| v.Set(reflect.MakeSlice(sliceType, 0, datalen)) |
| single := reflect.New(sliceType.Elem()) |
| for innerOffset := 0; innerOffset < len(inner); { |
| var err error |
| innerOffset, err = parseField(single.Elem(), inner, innerOffset, nil) |
| if err != nil { |
| return offset, err |
| } |
| v.Set(reflect.Append(v, single.Elem())) |
| } |
| return offset, nil |
| |
| default: |
| return offset, structuralError{info.fieldName(), fmt.Sprintf("unsupported type: %s of kind %s", fieldType, v.Kind())} |
| } |
| } |
| |
| // Marshal returns the TLS encoding of val. |
| func Marshal(val interface{}) ([]byte, error) { |
| return MarshalWithParams(val, "") |
| } |
| |
| // MarshalWithParams returns the TLS encoding of val, and allows field |
| // parameters to be specified for the top-level element. The form |
| // of the params is the same as the field tags. |
| func MarshalWithParams(val interface{}, params string) ([]byte, error) { |
| info, err := fieldTagToFieldInfo(params, "") |
| if err != nil { |
| return nil, err |
| } |
| var out bytes.Buffer |
| v := reflect.ValueOf(val) |
| if err := marshalField(&out, v, info); err != nil { |
| return nil, err |
| } |
| return out.Bytes(), err |
| } |
| |
| func marshalField(out *bytes.Buffer, v reflect.Value, info *fieldInfo) error { |
| var prefix string |
| if info != nil && len(info.name) > 0 { |
| prefix = info.name + ": " |
| } |
| fieldType := v.Type() |
| // First look for known fixed types. |
| switch fieldType { |
| case uint8Type: |
| out.WriteByte(byte(v.Uint())) |
| return nil |
| case uint16Type: |
| scratch := make([]byte, 2) |
| binary.BigEndian.PutUint16(scratch, uint16(v.Uint())) |
| out.Write(scratch) |
| return nil |
| case uint24Type: |
| i := v.Uint() |
| if i > 0xffffff { |
| return structuralError{info.fieldName(), fmt.Sprintf("uint24 overflow %d", i)} |
| } |
| scratch := make([]byte, 4) |
| binary.BigEndian.PutUint32(scratch, uint32(i)) |
| out.Write(scratch[1:]) |
| return nil |
| case uint32Type: |
| scratch := make([]byte, 4) |
| binary.BigEndian.PutUint32(scratch, uint32(v.Uint())) |
| out.Write(scratch) |
| return nil |
| case uint64Type: |
| scratch := make([]byte, 8) |
| binary.BigEndian.PutUint64(scratch, uint64(v.Uint())) |
| out.Write(scratch) |
| return nil |
| } |
| |
| // Now deal with user-defined types. |
| switch v.Kind() { |
| case enumType.Kind(): |
| i := v.Uint() |
| if info == nil { |
| return structuralError{info.fieldName(), "enum field tag missing"} |
| } |
| if err := info.check(i, prefix); err != nil { |
| return err |
| } |
| scratch := make([]byte, 8) |
| binary.BigEndian.PutUint64(scratch, uint64(i)) |
| out.Write(scratch[(8 - info.count):]) |
| return nil |
| case reflect.Struct: |
| structType := fieldType |
| enums := make(map[string]uint64) // Values of any Enum fields |
| // The comment parseField() describes the mapping of the TLS select(Enum) {..} construct; |
| // here we have selector and source (rather than destination) fields. |
| |
| // Track which selector names we've seen (in the source field tags), and whether a source |
| // value for that selector has been processed. |
| selectorSeen := make(map[string]bool) |
| for i := 0; i < structType.NumField(); i++ { |
| // Find information about this field. |
| tag := structType.Field(i).Tag.Get("tls") |
| fieldInfo, err := fieldTagToFieldInfo(tag, structType.Field(i).Name) |
| if err != nil { |
| return err |
| } |
| |
| source := v.Field(i) |
| if fieldInfo.selector != "" { |
| // This field is a possible source for a select(Enum) {..}. First check |
| // the selector field name has been seen. |
| choice, ok := enums[fieldInfo.selector] |
| if !ok { |
| return structuralError{fieldInfo.name, "selector not seen: " + fieldInfo.selector} |
| } |
| if structType.Field(i).Type.Kind() != reflect.Ptr { |
| return structuralError{fieldInfo.name, "choice field not a pointer type"} |
| } |
| // Is this the first mention of the selector field name? If so, remember it. |
| seen, ok := selectorSeen[fieldInfo.selector] |
| if !ok { |
| selectorSeen[fieldInfo.selector] = false |
| } |
| if choice != fieldInfo.val { |
| // This source was not chosen; police that it should be nil. |
| if v.Field(i).Pointer() != uintptr(0) { |
| return structuralError{fieldInfo.name, "unchosen field is non-nil"} |
| } |
| continue |
| } |
| if seen { |
| // We already saw a different source field generate the value for this |
| // selector value, which indicates a badly annotated structure. |
| return structuralError{fieldInfo.name, "duplicate selector value for " + fieldInfo.selector} |
| } |
| selectorSeen[fieldInfo.selector] = true |
| if v.Field(i).Pointer() == uintptr(0) { |
| return structuralError{fieldInfo.name, "chosen field is nil"} |
| } |
| // Marshal from the pointed-to source object. |
| source = v.Field(i).Elem() |
| } |
| |
| var fieldData bytes.Buffer |
| if err := marshalField(&fieldData, source, fieldInfo); err != nil { |
| return err |
| } |
| out.Write(fieldData.Bytes()) |
| |
| // Remember any tls.Enum values encountered in case they are selectors. |
| if structType.Field(i).Type.Kind() == enumType.Kind() { |
| enums[structType.Field(i).Name] = v.Field(i).Uint() |
| } |
| } |
| // Now we have seen all fields in the structure, check that all select(Enum) {..} selector |
| // fields found a source field get get their data from. |
| for selector, seen := range selectorSeen { |
| if !seen { |
| return syntaxError{info.fieldName(), selector + ": unhandled value for selector"} |
| } |
| } |
| return nil |
| |
| case reflect.Array: |
| datalen := v.Len() |
| arrayType := fieldType |
| if arrayType.Elem().Kind() != reflect.Uint8 { |
| // Only byte/uint8 arrays are supported |
| return structuralError{info.fieldName(), "unsupported array type"} |
| } |
| bytes := make([]byte, datalen) |
| for i := 0; i < datalen; i++ { |
| bytes[i] = uint8(v.Index(i).Uint()) |
| } |
| _, err := out.Write(bytes) |
| return err |
| |
| case reflect.Slice: |
| if info == nil { |
| return structuralError{info.fieldName(), "slice field tag missing"} |
| } |
| |
| sliceType := fieldType |
| if sliceType.Elem().Kind() == reflect.Uint8 { |
| // Fast version for []byte: first write the length as info.count bytes. |
| datalen := v.Len() |
| scratch := make([]byte, 8) |
| binary.BigEndian.PutUint64(scratch, uint64(datalen)) |
| out.Write(scratch[(8 - info.count):]) |
| |
| if err := info.check(uint64(datalen), prefix); err != nil { |
| return err |
| } |
| // Then just write the data. |
| bytes := make([]byte, datalen) |
| for i := 0; i < datalen; i++ { |
| bytes[i] = uint8(v.Index(i).Uint()) |
| } |
| _, err := out.Write(bytes) |
| return err |
| } |
| // General version: use a separate Buffer to write the slice entries into. |
| var innerBuf bytes.Buffer |
| for i := 0; i < v.Len(); i++ { |
| if err := marshalField(&innerBuf, v.Index(i), nil); err != nil { |
| return err |
| } |
| } |
| |
| // Now insert (and check) the size. |
| size := uint64(innerBuf.Len()) |
| if err := info.check(size, prefix); err != nil { |
| return err |
| } |
| scratch := make([]byte, 8) |
| binary.BigEndian.PutUint64(scratch, size) |
| out.Write(scratch[(8 - info.count):]) |
| |
| // Then copy the data. |
| _, err := out.Write(innerBuf.Bytes()) |
| return err |
| |
| default: |
| return structuralError{info.fieldName(), fmt.Sprintf("unsupported type: %s of kind %s", fieldType, v.Kind())} |
| } |
| } |