1
1
mirror of https://github.com/wader/fq.git synced 2024-12-23 05:13:30 +03:00
fq/format/protobuf/protobuf.go
Mattias Wadman 8e0dde03d0 decode: Support multiple format args and some rename and refactor
This will allow passing both cli options and format options to sub decoder.
Ex: pass keylog option to a tls decoder when decoding a pcap.
Ex: pass decode options to a format inside a http body inside a pcap.

Add ArgAs method to lookup argument based on type. This also makes the format
decode function have same signature as sub decoders in the decode API.

This change decode.Format a bit:
DecodeFn is now just func(d *D) any
DecodeInArg renamed to DefaultInArg
2023-02-18 21:38:51 +01:00

139 lines
3.6 KiB
Go

package protobuf
// https://developers.google.com/protocol-buffers/docs/encoding
import (
"embed"
"github.com/wader/fq/format"
"github.com/wader/fq/internal/mathex"
"github.com/wader/fq/pkg/bitio"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/interp"
"github.com/wader/fq/pkg/scalar"
)
//go:embed protobuf.md
var protobufFS embed.FS
func init() {
interp.RegisterFormat(decode.Format{
Name: format.PROTOBUF,
Description: "Protobuf",
DecodeFn: protobufDecode,
})
interp.RegisterFS(protobufFS)
}
const (
wireTypeVarint = 0
wireType64Bit = 1
wireTypeLengthDelimited = 2
wireType32Bit = 5
)
var wireTypeNames = scalar.UintMapSymStr{
0: "varint",
1: "64bit",
2: "length_delimited",
5: "32bit",
}
func protobufDecodeField(d *decode.D, pbm *format.ProtoBufMessage) {
d.FieldStruct("field", func(d *decode.D) {
keyN := d.FieldULEB128("key_n")
fieldNumber := keyN >> 3
wireType := keyN & 0x7
d.FieldValueUint("field_number", fieldNumber)
d.FieldValueUint("wire_type", wireType, scalar.UintSym(wireTypeNames[wireType]))
var value uint64
var length uint64
var valueStart int64
switch wireType {
case wireTypeVarint:
value = d.FieldULEB128("wire_value")
case wireType64Bit:
value = d.FieldU64("wire_value")
case wireTypeLengthDelimited:
length = d.FieldULEB128("length")
valueStart = d.Pos()
d.FieldRawLen("wire_value", int64(length)*8)
case wireType32Bit:
value = d.FieldU32("wire_value")
}
if pbm != nil {
if pbf, ok := (*pbm)[int(fieldNumber)]; ok {
d.FieldValueStr("name", pbf.Name)
d.FieldValueStr("type", format.ProtoBufTypeNames[uint64(pbf.Type)])
switch pbf.Type {
case format.ProtoBufTypeInt32, format.ProtoBufTypeInt64:
v := mathex.ZigZag[uint64, int64](value)
d.FieldValueSint("value", v)
if len(pbf.Enums) > 0 {
d.FieldValueStr("enum", pbf.Enums[uint64(v)])
}
case format.ProtoBufTypeUInt32, format.ProtoBufTypeUInt64:
d.FieldValueUint("value", value)
if len(pbf.Enums) > 0 {
d.FieldValueStr("enum", pbf.Enums[value])
}
case format.ProtoBufTypeSInt32, format.ProtoBufTypeSInt64:
// TODO: correct? 32 different?
v := mathex.TwosComplement(64, value)
d.FieldValueSint("value", v)
if len(pbf.Enums) > 0 {
d.FieldValueStr("enum", pbf.Enums[uint64(v)])
}
case format.ProtoBufTypeBool:
d.FieldValueBool("value", value != 0)
case format.ProtoBufTypeEnum:
d.FieldValueStr("enum", pbf.Enums[value])
case format.ProtoBufTypeFixed64:
// TODO:
case format.ProtoBufTypeSFixed64:
// TODO:
case format.ProtoBufTypeDouble:
// TODO:
case format.ProtoBufTypeString:
d.FieldValueStr("value", string(d.BytesRange(valueStart, int(length))))
case format.ProtoBufTypeBytes:
d.FieldValueBitBuf("value", bitio.NewBitReader(d.BytesRange(valueStart, int(length)), -1))
case format.ProtoBufTypeMessage:
// TODO: test
d.FramedFn(int64(length)*8, func(d *decode.D) {
protobufDecodeFields(d, &pbf.Message)
})
case format.ProtoBufTypePackedRepeated:
// TODO:
case format.ProtoBufTypeFixed32:
// TODO:
case format.ProtoBufTypeSFixed32:
// TODO:
case format.ProtoBufTypeFloat:
// TODO:
}
}
}
})
}
func protobufDecodeFields(d *decode.D, pbm *format.ProtoBufMessage) {
d.FieldArray("fields", func(d *decode.D) {
for d.BitsLeft() > 0 {
protobufDecodeField(d, pbm)
}
})
}
func protobufDecode(d *decode.D) any {
var pbi format.ProtoBufIn
d.ArgAs(&pbi)
protobufDecodeFields(d, &pbi.Message)
return nil
}