310 lines
6.7 KiB
Go
310 lines
6.7 KiB
Go
package binary
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/influxdata/telegraf/internal"
|
|
)
|
|
|
|
type Entry struct {
|
|
Name string `toml:"name"`
|
|
Type string `toml:"type"`
|
|
Bits uint64 `toml:"bits"`
|
|
Omit bool `toml:"omit"`
|
|
Terminator string `toml:"terminator"`
|
|
Timezone string `toml:"timezone"`
|
|
Assignment string `toml:"assignment"`
|
|
|
|
termination []byte
|
|
location *time.Location
|
|
}
|
|
|
|
func (e *Entry) check() error {
|
|
// Normalize cases
|
|
e.Assignment = strings.ToLower(e.Assignment)
|
|
e.Terminator = strings.ToLower(e.Terminator)
|
|
if e.Assignment != "time" {
|
|
e.Type = strings.ToLower(e.Type)
|
|
}
|
|
|
|
// Handle omitted fields
|
|
if e.Omit {
|
|
if e.Bits == 0 && e.Type == "" {
|
|
return errors.New("neither type nor bits given")
|
|
}
|
|
if e.Bits == 0 {
|
|
bits, err := bitsForType(e.Type)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.Bits = bits
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Set name for global options
|
|
if e.Assignment == "measurement" || e.Assignment == "time" {
|
|
e.Name = e.Assignment
|
|
}
|
|
|
|
// Check the name
|
|
if e.Name == "" {
|
|
return errors.New("missing name")
|
|
}
|
|
|
|
// Check the assignment
|
|
var defaultType string
|
|
switch e.Assignment {
|
|
case "measurement":
|
|
defaultType = "string"
|
|
if e.Type != "string" && e.Type != "" {
|
|
return errors.New("'measurement' type has to be 'string'")
|
|
}
|
|
case "time":
|
|
bits := uint64(64)
|
|
|
|
switch e.Type {
|
|
// Make 'unix' the default
|
|
case "":
|
|
defaultType = "unix"
|
|
// Special plugin specific names
|
|
case "unix", "unix_ms", "unix_us", "unix_ns":
|
|
// Format-specification string formats
|
|
default:
|
|
bits = uint64(len(e.Type) * 8)
|
|
}
|
|
if e.Bits == 0 {
|
|
e.Bits = bits
|
|
}
|
|
|
|
switch e.Timezone {
|
|
case "", "utc":
|
|
// Make UTC the default
|
|
e.location = time.UTC
|
|
case "local":
|
|
e.location = time.Local
|
|
default:
|
|
var err error
|
|
e.location, err = time.LoadLocation(e.Timezone)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case "tag":
|
|
defaultType = "string"
|
|
case "", "field":
|
|
e.Assignment = "field"
|
|
default:
|
|
return fmt.Errorf("no assignment for %q", e.Name)
|
|
}
|
|
|
|
// Check type (special type for "time")
|
|
switch e.Type {
|
|
case "uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64":
|
|
fallthrough
|
|
case "float32", "float64":
|
|
bits, err := bitsForType(e.Type)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if e.Bits == 0 {
|
|
e.Bits = bits
|
|
}
|
|
if bits < e.Bits {
|
|
return fmt.Errorf("type overflow for %q", e.Name)
|
|
}
|
|
case "bool":
|
|
if e.Bits == 0 {
|
|
e.Bits = 1
|
|
}
|
|
case "string":
|
|
// Check termination
|
|
switch e.Terminator {
|
|
case "", "fixed":
|
|
e.Terminator = "fixed"
|
|
if e.Bits == 0 {
|
|
return fmt.Errorf("require 'bits' for fixed-length string for %q", e.Name)
|
|
}
|
|
case "null":
|
|
e.termination = []byte{0}
|
|
if e.Bits != 0 {
|
|
return fmt.Errorf("cannot use 'bits' and 'null' terminator together for %q", e.Name)
|
|
}
|
|
default:
|
|
if e.Bits != 0 {
|
|
return fmt.Errorf("cannot use 'bits' and terminator together for %q", e.Name)
|
|
}
|
|
var err error
|
|
e.termination, err = hex.DecodeString(strings.TrimPrefix(e.Terminator, "0x"))
|
|
if err != nil {
|
|
return fmt.Errorf("decoding terminator failed for %q: %w", e.Name, err)
|
|
}
|
|
}
|
|
|
|
// We can only handle strings that adhere to byte-bounds
|
|
if e.Bits%8 != 0 {
|
|
return fmt.Errorf("non-byte length for string field %q", e.Name)
|
|
}
|
|
case "":
|
|
if defaultType == "" {
|
|
return fmt.Errorf("no type for %q", e.Name)
|
|
}
|
|
e.Type = defaultType
|
|
default:
|
|
if e.Assignment != "time" {
|
|
return fmt.Errorf("unknown type for %q", e.Name)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *Entry) extract(in []byte, offset uint64) ([]byte, uint64, error) {
|
|
if e.Bits > 0 {
|
|
data, err := extractPart(in, offset, e.Bits)
|
|
return data, e.Bits, err
|
|
}
|
|
|
|
if e.Type != "string" {
|
|
return nil, 0, fmt.Errorf("unexpected entry: %v", e)
|
|
}
|
|
|
|
inbits := uint64(len(in)) * 8
|
|
|
|
// Read up to the termination
|
|
var found bool
|
|
var data []byte
|
|
var termOffset int
|
|
var n uint64
|
|
for offset+n+8 <= inbits {
|
|
buf, err := extractPart(in, offset+n, 8)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if len(buf) != 1 {
|
|
return nil, 0, fmt.Errorf("unexpected length %d", len(buf))
|
|
}
|
|
data = append(data, buf[0])
|
|
n += 8
|
|
|
|
// Check for terminator
|
|
if buf[0] == e.termination[termOffset] {
|
|
termOffset++
|
|
}
|
|
if termOffset == len(e.termination) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return nil, n, fmt.Errorf("terminator not found for %q", e.Name)
|
|
}
|
|
|
|
// Strip the terminator
|
|
return data[:len(data)-len(e.termination)], n, nil
|
|
}
|
|
|
|
func (e *Entry) convertType(in []byte, order binary.ByteOrder) (interface{}, error) {
|
|
switch e.Type {
|
|
case "uint8", "int8", "uint16", "int16", "uint32", "int32", "float32", "uint64", "int64", "float64":
|
|
return convertNumericType(in, e.Type, order)
|
|
case "bool":
|
|
return convertBoolType(in), nil
|
|
case "string":
|
|
return convertStringType(in), nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("cannot handle type %q", e.Type)
|
|
}
|
|
|
|
func (e *Entry) convertTimeType(in []byte, order binary.ByteOrder) (time.Time, error) {
|
|
factor := int64(1)
|
|
|
|
switch e.Type {
|
|
case "unix":
|
|
factor *= 1000
|
|
fallthrough
|
|
case "unix_ms":
|
|
factor *= 1000
|
|
fallthrough
|
|
case "unix_us":
|
|
factor *= 1000
|
|
fallthrough
|
|
case "unix_ns":
|
|
raw, err := convertNumericType(in, "int64", order)
|
|
if err != nil {
|
|
return time.Unix(0, 0), err
|
|
}
|
|
v := raw.(int64)
|
|
return time.Unix(0, v*factor).In(e.location), nil
|
|
}
|
|
// We have a format specification (hopefully)
|
|
v := convertStringType(in)
|
|
return internal.ParseTimestamp(e.Type, v, e.location)
|
|
}
|
|
|
|
func convertStringType(in []byte) string {
|
|
return string(in)
|
|
}
|
|
|
|
func convertNumericType(in []byte, t string, order binary.ByteOrder) (interface{}, error) {
|
|
bits, err := bitsForType(t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
inlen := uint64(len(in))
|
|
expected := bits / 8
|
|
if inlen > expected {
|
|
// Should never happen
|
|
return 0, fmt.Errorf("too many bytes %d vs %d", len(in), expected)
|
|
}
|
|
|
|
// Pad the data if shorter than the datatype length
|
|
buf := make([]byte, expected-inlen, expected)
|
|
buf = append(buf, in...)
|
|
|
|
switch t {
|
|
case "uint8":
|
|
return buf[0], nil
|
|
case "int8":
|
|
return int8(buf[0]), nil
|
|
case "uint16":
|
|
return order.Uint16(buf), nil
|
|
case "int16":
|
|
v := order.Uint16(buf)
|
|
return int16(v), nil
|
|
case "uint32":
|
|
return order.Uint32(buf), nil
|
|
case "int32":
|
|
v := order.Uint32(buf)
|
|
return int32(v), nil
|
|
case "uint64":
|
|
return order.Uint64(buf), nil
|
|
case "int64":
|
|
v := order.Uint64(buf)
|
|
return int64(v), nil
|
|
case "float32":
|
|
v := order.Uint32(buf)
|
|
return math.Float32frombits(v), nil
|
|
case "float64":
|
|
v := order.Uint64(buf)
|
|
return math.Float64frombits(v), nil
|
|
}
|
|
return nil, fmt.Errorf("no numeric type %q", t)
|
|
}
|
|
|
|
func convertBoolType(in []byte) bool {
|
|
for _, x := range in {
|
|
if x != 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|