1
0
Fork 0
telegraf/plugins/parsers/binary/entry.go
Daniel Baumann 4978089aab
Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-24 07:26:29 +02:00

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
}