add other session providers (#5963)
This commit is contained in:
parent
bf4badad1d
commit
9de871a0f8
160 changed files with 37644 additions and 66 deletions
19
vendor/github.com/couchbase/gomemcached/LICENSE
generated
vendored
Normal file
19
vendor/github.com/couchbase/gomemcached/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2013 Dustin Sallings
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
1074
vendor/github.com/couchbase/gomemcached/client/mc.go
generated
vendored
Normal file
1074
vendor/github.com/couchbase/gomemcached/client/mc.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
333
vendor/github.com/couchbase/gomemcached/client/tap_feed.go
generated
vendored
Normal file
333
vendor/github.com/couchbase/gomemcached/client/tap_feed.go
generated
vendored
Normal file
|
@ -0,0 +1,333 @@
|
|||
package memcached
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/couchbase/gomemcached"
|
||||
"github.com/couchbase/goutils/logging"
|
||||
)
|
||||
|
||||
// TAP protocol docs: <http://www.couchbase.com/wiki/display/couchbase/TAP+Protocol>
|
||||
|
||||
// TapOpcode is the tap operation type (found in TapEvent)
|
||||
type TapOpcode uint8
|
||||
|
||||
// Tap opcode values.
|
||||
const (
|
||||
TapBeginBackfill = TapOpcode(iota)
|
||||
TapEndBackfill
|
||||
TapMutation
|
||||
TapDeletion
|
||||
TapCheckpointStart
|
||||
TapCheckpointEnd
|
||||
tapEndStream
|
||||
)
|
||||
|
||||
const tapMutationExtraLen = 16
|
||||
|
||||
var tapOpcodeNames map[TapOpcode]string
|
||||
|
||||
func init() {
|
||||
tapOpcodeNames = map[TapOpcode]string{
|
||||
TapBeginBackfill: "BeginBackfill",
|
||||
TapEndBackfill: "EndBackfill",
|
||||
TapMutation: "Mutation",
|
||||
TapDeletion: "Deletion",
|
||||
TapCheckpointStart: "TapCheckpointStart",
|
||||
TapCheckpointEnd: "TapCheckpointEnd",
|
||||
tapEndStream: "EndStream",
|
||||
}
|
||||
}
|
||||
|
||||
func (opcode TapOpcode) String() string {
|
||||
name := tapOpcodeNames[opcode]
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("#%d", opcode)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// TapEvent is a TAP notification of an operation on the server.
|
||||
type TapEvent struct {
|
||||
Opcode TapOpcode // Type of event
|
||||
VBucket uint16 // VBucket this event applies to
|
||||
Flags uint32 // Item flags
|
||||
Expiry uint32 // Item expiration time
|
||||
Key, Value []byte // Item key/value
|
||||
Cas uint64
|
||||
}
|
||||
|
||||
func makeTapEvent(req gomemcached.MCRequest) *TapEvent {
|
||||
event := TapEvent{
|
||||
VBucket: req.VBucket,
|
||||
}
|
||||
switch req.Opcode {
|
||||
case gomemcached.TAP_MUTATION:
|
||||
event.Opcode = TapMutation
|
||||
event.Key = req.Key
|
||||
event.Value = req.Body
|
||||
event.Cas = req.Cas
|
||||
case gomemcached.TAP_DELETE:
|
||||
event.Opcode = TapDeletion
|
||||
event.Key = req.Key
|
||||
event.Cas = req.Cas
|
||||
case gomemcached.TAP_CHECKPOINT_START:
|
||||
event.Opcode = TapCheckpointStart
|
||||
case gomemcached.TAP_CHECKPOINT_END:
|
||||
event.Opcode = TapCheckpointEnd
|
||||
case gomemcached.TAP_OPAQUE:
|
||||
if len(req.Extras) < 8+4 {
|
||||
return nil
|
||||
}
|
||||
switch op := int(binary.BigEndian.Uint32(req.Extras[8:])); op {
|
||||
case gomemcached.TAP_OPAQUE_INITIAL_VBUCKET_STREAM:
|
||||
event.Opcode = TapBeginBackfill
|
||||
case gomemcached.TAP_OPAQUE_CLOSE_BACKFILL:
|
||||
event.Opcode = TapEndBackfill
|
||||
case gomemcached.TAP_OPAQUE_CLOSE_TAP_STREAM:
|
||||
event.Opcode = tapEndStream
|
||||
case gomemcached.TAP_OPAQUE_ENABLE_AUTO_NACK:
|
||||
return nil
|
||||
case gomemcached.TAP_OPAQUE_ENABLE_CHECKPOINT_SYNC:
|
||||
return nil
|
||||
default:
|
||||
logging.Infof("TapFeed: Ignoring TAP_OPAQUE/%d", op)
|
||||
return nil // unknown opaque event
|
||||
}
|
||||
case gomemcached.NOOP:
|
||||
return nil // ignore
|
||||
default:
|
||||
logging.Infof("TapFeed: Ignoring %s", req.Opcode)
|
||||
return nil // unknown event
|
||||
}
|
||||
|
||||
if len(req.Extras) >= tapMutationExtraLen &&
|
||||
(event.Opcode == TapMutation || event.Opcode == TapDeletion) {
|
||||
|
||||
event.Flags = binary.BigEndian.Uint32(req.Extras[8:])
|
||||
event.Expiry = binary.BigEndian.Uint32(req.Extras[12:])
|
||||
}
|
||||
|
||||
return &event
|
||||
}
|
||||
|
||||
func (event TapEvent) String() string {
|
||||
switch event.Opcode {
|
||||
case TapBeginBackfill, TapEndBackfill, TapCheckpointStart, TapCheckpointEnd:
|
||||
return fmt.Sprintf("<TapEvent %s, vbucket=%d>",
|
||||
event.Opcode, event.VBucket)
|
||||
default:
|
||||
return fmt.Sprintf("<TapEvent %s, key=%q (%d bytes) flags=%x, exp=%d>",
|
||||
event.Opcode, event.Key, len(event.Value),
|
||||
event.Flags, event.Expiry)
|
||||
}
|
||||
}
|
||||
|
||||
// TapArguments are parameters for requesting a TAP feed.
|
||||
//
|
||||
// Call DefaultTapArguments to get a default one.
|
||||
type TapArguments struct {
|
||||
// Timestamp of oldest item to send.
|
||||
//
|
||||
// Use TapNoBackfill to suppress all past items.
|
||||
Backfill uint64
|
||||
// If set, server will disconnect after sending existing items.
|
||||
Dump bool
|
||||
// The indices of the vbuckets to watch; empty/nil to watch all.
|
||||
VBuckets []uint16
|
||||
// Transfers ownership of vbuckets during cluster rebalance.
|
||||
Takeover bool
|
||||
// If true, server will wait for client ACK after every notification.
|
||||
SupportAck bool
|
||||
// If true, client doesn't want values so server shouldn't send them.
|
||||
KeysOnly bool
|
||||
// If true, client wants the server to send checkpoint events.
|
||||
Checkpoint bool
|
||||
// Optional identifier to use for this client, to allow reconnects
|
||||
ClientName string
|
||||
// Registers this client (by name) till explicitly deregistered.
|
||||
RegisteredClient bool
|
||||
}
|
||||
|
||||
// Value for TapArguments.Backfill denoting that no past events at all
|
||||
// should be sent.
|
||||
const TapNoBackfill = math.MaxUint64
|
||||
|
||||
// DefaultTapArguments returns a default set of parameter values to
|
||||
// pass to StartTapFeed.
|
||||
func DefaultTapArguments() TapArguments {
|
||||
return TapArguments{
|
||||
Backfill: TapNoBackfill,
|
||||
}
|
||||
}
|
||||
|
||||
func (args *TapArguments) flags() []byte {
|
||||
var flags gomemcached.TapConnectFlag
|
||||
if args.Backfill != 0 {
|
||||
flags |= gomemcached.BACKFILL
|
||||
}
|
||||
if args.Dump {
|
||||
flags |= gomemcached.DUMP
|
||||
}
|
||||
if len(args.VBuckets) > 0 {
|
||||
flags |= gomemcached.LIST_VBUCKETS
|
||||
}
|
||||
if args.Takeover {
|
||||
flags |= gomemcached.TAKEOVER_VBUCKETS
|
||||
}
|
||||
if args.SupportAck {
|
||||
flags |= gomemcached.SUPPORT_ACK
|
||||
}
|
||||
if args.KeysOnly {
|
||||
flags |= gomemcached.REQUEST_KEYS_ONLY
|
||||
}
|
||||
if args.Checkpoint {
|
||||
flags |= gomemcached.CHECKPOINT
|
||||
}
|
||||
if args.RegisteredClient {
|
||||
flags |= gomemcached.REGISTERED_CLIENT
|
||||
}
|
||||
encoded := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(encoded, uint32(flags))
|
||||
return encoded
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (args *TapArguments) bytes() (rv []byte) {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
if args.Backfill > 0 {
|
||||
must(binary.Write(buf, binary.BigEndian, uint64(args.Backfill)))
|
||||
}
|
||||
|
||||
if len(args.VBuckets) > 0 {
|
||||
must(binary.Write(buf, binary.BigEndian, uint16(len(args.VBuckets))))
|
||||
for i := 0; i < len(args.VBuckets); i++ {
|
||||
must(binary.Write(buf, binary.BigEndian, uint16(args.VBuckets[i])))
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// TapFeed represents a stream of events from a server.
|
||||
type TapFeed struct {
|
||||
C <-chan TapEvent
|
||||
Error error
|
||||
closer chan bool
|
||||
}
|
||||
|
||||
// StartTapFeed starts a TAP feed on a client connection.
|
||||
//
|
||||
// The events can be read from the returned channel. The connection
|
||||
// can no longer be used for other purposes; it's now reserved for
|
||||
// receiving the TAP messages. To stop receiving events, close the
|
||||
// client connection.
|
||||
func (mc *Client) StartTapFeed(args TapArguments) (*TapFeed, error) {
|
||||
rq := &gomemcached.MCRequest{
|
||||
Opcode: gomemcached.TAP_CONNECT,
|
||||
Key: []byte(args.ClientName),
|
||||
Extras: args.flags(),
|
||||
Body: args.bytes()}
|
||||
|
||||
err := mc.Transmit(rq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch := make(chan TapEvent)
|
||||
feed := &TapFeed{
|
||||
C: ch,
|
||||
closer: make(chan bool),
|
||||
}
|
||||
go mc.runFeed(ch, feed)
|
||||
return feed, nil
|
||||
}
|
||||
|
||||
// TapRecvHook is called after every incoming tap packet is received.
|
||||
var TapRecvHook func(*gomemcached.MCRequest, int, error)
|
||||
|
||||
// Internal goroutine that reads from the socket and writes events to
|
||||
// the channel
|
||||
func (mc *Client) runFeed(ch chan TapEvent, feed *TapFeed) {
|
||||
defer close(ch)
|
||||
var headerBuf [gomemcached.HDR_LEN]byte
|
||||
loop:
|
||||
for {
|
||||
// Read the next request from the server.
|
||||
//
|
||||
// (Can't call mc.Receive() because it reads a
|
||||
// _response_ not a request.)
|
||||
var pkt gomemcached.MCRequest
|
||||
n, err := pkt.Receive(mc.conn, headerBuf[:])
|
||||
if TapRecvHook != nil {
|
||||
TapRecvHook(&pkt, n, err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
feed.Error = err
|
||||
}
|
||||
break loop
|
||||
}
|
||||
|
||||
//logging.Infof("** TapFeed received %#v : %q", pkt, pkt.Body)
|
||||
|
||||
if pkt.Opcode == gomemcached.TAP_CONNECT {
|
||||
// This is not an event from the server; it's
|
||||
// an error response to my connect request.
|
||||
feed.Error = fmt.Errorf("tap connection failed: %s", pkt.Body)
|
||||
break loop
|
||||
}
|
||||
|
||||
event := makeTapEvent(pkt)
|
||||
if event != nil {
|
||||
if event.Opcode == tapEndStream {
|
||||
break loop
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- *event:
|
||||
case <-feed.closer:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
if len(pkt.Extras) >= 4 {
|
||||
reqFlags := binary.BigEndian.Uint16(pkt.Extras[2:])
|
||||
if reqFlags&gomemcached.TAP_ACK != 0 {
|
||||
if _, err := mc.sendAck(&pkt); err != nil {
|
||||
feed.Error = err
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := mc.Close(); err != nil {
|
||||
logging.Errorf("Error closing memcached client: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *Client) sendAck(pkt *gomemcached.MCRequest) (int, error) {
|
||||
res := gomemcached.MCResponse{
|
||||
Opcode: pkt.Opcode,
|
||||
Opaque: pkt.Opaque,
|
||||
Status: gomemcached.SUCCESS,
|
||||
}
|
||||
return res.Transmit(mc.conn)
|
||||
}
|
||||
|
||||
// Close terminates a TapFeed.
|
||||
//
|
||||
// Call this if you stop using a TapFeed before its channel ends.
|
||||
func (feed *TapFeed) Close() {
|
||||
close(feed.closer)
|
||||
}
|
67
vendor/github.com/couchbase/gomemcached/client/transport.go
generated
vendored
Normal file
67
vendor/github.com/couchbase/gomemcached/client/transport.go
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
package memcached
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/couchbase/gomemcached"
|
||||
)
|
||||
|
||||
var errNoConn = errors.New("no connection")
|
||||
|
||||
// UnwrapMemcachedError converts memcached errors to normal responses.
|
||||
//
|
||||
// If the error is a memcached response, declare the error to be nil
|
||||
// so a client can handle the status without worrying about whether it
|
||||
// indicates success or failure.
|
||||
func UnwrapMemcachedError(rv *gomemcached.MCResponse,
|
||||
err error) (*gomemcached.MCResponse, error) {
|
||||
|
||||
if rv == err {
|
||||
return rv, nil
|
||||
}
|
||||
return rv, err
|
||||
}
|
||||
|
||||
// ReceiveHook is called after every packet is received (or attempted to be)
|
||||
var ReceiveHook func(*gomemcached.MCResponse, int, error)
|
||||
|
||||
func getResponse(s io.Reader, hdrBytes []byte) (rv *gomemcached.MCResponse, n int, err error) {
|
||||
if s == nil {
|
||||
return nil, 0, errNoConn
|
||||
}
|
||||
|
||||
rv = &gomemcached.MCResponse{}
|
||||
n, err = rv.Receive(s, hdrBytes)
|
||||
|
||||
if ReceiveHook != nil {
|
||||
ReceiveHook(rv, n, err)
|
||||
}
|
||||
|
||||
if err == nil && (rv.Status != gomemcached.SUCCESS && rv.Status != gomemcached.AUTH_CONTINUE) {
|
||||
err = rv
|
||||
}
|
||||
return rv, n, err
|
||||
}
|
||||
|
||||
// TransmitHook is called after each packet is transmitted.
|
||||
var TransmitHook func(*gomemcached.MCRequest, int, error)
|
||||
|
||||
func transmitRequest(o io.Writer, req *gomemcached.MCRequest) (int, error) {
|
||||
if o == nil {
|
||||
return 0, errNoConn
|
||||
}
|
||||
n, err := req.Transmit(o)
|
||||
if TransmitHook != nil {
|
||||
TransmitHook(req, n, err)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func transmitResponse(o io.Writer, res *gomemcached.MCResponse) (int, error) {
|
||||
if o == nil {
|
||||
return 0, errNoConn
|
||||
}
|
||||
n, err := res.Transmit(o)
|
||||
return n, err
|
||||
}
|
1005
vendor/github.com/couchbase/gomemcached/client/upr_feed.go
generated
vendored
Normal file
1005
vendor/github.com/couchbase/gomemcached/client/upr_feed.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
335
vendor/github.com/couchbase/gomemcached/mc_constants.go
generated
vendored
Normal file
335
vendor/github.com/couchbase/gomemcached/mc_constants.go
generated
vendored
Normal file
|
@ -0,0 +1,335 @@
|
|||
// Package gomemcached is binary protocol packet formats and constants.
|
||||
package gomemcached
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
REQ_MAGIC = 0x80
|
||||
RES_MAGIC = 0x81
|
||||
)
|
||||
|
||||
// CommandCode for memcached packets.
|
||||
type CommandCode uint8
|
||||
|
||||
const (
|
||||
GET = CommandCode(0x00)
|
||||
SET = CommandCode(0x01)
|
||||
ADD = CommandCode(0x02)
|
||||
REPLACE = CommandCode(0x03)
|
||||
DELETE = CommandCode(0x04)
|
||||
INCREMENT = CommandCode(0x05)
|
||||
DECREMENT = CommandCode(0x06)
|
||||
QUIT = CommandCode(0x07)
|
||||
FLUSH = CommandCode(0x08)
|
||||
GETQ = CommandCode(0x09)
|
||||
NOOP = CommandCode(0x0a)
|
||||
VERSION = CommandCode(0x0b)
|
||||
GETK = CommandCode(0x0c)
|
||||
GETKQ = CommandCode(0x0d)
|
||||
APPEND = CommandCode(0x0e)
|
||||
PREPEND = CommandCode(0x0f)
|
||||
STAT = CommandCode(0x10)
|
||||
SETQ = CommandCode(0x11)
|
||||
ADDQ = CommandCode(0x12)
|
||||
REPLACEQ = CommandCode(0x13)
|
||||
DELETEQ = CommandCode(0x14)
|
||||
INCREMENTQ = CommandCode(0x15)
|
||||
DECREMENTQ = CommandCode(0x16)
|
||||
QUITQ = CommandCode(0x17)
|
||||
FLUSHQ = CommandCode(0x18)
|
||||
APPENDQ = CommandCode(0x19)
|
||||
AUDIT = CommandCode(0x27)
|
||||
PREPENDQ = CommandCode(0x1a)
|
||||
GAT = CommandCode(0x1d)
|
||||
HELLO = CommandCode(0x1f)
|
||||
RGET = CommandCode(0x30)
|
||||
RSET = CommandCode(0x31)
|
||||
RSETQ = CommandCode(0x32)
|
||||
RAPPEND = CommandCode(0x33)
|
||||
RAPPENDQ = CommandCode(0x34)
|
||||
RPREPEND = CommandCode(0x35)
|
||||
RPREPENDQ = CommandCode(0x36)
|
||||
RDELETE = CommandCode(0x37)
|
||||
RDELETEQ = CommandCode(0x38)
|
||||
RINCR = CommandCode(0x39)
|
||||
RINCRQ = CommandCode(0x3a)
|
||||
RDECR = CommandCode(0x3b)
|
||||
RDECRQ = CommandCode(0x3c)
|
||||
|
||||
SASL_LIST_MECHS = CommandCode(0x20)
|
||||
SASL_AUTH = CommandCode(0x21)
|
||||
SASL_STEP = CommandCode(0x22)
|
||||
|
||||
SET_VBUCKET = CommandCode(0x3d)
|
||||
|
||||
TAP_CONNECT = CommandCode(0x40) // Client-sent request to initiate Tap feed
|
||||
TAP_MUTATION = CommandCode(0x41) // Notification of a SET/ADD/REPLACE/etc. on the server
|
||||
TAP_DELETE = CommandCode(0x42) // Notification of a DELETE on the server
|
||||
TAP_FLUSH = CommandCode(0x43) // Replicates a flush_all command
|
||||
TAP_OPAQUE = CommandCode(0x44) // Opaque control data from the engine
|
||||
TAP_VBUCKET_SET = CommandCode(0x45) // Sets state of vbucket in receiver (used in takeover)
|
||||
TAP_CHECKPOINT_START = CommandCode(0x46) // Notifies start of new checkpoint
|
||||
TAP_CHECKPOINT_END = CommandCode(0x47) // Notifies end of checkpoint
|
||||
|
||||
UPR_OPEN = CommandCode(0x50) // Open a UPR connection with a name
|
||||
UPR_ADDSTREAM = CommandCode(0x51) // Sent by ebucketMigrator to UPR Consumer
|
||||
UPR_CLOSESTREAM = CommandCode(0x52) // Sent by eBucketMigrator to UPR Consumer
|
||||
UPR_FAILOVERLOG = CommandCode(0x54) // Request failover logs
|
||||
UPR_STREAMREQ = CommandCode(0x53) // Stream request from consumer to producer
|
||||
UPR_STREAMEND = CommandCode(0x55) // Sent by producer when it has no more messages to stream
|
||||
UPR_SNAPSHOT = CommandCode(0x56) // Start of a new snapshot
|
||||
UPR_MUTATION = CommandCode(0x57) // Key mutation
|
||||
UPR_DELETION = CommandCode(0x58) // Key deletion
|
||||
UPR_EXPIRATION = CommandCode(0x59) // Key expiration
|
||||
UPR_FLUSH = CommandCode(0x5a) // Delete all the data for a vbucket
|
||||
UPR_NOOP = CommandCode(0x5c) // UPR NOOP
|
||||
UPR_BUFFERACK = CommandCode(0x5d) // UPR Buffer Acknowledgement
|
||||
UPR_CONTROL = CommandCode(0x5e) // Set flow control params
|
||||
|
||||
SELECT_BUCKET = CommandCode(0x89) // Select bucket
|
||||
|
||||
OBSERVE_SEQNO = CommandCode(0x91) // Sequence Number based Observe
|
||||
OBSERVE = CommandCode(0x92)
|
||||
|
||||
GET_META = CommandCode(0xA0) // Get meta. returns with expiry, flags, cas etc
|
||||
SUBDOC_GET = CommandCode(0xc5) // Get subdoc. Returns with xattrs
|
||||
SUBDOC_MULTI_LOOKUP = CommandCode(0xd0) // Multi lookup. Doc xattrs and meta.
|
||||
)
|
||||
|
||||
// command codes that are counted toward DCP control buffer
|
||||
// when DCP clients receive DCP messages with these command codes, they need to provide acknowledgement
|
||||
var BufferedCommandCodeMap = map[CommandCode]bool{
|
||||
SET_VBUCKET: true,
|
||||
UPR_STREAMEND: true,
|
||||
UPR_SNAPSHOT: true,
|
||||
UPR_MUTATION: true,
|
||||
UPR_DELETION: true,
|
||||
UPR_EXPIRATION: true}
|
||||
|
||||
// Status field for memcached response.
|
||||
type Status uint16
|
||||
|
||||
// Matches with protocol_binary.h as source of truth
|
||||
const (
|
||||
SUCCESS = Status(0x00)
|
||||
KEY_ENOENT = Status(0x01)
|
||||
KEY_EEXISTS = Status(0x02)
|
||||
E2BIG = Status(0x03)
|
||||
EINVAL = Status(0x04)
|
||||
NOT_STORED = Status(0x05)
|
||||
DELTA_BADVAL = Status(0x06)
|
||||
NOT_MY_VBUCKET = Status(0x07)
|
||||
NO_BUCKET = Status(0x08)
|
||||
LOCKED = Status(0x09)
|
||||
AUTH_STALE = Status(0x1f)
|
||||
AUTH_ERROR = Status(0x20)
|
||||
AUTH_CONTINUE = Status(0x21)
|
||||
ERANGE = Status(0x22)
|
||||
ROLLBACK = Status(0x23)
|
||||
EACCESS = Status(0x24)
|
||||
NOT_INITIALIZED = Status(0x25)
|
||||
UNKNOWN_COMMAND = Status(0x81)
|
||||
ENOMEM = Status(0x82)
|
||||
NOT_SUPPORTED = Status(0x83)
|
||||
EINTERNAL = Status(0x84)
|
||||
EBUSY = Status(0x85)
|
||||
TMPFAIL = Status(0x86)
|
||||
|
||||
// SUBDOC
|
||||
SUBDOC_PATH_NOT_FOUND = Status(0xc0)
|
||||
SUBDOC_BAD_MULTI = Status(0xcc)
|
||||
SUBDOC_MULTI_PATH_FAILURE_DELETED = Status(0xd3)
|
||||
)
|
||||
|
||||
// for log redaction
|
||||
const (
|
||||
UdTagBegin = "<ud>"
|
||||
UdTagEnd = "</ud>"
|
||||
)
|
||||
|
||||
var isFatal = map[Status]bool{
|
||||
DELTA_BADVAL: true,
|
||||
NO_BUCKET: true,
|
||||
AUTH_STALE: true,
|
||||
AUTH_ERROR: true,
|
||||
ERANGE: true,
|
||||
ROLLBACK: true,
|
||||
EACCESS: true,
|
||||
ENOMEM: true,
|
||||
NOT_SUPPORTED: true,
|
||||
}
|
||||
|
||||
// the producer/consumer bit in dcp flags
|
||||
var DCP_PRODUCER uint32 = 0x01
|
||||
|
||||
// the include XATTRS bit in dcp flags
|
||||
var DCP_OPEN_INCLUDE_XATTRS uint32 = 0x04
|
||||
|
||||
// the include deletion time bit in dcp flags
|
||||
var DCP_OPEN_INCLUDE_DELETE_TIMES uint32 = 0x20
|
||||
|
||||
// Datatype to Include XATTRS in SUBDOC GET
|
||||
var SUBDOC_FLAG_XATTR uint8 = 0x04
|
||||
|
||||
// MCItem is an internal representation of an item.
|
||||
type MCItem struct {
|
||||
Cas uint64
|
||||
Flags, Expiration uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// Number of bytes in a binary protocol header.
|
||||
const HDR_LEN = 24
|
||||
|
||||
// Mapping of CommandCode -> name of command (not exhaustive)
|
||||
var CommandNames map[CommandCode]string
|
||||
|
||||
// StatusNames human readable names for memcached response.
|
||||
var StatusNames map[Status]string
|
||||
|
||||
func init() {
|
||||
CommandNames = make(map[CommandCode]string)
|
||||
CommandNames[GET] = "GET"
|
||||
CommandNames[SET] = "SET"
|
||||
CommandNames[ADD] = "ADD"
|
||||
CommandNames[REPLACE] = "REPLACE"
|
||||
CommandNames[DELETE] = "DELETE"
|
||||
CommandNames[INCREMENT] = "INCREMENT"
|
||||
CommandNames[DECREMENT] = "DECREMENT"
|
||||
CommandNames[QUIT] = "QUIT"
|
||||
CommandNames[FLUSH] = "FLUSH"
|
||||
CommandNames[GETQ] = "GETQ"
|
||||
CommandNames[NOOP] = "NOOP"
|
||||
CommandNames[VERSION] = "VERSION"
|
||||
CommandNames[GETK] = "GETK"
|
||||
CommandNames[GETKQ] = "GETKQ"
|
||||
CommandNames[APPEND] = "APPEND"
|
||||
CommandNames[PREPEND] = "PREPEND"
|
||||
CommandNames[STAT] = "STAT"
|
||||
CommandNames[SETQ] = "SETQ"
|
||||
CommandNames[ADDQ] = "ADDQ"
|
||||
CommandNames[REPLACEQ] = "REPLACEQ"
|
||||
CommandNames[DELETEQ] = "DELETEQ"
|
||||
CommandNames[INCREMENTQ] = "INCREMENTQ"
|
||||
CommandNames[DECREMENTQ] = "DECREMENTQ"
|
||||
CommandNames[QUITQ] = "QUITQ"
|
||||
CommandNames[FLUSHQ] = "FLUSHQ"
|
||||
CommandNames[APPENDQ] = "APPENDQ"
|
||||
CommandNames[PREPENDQ] = "PREPENDQ"
|
||||
CommandNames[RGET] = "RGET"
|
||||
CommandNames[RSET] = "RSET"
|
||||
CommandNames[RSETQ] = "RSETQ"
|
||||
CommandNames[RAPPEND] = "RAPPEND"
|
||||
CommandNames[RAPPENDQ] = "RAPPENDQ"
|
||||
CommandNames[RPREPEND] = "RPREPEND"
|
||||
CommandNames[RPREPENDQ] = "RPREPENDQ"
|
||||
CommandNames[RDELETE] = "RDELETE"
|
||||
CommandNames[RDELETEQ] = "RDELETEQ"
|
||||
CommandNames[RINCR] = "RINCR"
|
||||
CommandNames[RINCRQ] = "RINCRQ"
|
||||
CommandNames[RDECR] = "RDECR"
|
||||
CommandNames[RDECRQ] = "RDECRQ"
|
||||
|
||||
CommandNames[SASL_LIST_MECHS] = "SASL_LIST_MECHS"
|
||||
CommandNames[SASL_AUTH] = "SASL_AUTH"
|
||||
CommandNames[SASL_STEP] = "SASL_STEP"
|
||||
|
||||
CommandNames[TAP_CONNECT] = "TAP_CONNECT"
|
||||
CommandNames[TAP_MUTATION] = "TAP_MUTATION"
|
||||
CommandNames[TAP_DELETE] = "TAP_DELETE"
|
||||
CommandNames[TAP_FLUSH] = "TAP_FLUSH"
|
||||
CommandNames[TAP_OPAQUE] = "TAP_OPAQUE"
|
||||
CommandNames[TAP_VBUCKET_SET] = "TAP_VBUCKET_SET"
|
||||
CommandNames[TAP_CHECKPOINT_START] = "TAP_CHECKPOINT_START"
|
||||
CommandNames[TAP_CHECKPOINT_END] = "TAP_CHECKPOINT_END"
|
||||
|
||||
CommandNames[UPR_OPEN] = "UPR_OPEN"
|
||||
CommandNames[UPR_ADDSTREAM] = "UPR_ADDSTREAM"
|
||||
CommandNames[UPR_CLOSESTREAM] = "UPR_CLOSESTREAM"
|
||||
CommandNames[UPR_FAILOVERLOG] = "UPR_FAILOVERLOG"
|
||||
CommandNames[UPR_STREAMREQ] = "UPR_STREAMREQ"
|
||||
CommandNames[UPR_STREAMEND] = "UPR_STREAMEND"
|
||||
CommandNames[UPR_SNAPSHOT] = "UPR_SNAPSHOT"
|
||||
CommandNames[UPR_MUTATION] = "UPR_MUTATION"
|
||||
CommandNames[UPR_DELETION] = "UPR_DELETION"
|
||||
CommandNames[UPR_EXPIRATION] = "UPR_EXPIRATION"
|
||||
CommandNames[UPR_FLUSH] = "UPR_FLUSH"
|
||||
CommandNames[UPR_NOOP] = "UPR_NOOP"
|
||||
CommandNames[UPR_BUFFERACK] = "UPR_BUFFERACK"
|
||||
CommandNames[UPR_CONTROL] = "UPR_CONTROL"
|
||||
CommandNames[SUBDOC_GET] = "SUBDOC_GET"
|
||||
CommandNames[SUBDOC_MULTI_LOOKUP] = "SUBDOC_MULTI_LOOKUP"
|
||||
|
||||
StatusNames = make(map[Status]string)
|
||||
StatusNames[SUCCESS] = "SUCCESS"
|
||||
StatusNames[KEY_ENOENT] = "KEY_ENOENT"
|
||||
StatusNames[KEY_EEXISTS] = "KEY_EEXISTS"
|
||||
StatusNames[E2BIG] = "E2BIG"
|
||||
StatusNames[EINVAL] = "EINVAL"
|
||||
StatusNames[NOT_STORED] = "NOT_STORED"
|
||||
StatusNames[DELTA_BADVAL] = "DELTA_BADVAL"
|
||||
StatusNames[NOT_MY_VBUCKET] = "NOT_MY_VBUCKET"
|
||||
StatusNames[NO_BUCKET] = "NO_BUCKET"
|
||||
StatusNames[AUTH_STALE] = "AUTH_STALE"
|
||||
StatusNames[AUTH_ERROR] = "AUTH_ERROR"
|
||||
StatusNames[AUTH_CONTINUE] = "AUTH_CONTINUE"
|
||||
StatusNames[ERANGE] = "ERANGE"
|
||||
StatusNames[ROLLBACK] = "ROLLBACK"
|
||||
StatusNames[EACCESS] = "EACCESS"
|
||||
StatusNames[NOT_INITIALIZED] = "NOT_INITIALIZED"
|
||||
StatusNames[UNKNOWN_COMMAND] = "UNKNOWN_COMMAND"
|
||||
StatusNames[ENOMEM] = "ENOMEM"
|
||||
StatusNames[NOT_SUPPORTED] = "NOT_SUPPORTED"
|
||||
StatusNames[EINTERNAL] = "EINTERNAL"
|
||||
StatusNames[EBUSY] = "EBUSY"
|
||||
StatusNames[TMPFAIL] = "TMPFAIL"
|
||||
StatusNames[SUBDOC_PATH_NOT_FOUND] = "SUBDOC_PATH_NOT_FOUND"
|
||||
StatusNames[SUBDOC_BAD_MULTI] = "SUBDOC_BAD_MULTI"
|
||||
|
||||
}
|
||||
|
||||
// String an op code.
|
||||
func (o CommandCode) String() (rv string) {
|
||||
rv = CommandNames[o]
|
||||
if rv == "" {
|
||||
rv = fmt.Sprintf("0x%02x", int(o))
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// String an op code.
|
||||
func (s Status) String() (rv string) {
|
||||
rv = StatusNames[s]
|
||||
if rv == "" {
|
||||
rv = fmt.Sprintf("0x%02x", int(s))
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// IsQuiet will return true if a command is a "quiet" command.
|
||||
func (o CommandCode) IsQuiet() bool {
|
||||
switch o {
|
||||
case GETQ,
|
||||
GETKQ,
|
||||
SETQ,
|
||||
ADDQ,
|
||||
REPLACEQ,
|
||||
DELETEQ,
|
||||
INCREMENTQ,
|
||||
DECREMENTQ,
|
||||
QUITQ,
|
||||
FLUSHQ,
|
||||
APPENDQ,
|
||||
PREPENDQ,
|
||||
RSETQ,
|
||||
RAPPENDQ,
|
||||
RPREPENDQ,
|
||||
RDELETEQ,
|
||||
RINCRQ,
|
||||
RDECRQ:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
197
vendor/github.com/couchbase/gomemcached/mc_req.go
generated
vendored
Normal file
197
vendor/github.com/couchbase/gomemcached/mc_req.go
generated
vendored
Normal file
|
@ -0,0 +1,197 @@
|
|||
package gomemcached
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// The maximum reasonable body length to expect.
|
||||
// Anything larger than this will result in an error.
|
||||
// The current limit, 20MB, is the size limit supported by ep-engine.
|
||||
var MaxBodyLen = int(20 * 1024 * 1024)
|
||||
|
||||
// MCRequest is memcached Request
|
||||
type MCRequest struct {
|
||||
// The command being issued
|
||||
Opcode CommandCode
|
||||
// The CAS (if applicable, or 0)
|
||||
Cas uint64
|
||||
// An opaque value to be returned with this request
|
||||
Opaque uint32
|
||||
// The vbucket to which this command belongs
|
||||
VBucket uint16
|
||||
// Command extras, key, and body
|
||||
Extras, Key, Body, ExtMeta []byte
|
||||
// Datatype identifier
|
||||
DataType uint8
|
||||
}
|
||||
|
||||
// Size gives the number of bytes this request requires.
|
||||
func (req *MCRequest) Size() int {
|
||||
return HDR_LEN + len(req.Extras) + len(req.Key) + len(req.Body) + len(req.ExtMeta)
|
||||
}
|
||||
|
||||
// A debugging string representation of this request
|
||||
func (req MCRequest) String() string {
|
||||
return fmt.Sprintf("{MCRequest opcode=%s, bodylen=%d, key='%s'}",
|
||||
req.Opcode, len(req.Body), req.Key)
|
||||
}
|
||||
|
||||
func (req *MCRequest) fillHeaderBytes(data []byte) int {
|
||||
|
||||
pos := 0
|
||||
data[pos] = REQ_MAGIC
|
||||
pos++
|
||||
data[pos] = byte(req.Opcode)
|
||||
pos++
|
||||
binary.BigEndian.PutUint16(data[pos:pos+2],
|
||||
uint16(len(req.Key)))
|
||||
pos += 2
|
||||
|
||||
// 4
|
||||
data[pos] = byte(len(req.Extras))
|
||||
pos++
|
||||
// Data type
|
||||
if req.DataType != 0 {
|
||||
data[pos] = byte(req.DataType)
|
||||
}
|
||||
pos++
|
||||
binary.BigEndian.PutUint16(data[pos:pos+2], req.VBucket)
|
||||
pos += 2
|
||||
|
||||
// 8
|
||||
binary.BigEndian.PutUint32(data[pos:pos+4],
|
||||
uint32(len(req.Body)+len(req.Key)+len(req.Extras)+len(req.ExtMeta)))
|
||||
pos += 4
|
||||
|
||||
// 12
|
||||
binary.BigEndian.PutUint32(data[pos:pos+4], req.Opaque)
|
||||
pos += 4
|
||||
|
||||
// 16
|
||||
if req.Cas != 0 {
|
||||
binary.BigEndian.PutUint64(data[pos:pos+8], req.Cas)
|
||||
}
|
||||
pos += 8
|
||||
|
||||
if len(req.Extras) > 0 {
|
||||
copy(data[pos:pos+len(req.Extras)], req.Extras)
|
||||
pos += len(req.Extras)
|
||||
}
|
||||
|
||||
if len(req.Key) > 0 {
|
||||
copy(data[pos:pos+len(req.Key)], req.Key)
|
||||
pos += len(req.Key)
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
// HeaderBytes will return the wire representation of the request header
|
||||
// (with the extras and key).
|
||||
func (req *MCRequest) HeaderBytes() []byte {
|
||||
data := make([]byte, HDR_LEN+len(req.Extras)+len(req.Key))
|
||||
|
||||
req.fillHeaderBytes(data)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Bytes will return the wire representation of this request.
|
||||
func (req *MCRequest) Bytes() []byte {
|
||||
data := make([]byte, req.Size())
|
||||
|
||||
pos := req.fillHeaderBytes(data)
|
||||
|
||||
if len(req.Body) > 0 {
|
||||
copy(data[pos:pos+len(req.Body)], req.Body)
|
||||
}
|
||||
|
||||
if len(req.ExtMeta) > 0 {
|
||||
copy(data[pos+len(req.Body):pos+len(req.Body)+len(req.ExtMeta)], req.ExtMeta)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Transmit will send this request message across a writer.
|
||||
func (req *MCRequest) Transmit(w io.Writer) (n int, err error) {
|
||||
if len(req.Body) < 128 {
|
||||
n, err = w.Write(req.Bytes())
|
||||
} else {
|
||||
n, err = w.Write(req.HeaderBytes())
|
||||
if err == nil {
|
||||
m := 0
|
||||
m, err = w.Write(req.Body)
|
||||
n += m
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Receive will fill this MCRequest with the data from a reader.
|
||||
func (req *MCRequest) Receive(r io.Reader, hdrBytes []byte) (int, error) {
|
||||
if len(hdrBytes) < HDR_LEN {
|
||||
hdrBytes = []byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0}
|
||||
}
|
||||
n, err := io.ReadFull(r, hdrBytes)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if hdrBytes[0] != RES_MAGIC && hdrBytes[0] != REQ_MAGIC {
|
||||
return n, fmt.Errorf("bad magic: 0x%02x", hdrBytes[0])
|
||||
}
|
||||
|
||||
klen := int(binary.BigEndian.Uint16(hdrBytes[2:]))
|
||||
elen := int(hdrBytes[4])
|
||||
// Data type at 5
|
||||
req.DataType = uint8(hdrBytes[5])
|
||||
|
||||
req.Opcode = CommandCode(hdrBytes[1])
|
||||
// Vbucket at 6:7
|
||||
req.VBucket = binary.BigEndian.Uint16(hdrBytes[6:])
|
||||
totalBodyLen := int(binary.BigEndian.Uint32(hdrBytes[8:]))
|
||||
|
||||
req.Opaque = binary.BigEndian.Uint32(hdrBytes[12:])
|
||||
req.Cas = binary.BigEndian.Uint64(hdrBytes[16:])
|
||||
|
||||
if totalBodyLen > 0 {
|
||||
buf := make([]byte, totalBodyLen)
|
||||
m, err := io.ReadFull(r, buf)
|
||||
n += m
|
||||
if err == nil {
|
||||
if req.Opcode >= TAP_MUTATION &&
|
||||
req.Opcode <= TAP_CHECKPOINT_END &&
|
||||
len(buf) > 1 {
|
||||
// In these commands there is "engine private"
|
||||
// data at the end of the extras. The first 2
|
||||
// bytes of extra data give its length.
|
||||
elen += int(binary.BigEndian.Uint16(buf))
|
||||
}
|
||||
|
||||
req.Extras = buf[0:elen]
|
||||
req.Key = buf[elen : klen+elen]
|
||||
|
||||
// get the length of extended metadata
|
||||
extMetaLen := 0
|
||||
if elen > 29 {
|
||||
extMetaLen = int(binary.BigEndian.Uint16(req.Extras[28:30]))
|
||||
}
|
||||
|
||||
bodyLen := totalBodyLen - klen - elen - extMetaLen
|
||||
if bodyLen > MaxBodyLen {
|
||||
return n, fmt.Errorf("%d is too big (max %d)",
|
||||
bodyLen, MaxBodyLen)
|
||||
}
|
||||
|
||||
req.Body = buf[klen+elen : klen+elen+bodyLen]
|
||||
req.ExtMeta = buf[klen+elen+bodyLen:]
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
267
vendor/github.com/couchbase/gomemcached/mc_res.go
generated
vendored
Normal file
267
vendor/github.com/couchbase/gomemcached/mc_res.go
generated
vendored
Normal file
|
@ -0,0 +1,267 @@
|
|||
package gomemcached
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MCResponse is memcached response
|
||||
type MCResponse struct {
|
||||
// The command opcode of the command that sent the request
|
||||
Opcode CommandCode
|
||||
// The status of the response
|
||||
Status Status
|
||||
// The opaque sent in the request
|
||||
Opaque uint32
|
||||
// The CAS identifier (if applicable)
|
||||
Cas uint64
|
||||
// Extras, key, and body for this response
|
||||
Extras, Key, Body []byte
|
||||
// If true, this represents a fatal condition and we should hang up
|
||||
Fatal bool
|
||||
// Datatype identifier
|
||||
DataType uint8
|
||||
}
|
||||
|
||||
// A debugging string representation of this response
|
||||
func (res MCResponse) String() string {
|
||||
return fmt.Sprintf("{MCResponse status=%v keylen=%d, extralen=%d, bodylen=%d}",
|
||||
res.Status, len(res.Key), len(res.Extras), len(res.Body))
|
||||
}
|
||||
|
||||
// Response as an error.
|
||||
func (res *MCResponse) Error() string {
|
||||
return fmt.Sprintf("MCResponse status=%v, opcode=%v, opaque=%v, msg: %s",
|
||||
res.Status, res.Opcode, res.Opaque, string(res.Body))
|
||||
}
|
||||
|
||||
func errStatus(e error) Status {
|
||||
status := Status(0xffff)
|
||||
if res, ok := e.(*MCResponse); ok {
|
||||
status = res.Status
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
// IsNotFound is true if this error represents a "not found" response.
|
||||
func IsNotFound(e error) bool {
|
||||
return errStatus(e) == KEY_ENOENT
|
||||
}
|
||||
|
||||
// IsFatal is false if this error isn't believed to be fatal to a connection.
|
||||
func IsFatal(e error) bool {
|
||||
if e == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := isFatal[errStatus(e)]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Size is number of bytes this response consumes on the wire.
|
||||
func (res *MCResponse) Size() int {
|
||||
return HDR_LEN + len(res.Extras) + len(res.Key) + len(res.Body)
|
||||
}
|
||||
|
||||
func (res *MCResponse) fillHeaderBytes(data []byte) int {
|
||||
pos := 0
|
||||
data[pos] = RES_MAGIC
|
||||
pos++
|
||||
data[pos] = byte(res.Opcode)
|
||||
pos++
|
||||
binary.BigEndian.PutUint16(data[pos:pos+2],
|
||||
uint16(len(res.Key)))
|
||||
pos += 2
|
||||
|
||||
// 4
|
||||
data[pos] = byte(len(res.Extras))
|
||||
pos++
|
||||
// Data type
|
||||
if res.DataType != 0 {
|
||||
data[pos] = byte(res.DataType)
|
||||
} else {
|
||||
data[pos] = 0
|
||||
}
|
||||
pos++
|
||||
binary.BigEndian.PutUint16(data[pos:pos+2], uint16(res.Status))
|
||||
pos += 2
|
||||
|
||||
// 8
|
||||
binary.BigEndian.PutUint32(data[pos:pos+4],
|
||||
uint32(len(res.Body)+len(res.Key)+len(res.Extras)))
|
||||
pos += 4
|
||||
|
||||
// 12
|
||||
binary.BigEndian.PutUint32(data[pos:pos+4], res.Opaque)
|
||||
pos += 4
|
||||
|
||||
// 16
|
||||
binary.BigEndian.PutUint64(data[pos:pos+8], res.Cas)
|
||||
pos += 8
|
||||
|
||||
if len(res.Extras) > 0 {
|
||||
copy(data[pos:pos+len(res.Extras)], res.Extras)
|
||||
pos += len(res.Extras)
|
||||
}
|
||||
|
||||
if len(res.Key) > 0 {
|
||||
copy(data[pos:pos+len(res.Key)], res.Key)
|
||||
pos += len(res.Key)
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
// HeaderBytes will get just the header bytes for this response.
|
||||
func (res *MCResponse) HeaderBytes() []byte {
|
||||
data := make([]byte, HDR_LEN+len(res.Extras)+len(res.Key))
|
||||
|
||||
res.fillHeaderBytes(data)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Bytes will return the actual bytes transmitted for this response.
|
||||
func (res *MCResponse) Bytes() []byte {
|
||||
data := make([]byte, res.Size())
|
||||
|
||||
pos := res.fillHeaderBytes(data)
|
||||
|
||||
copy(data[pos:pos+len(res.Body)], res.Body)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Transmit will send this response message across a writer.
|
||||
func (res *MCResponse) Transmit(w io.Writer) (n int, err error) {
|
||||
if len(res.Body) < 128 {
|
||||
n, err = w.Write(res.Bytes())
|
||||
} else {
|
||||
n, err = w.Write(res.HeaderBytes())
|
||||
if err == nil {
|
||||
m := 0
|
||||
m, err = w.Write(res.Body)
|
||||
m += n
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Receive will fill this MCResponse with the data from this reader.
|
||||
func (res *MCResponse) Receive(r io.Reader, hdrBytes []byte) (n int, err error) {
|
||||
if len(hdrBytes) < HDR_LEN {
|
||||
hdrBytes = []byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0}
|
||||
}
|
||||
n, err = io.ReadFull(r, hdrBytes)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if hdrBytes[0] != RES_MAGIC && hdrBytes[0] != REQ_MAGIC {
|
||||
return n, fmt.Errorf("bad magic: 0x%02x", hdrBytes[0])
|
||||
}
|
||||
|
||||
klen := int(binary.BigEndian.Uint16(hdrBytes[2:4]))
|
||||
elen := int(hdrBytes[4])
|
||||
|
||||
res.Opcode = CommandCode(hdrBytes[1])
|
||||
res.DataType = uint8(hdrBytes[5])
|
||||
res.Status = Status(binary.BigEndian.Uint16(hdrBytes[6:8]))
|
||||
res.Opaque = binary.BigEndian.Uint32(hdrBytes[12:16])
|
||||
res.Cas = binary.BigEndian.Uint64(hdrBytes[16:24])
|
||||
|
||||
bodyLen := int(binary.BigEndian.Uint32(hdrBytes[8:12])) - (klen + elen)
|
||||
|
||||
//defer function to debug the panic seen with MB-15557
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf(`Panic in Receive. Response %v \n
|
||||
key len %v extra len %v bodylen %v`, res, klen, elen, bodyLen)
|
||||
}
|
||||
}()
|
||||
|
||||
buf := make([]byte, klen+elen+bodyLen)
|
||||
m, err := io.ReadFull(r, buf)
|
||||
if err == nil {
|
||||
res.Extras = buf[0:elen]
|
||||
res.Key = buf[elen : klen+elen]
|
||||
res.Body = buf[klen+elen:]
|
||||
}
|
||||
|
||||
return n + m, err
|
||||
}
|
||||
|
||||
type MCResponsePool struct {
|
||||
pool *sync.Pool
|
||||
}
|
||||
|
||||
func NewMCResponsePool() *MCResponsePool {
|
||||
rv := &MCResponsePool{
|
||||
pool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &MCResponse{}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
func (this *MCResponsePool) Get() *MCResponse {
|
||||
return this.pool.Get().(*MCResponse)
|
||||
}
|
||||
|
||||
func (this *MCResponsePool) Put(r *MCResponse) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.Extras = nil
|
||||
r.Key = nil
|
||||
r.Body = nil
|
||||
r.Fatal = false
|
||||
|
||||
this.pool.Put(r)
|
||||
}
|
||||
|
||||
type StringMCResponsePool struct {
|
||||
pool *sync.Pool
|
||||
size int
|
||||
}
|
||||
|
||||
func NewStringMCResponsePool(size int) *StringMCResponsePool {
|
||||
rv := &StringMCResponsePool{
|
||||
pool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make(map[string]*MCResponse, size)
|
||||
},
|
||||
},
|
||||
size: size,
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
func (this *StringMCResponsePool) Get() map[string]*MCResponse {
|
||||
return this.pool.Get().(map[string]*MCResponse)
|
||||
}
|
||||
|
||||
func (this *StringMCResponsePool) Put(m map[string]*MCResponse) {
|
||||
if m == nil || len(m) > 2*this.size {
|
||||
return
|
||||
}
|
||||
|
||||
for k := range m {
|
||||
m[k] = nil
|
||||
delete(m, k)
|
||||
}
|
||||
|
||||
this.pool.Put(m)
|
||||
}
|
168
vendor/github.com/couchbase/gomemcached/tap.go
generated
vendored
Normal file
168
vendor/github.com/couchbase/gomemcached/tap.go
generated
vendored
Normal file
|
@ -0,0 +1,168 @@
|
|||
package gomemcached
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TapConnectFlag uint32
|
||||
|
||||
// Tap connect option flags
|
||||
const (
|
||||
BACKFILL = TapConnectFlag(0x01)
|
||||
DUMP = TapConnectFlag(0x02)
|
||||
LIST_VBUCKETS = TapConnectFlag(0x04)
|
||||
TAKEOVER_VBUCKETS = TapConnectFlag(0x08)
|
||||
SUPPORT_ACK = TapConnectFlag(0x10)
|
||||
REQUEST_KEYS_ONLY = TapConnectFlag(0x20)
|
||||
CHECKPOINT = TapConnectFlag(0x40)
|
||||
REGISTERED_CLIENT = TapConnectFlag(0x80)
|
||||
FIX_FLAG_BYTEORDER = TapConnectFlag(0x100)
|
||||
)
|
||||
|
||||
// Tap opaque event subtypes
|
||||
const (
|
||||
TAP_OPAQUE_ENABLE_AUTO_NACK = 0
|
||||
TAP_OPAQUE_INITIAL_VBUCKET_STREAM = 1
|
||||
TAP_OPAQUE_ENABLE_CHECKPOINT_SYNC = 2
|
||||
TAP_OPAQUE_CLOSE_TAP_STREAM = 7
|
||||
TAP_OPAQUE_CLOSE_BACKFILL = 8
|
||||
)
|
||||
|
||||
// Tap item flags
|
||||
const (
|
||||
TAP_ACK = 1
|
||||
TAP_NO_VALUE = 2
|
||||
TAP_FLAG_NETWORK_BYTE_ORDER = 4
|
||||
)
|
||||
|
||||
// TapConnectFlagNames for TapConnectFlag
|
||||
var TapConnectFlagNames = map[TapConnectFlag]string{
|
||||
BACKFILL: "BACKFILL",
|
||||
DUMP: "DUMP",
|
||||
LIST_VBUCKETS: "LIST_VBUCKETS",
|
||||
TAKEOVER_VBUCKETS: "TAKEOVER_VBUCKETS",
|
||||
SUPPORT_ACK: "SUPPORT_ACK",
|
||||
REQUEST_KEYS_ONLY: "REQUEST_KEYS_ONLY",
|
||||
CHECKPOINT: "CHECKPOINT",
|
||||
REGISTERED_CLIENT: "REGISTERED_CLIENT",
|
||||
FIX_FLAG_BYTEORDER: "FIX_FLAG_BYTEORDER",
|
||||
}
|
||||
|
||||
// TapItemParser is a function to parse a single tap extra.
|
||||
type TapItemParser func(io.Reader) (interface{}, error)
|
||||
|
||||
// TapParseUint64 is a function to parse a single tap uint64.
|
||||
func TapParseUint64(r io.Reader) (interface{}, error) {
|
||||
var rv uint64
|
||||
err := binary.Read(r, binary.BigEndian, &rv)
|
||||
return rv, err
|
||||
}
|
||||
|
||||
// TapParseUint16 is a function to parse a single tap uint16.
|
||||
func TapParseUint16(r io.Reader) (interface{}, error) {
|
||||
var rv uint16
|
||||
err := binary.Read(r, binary.BigEndian, &rv)
|
||||
return rv, err
|
||||
}
|
||||
|
||||
// TapParseBool is a function to parse a single tap boolean.
|
||||
func TapParseBool(r io.Reader) (interface{}, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// TapParseVBList parses a list of vBucket numbers as []uint16.
|
||||
func TapParseVBList(r io.Reader) (interface{}, error) {
|
||||
num, err := TapParseUint16(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n := int(num.(uint16))
|
||||
|
||||
rv := make([]uint16, n)
|
||||
for i := 0; i < n; i++ {
|
||||
x, err := TapParseUint16(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rv[i] = x.(uint16)
|
||||
}
|
||||
|
||||
return rv, err
|
||||
}
|
||||
|
||||
// TapFlagParsers parser functions for TAP fields.
|
||||
var TapFlagParsers = map[TapConnectFlag]TapItemParser{
|
||||
BACKFILL: TapParseUint64,
|
||||
LIST_VBUCKETS: TapParseVBList,
|
||||
}
|
||||
|
||||
// SplitFlags will split the ORed flags into the individual bit flags.
|
||||
func (f TapConnectFlag) SplitFlags() []TapConnectFlag {
|
||||
rv := []TapConnectFlag{}
|
||||
for i := uint32(1); f != 0; i = i << 1 {
|
||||
if uint32(f)&i == i {
|
||||
rv = append(rv, TapConnectFlag(i))
|
||||
}
|
||||
f = TapConnectFlag(uint32(f) & (^i))
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (f TapConnectFlag) String() string {
|
||||
parts := []string{}
|
||||
for _, x := range f.SplitFlags() {
|
||||
p := TapConnectFlagNames[x]
|
||||
if p == "" {
|
||||
p = fmt.Sprintf("0x%x", int(x))
|
||||
}
|
||||
parts = append(parts, p)
|
||||
}
|
||||
return strings.Join(parts, "|")
|
||||
}
|
||||
|
||||
type TapConnect struct {
|
||||
Flags map[TapConnectFlag]interface{}
|
||||
RemainingBody []byte
|
||||
Name string
|
||||
}
|
||||
|
||||
// ParseTapCommands parse the tap request into the interesting bits we may
|
||||
// need to do something with.
|
||||
func (req *MCRequest) ParseTapCommands() (TapConnect, error) {
|
||||
rv := TapConnect{
|
||||
Flags: map[TapConnectFlag]interface{}{},
|
||||
Name: string(req.Key),
|
||||
}
|
||||
|
||||
if len(req.Extras) < 4 {
|
||||
return rv, fmt.Errorf("not enough extra bytes: %x", req.Extras)
|
||||
}
|
||||
|
||||
flags := TapConnectFlag(binary.BigEndian.Uint32(req.Extras))
|
||||
|
||||
r := bytes.NewReader(req.Body)
|
||||
|
||||
for _, f := range flags.SplitFlags() {
|
||||
fun := TapFlagParsers[f]
|
||||
if fun == nil {
|
||||
fun = TapParseBool
|
||||
}
|
||||
|
||||
val, err := fun(r)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
|
||||
rv.Flags[f] = val
|
||||
}
|
||||
|
||||
var err error
|
||||
rv.RemainingBody, err = ioutil.ReadAll(r)
|
||||
|
||||
return rv, err
|
||||
}
|
47
vendor/github.com/couchbase/goutils/LICENSE.md
generated
vendored
Normal file
47
vendor/github.com/couchbase/goutils/LICENSE.md
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
COUCHBASE INC. COMMUNITY EDITION LICENSE AGREEMENT
|
||||
|
||||
IMPORTANT-READ CAREFULLY: BY CLICKING THE "I ACCEPT" BOX OR INSTALLING,
|
||||
DOWNLOADING OR OTHERWISE USING THIS SOFTWARE AND ANY ASSOCIATED
|
||||
DOCUMENTATION, YOU, ON BEHALF OF YOURSELF OR AS AN AUTHORIZED
|
||||
REPRESENTATIVE ON BEHALF OF AN ENTITY ("LICENSEE") AGREE TO ALL THE
|
||||
TERMS OF THIS COMMUNITY EDITION LICENSE AGREEMENT (THE "AGREEMENT")
|
||||
REGARDING YOUR USE OF THE SOFTWARE. YOU REPRESENT AND WARRANT THAT YOU
|
||||
HAVE FULL LEGAL AUTHORITY TO BIND THE LICENSEE TO THIS AGREEMENT. IF YOU
|
||||
DO NOT AGREE WITH ALL OF THESE TERMS, DO NOT SELECT THE "I ACCEPT" BOX
|
||||
AND DO NOT INSTALL, DOWNLOAD OR OTHERWISE USE THE SOFTWARE. THE
|
||||
EFFECTIVE DATE OF THIS AGREEMENT IS THE DATE ON WHICH YOU CLICK "I
|
||||
ACCEPT" OR OTHERWISE INSTALL, DOWNLOAD OR USE THE SOFTWARE.
|
||||
|
||||
1. License Grant. Couchbase Inc. hereby grants Licensee, free of charge,
|
||||
the non-exclusive right to use, copy, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to
|
||||
whom the Software is furnished to do so, subject to Licensee including
|
||||
the following copyright notice in all copies or substantial portions of
|
||||
the Software:
|
||||
|
||||
Couchbase (r) http://www.Couchbase.com Copyright 2016 Couchbase, Inc.
|
||||
|
||||
As used in this Agreement, "Software" means the object code version of
|
||||
the applicable elastic data management server software provided by
|
||||
Couchbase Inc.
|
||||
|
||||
2. Restrictions. Licensee will not reverse engineer, disassemble, or
|
||||
decompile the Software (except to the extent such restrictions are
|
||||
prohibited by law).
|
||||
|
||||
3. Support. Couchbase, Inc. will provide Licensee with access to, and
|
||||
use of, the Couchbase, Inc. support forum available at the following
|
||||
URL: http://www.couchbase.org/forums/. Couchbase, Inc. may, at its
|
||||
discretion, modify, suspend or terminate support at any time upon notice
|
||||
to Licensee.
|
||||
|
||||
4. Warranty Disclaimer and Limitation of Liability. THE SOFTWARE IS
|
||||
PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
COUCHBASE INC. OR THE AUTHORS OR COPYRIGHT HOLDERS IN THE SOFTWARE BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES (IINCLUDING, WITHOUT LIMITATION, DIRECT,
|
||||
INDIRECT OR CONSEQUENTIAL DAMAGES) OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
481
vendor/github.com/couchbase/goutils/logging/logger.go
generated
vendored
Normal file
481
vendor/github.com/couchbase/goutils/logging/logger.go
generated
vendored
Normal file
|
@ -0,0 +1,481 @@
|
|||
// Copyright (c) 2016 Couchbase, Inc.
|
||||
// Licensed 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 logging
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Level int
|
||||
|
||||
const (
|
||||
NONE = Level(iota) // Disable all logging
|
||||
FATAL // System is in severe error state and has to abort
|
||||
SEVERE // System is in severe error state and cannot recover reliably
|
||||
ERROR // System is in error state but can recover and continue reliably
|
||||
WARN // System approaching error state, or is in a correct but undesirable state
|
||||
INFO // System-level events and status, in correct states
|
||||
REQUEST // Request-level events, with request-specific rlevel
|
||||
TRACE // Trace detailed system execution, e.g. function entry / exit
|
||||
DEBUG // Debug
|
||||
)
|
||||
|
||||
type LogEntryFormatter int
|
||||
|
||||
const (
|
||||
TEXTFORMATTER = LogEntryFormatter(iota)
|
||||
JSONFORMATTER
|
||||
KVFORMATTER
|
||||
)
|
||||
|
||||
func (level Level) String() string {
|
||||
return _LEVEL_NAMES[level]
|
||||
}
|
||||
|
||||
var _LEVEL_NAMES = []string{
|
||||
DEBUG: "DEBUG",
|
||||
TRACE: "TRACE",
|
||||
REQUEST: "REQUEST",
|
||||
INFO: "INFO",
|
||||
WARN: "WARN",
|
||||
ERROR: "ERROR",
|
||||
SEVERE: "SEVERE",
|
||||
FATAL: "FATAL",
|
||||
NONE: "NONE",
|
||||
}
|
||||
|
||||
var _LEVEL_MAP = map[string]Level{
|
||||
"debug": DEBUG,
|
||||
"trace": TRACE,
|
||||
"request": REQUEST,
|
||||
"info": INFO,
|
||||
"warn": WARN,
|
||||
"error": ERROR,
|
||||
"severe": SEVERE,
|
||||
"fatal": FATAL,
|
||||
"none": NONE,
|
||||
}
|
||||
|
||||
func ParseLevel(name string) (level Level, ok bool) {
|
||||
level, ok = _LEVEL_MAP[strings.ToLower(name)]
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Pair supports logging of key-value pairs. Keys beginning with _ are
|
||||
reserved for the logger, e.g. _time, _level, _msg, and _rlevel. The
|
||||
Pair APIs are designed to avoid heap allocation and garbage
|
||||
collection.
|
||||
|
||||
*/
|
||||
type Pairs []Pair
|
||||
type Pair struct {
|
||||
Name string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Map allows key-value pairs to be specified using map literals or data
|
||||
structures. For example:
|
||||
|
||||
Errorm(msg, Map{...})
|
||||
|
||||
Map incurs heap allocation and garbage collection, so the Pair APIs
|
||||
should be preferred.
|
||||
|
||||
*/
|
||||
type Map map[string]interface{}
|
||||
|
||||
// Logger provides a common interface for logging libraries
|
||||
type Logger interface {
|
||||
/*
|
||||
These APIs write all the given pairs in addition to standard logger keys.
|
||||
*/
|
||||
Logp(level Level, msg string, kv ...Pair)
|
||||
|
||||
Debugp(msg string, kv ...Pair)
|
||||
|
||||
Tracep(msg string, kv ...Pair)
|
||||
|
||||
Requestp(rlevel Level, msg string, kv ...Pair)
|
||||
|
||||
Infop(msg string, kv ...Pair)
|
||||
|
||||
Warnp(msg string, kv ...Pair)
|
||||
|
||||
Errorp(msg string, kv ...Pair)
|
||||
|
||||
Severep(msg string, kv ...Pair)
|
||||
|
||||
Fatalp(msg string, kv ...Pair)
|
||||
|
||||
/*
|
||||
These APIs write the fields in the given kv Map in addition to standard logger keys.
|
||||
*/
|
||||
Logm(level Level, msg string, kv Map)
|
||||
|
||||
Debugm(msg string, kv Map)
|
||||
|
||||
Tracem(msg string, kv Map)
|
||||
|
||||
Requestm(rlevel Level, msg string, kv Map)
|
||||
|
||||
Infom(msg string, kv Map)
|
||||
|
||||
Warnm(msg string, kv Map)
|
||||
|
||||
Errorm(msg string, kv Map)
|
||||
|
||||
Severem(msg string, kv Map)
|
||||
|
||||
Fatalm(msg string, kv Map)
|
||||
|
||||
/*
|
||||
|
||||
These APIs only write _msg, _time, _level, and other logger keys. If
|
||||
the msg contains other fields, use the Pair or Map APIs instead.
|
||||
|
||||
*/
|
||||
Logf(level Level, fmt string, args ...interface{})
|
||||
|
||||
Debugf(fmt string, args ...interface{})
|
||||
|
||||
Tracef(fmt string, args ...interface{})
|
||||
|
||||
Requestf(rlevel Level, fmt string, args ...interface{})
|
||||
|
||||
Infof(fmt string, args ...interface{})
|
||||
|
||||
Warnf(fmt string, args ...interface{})
|
||||
|
||||
Errorf(fmt string, args ...interface{})
|
||||
|
||||
Severef(fmt string, args ...interface{})
|
||||
|
||||
Fatalf(fmt string, args ...interface{})
|
||||
|
||||
/*
|
||||
These APIs control the logging level
|
||||
*/
|
||||
|
||||
SetLevel(Level) // Set the logging level
|
||||
|
||||
Level() Level // Get the current logging level
|
||||
}
|
||||
|
||||
var logger Logger = nil
|
||||
var curLevel Level = DEBUG // initially set to never skip
|
||||
|
||||
var loggerMutex sync.RWMutex
|
||||
|
||||
// All the methods below first acquire the mutex (mostly in exclusive mode)
|
||||
// and only then check if logging at the current level is enabled.
|
||||
// This introduces a fair bottleneck for those log entries that should be
|
||||
// skipped (the majority, at INFO or below levels)
|
||||
// We try to predict here if we should lock the mutex at all by caching
|
||||
// the current log level: while dynamically changing logger, there might
|
||||
// be the odd entry skipped as the new level is cached.
|
||||
// Since we seem to never change the logger, this is not an issue.
|
||||
func skipLogging(level Level) bool {
|
||||
if logger == nil {
|
||||
return true
|
||||
}
|
||||
return level > curLevel
|
||||
}
|
||||
|
||||
func SetLogger(newLogger Logger) {
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger = newLogger
|
||||
if logger == nil {
|
||||
curLevel = NONE
|
||||
} else {
|
||||
curLevel = newLogger.Level()
|
||||
}
|
||||
}
|
||||
|
||||
func Logp(level Level, msg string, kv ...Pair) {
|
||||
if skipLogging(level) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Logp(level, msg, kv...)
|
||||
}
|
||||
|
||||
func Debugp(msg string, kv ...Pair) {
|
||||
if skipLogging(DEBUG) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Debugp(msg, kv...)
|
||||
}
|
||||
|
||||
func Tracep(msg string, kv ...Pair) {
|
||||
if skipLogging(TRACE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Tracep(msg, kv...)
|
||||
}
|
||||
|
||||
func Requestp(rlevel Level, msg string, kv ...Pair) {
|
||||
if skipLogging(REQUEST) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Requestp(rlevel, msg, kv...)
|
||||
}
|
||||
|
||||
func Infop(msg string, kv ...Pair) {
|
||||
if skipLogging(INFO) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Infop(msg, kv...)
|
||||
}
|
||||
|
||||
func Warnp(msg string, kv ...Pair) {
|
||||
if skipLogging(WARN) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Warnp(msg, kv...)
|
||||
}
|
||||
|
||||
func Errorp(msg string, kv ...Pair) {
|
||||
if skipLogging(ERROR) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Errorp(msg, kv...)
|
||||
}
|
||||
|
||||
func Severep(msg string, kv ...Pair) {
|
||||
if skipLogging(SEVERE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Severep(msg, kv...)
|
||||
}
|
||||
|
||||
func Fatalp(msg string, kv ...Pair) {
|
||||
if skipLogging(FATAL) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Fatalp(msg, kv...)
|
||||
}
|
||||
|
||||
func Logm(level Level, msg string, kv Map) {
|
||||
if skipLogging(level) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Logm(level, msg, kv)
|
||||
}
|
||||
|
||||
func Debugm(msg string, kv Map) {
|
||||
if skipLogging(DEBUG) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Debugm(msg, kv)
|
||||
}
|
||||
|
||||
func Tracem(msg string, kv Map) {
|
||||
if skipLogging(TRACE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Tracem(msg, kv)
|
||||
}
|
||||
|
||||
func Requestm(rlevel Level, msg string, kv Map) {
|
||||
if skipLogging(REQUEST) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Requestm(rlevel, msg, kv)
|
||||
}
|
||||
|
||||
func Infom(msg string, kv Map) {
|
||||
if skipLogging(INFO) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Infom(msg, kv)
|
||||
}
|
||||
|
||||
func Warnm(msg string, kv Map) {
|
||||
if skipLogging(WARN) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Warnm(msg, kv)
|
||||
}
|
||||
|
||||
func Errorm(msg string, kv Map) {
|
||||
if skipLogging(ERROR) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Errorm(msg, kv)
|
||||
}
|
||||
|
||||
func Severem(msg string, kv Map) {
|
||||
if skipLogging(SEVERE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Severem(msg, kv)
|
||||
}
|
||||
|
||||
func Fatalm(msg string, kv Map) {
|
||||
if skipLogging(FATAL) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Fatalm(msg, kv)
|
||||
}
|
||||
|
||||
func Logf(level Level, fmt string, args ...interface{}) {
|
||||
if skipLogging(level) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Logf(level, fmt, args...)
|
||||
}
|
||||
|
||||
func Debugf(fmt string, args ...interface{}) {
|
||||
if skipLogging(DEBUG) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Debugf(fmt, args...)
|
||||
}
|
||||
|
||||
func Tracef(fmt string, args ...interface{}) {
|
||||
if skipLogging(TRACE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Tracef(fmt, args...)
|
||||
}
|
||||
|
||||
func Requestf(rlevel Level, fmt string, args ...interface{}) {
|
||||
if skipLogging(REQUEST) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Requestf(rlevel, fmt, args...)
|
||||
}
|
||||
|
||||
func Infof(fmt string, args ...interface{}) {
|
||||
if skipLogging(INFO) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Infof(fmt, args...)
|
||||
}
|
||||
|
||||
func Warnf(fmt string, args ...interface{}) {
|
||||
if skipLogging(WARN) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Warnf(fmt, args...)
|
||||
}
|
||||
|
||||
func Errorf(fmt string, args ...interface{}) {
|
||||
if skipLogging(ERROR) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Errorf(fmt, args...)
|
||||
}
|
||||
|
||||
func Severef(fmt string, args ...interface{}) {
|
||||
if skipLogging(SEVERE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Severef(fmt, args...)
|
||||
}
|
||||
|
||||
func Fatalf(fmt string, args ...interface{}) {
|
||||
if skipLogging(FATAL) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Fatalf(fmt, args...)
|
||||
}
|
||||
|
||||
func SetLevel(level Level) {
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.SetLevel(level)
|
||||
curLevel = level
|
||||
}
|
||||
|
||||
func LogLevel() Level {
|
||||
loggerMutex.RLock()
|
||||
defer loggerMutex.RUnlock()
|
||||
return logger.Level()
|
||||
}
|
||||
|
||||
func Stackf(level Level, fmt string, args ...interface{}) {
|
||||
if skipLogging(level) {
|
||||
return
|
||||
}
|
||||
buf := make([]byte, 1<<16)
|
||||
n := runtime.Stack(buf, false)
|
||||
s := string(buf[0:n])
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Logf(level, fmt, args...)
|
||||
logger.Logf(level, s)
|
||||
}
|
||||
|
||||
func init() {
|
||||
logger = NewLogger(os.Stderr, INFO, TEXTFORMATTER)
|
||||
SetLogger(logger)
|
||||
}
|
318
vendor/github.com/couchbase/goutils/logging/logger_golog.go
generated
vendored
Normal file
318
vendor/github.com/couchbase/goutils/logging/logger_golog.go
generated
vendored
Normal file
|
@ -0,0 +1,318 @@
|
|||
// Copyright (c) 2016 Couchbase, Inc.
|
||||
// Licensed 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 logging
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type goLogger struct {
|
||||
logger *log.Logger
|
||||
level Level
|
||||
entryFormatter formatter
|
||||
}
|
||||
|
||||
const (
|
||||
_LEVEL = "_level"
|
||||
_MSG = "_msg"
|
||||
_TIME = "_time"
|
||||
_RLEVEL = "_rlevel"
|
||||
)
|
||||
|
||||
func NewLogger(out io.Writer, lvl Level, fmtLogging LogEntryFormatter) *goLogger {
|
||||
logger := &goLogger{
|
||||
logger: log.New(out, "", 0),
|
||||
level: lvl,
|
||||
}
|
||||
if fmtLogging == JSONFORMATTER {
|
||||
logger.entryFormatter = &jsonFormatter{}
|
||||
} else if fmtLogging == KVFORMATTER {
|
||||
logger.entryFormatter = &keyvalueFormatter{}
|
||||
} else {
|
||||
logger.entryFormatter = &textFormatter{}
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
func (gl *goLogger) Logp(level Level, msg string, kv ...Pair) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if level <= gl.level {
|
||||
e := newLogEntry(msg, level)
|
||||
copyPairs(e, kv)
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Debugp(msg string, kv ...Pair) {
|
||||
gl.Logp(DEBUG, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Tracep(msg string, kv ...Pair) {
|
||||
gl.Logp(TRACE, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Requestp(rlevel Level, msg string, kv ...Pair) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if REQUEST <= gl.level {
|
||||
e := newLogEntry(msg, REQUEST)
|
||||
e.Rlevel = rlevel
|
||||
copyPairs(e, kv)
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Infop(msg string, kv ...Pair) {
|
||||
gl.Logp(INFO, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Warnp(msg string, kv ...Pair) {
|
||||
gl.Logp(WARN, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Errorp(msg string, kv ...Pair) {
|
||||
gl.Logp(ERROR, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Severep(msg string, kv ...Pair) {
|
||||
gl.Logp(SEVERE, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Fatalp(msg string, kv ...Pair) {
|
||||
gl.Logp(FATAL, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Logm(level Level, msg string, kv Map) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if level <= gl.level {
|
||||
e := newLogEntry(msg, level)
|
||||
e.Data = kv
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Debugm(msg string, kv Map) {
|
||||
gl.Logm(DEBUG, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Tracem(msg string, kv Map) {
|
||||
gl.Logm(TRACE, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Requestm(rlevel Level, msg string, kv Map) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if REQUEST <= gl.level {
|
||||
e := newLogEntry(msg, REQUEST)
|
||||
e.Rlevel = rlevel
|
||||
e.Data = kv
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Infom(msg string, kv Map) {
|
||||
gl.Logm(INFO, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Warnm(msg string, kv Map) {
|
||||
gl.Logm(WARN, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Errorm(msg string, kv Map) {
|
||||
gl.Logm(ERROR, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Severem(msg string, kv Map) {
|
||||
gl.Logm(SEVERE, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Fatalm(msg string, kv Map) {
|
||||
gl.Logm(FATAL, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Logf(level Level, format string, args ...interface{}) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if level <= gl.level {
|
||||
e := newLogEntry(fmt.Sprintf(format, args...), level)
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Debugf(format string, args ...interface{}) {
|
||||
gl.Logf(DEBUG, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Tracef(format string, args ...interface{}) {
|
||||
gl.Logf(TRACE, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Requestf(rlevel Level, format string, args ...interface{}) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if REQUEST <= gl.level {
|
||||
e := newLogEntry(fmt.Sprintf(format, args...), REQUEST)
|
||||
e.Rlevel = rlevel
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Infof(format string, args ...interface{}) {
|
||||
gl.Logf(INFO, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Warnf(format string, args ...interface{}) {
|
||||
gl.Logf(WARN, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Errorf(format string, args ...interface{}) {
|
||||
gl.Logf(ERROR, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Severef(format string, args ...interface{}) {
|
||||
gl.Logf(SEVERE, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Fatalf(format string, args ...interface{}) {
|
||||
gl.Logf(FATAL, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Level() Level {
|
||||
return gl.level
|
||||
}
|
||||
|
||||
func (gl *goLogger) SetLevel(level Level) {
|
||||
gl.level = level
|
||||
}
|
||||
|
||||
func (gl *goLogger) log(newEntry *logEntry) {
|
||||
s := gl.entryFormatter.format(newEntry)
|
||||
gl.logger.Print(s)
|
||||
}
|
||||
|
||||
type logEntry struct {
|
||||
Time string
|
||||
Level Level
|
||||
Rlevel Level
|
||||
Message string
|
||||
Data Map
|
||||
}
|
||||
|
||||
func newLogEntry(msg string, level Level) *logEntry {
|
||||
return &logEntry{
|
||||
Time: time.Now().Format("2006-01-02T15:04:05.000-07:00"), // time.RFC3339 with milliseconds
|
||||
Level: level,
|
||||
Rlevel: NONE,
|
||||
Message: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func copyPairs(newEntry *logEntry, pairs []Pair) {
|
||||
newEntry.Data = make(Map, len(pairs))
|
||||
for _, p := range pairs {
|
||||
newEntry.Data[p.Name] = p.Value
|
||||
}
|
||||
}
|
||||
|
||||
type formatter interface {
|
||||
format(*logEntry) string
|
||||
}
|
||||
|
||||
type textFormatter struct {
|
||||
}
|
||||
|
||||
// ex. 2016-02-10T09:15:25.498-08:00 [INFO] This is a message from test in text format
|
||||
|
||||
func (*textFormatter) format(newEntry *logEntry) string {
|
||||
b := &bytes.Buffer{}
|
||||
appendValue(b, newEntry.Time)
|
||||
if newEntry.Rlevel != NONE {
|
||||
fmt.Fprintf(b, "[%s,%s] ", newEntry.Level.String(), newEntry.Rlevel.String())
|
||||
} else {
|
||||
fmt.Fprintf(b, "[%s] ", newEntry.Level.String())
|
||||
}
|
||||
appendValue(b, newEntry.Message)
|
||||
for key, value := range newEntry.Data {
|
||||
appendKeyValue(b, key, value)
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
s := bytes.NewBuffer(b.Bytes())
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func appendValue(b *bytes.Buffer, value interface{}) {
|
||||
if _, ok := value.(string); ok {
|
||||
fmt.Fprintf(b, "%s ", value)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%v ", value)
|
||||
}
|
||||
}
|
||||
|
||||
type keyvalueFormatter struct {
|
||||
}
|
||||
|
||||
// ex. _time=2016-02-10T09:15:25.498-08:00 _level=INFO _msg=This is a message from test in key-value format
|
||||
|
||||
func (*keyvalueFormatter) format(newEntry *logEntry) string {
|
||||
b := &bytes.Buffer{}
|
||||
appendKeyValue(b, _TIME, newEntry.Time)
|
||||
appendKeyValue(b, _LEVEL, newEntry.Level.String())
|
||||
if newEntry.Rlevel != NONE {
|
||||
appendKeyValue(b, _RLEVEL, newEntry.Rlevel.String())
|
||||
}
|
||||
appendKeyValue(b, _MSG, newEntry.Message)
|
||||
for key, value := range newEntry.Data {
|
||||
appendKeyValue(b, key, value)
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
s := bytes.NewBuffer(b.Bytes())
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func appendKeyValue(b *bytes.Buffer, key, value interface{}) {
|
||||
if _, ok := value.(string); ok {
|
||||
fmt.Fprintf(b, "%v=%s ", key, value)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%v=%v ", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
type jsonFormatter struct {
|
||||
}
|
||||
|
||||
// ex. {"_level":"INFO","_msg":"This is a message from test in json format","_time":"2016-02-10T09:12:59.518-08:00"}
|
||||
|
||||
func (*jsonFormatter) format(newEntry *logEntry) string {
|
||||
if newEntry.Data == nil {
|
||||
newEntry.Data = make(Map, 5)
|
||||
}
|
||||
newEntry.Data[_TIME] = newEntry.Time
|
||||
newEntry.Data[_LEVEL] = newEntry.Level.String()
|
||||
if newEntry.Rlevel != NONE {
|
||||
newEntry.Data[_RLEVEL] = newEntry.Rlevel.String()
|
||||
}
|
||||
newEntry.Data[_MSG] = newEntry.Message
|
||||
serialized, _ := json.Marshal(newEntry.Data)
|
||||
s := bytes.NewBuffer(append(serialized, '\n'))
|
||||
return s.String()
|
||||
}
|
207
vendor/github.com/couchbase/goutils/scramsha/scramsha.go
generated
vendored
Normal file
207
vendor/github.com/couchbase/goutils/scramsha/scramsha.go
generated
vendored
Normal file
|
@ -0,0 +1,207 @@
|
|||
// @author Couchbase <info@couchbase.com>
|
||||
// @copyright 2018 Couchbase, Inc.
|
||||
//
|
||||
// Licensed 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 scramsha provides implementation of client side SCRAM-SHA
|
||||
// according to https://tools.ietf.org/html/rfc5802
|
||||
package scramsha
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"hash"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func hmacHash(message []byte, secret []byte, hashFunc func() hash.Hash) []byte {
|
||||
h := hmac.New(hashFunc, secret)
|
||||
h.Write(message)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func shaHash(message []byte, hashFunc func() hash.Hash) []byte {
|
||||
h := hashFunc()
|
||||
h.Write(message)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func generateClientNonce(size int) (string, error) {
|
||||
randomBytes := make([]byte, size)
|
||||
_, err := rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Unable to generate nonce")
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(randomBytes), nil
|
||||
}
|
||||
|
||||
// ScramSha provides context for SCRAM-SHA handling
|
||||
type ScramSha struct {
|
||||
hashSize int
|
||||
hashFunc func() hash.Hash
|
||||
clientNonce string
|
||||
serverNonce string
|
||||
salt []byte
|
||||
i int
|
||||
saltedPassword []byte
|
||||
authMessage string
|
||||
}
|
||||
|
||||
var knownMethods = []string{"SCRAM-SHA512", "SCRAM-SHA256", "SCRAM-SHA1"}
|
||||
|
||||
// BestMethod returns SCRAM-SHA method we consider the best out of suggested
|
||||
// by server
|
||||
func BestMethod(methods string) (string, error) {
|
||||
for _, m := range knownMethods {
|
||||
if strings.Index(methods, m) != -1 {
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return "", errors.Errorf(
|
||||
"None of the server suggested methods [%s] are supported",
|
||||
methods)
|
||||
}
|
||||
|
||||
// NewScramSha creates context for SCRAM-SHA handling
|
||||
func NewScramSha(method string) (*ScramSha, error) {
|
||||
s := &ScramSha{}
|
||||
|
||||
if method == knownMethods[0] {
|
||||
s.hashFunc = sha512.New
|
||||
s.hashSize = 64
|
||||
} else if method == knownMethods[1] {
|
||||
s.hashFunc = sha256.New
|
||||
s.hashSize = 32
|
||||
} else if method == knownMethods[2] {
|
||||
s.hashFunc = sha1.New
|
||||
s.hashSize = 20
|
||||
} else {
|
||||
return nil, errors.Errorf("Unsupported method %s", method)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetStartRequest builds start SCRAM-SHA request to be sent to server
|
||||
func (s *ScramSha) GetStartRequest(user string) (string, error) {
|
||||
var err error
|
||||
s.clientNonce, err = generateClientNonce(24)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "Unable to generate SCRAM-SHA "+
|
||||
"start request for user %s", user)
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("n,,n=%s,r=%s", user, s.clientNonce)
|
||||
s.authMessage = message[3:]
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// HandleStartResponse handles server response on start SCRAM-SHA request
|
||||
func (s *ScramSha) HandleStartResponse(response string) error {
|
||||
parts := strings.Split(response, ",")
|
||||
if len(parts) != 3 {
|
||||
return errors.Errorf("expected 3 fields in first SCRAM-SHA-1 "+
|
||||
"server message %s", response)
|
||||
}
|
||||
if !strings.HasPrefix(parts[0], "r=") || len(parts[0]) < 3 {
|
||||
return errors.Errorf("Server sent an invalid nonce %s",
|
||||
parts[0])
|
||||
}
|
||||
if !strings.HasPrefix(parts[1], "s=") || len(parts[1]) < 3 {
|
||||
return errors.Errorf("Server sent an invalid salt %s", parts[1])
|
||||
}
|
||||
if !strings.HasPrefix(parts[2], "i=") || len(parts[2]) < 3 {
|
||||
return errors.Errorf("Server sent an invalid iteration count %s",
|
||||
parts[2])
|
||||
}
|
||||
|
||||
s.serverNonce = parts[0][2:]
|
||||
encodedSalt := parts[1][2:]
|
||||
var err error
|
||||
s.i, err = strconv.Atoi(parts[2][2:])
|
||||
if err != nil {
|
||||
return errors.Errorf("Iteration count %s must be integer.",
|
||||
parts[2][2:])
|
||||
}
|
||||
|
||||
if s.i < 1 {
|
||||
return errors.New("Iteration count should be positive")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(s.serverNonce, s.clientNonce) {
|
||||
return errors.Errorf("Server nonce %s doesn't contain client"+
|
||||
" nonce %s", s.serverNonce, s.clientNonce)
|
||||
}
|
||||
|
||||
s.salt, err = base64.StdEncoding.DecodeString(encodedSalt)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Unable to decode salt %s",
|
||||
encodedSalt)
|
||||
}
|
||||
|
||||
s.authMessage = s.authMessage + "," + response
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFinalRequest builds final SCRAM-SHA request to be sent to server
|
||||
func (s *ScramSha) GetFinalRequest(pass string) string {
|
||||
clientFinalMessageBare := "c=biws,r=" + s.serverNonce
|
||||
s.authMessage = s.authMessage + "," + clientFinalMessageBare
|
||||
|
||||
s.saltedPassword = pbkdf2.Key([]byte(pass), s.salt, s.i,
|
||||
s.hashSize, s.hashFunc)
|
||||
|
||||
clientKey := hmacHash([]byte("Client Key"), s.saltedPassword, s.hashFunc)
|
||||
storedKey := shaHash(clientKey, s.hashFunc)
|
||||
clientSignature := hmacHash([]byte(s.authMessage), storedKey, s.hashFunc)
|
||||
|
||||
clientProof := make([]byte, len(clientSignature))
|
||||
for i := 0; i < len(clientSignature); i++ {
|
||||
clientProof[i] = clientKey[i] ^ clientSignature[i]
|
||||
}
|
||||
|
||||
return clientFinalMessageBare + ",p=" +
|
||||
base64.StdEncoding.EncodeToString(clientProof)
|
||||
}
|
||||
|
||||
// HandleFinalResponse handles server's response on final SCRAM-SHA request
|
||||
func (s *ScramSha) HandleFinalResponse(response string) error {
|
||||
if strings.Contains(response, ",") ||
|
||||
!strings.HasPrefix(response, "v=") {
|
||||
return errors.Errorf("Server sent an invalid final message %s",
|
||||
response)
|
||||
}
|
||||
|
||||
decodedMessage, err := base64.StdEncoding.DecodeString(response[2:])
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Unable to decode server message %s",
|
||||
response[2:])
|
||||
}
|
||||
serverKey := hmacHash([]byte("Server Key"), s.saltedPassword,
|
||||
s.hashFunc)
|
||||
serverSignature := hmacHash([]byte(s.authMessage), serverKey,
|
||||
s.hashFunc)
|
||||
if string(decodedMessage) != string(serverSignature) {
|
||||
return errors.Errorf("Server proof %s doesn't match "+
|
||||
"the expected: %s",
|
||||
string(decodedMessage), string(serverSignature))
|
||||
}
|
||||
return nil
|
||||
}
|
252
vendor/github.com/couchbase/goutils/scramsha/scramsha_http.go
generated
vendored
Normal file
252
vendor/github.com/couchbase/goutils/scramsha/scramsha_http.go
generated
vendored
Normal file
|
@ -0,0 +1,252 @@
|
|||
// @author Couchbase <info@couchbase.com>
|
||||
// @copyright 2018 Couchbase, Inc.
|
||||
//
|
||||
// Licensed 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 scramsha provides implementation of client side SCRAM-SHA
|
||||
// via Http according to https://tools.ietf.org/html/rfc7804
|
||||
package scramsha
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// consts used to parse scramsha response from target
|
||||
const (
|
||||
WWWAuthenticate = "WWW-Authenticate"
|
||||
AuthenticationInfo = "Authentication-Info"
|
||||
Authorization = "Authorization"
|
||||
DataPrefix = "data="
|
||||
SidPrefix = "sid="
|
||||
)
|
||||
|
||||
// Request provides implementation of http request that can be retried
|
||||
type Request struct {
|
||||
body io.ReadSeeker
|
||||
|
||||
// Embed an HTTP request directly. This makes a *Request act exactly
|
||||
// like an *http.Request so that all meta methods are supported.
|
||||
*http.Request
|
||||
}
|
||||
|
||||
type lenReader interface {
|
||||
Len() int
|
||||
}
|
||||
|
||||
// NewRequest creates http request that can be retried
|
||||
func NewRequest(method, url string, body io.ReadSeeker) (*Request, error) {
|
||||
// Wrap the body in a noop ReadCloser if non-nil. This prevents the
|
||||
// reader from being closed by the HTTP client.
|
||||
var rcBody io.ReadCloser
|
||||
if body != nil {
|
||||
rcBody = ioutil.NopCloser(body)
|
||||
}
|
||||
|
||||
// Make the request with the noop-closer for the body.
|
||||
httpReq, err := http.NewRequest(method, url, rcBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if we can set the Content-Length automatically.
|
||||
if lr, ok := body.(lenReader); ok {
|
||||
httpReq.ContentLength = int64(lr.Len())
|
||||
}
|
||||
|
||||
return &Request{body, httpReq}, nil
|
||||
}
|
||||
|
||||
func encode(str string) string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(str))
|
||||
}
|
||||
|
||||
func decode(str string) (string, error) {
|
||||
bytes, err := base64.StdEncoding.DecodeString(str)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("Cannot base64 decode %s",
|
||||
str)
|
||||
}
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func trimPrefix(s, prefix string) (string, error) {
|
||||
l := len(s)
|
||||
trimmed := strings.TrimPrefix(s, prefix)
|
||||
if l == len(trimmed) {
|
||||
return trimmed, errors.Errorf("Prefix %s not found in %s",
|
||||
prefix, s)
|
||||
}
|
||||
return trimmed, nil
|
||||
}
|
||||
|
||||
func drainBody(resp *http.Response) {
|
||||
defer resp.Body.Close()
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
}
|
||||
|
||||
// DoScramSha performs SCRAM-SHA handshake via Http
|
||||
func DoScramSha(req *Request,
|
||||
username string,
|
||||
password string,
|
||||
client *http.Client) (*http.Response, error) {
|
||||
|
||||
method := "SCRAM-SHA-512"
|
||||
s, err := NewScramSha("SCRAM-SHA512")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err,
|
||||
"Unable to initialize SCRAM-SHA handler")
|
||||
}
|
||||
|
||||
message, err := s.GetStartRequest(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encodedMessage := method + " " + DataPrefix + encode(message)
|
||||
|
||||
req.Header.Set(Authorization, encodedMessage)
|
||||
|
||||
res, err := client.Do(req.Request)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Problem sending SCRAM-SHA start"+
|
||||
"request")
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusUnauthorized {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
authHeader := res.Header.Get(WWWAuthenticate)
|
||||
if authHeader == "" {
|
||||
drainBody(res)
|
||||
return nil, errors.Errorf("Header %s is not populated in "+
|
||||
"SCRAM-SHA start response", WWWAuthenticate)
|
||||
}
|
||||
|
||||
authHeader, err = trimPrefix(authHeader, method+" ")
|
||||
if err != nil {
|
||||
if strings.HasPrefix(authHeader, "Basic ") {
|
||||
// user not found
|
||||
return res, nil
|
||||
}
|
||||
drainBody(res)
|
||||
return nil, errors.Wrapf(err, "Error while parsing SCRAM-SHA "+
|
||||
"start response %s", authHeader)
|
||||
}
|
||||
|
||||
drainBody(res)
|
||||
|
||||
sid, response, err := parseSidAndData(authHeader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error while parsing SCRAM-SHA "+
|
||||
"start response %s", authHeader)
|
||||
}
|
||||
|
||||
err = s.HandleStartResponse(response)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error parsing SCRAM-SHA start "+
|
||||
"response %s", response)
|
||||
}
|
||||
|
||||
message = s.GetFinalRequest(password)
|
||||
encodedMessage = method + " " + SidPrefix + sid + "," + DataPrefix +
|
||||
encode(message)
|
||||
|
||||
req.Header.Set(Authorization, encodedMessage)
|
||||
|
||||
// rewind request body so it can be resent again
|
||||
if req.body != nil {
|
||||
if _, err = req.body.Seek(0, 0); err != nil {
|
||||
return nil, errors.Errorf("Failed to seek body: %v",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
res, err = client.Do(req.Request)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Problem sending SCRAM-SHA final"+
|
||||
"request")
|
||||
}
|
||||
|
||||
if res.StatusCode == http.StatusUnauthorized {
|
||||
// TODO retrieve and return error
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if res.StatusCode >= http.StatusInternalServerError {
|
||||
// in this case we cannot expect server to set headers properly
|
||||
return res, nil
|
||||
}
|
||||
|
||||
authHeader = res.Header.Get(AuthenticationInfo)
|
||||
if authHeader == "" {
|
||||
drainBody(res)
|
||||
return nil, errors.Errorf("Header %s is not populated in "+
|
||||
"SCRAM-SHA final response", AuthenticationInfo)
|
||||
}
|
||||
|
||||
finalSid, response, err := parseSidAndData(authHeader)
|
||||
if err != nil {
|
||||
drainBody(res)
|
||||
return nil, errors.Wrapf(err, "Error while parsing SCRAM-SHA "+
|
||||
"final response %s", authHeader)
|
||||
}
|
||||
|
||||
if finalSid != sid {
|
||||
drainBody(res)
|
||||
return nil, errors.Errorf("Sid %s returned by server "+
|
||||
"doesn't match the original sid %s", finalSid, sid)
|
||||
}
|
||||
|
||||
err = s.HandleFinalResponse(response)
|
||||
if err != nil {
|
||||
drainBody(res)
|
||||
return nil, errors.Wrapf(err,
|
||||
"Error handling SCRAM-SHA final server response %s",
|
||||
response)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func parseSidAndData(authHeader string) (string, string, error) {
|
||||
sidIndex := strings.Index(authHeader, SidPrefix)
|
||||
if sidIndex < 0 {
|
||||
return "", "", errors.Errorf("Cannot find %s in %s",
|
||||
SidPrefix, authHeader)
|
||||
}
|
||||
|
||||
sidEndIndex := strings.Index(authHeader, ",")
|
||||
if sidEndIndex < 0 {
|
||||
return "", "", errors.Errorf("Cannot find ',' in %s",
|
||||
authHeader)
|
||||
}
|
||||
|
||||
sid := authHeader[sidIndex+len(SidPrefix) : sidEndIndex]
|
||||
|
||||
dataIndex := strings.Index(authHeader, DataPrefix)
|
||||
if dataIndex < 0 {
|
||||
return "", "", errors.Errorf("Cannot find %s in %s",
|
||||
DataPrefix, authHeader)
|
||||
}
|
||||
|
||||
data, err := decode(authHeader[dataIndex+len(DataPrefix):])
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return sid, data, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue