190 lines
4.5 KiB
Go
190 lines
4.5 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 memcache
|
|
|
|
// Generic memcache parser types and helper functions for use by binary and text parser protocol parsers.
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/elastic/beats/libbeat/common/streambuf"
|
|
)
|
|
|
|
const (
|
|
codeSpace byte = ' '
|
|
codeTab = '\t'
|
|
)
|
|
|
|
type parserConfig struct {
|
|
maxValues int
|
|
maxBytesPerValue int
|
|
parseUnknown bool
|
|
}
|
|
|
|
type parser struct {
|
|
state parserState
|
|
message *message
|
|
config *parserConfig
|
|
}
|
|
|
|
type parserState uint8
|
|
|
|
const (
|
|
parseStateCommand parserState = iota
|
|
parseStateTextCommand
|
|
parseStateBinaryCommand
|
|
parseStateData
|
|
parseStateDataBinary
|
|
parseStateIncompleteData
|
|
parseStateIncompleteDataBinary
|
|
parseStateFailing
|
|
)
|
|
|
|
type parserStateFn func(parser *parser, buf *streambuf.Buffer) parseResult
|
|
|
|
type argParser func(parser *parser, hdr, buf *streambuf.Buffer) error
|
|
|
|
type parseResult struct {
|
|
err error
|
|
msg *message
|
|
}
|
|
|
|
// module init
|
|
func init() {
|
|
// link parseCommand (break compile time initialization loop check)
|
|
parseCommand = doParseCommand
|
|
}
|
|
|
|
func newParser(config *parserConfig) *parser {
|
|
var p parser
|
|
p.init(config)
|
|
return &p
|
|
}
|
|
|
|
func (p *parser) init(config *parserConfig) {
|
|
p.state = parseStateCommand
|
|
p.message = nil
|
|
p.config = config
|
|
}
|
|
|
|
func (p *parser) reset() {
|
|
debug("parser(%p) reset", p)
|
|
p.init(p.config)
|
|
}
|
|
|
|
func (p *parser) parse(buf *streambuf.Buffer, ts time.Time) (*message, error) {
|
|
if p.message == nil {
|
|
p.message = newMessage(ts)
|
|
}
|
|
|
|
res := p.dispatch(p.state, buf)
|
|
return res.msg, res.err
|
|
}
|
|
|
|
func (p *parser) dispatch(state parserState, buf *streambuf.Buffer) parseResult {
|
|
var f parserStateFn
|
|
switch state {
|
|
case parseStateCommand:
|
|
f = parseCommand
|
|
case parseStateTextCommand:
|
|
f = parseTextCommand
|
|
case parseStateBinaryCommand:
|
|
f = parseBinaryCommand
|
|
case parseStateData:
|
|
f = parseData
|
|
case parseStateIncompleteData:
|
|
f = parseData
|
|
case parseStateDataBinary:
|
|
f = parseDataBinary
|
|
case parseStateIncompleteDataBinary:
|
|
f = parseDataBinary
|
|
case parseStateFailing:
|
|
f = parseFailing
|
|
}
|
|
return f(p, buf)
|
|
}
|
|
|
|
func (p *parser) needMore() parseResult {
|
|
return parseResult{nil, nil}
|
|
}
|
|
|
|
func (p *parser) yield(nbytes int) parseResult {
|
|
p.state = parseStateCommand
|
|
msg := p.message
|
|
msg.Size = uint64(nbytes - int(msg.bytesLost))
|
|
p.message = nil
|
|
debug("yield(%p) memcache message type %v", p, msg.command.code)
|
|
return parseResult{nil, msg}
|
|
}
|
|
|
|
func (p *parser) yieldNoData(buf *streambuf.Buffer) parseResult {
|
|
return p.yield(buf.BufferConsumed())
|
|
}
|
|
|
|
func (p *parser) failing(err error) parseResult {
|
|
p.state = parseStateFailing
|
|
return parseResult{err, nil}
|
|
}
|
|
|
|
func (p *parser) contWith(buf *streambuf.Buffer, state parserState) parseResult {
|
|
p.state = state
|
|
return p.dispatch(state, buf)
|
|
}
|
|
|
|
func (p *parser) contWithShallow(
|
|
buf *streambuf.Buffer,
|
|
fn parserStateFn,
|
|
) parseResult {
|
|
return fn(p, buf)
|
|
}
|
|
|
|
func (p *parser) appendMessageData(data []byte) {
|
|
msg := p.message
|
|
if p.config.maxValues != 0 {
|
|
msg.data = memcacheData{data}
|
|
if len(msg.data.data) > p.config.maxBytesPerValue {
|
|
msg.data.data = msg.data.data[0:p.config.maxBytesPerValue]
|
|
}
|
|
msg.values = append(msg.values, msg.data)
|
|
}
|
|
msg.countValues++
|
|
}
|
|
|
|
func parseFailing(parser *parser, buf *streambuf.Buffer) parseResult {
|
|
return parser.failing(errParserCaughtInError)
|
|
}
|
|
|
|
// required to break initialization loop warning
|
|
var parseCommand parserStateFn
|
|
|
|
func doParseCommand(parser *parser, buf *streambuf.Buffer) parseResult {
|
|
// check if binary + text command and dispatch
|
|
if !buf.Avail(2) {
|
|
return parser.needMore()
|
|
}
|
|
magic := buf.Bytes()[0]
|
|
isBinary := magic == memcacheMagicRequest || magic == memcacheMagicResponse
|
|
if isBinary {
|
|
return parser.contWith(buf, parseStateBinaryCommand)
|
|
}
|
|
return parser.contWith(buf, parseStateTextCommand)
|
|
}
|
|
|
|
func argparseNoop(p *parser, h, b *streambuf.Buffer) error {
|
|
return nil
|
|
}
|