add other session providers (#5963)

This commit is contained in:
techknowlogick 2019-02-05 11:52:51 -05:00 committed by GitHub
parent bf4badad1d
commit 9de871a0f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
160 changed files with 37644 additions and 66 deletions

19
vendor/github.com/couchbase/gomemcached/LICENSE generated vendored Normal file
View 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

File diff suppressed because it is too large Load diff

View 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)
}

View 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
}

File diff suppressed because it is too large Load diff

335
vendor/github.com/couchbase/gomemcached/mc_constants.go generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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)
}

View 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()
}

View 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
}

View 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
}