1
0
Fork 0
telegraf/plugins/parsers/binary/parser.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

213 lines
4.9 KiB
Go

package binary
import (
"encoding/base64"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/parsers"
)
type Parser struct {
AllowNoMatch bool `toml:"allow_no_match"`
Endianess string `toml:"endianess" deprecated:"1.27.4;1.35.0;use 'endianness' instead"`
Endianness string `toml:"endianness"`
Configs []Config `toml:"binary"`
HexEncoding bool `toml:"hex_encoding" deprecated:"1.30.0;1.35.0;use 'binary_encoding' instead"`
Encoding string `toml:"binary_encoding"`
Log telegraf.Logger `toml:"-"`
metricName string
defaultTags map[string]string
converter binary.ByteOrder
}
func (p *Parser) Init() error {
// Keep backward compatibility
if p.Endianess != "" && p.Endianness == "" {
p.Endianness = p.Endianess
}
if p.HexEncoding {
if p.Encoding != "" && p.Encoding != "hex" {
return errors.New("conflicting settings between 'hex_encoding' and 'binary_encoding'")
}
p.Encoding = "hex"
}
switch p.Endianness {
case "le":
p.converter = binary.LittleEndian
case "be":
p.converter = binary.BigEndian
case "", "host":
p.converter = internal.HostEndianness
default:
return fmt.Errorf("unknown endianness %q", p.Endianness)
}
switch p.Encoding {
case "", "none", "hex", "base64":
default:
return fmt.Errorf("unknown encoding %q", p.Encoding)
}
// Pre-process the configurations
if len(p.Configs) == 0 {
return errors.New("no configuration given")
}
for i, cfg := range p.Configs {
if err := cfg.preprocess(p.metricName); err != nil {
return fmt.Errorf("config %d invalid: %w", i, err)
}
p.Configs[i] = cfg
}
return nil
}
func (p *Parser) Parse(data []byte) ([]telegraf.Metric, error) {
t := time.Now()
// If the data is encoded in HEX, we need to decode it first
buf := data
switch p.Encoding {
case "hex":
s := strings.TrimPrefix(string(data), "0x")
s = strings.TrimPrefix(s, "x")
s = strings.TrimSpace(s)
s = strings.ReplaceAll(s, " ", "")
s = strings.ReplaceAll(s, "\t", "")
var err error
buf, err = hex.DecodeString(s)
if err != nil {
return nil, fmt.Errorf("decoding hex failed: %w", err)
}
case "base64":
decoder := base64.StdEncoding.WithPadding(base64.StdPadding)
var err error
buf, err = decoder.DecodeString(strings.TrimSpace(string(data)))
if err != nil {
return nil, fmt.Errorf("decoding base64 failed: %w", err)
}
}
matches := 0
metrics := make([]telegraf.Metric, 0)
for i, cfg := range p.Configs {
// Apply the filter and see if we should match this
if !cfg.matches(buf) {
p.Log.Debugf("ignoring data in config %d", i)
continue
}
matches++
// Collect the metric
m, err := cfg.collect(buf, p.converter, t)
if err != nil {
return nil, err
}
metrics = append(metrics, m)
}
if matches == 0 && !p.AllowNoMatch {
return nil, errors.New("no matching configuration")
}
return metrics, nil
}
func (p *Parser) ParseLine(line string) (telegraf.Metric, error) {
metrics, err := p.Parse([]byte(line))
if err != nil {
return nil, err
}
switch len(metrics) {
case 0:
return nil, nil
case 1:
return metrics[0], nil
default:
return metrics[0], fmt.Errorf("cannot parse line with multiple (%d) metrics", len(metrics))
}
}
func (p *Parser) SetDefaultTags(tags map[string]string) {
p.defaultTags = tags
}
func init() {
// Register all variants
parsers.Add("binary",
func(defaultMetricName string) telegraf.Parser {
return &Parser{metricName: defaultMetricName}
},
)
}
func extractPart(in []byte, offset, bits uint64) ([]byte, error) {
inLen := uint64(len(in))
start := offset / 8
bitend := offset%8 + bits
length := bitend / 8
if bitend%8 != 0 {
length++
}
if start+length > inLen {
return nil, fmt.Errorf("out-of-bounds @%d with %d bits", offset, bits)
}
var out []byte
out = append(out, in[start:start+length]...)
if offset%8 != 0 {
// Mask the start-byte with the non-aligned bit-mask
startmask := (byte(1) << (8 - offset%8)) - 1
out[0] = out[0] & startmask
}
if bitend%8 == 0 {
// The end is aligned to byte-boundaries
return out, nil
}
shift := 8 - bitend%8
carryshift := bitend % 8
// We need to shift right in case of not ending at a byte boundary
// to make the bits right aligned.
// Carry over the bits from the byte left to fill in...
var carry byte
for i, x := range out {
out[i] = (x >> shift) | carry
carry = x << carryshift
}
if bits%8 == 0 {
// Avoid an empty leading byte
return out[1:], nil
}
return out, nil
}
func bitsForType(t string) (uint64, error) {
switch t {
case "uint8", "int8":
return 8, nil
case "uint16", "int16":
return 16, nil
case "uint32", "int32", "float32":
return 32, nil
case "uint64", "int64", "float64":
return 64, nil
}
return 0, fmt.Errorf("cannot determine length for type %q", t)
}