1
0
Fork 0
telegraf/plugins/common/socket/splitters.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

167 lines
4.2 KiB
Go

package socket
import (
"bufio"
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"regexp"
"strings"
)
type lengthFieldSpec struct {
Offset int64 `toml:"offset"`
Bytes int64 `toml:"bytes"`
Endianness string `toml:"endianness"`
HeaderLength int64 `toml:"header_length"`
converter func([]byte) int
}
type SplitConfig struct {
SplittingStrategy string `toml:"splitting_strategy"`
SplittingDelimiter string `toml:"splitting_delimiter"`
SplittingLength int `toml:"splitting_length"`
SplittingLengthField lengthFieldSpec `toml:"splitting_length_field"`
}
func (cfg *SplitConfig) NewSplitter() (bufio.SplitFunc, error) {
switch cfg.SplittingStrategy {
case "", "newline":
return bufio.ScanLines, nil
case "null":
return scanNull, nil
case "delimiter":
re := regexp.MustCompile(`(\s*0?x)`)
d := re.ReplaceAllString(strings.ToLower(cfg.SplittingDelimiter), "")
delimiter, err := hex.DecodeString(d)
if err != nil {
return nil, fmt.Errorf("decoding delimiter failed: %w", err)
}
return createScanDelimiter(delimiter), nil
case "fixed length":
return createScanFixedLength(cfg.SplittingLength), nil
case "variable length":
// Create the converter function
var order binary.ByteOrder
switch strings.ToLower(cfg.SplittingLengthField.Endianness) {
case "", "be":
order = binary.BigEndian
case "le":
order = binary.LittleEndian
default:
return nil, fmt.Errorf("invalid 'endianness' %q", cfg.SplittingLengthField.Endianness)
}
switch cfg.SplittingLengthField.Bytes {
case 1:
cfg.SplittingLengthField.converter = func(b []byte) int {
return int(b[0])
}
case 2:
cfg.SplittingLengthField.converter = func(b []byte) int {
return int(order.Uint16(b))
}
case 4:
cfg.SplittingLengthField.converter = func(b []byte) int {
return int(order.Uint32(b))
}
case 8:
cfg.SplittingLengthField.converter = func(b []byte) int {
return int(order.Uint64(b))
}
default:
cfg.SplittingLengthField.converter = func(b []byte) int {
buf := make([]byte, 8)
start := 0
if order == binary.BigEndian {
start = 8 - len(b)
}
for i := 0; i < len(b); i++ {
buf[start+i] = b[i]
}
return int(order.Uint64(buf))
}
}
// Check if we have enough bytes in the header
return createScanVariableLength(cfg.SplittingLengthField), nil
}
return nil, fmt.Errorf("unknown 'splitting_strategy' %q", cfg.SplittingStrategy)
}
func scanNull(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, 0); i >= 0 {
return i + 1, data[:i], nil
}
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}
func createScanDelimiter(delimiter []byte) bufio.SplitFunc {
return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.Index(data, delimiter); i >= 0 {
return i + len(delimiter), data[:i], nil
}
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}
}
func createScanFixedLength(length int) bufio.SplitFunc {
return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if len(data) >= length {
return length, data[:length], nil
}
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}
}
func createScanVariableLength(spec lengthFieldSpec) bufio.SplitFunc {
minlen := int(spec.Offset)
minlen += int(spec.Bytes)
headerLen := int(spec.HeaderLength)
return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
dataLen := len(data)
if dataLen >= minlen {
// Extract the length field and convert it to a number
lf := data[spec.Offset : spec.Offset+spec.Bytes]
length := spec.converter(lf)
start := headerLen
end := length + headerLen
// If we have enough data return it without the header
if end <= dataLen {
return end, data[start:end], nil
}
}
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}
}