277 lines
6.2 KiB
Go
277 lines
6.2 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package cassandra
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/elastic/beats/libbeat/common/streambuf"
|
|
"github.com/elastic/beats/libbeat/logp"
|
|
"github.com/elastic/beats/packetbeat/protos/applayer"
|
|
|
|
gocql "github.com/elastic/beats/packetbeat/protos/cassandra/internal/gocql"
|
|
)
|
|
|
|
type parser struct {
|
|
buf streambuf.Buffer
|
|
config *parserConfig
|
|
framer *gocql.Framer
|
|
message *message
|
|
onMessage func(m *message) error
|
|
}
|
|
|
|
type parserConfig struct {
|
|
maxBytes int
|
|
compressor gocql.Compressor
|
|
ignoredOps map[gocql.FrameOp]bool
|
|
}
|
|
|
|
// check whether this ops is enabled or not
|
|
func (p *parser) CheckFrameOpsIgnored() bool {
|
|
if p.config.ignoredOps != nil && len(p.config.ignoredOps) > 0 {
|
|
//default map value is false
|
|
v := p.config.ignoredOps[p.framer.Header.Op]
|
|
if v {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
type message struct {
|
|
applayer.Message
|
|
|
|
// indicator for parsed message being complete or requires more messages
|
|
// (if false) to be merged to generate full message.
|
|
isComplete bool
|
|
|
|
failed bool
|
|
ignored bool
|
|
|
|
data map[string]interface{}
|
|
header map[string]interface{}
|
|
// list element use by 'transactions' for correlation
|
|
next *message
|
|
|
|
transactionTimeout time.Duration
|
|
|
|
results transactions
|
|
}
|
|
|
|
// Error code if stream exceeds max allowed size on append.
|
|
var (
|
|
errStreamTooLarge = errors.New("Stream data too large")
|
|
isDebug = false
|
|
)
|
|
|
|
func (p *parser) init(
|
|
cfg *parserConfig,
|
|
onMessage func(*message) error,
|
|
) {
|
|
*p = parser{
|
|
buf: streambuf.Buffer{},
|
|
config: cfg,
|
|
onMessage: onMessage,
|
|
}
|
|
|
|
isDebug = logp.IsDebug("cassandra")
|
|
|
|
}
|
|
|
|
func (p *parser) append(data []byte) error {
|
|
_, err := p.buf.Write(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if p.config.maxBytes > 0 && p.buf.Total() > p.config.maxBytes {
|
|
return errStreamTooLarge
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *parser) feed(ts time.Time, data []byte) error {
|
|
if err := p.append(data); err != nil {
|
|
return err
|
|
}
|
|
|
|
for p.buf.Total() > 0 {
|
|
if p.message == nil {
|
|
// allocate new message object to be used by parser with current timestamp
|
|
p.message = p.newMessage(ts)
|
|
}
|
|
|
|
msg, err := p.parse()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if msg == nil {
|
|
break // wait for more data
|
|
}
|
|
|
|
// reset buffer and message -> handle next message in buffer
|
|
p.buf.Reset()
|
|
p.message = nil
|
|
|
|
// call message handler callback
|
|
if err := p.onMessage(msg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *parser) newMessage(ts time.Time) *message {
|
|
return &message{
|
|
Message: applayer.Message{
|
|
Ts: ts,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (p *parser) parserBody() (bool, error) {
|
|
headLen := p.framer.Header.HeadLength
|
|
bdyLen := p.framer.Header.BodyLength
|
|
if bdyLen <= 0 {
|
|
return true, nil
|
|
}
|
|
|
|
//let's wait for enough buf
|
|
debugf("bodyLength: %d", bdyLen)
|
|
if !p.buf.Avail(bdyLen) {
|
|
if isDebug {
|
|
debugf("buf not enough for body, waiting for more, return")
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
//check if the ops already ignored
|
|
if p.message.ignored {
|
|
if isDebug {
|
|
debugf("message marked to be ignored, let's do this")
|
|
}
|
|
p.buf.Collect(bdyLen)
|
|
} else {
|
|
// start to parse body
|
|
data, err := p.framer.ReadFrame()
|
|
if err != nil {
|
|
// if the frame parsed failed, should ignore the whole message
|
|
p.framer = nil
|
|
return false, err
|
|
}
|
|
|
|
// dealing with un-parsed content
|
|
frameParsedLength := p.buf.BufferConsumed()
|
|
|
|
// collect leftover
|
|
unParsedSize := bdyLen + headLen - frameParsedLength
|
|
if unParsedSize > 0 {
|
|
if !p.buf.Avail(unParsedSize) {
|
|
err := errors.New("should be enough bytes for cleanup,but not enough")
|
|
logp.Err("Finishing frame failed with: %v", err)
|
|
return false, err
|
|
}
|
|
|
|
p.buf.Collect(unParsedSize)
|
|
}
|
|
|
|
p.message.data = data
|
|
}
|
|
|
|
finalCollectedFrameLength := p.buf.BufferConsumed()
|
|
if finalCollectedFrameLength-headLen != bdyLen {
|
|
logp.Err("body_length:%d, head_length:%d, all_consumed:%d",
|
|
bdyLen, headLen, finalCollectedFrameLength)
|
|
return false, errors.New("data messed while parse frame body")
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (p *parser) parse() (*message, error) {
|
|
// if p.frame is nil then create a new framer, or continue to process the last message
|
|
if p.framer == nil {
|
|
if isDebug {
|
|
debugf("start new framer")
|
|
}
|
|
p.framer = gocql.NewFramer(&p.buf, p.config.compressor)
|
|
}
|
|
|
|
// check if the frame header were parsed or not
|
|
if p.framer.Header == nil {
|
|
if isDebug {
|
|
debugf("start to parse header")
|
|
}
|
|
if !p.buf.Avail(9) {
|
|
debugf("not enough head bytes, ignore")
|
|
return nil, nil
|
|
}
|
|
|
|
_, err := p.framer.ReadHeader()
|
|
if err != nil {
|
|
logp.Err("%v", err)
|
|
p.framer = nil
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
//check if the ops need to be ignored
|
|
if p.CheckFrameOpsIgnored() {
|
|
// as we already ignore the content, we now mark the result is ignored
|
|
p.message.ignored = true
|
|
if isDebug {
|
|
debugf("Ops: %s was marked to be ignored, ignoring, request:%v", p.framer.Header.Op.String(), p.framer.Header.Version.IsRequest())
|
|
}
|
|
}
|
|
|
|
msg := p.message
|
|
|
|
finished, err := p.parserBody()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
//ignore and wait for more data
|
|
if !finished {
|
|
return nil, nil
|
|
}
|
|
|
|
dir := applayer.NetOriginalDirection
|
|
|
|
isRequest := true
|
|
if p.framer.Header.Version.IsResponse() {
|
|
dir = applayer.NetReverseDirection
|
|
isRequest = false
|
|
}
|
|
|
|
msg.Size = uint64(p.buf.BufferConsumed())
|
|
msg.IsRequest = isRequest
|
|
msg.Direction = dir
|
|
|
|
msg.header = p.framer.Header.ToMap()
|
|
|
|
if msg.IsRequest {
|
|
p.message.results.requests.append(msg)
|
|
} else {
|
|
p.message.results.responses.append(msg)
|
|
}
|
|
p.framer = nil
|
|
return msg, nil
|
|
}
|