<commit_before>package stun

import (
	"encoding/binary"
	"fmt"
	"net"

	"github.com/pkg/errors"
	"golang.org/x/net/ipv4"
)

// MessageClass of 0b00 is a request, a class of 0b01 is an
//   indication, a class of 0b10 is a success response, and a class of
//   0b11 is an error response.
// https://tools.ietf.org/html/rfc5389#section-6
type MessageClass byte

const (
	// ClassRequest describes a request method type
	ClassRequest MessageClass = 0x00
	// ClassIndication describes an indication method type
	ClassIndication MessageClass = 0x01
	// ClassSuccessResponse describes an success response method type
	ClassSuccessResponse MessageClass = 0x02
	// ClassErrorResponse describes an error response method type
	ClassErrorResponse MessageClass = 0x03
)

type Method uint16

const (
	MethodBinding          Method = 0x01 // STUN
	MethodSharedSecret     Method = 0x02 // STUN
	MethodAllocate         Method = 0x03 // TURN (Req/Rsp)
	MethodRefresh          Method = 0x04 // TURN (Req/Rsp)
	MethodSend             Method = 0x06 // TURN (Ind)
	MethodData             Method = 0x07 // TURN (Ind)
	MethodCreatePermission Method = 0x08 // TURN (Req/Rsp)
	MethodChannelBind      Method = 0x09 // TURN (Req/Rsp)
)

var messageClassName = map[MessageClass]string{
	ClassRequest:         "REQUEST",
	ClassIndication:      "INDICATION",
	ClassSuccessResponse: "SUCCESS-RESPONSE",
	ClassErrorResponse:   "ERROR-RESPONSE",
}

func (m MessageClass) String() string {
	s, err := messageClassName[m]
	if !err {
		// Falling back to hex representation.
		s = fmt.Sprintf("Unk 0x%x", uint16(m))
	}
	return s
}

var methodName = map[Method]string{
	MethodBinding:          "BINDING",
	MethodSharedSecret:     "SHARED-SECRET",
	MethodAllocate:         "ALLOCATE",
	MethodRefresh:          "REFRESH",
	MethodSend:             "SEND",
	MethodData:             "DATA",
	MethodCreatePermission: "CREATE-PERMISSION",
	MethodChannelBind:      "CHANNEL-BIND",
}

func (m Method) String() string {
	s, err := methodName[m]
	if !err {
		s = fmt.Sprintf("Unk 0x%x", uint16(m))
	}
	return s
}

//       0                   1                   2                   3
//       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//      |0 0|     STUN Message Type     |         Message Length        |
//      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//      |                         Magic Cookie                          |
//      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//      |                                                               |
//      |                     Transaction ID (96 bits)                  |
//      |                                                               |
//      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//

const (
	messageHeaderStart  int = 0
	messageHeaderLength int = 20
	messageLengthStart  int = 2
	messageLengthLength int = 2
	magicCookieStart    int = 4
	magicCookieLength   int = 4
	transactionIDStart  int = 4
	transactionIDLength int = 16
)

type Message struct {
	Class         MessageClass
	Method        Method
	Length        uint16
	TransactionID []byte
	Attributes    []*RawAttribute
	Raw           []byte
}

// The most significant 2 bits of every STUN message MUST be zeroes.
// This can be used to differentiate STUN packets from other protocols
// when STUN is multiplexed with other protocols on the same port.
// https://tools.ietf.org/html/rfc5389#section-6
func verifyStunHeaderMostSignificant2Bits(header []byte) bool {
	return (header[0] >> 6) == 0
}

func verifyMagicCookie(header []byte) error {
	const magicCookie = 0x2112A442
	c := header[magicCookieStart : magicCookieStart+magicCookieLength]
	if binary.BigEndian.Uint32(c) != magicCookie {
		return errors.Errorf("stun header magic cookie invalid; %v != %v (expected)", binary.BigEndian.Uint32(c), magicCookie)
	}
	return nil
}

// The message length MUST contain the size, in bytes, of the message
// not including the 20-byte STUN header.  Since all STUN attributes are
// padded to a multiple of 4 bytes, the last 2 bits of this field are
// always zero.  This provides another way to distinguish STUN packets
// from packets of other protocols.
// https://tools.ietf.org/html/rfc5389#section-6
func getMessageLength(header []byte) (uint16, error) {
	messageLength := binary.BigEndian.Uint16(header[messageLengthStart : messageLengthStart+messageLengthLength])
	if messageLength%4 != 0 {
		return 0, errors.Errorf("stun header message length must be a factor of 4 (%d)", messageLength)
	}

	return messageLength, nil
}

//  0                 1
//  2  3  4 5 6 7 8 9 0 1 2 3 4 5
//
// +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
// |M |M |M|M|M|C|M|M|M|C|M|M|M|M|
// |11|10|9|8|7|1|6|5|4|0|3|2|1|0|
// +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
const (
	c0Mask   = 0x10 // 0b10000
	c1Mask   = 0x01 // 0b00001
	c0ShiftR = 4    // R 0b10000 -> 0b00001
	c1ShiftL = 1    // L 0b00001 -> 0b00010

	m0Mask   = 0x0F // 0b00001111
	m4Mask   = 0xE0 // 0b11100000
	m7Mask   = 0x3E // 0b00111110
	m4ShiftR = 1    // R 0b01110000 -> 0b00111000
	m7ShiftL = 5    // L 0b00111110 -> 0b0000011111000000
)

func setMessageType(header []byte, class MessageClass, method Method) {
	m := uint16(method)
	c := uint16(class)

	mt := m & m0Mask
	// Make room for c0
	mt |= (m & (m4Mask >> m4ShiftR)) << 1
	mt |= (m & (m7Mask << 6)) << 2
	mt |= (c & 0x1) << 4
	mt |= (c >> 1) << 8

	binary.BigEndian.PutUint16(header[messageHeaderStart:], mt)
}

func getMessageType(header []byte) (MessageClass, Method) {
	mByte0 := header[0]
	mByte1 := header[1]

	c0 := (mByte1 & c0Mask) >> c0ShiftR
	c1 := (mByte0 & c1Mask) << c1ShiftL

	class := MessageClass(c1 | c0)

	var m uint16
	m = (uint16(mByte0) & m7Mask) << m7ShiftL
	m |= uint16(mByte1 & m0Mask)
	m |= uint16((mByte1 & m4Mask) >> m4ShiftR)

	method := Method(m)

	return class, method
}

//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |         Type                  |            Length             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Value (variable)                ....
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
func getAttribute(attribute []byte, offset int) *RawAttribute {
	typ := AttrType(binary.BigEndian.Uint16(attribute))
	len := binary.BigEndian.Uint16(attribute[attrLengthStart : attrLengthStart+attrLengthLength])
	pad := (attrLengthMultiple - (len % attrLengthMultiple)) % attrLengthMultiple
	return &RawAttribute{typ, len, attribute[attrValueStart : attrValueStart+len], pad, offset}
}

// TODO Break this apart, too big
func NewMessage(packet []byte) (*Message, error) {

	if len(packet) < 20 {
		return nil, errors.Errorf("stun header must be at least 20 bytes, was %d", len(packet))
	}

	header := packet[messageHeaderStart : messageHeaderStart+messageHeaderLength]

	if !verifyStunHeaderMostSignificant2Bits(header) {
		return nil, errors.New("stun header most significant 2 bits must equal 0b00")
	}

	err := verifyMagicCookie(header)
	if err != nil {
		return nil, errors.Wrap(err, "stun header invalid")
	}

	ml, err := getMessageLength(header)
	if err != nil {
		return nil, errors.Wrap(err, "stun header invalid")
	}

	if len(packet) != messageHeaderLength+int(ml) {
		return nil, errors.Errorf("stun header length invalid; %d != %d (expected)", messageHeaderLength+int(ml), len(packet))
	}

	t := header[transactionIDStart : transactionIDStart+transactionIDLength]

	class, method := getMessageType(header)

	ra := []*RawAttribute{}
	// TODO Check attr length <= attr slice remaining
	attr := packet[messageHeaderLength:]
	for len(attr) > 0 {
		a := getAttribute(attr, cap(packet)-cap(attr))
		attr = attr[attrValueStart+a.Length+a.Pad:]
		ra = append(ra, a)
	}

	m := Message{}
	m.Class = class
	m.Method = method
	m.Length = ml
	m.TransactionID = t[0:transactionIDLength]
	m.Attributes = ra
	m.Raw = packet

	return &m, nil
}

func (m *Message) GetOneAttribute(attrType AttrType) (*RawAttribute, bool) {
	for _, v := range m.Attributes {
		if v.Type == attrType {
			return v, true
		}
	}

	return nil, false
}

func (m *Message) GetAllAttributes(attrType AttrType) ([]*RawAttribute, bool) {
	var attrs []*RawAttribute
	for _, v := range m.Attributes {
		if v.Type == attrType {
			attrs = append(attrs, v)
		}
	}

	return attrs, len(attrs) > 0
}

func (m *Message) CommitLength() {
	binary.BigEndian.PutUint16(m.Raw[messageLengthStart:], uint16(m.Length))
}

func (m *Message) AddAttribute(attrType AttrType, v []byte) {

	ra := RawAttribute{
		Type:   attrType,
		Value:  v,
		Pad:    uint16(GetAttrPadding(len(v))),
		Length: uint16(len(v)),
		Offset: int(m.Length),
	}

	a := make([]byte, attrHeaderLength+ra.Length+ra.Pad)

	binary.BigEndian.PutUint16(a, uint16(ra.Type))
	binary.BigEndian.PutUint16(a[attrLengthStart:attrLengthStart+attrLengthLength], ra.Length)

	copy(a[attrValueStart:], ra.Value)

	m.Attributes = append(m.Attributes, &ra)
	m.Raw = append(m.Raw, a...)
	m.Length += uint16(len(a))
	m.CommitLength()
}

func (m *Message) Pack() []byte {

	setMessageType(m.Raw[messageHeaderStart:2], m.Class, m.Method)
	m.CommitLength()
	copy(m.Raw[transactionIDStart:], m.TransactionID)

	return m.Raw
}

func BuildAndSend(conn *ipv4.PacketConn, addr *TransportAddr, class MessageClass, method Method, transactionID []byte, attrs ...Attribute) error {

	dst := &net.UDPAddr{
		IP:   addr.IP,
		Port: addr.Port,
	}

	rsp, err := Build(class, method, transactionID, attrs...)
	if err != nil {
		return err
	}

	b := rsp.Pack()
	l, err := conn.WriteTo(b, nil, dst)
	if err != nil {
		return errors.Wrap(err, "failed writing to socket")
	}

	if l != len(b) {
		return errors.Errorf("packet write smaller than packet %d != %d (expected)", l, len(b))
	}

	return nil
}
<commit_msg>turn: Remove manual instantiation of UDPAddr<commit_after>package stun

import (
	"encoding/binary"
	"fmt"

	"github.com/pkg/errors"
	"golang.org/x/net/ipv4"
)

// MessageClass of 0b00 is a request, a class of 0b01 is an
//   indication, a class of 0b10 is a success response, and a class of
//   0b11 is an error response.
// https://tools.ietf.org/html/rfc5389#section-6
type MessageClass byte

const (
	// ClassRequest describes a request method type
	ClassRequest MessageClass = 0x00
	// ClassIndication describes an indication method type
	ClassIndication MessageClass = 0x01
	// ClassSuccessResponse describes an success response method type
	ClassSuccessResponse MessageClass = 0x02
	// ClassErrorResponse describes an error response method type
	ClassErrorResponse MessageClass = 0x03
)

type Method uint16

const (
	MethodBinding          Method = 0x01 // STUN
	MethodSharedSecret     Method = 0x02 // STUN
	MethodAllocate         Method = 0x03 // TURN (Req/Rsp)
	MethodRefresh          Method = 0x04 // TURN (Req/Rsp)
	MethodSend             Method = 0x06 // TURN (Ind)
	MethodData             Method = 0x07 // TURN (Ind)
	MethodCreatePermission Method = 0x08 // TURN (Req/Rsp)
	MethodChannelBind      Method = 0x09 // TURN (Req/Rsp)
)

var messageClassName = map[MessageClass]string{
	ClassRequest:         "REQUEST",
	ClassIndication:      "INDICATION",
	ClassSuccessResponse: "SUCCESS-RESPONSE",
	ClassErrorResponse:   "ERROR-RESPONSE",
}

func (m MessageClass) String() string {
	s, err := messageClassName[m]
	if !err {
		// Falling back to hex representation.
		s = fmt.Sprintf("Unk 0x%x", uint16(m))
	}
	return s
}

var methodName = map[Method]string{
	MethodBinding:          "BINDING",
	MethodSharedSecret:     "SHARED-SECRET",
	MethodAllocate:         "ALLOCATE",
	MethodRefresh:          "REFRESH",
	MethodSend:             "SEND",
	MethodData:             "DATA",
	MethodCreatePermission: "CREATE-PERMISSION",
	MethodChannelBind:      "CHANNEL-BIND",
}

func (m Method) String() string {
	s, err := methodName[m]
	if !err {
		s = fmt.Sprintf("Unk 0x%x", uint16(m))
	}
	return s
}

//       0                   1                   2                   3
//       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//      |0 0|     STUN Message Type     |         Message Length        |
//      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//      |                         Magic Cookie                          |
//      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//      |                                                               |
//      |                     Transaction ID (96 bits)                  |
//      |                                                               |
//      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//

const (
	messageHeaderStart  int = 0
	messageHeaderLength int = 20
	messageLengthStart  int = 2
	messageLengthLength int = 2
	magicCookieStart    int = 4
	magicCookieLength   int = 4
	transactionIDStart  int = 4
	transactionIDLength int = 16
)

type Message struct {
	Class         MessageClass
	Method        Method
	Length        uint16
	TransactionID []byte
	Attributes    []*RawAttribute
	Raw           []byte
}

// The most significant 2 bits of every STUN message MUST be zeroes.
// This can be used to differentiate STUN packets from other protocols
// when STUN is multiplexed with other protocols on the same port.
// https://tools.ietf.org/html/rfc5389#section-6
func verifyStunHeaderMostSignificant2Bits(header []byte) bool {
	return (header[0] >> 6) == 0
}

func verifyMagicCookie(header []byte) error {
	const magicCookie = 0x2112A442
	c := header[magicCookieStart : magicCookieStart+magicCookieLength]
	if binary.BigEndian.Uint32(c) != magicCookie {
		return errors.Errorf("stun header magic cookie invalid; %v != %v (expected)", binary.BigEndian.Uint32(c), magicCookie)
	}
	return nil
}

// The message length MUST contain the size, in bytes, of the message
// not including the 20-byte STUN header.  Since all STUN attributes are
// padded to a multiple of 4 bytes, the last 2 bits of this field are
// always zero.  This provides another way to distinguish STUN packets
// from packets of other protocols.
// https://tools.ietf.org/html/rfc5389#section-6
func getMessageLength(header []byte) (uint16, error) {
	messageLength := binary.BigEndian.Uint16(header[messageLengthStart : messageLengthStart+messageLengthLength])
	if messageLength%4 != 0 {
		return 0, errors.Errorf("stun header message length must be a factor of 4 (%d)", messageLength)
	}

	return messageLength, nil
}

//  0                 1
//  2  3  4 5 6 7 8 9 0 1 2 3 4 5
//
// +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
// |M |M |M|M|M|C|M|M|M|C|M|M|M|M|
// |11|10|9|8|7|1|6|5|4|0|3|2|1|0|
// +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
const (
	c0Mask   = 0x10 // 0b10000
	c1Mask   = 0x01 // 0b00001
	c0ShiftR = 4    // R 0b10000 -> 0b00001
	c1ShiftL = 1    // L 0b00001 -> 0b00010

	m0Mask   = 0x0F // 0b00001111
	m4Mask   = 0xE0 // 0b11100000
	m7Mask   = 0x3E // 0b00111110
	m4ShiftR = 1    // R 0b01110000 -> 0b00111000
	m7ShiftL = 5    // L 0b00111110 -> 0b0000011111000000
)

func setMessageType(header []byte, class MessageClass, method Method) {
	m := uint16(method)
	c := uint16(class)

	mt := m & m0Mask
	// Make room for c0
	mt |= (m & (m4Mask >> m4ShiftR)) << 1
	mt |= (m & (m7Mask << 6)) << 2
	mt |= (c & 0x1) << 4
	mt |= (c >> 1) << 8

	binary.BigEndian.PutUint16(header[messageHeaderStart:], mt)
}

func getMessageType(header []byte) (MessageClass, Method) {
	mByte0 := header[0]
	mByte1 := header[1]

	c0 := (mByte1 & c0Mask) >> c0ShiftR
	c1 := (mByte0 & c1Mask) << c1ShiftL

	class := MessageClass(c1 | c0)

	var m uint16
	m = (uint16(mByte0) & m7Mask) << m7ShiftL
	m |= uint16(mByte1 & m0Mask)
	m |= uint16((mByte1 & m4Mask) >> m4ShiftR)

	method := Method(m)

	return class, method
}

//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |         Type                  |            Length             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Value (variable)                ....
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
func getAttribute(attribute []byte, offset int) *RawAttribute {
	typ := AttrType(binary.BigEndian.Uint16(attribute))
	len := binary.BigEndian.Uint16(attribute[attrLengthStart : attrLengthStart+attrLengthLength])
	pad := (attrLengthMultiple - (len % attrLengthMultiple)) % attrLengthMultiple
	return &RawAttribute{typ, len, attribute[attrValueStart : attrValueStart+len], pad, offset}
}

// TODO Break this apart, too big
func NewMessage(packet []byte) (*Message, error) {

	if len(packet) < 20 {
		return nil, errors.Errorf("stun header must be at least 20 bytes, was %d", len(packet))
	}

	header := packet[messageHeaderStart : messageHeaderStart+messageHeaderLength]

	if !verifyStunHeaderMostSignificant2Bits(header) {
		return nil, errors.New("stun header most significant 2 bits must equal 0b00")
	}

	err := verifyMagicCookie(header)
	if err != nil {
		return nil, errors.Wrap(err, "stun header invalid")
	}

	ml, err := getMessageLength(header)
	if err != nil {
		return nil, errors.Wrap(err, "stun header invalid")
	}

	if len(packet) != messageHeaderLength+int(ml) {
		return nil, errors.Errorf("stun header length invalid; %d != %d (expected)", messageHeaderLength+int(ml), len(packet))
	}

	t := header[transactionIDStart : transactionIDStart+transactionIDLength]

	class, method := getMessageType(header)

	ra := []*RawAttribute{}
	// TODO Check attr length <= attr slice remaining
	attr := packet[messageHeaderLength:]
	for len(attr) > 0 {
		a := getAttribute(attr, cap(packet)-cap(attr))
		attr = attr[attrValueStart+a.Length+a.Pad:]
		ra = append(ra, a)
	}

	m := Message{}
	m.Class = class
	m.Method = method
	m.Length = ml
	m.TransactionID = t[0:transactionIDLength]
	m.Attributes = ra
	m.Raw = packet

	return &m, nil
}

func (m *Message) GetOneAttribute(attrType AttrType) (*RawAttribute, bool) {
	for _, v := range m.Attributes {
		if v.Type == attrType {
			return v, true
		}
	}

	return nil, false
}

func (m *Message) GetAllAttributes(attrType AttrType) ([]*RawAttribute, bool) {
	var attrs []*RawAttribute
	for _, v := range m.Attributes {
		if v.Type == attrType {
			attrs = append(attrs, v)
		}
	}

	return attrs, len(attrs) > 0
}

func (m *Message) CommitLength() {
	binary.BigEndian.PutUint16(m.Raw[messageLengthStart:], uint16(m.Length))
}

func (m *Message) AddAttribute(attrType AttrType, v []byte) {

	ra := RawAttribute{
		Type:   attrType,
		Value:  v,
		Pad:    uint16(GetAttrPadding(len(v))),
		Length: uint16(len(v)),
		Offset: int(m.Length),
	}

	a := make([]byte, attrHeaderLength+ra.Length+ra.Pad)

	binary.BigEndian.PutUint16(a, uint16(ra.Type))
	binary.BigEndian.PutUint16(a[attrLengthStart:attrLengthStart+attrLengthLength], ra.Length)

	copy(a[attrValueStart:], ra.Value)

	m.Attributes = append(m.Attributes, &ra)
	m.Raw = append(m.Raw, a...)
	m.Length += uint16(len(a))
	m.CommitLength()
}

func (m *Message) Pack() []byte {

	setMessageType(m.Raw[messageHeaderStart:2], m.Class, m.Method)
	m.CommitLength()
	copy(m.Raw[transactionIDStart:], m.TransactionID)

	return m.Raw
}

func BuildAndSend(conn *ipv4.PacketConn, addr *TransportAddr, class MessageClass, method Method, transactionID []byte, attrs ...Attribute) error {

	rsp, err := Build(class, method, transactionID, attrs...)
	if err != nil {
		return err
	}

	b := rsp.Pack()
	l, err := conn.WriteTo(b, nil, addr.Addr())
	if err != nil {
		return errors.Wrap(err, "failed writing to socket")
	}

	if l != len(b) {
		return errors.Errorf("packet write smaller than packet %d != %d (expected)", l, len(b))
	}

	return nil
}
<|endoftext|>


<commit_before>package coap

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"sort"
	"strings"
)

type COAPType uint8

const (
	Confirmable     = COAPType(0)
	NonConfirmable  = COAPType(1)
	Acknowledgement = COAPType(2)
	Reset           = COAPType(3)
)

const (
	GET       = 1
	POST      = 2
	PUT       = 3
	DELETE    = 4
	SUBSCRIBE = 5
)

const (
	Created               = 65
	Deleted               = 66
	Valid                 = 67
	Changed               = 68
	Content               = 69
	BadRequest            = 128
	Unauthorized          = 129
	BadOption             = 130
	Forbidden             = 131
	NotFound              = 132
	MethodNotAllowed      = 133
	NotAcceptable         = 134
	PreconditionFailed    = 140
	RequestEntityTooLarge = 141
	UnsupportedMediaType  = 143
	InternalServerError   = 160
	NotImplemented        = 161
	BadGateway            = 162
	ServiceUnavailable    = 163
	GatewayTimeout        = 164
	ProxyingNotSupported  = 165
)

var TooManyOptions = errors.New("Too many options")
var OptionTooLong = errors.New("Option is too long")

type OptionID uint8

const (
	ContentType   = OptionID(1)
	MaxAge        = OptionID(2)
	ProxyURI      = OptionID(3)
	ETag          = OptionID(4)
	URIHost       = OptionID(5)
	LocationPath  = OptionID(6)
	URIPort       = OptionID(7)
	LocationQuery = OptionID(8)
	URIPath       = OptionID(9)
	Token         = OptionID(11)
	Accept        = OptionID(12)
	IfMatch       = OptionID(13)
	UriQuery      = OptionID(15)
	IfNoneMatch   = OptionID(21)
)

type MediaType byte

const (
	TextPlain     = MediaType(0)  // text/plain;charset=utf-8
	AppLinkFormat = MediaType(40) // application/link-format
	AppXML        = MediaType(41) // application/xml
	AppOctets     = MediaType(42) // application/octet-stream
	AppExi        = MediaType(47) // application/exi
	AppJSON       = MediaType(50) // application/json
)

/*
   +-----+---+---+----------------+--------+---------+-------------+
   | No. | C | R | Name           | Format | Length  | Default     |
   +-----+---+---+----------------+--------+---------+-------------+
   |   1 | x |   | Content-Type   | uint   | 0-2 B   | (none)      |
   |   2 |   |   | Max-Age        | uint   | 0-4 B   | 60          |
   |   3 | x | x | Proxy-Uri      | string | 1-270 B | (none)      |
   |   4 |   | x | ETag           | opaque | 1-8 B   | (none)      |
   |   5 | x |   | Uri-Host       | string | 1-270 B | (see below) |
   |   6 |   | x | Location-Path  | string | 0-270 B | (none)      |
   |   7 | x |   | Uri-Port       | uint   | 0-2 B   | (see below) |
   |   8 |   | x | Location-Query | string | 0-270 B | (none)      |
   |   9 | x | x | Uri-Path       | string | 0-270 B | (none)      |
   |  11 | x |   | Token          | opaque | 1-8 B   | (empty)     |
   |  12 |   | x | Accept         | uint   | 0-2 B   | (none)      |
   |  13 | x | x | If-Match       | opaque | 0-8 B   | (none)      |
   |  15 | x | x | Uri-Query      | string | 0-270 B | (none)      |
   |  21 | x |   | If-None-Match  | empty  | 0 B     | (none)      |
   +-----+---+---+----------------+--------+---------+-------------+
*/

type Option struct {
	ID    OptionID
	Value interface{}
}

func encodeInt(v uint32) []byte {
	switch {
	case v == 0:
		return []byte{}
	case v < 256:
		return []byte{byte(v)}
	case v < 65536:
		rv := []byte{0, 0}
		binary.BigEndian.PutUint16(rv, uint16(v))
		return rv
	case v < 16777216:
		rv := []byte{0, 0, 0, 0}
		binary.BigEndian.PutUint32(rv, uint32(v))
		return rv[1:]
	default:
		rv := []byte{0, 0, 0, 0}
		binary.BigEndian.PutUint32(rv, uint32(v))
		return rv
	}
}

func decodeInt(b []byte) uint32 {
	tmp := []byte{0, 0, 0, 0}
	copy(tmp[4-len(b):], b)
	return binary.BigEndian.Uint32(tmp)
}

func (o Option) toBytes() []byte {
	var v uint32

	switch i := o.Value.(type) {
	case string:
		return []byte(i)
	case []byte:
		return i
	case int:
		v = uint32(i)
	case int32:
		v = uint32(i)
	case uint:
		v = uint32(i)
	case uint32:
		v = i
	default:
		panic(fmt.Errorf("Invalid type for option %x", o.ID))
	}

	return encodeInt(v)
}

type Options []Option

func (o Options) Len() int {
	return len(o)
}

func (o Options) Less(i, j int) bool {
	if o[i].ID == o[j].ID {
		return i < j
	}
	return o[i].ID < o[j].ID
}

func (o Options) Swap(i, j int) {
	o[i], o[j] = o[j], o[i]
}

func (o Options) Minus(oid OptionID) Options {
	rv := Options{}
	for _, opt := range o {
		if opt.ID != oid {
			rv = append(rv, opt)
		}
	}
	return rv
}

// A CoAP message.
type Message struct {
	Type      COAPType
	Code      uint8
	MessageID uint16

	Options Options

	Payload []byte
}

// Return True if this message is confirmable.
func (m Message) IsConfirmable() bool {
	return m.Type == Confirmable
}

// Get the Path set on this message if any.
func (m Message) Path() []string {
	rv := []string{}
	for _, o := range m.Options {
		if o.ID == URIPath {
			rv = append(rv, o.Value.(string))
		}
	}
	return rv
}

// Get a path as a / separated string.
func (m Message) PathString() string {
	return strings.Join(m.Path(), "/")
}

// Set a path by a / separated string.
func (m *Message) SetPathString(s string) {
	m.SetPath(strings.Split(s, "/"))
}

// Update or add a LocationPath attribute on this message.
func (m *Message) SetPath(s []string) {
	m.Options = m.Options.Minus(URIPath)
	for _, p := range s {
		m.Options = append(m.Options, Option{URIPath, p})
	}
}

func encodeMessage(r Message) ([]byte, error) {
	if len(r.Options) > 14 {
		return []byte{}, TooManyOptions
	}

	tmpbuf := []byte{0, 0}
	binary.BigEndian.PutUint16(tmpbuf, r.MessageID)

	/*
	     0                   1                   2                   3
	    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |Ver| T |  OC   |      Code     |          Message ID           |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |   Options (if any) ...
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |   Payload (if any) ...
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	*/

	buf := bytes.Buffer{}
	buf.Write([]byte{
		(1 << 6) | (uint8(r.Type) << 4) | uint8(0xf&len(r.Options)),
		byte(r.Code),
		tmpbuf[0], tmpbuf[1],
	})

	/*
	     0   1   2   3   4   5   6   7
	   +---+---+---+---+---+---+---+---+
	   | Option Delta  |    Length     | for 0..14
	   +---+---+---+---+---+---+---+---+
	   |   Option Value ...
	   +---+---+---+---+---+---+---+---+
	                                               for 15..270:
	   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
	   | Option Delta  | 1   1   1   1 |          Length - 15          |
	   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
	   |   Option Value ...
	   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
	*/

	sort.Sort(&r.Options)

	prev := 0
	for _, o := range r.Options {
		b := o.toBytes()
		if len(b) > 15 {
			buf.Write([]byte{
				byte(int(o.ID)-prev)<<4 | 15,
				byte(len(b) - 15),
			})
		} else {
			buf.Write([]byte{byte(int(o.ID)-prev)<<4 | byte(len(b))})
		}
		if int(o.ID)-prev > 15 {
			return []byte{}, errors.New("Gap too large")
		}

		buf.Write(b)
		prev = int(o.ID)
	}

	buf.Write(r.Payload)

	return buf.Bytes(), nil
}

func parseMessage(data []byte) (rv Message, err error) {
	if len(data) < 6 {
		return rv, errors.New("Short packet")
	}

	if data[0]>>6 != 1 {
		return rv, errors.New("Invalid version")
	}

	rv.Type = COAPType((data[0] >> 4) & 0x3)
	opCount := int(data[0] & 0xf)
	if opCount > 14 {
		return rv, TooManyOptions
	}

	rv.Code = data[1]
	rv.MessageID = binary.BigEndian.Uint16(data[2:4])

	b := data[4:]
	prev := 0
	for i := 0; i < opCount && len(b) > 0; i++ {
		oid := OptionID(prev + int(b[0]>>4))
		l := int(b[0] & 0xf)
		b = b[1:]
		if l > 14 {
			l += int(b[0])
			b = b[1:]
		}
		if len(b) < l {
			return rv, errors.New("Truncated")
		}
		var opval interface{} = b[:l]
		switch oid {
		case ContentType,
			MaxAge,
			URIPort,
			Accept:
			opval = decodeInt(b[:l])
		case ProxyURI, URIHost, LocationPath, LocationQuery, URIPath, UriQuery:
			opval = string(b[:l])
		}

		option := Option{
			ID:    oid,
			Value: opval,
		}
		b = b[l:]
		prev = int(option.ID)

		rv.Options = append(rv.Options, option)
	}

	rv.Payload = b
	return rv, nil
}
<commit_msg>Better error message on invalid option value<commit_after>package coap

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"sort"
	"strings"
)

type COAPType uint8

const (
	Confirmable     = COAPType(0)
	NonConfirmable  = COAPType(1)
	Acknowledgement = COAPType(2)
	Reset           = COAPType(3)
)

const (
	GET       = 1
	POST      = 2
	PUT       = 3
	DELETE    = 4
	SUBSCRIBE = 5
)

const (
	Created               = 65
	Deleted               = 66
	Valid                 = 67
	Changed               = 68
	Content               = 69
	BadRequest            = 128
	Unauthorized          = 129
	BadOption             = 130
	Forbidden             = 131
	NotFound              = 132
	MethodNotAllowed      = 133
	NotAcceptable         = 134
	PreconditionFailed    = 140
	RequestEntityTooLarge = 141
	UnsupportedMediaType  = 143
	InternalServerError   = 160
	NotImplemented        = 161
	BadGateway            = 162
	ServiceUnavailable    = 163
	GatewayTimeout        = 164
	ProxyingNotSupported  = 165
)

var TooManyOptions = errors.New("Too many options")
var OptionTooLong = errors.New("Option is too long")

type OptionID uint8

const (
	ContentType   = OptionID(1)
	MaxAge        = OptionID(2)
	ProxyURI      = OptionID(3)
	ETag          = OptionID(4)
	URIHost       = OptionID(5)
	LocationPath  = OptionID(6)
	URIPort       = OptionID(7)
	LocationQuery = OptionID(8)
	URIPath       = OptionID(9)
	Token         = OptionID(11)
	Accept        = OptionID(12)
	IfMatch       = OptionID(13)
	UriQuery      = OptionID(15)
	IfNoneMatch   = OptionID(21)
)

type MediaType byte

const (
	TextPlain     = MediaType(0)  // text/plain;charset=utf-8
	AppLinkFormat = MediaType(40) // application/link-format
	AppXML        = MediaType(41) // application/xml
	AppOctets     = MediaType(42) // application/octet-stream
	AppExi        = MediaType(47) // application/exi
	AppJSON       = MediaType(50) // application/json
)

/*
   +-----+---+---+----------------+--------+---------+-------------+
   | No. | C | R | Name           | Format | Length  | Default     |
   +-----+---+---+----------------+--------+---------+-------------+
   |   1 | x |   | Content-Type   | uint   | 0-2 B   | (none)      |
   |   2 |   |   | Max-Age        | uint   | 0-4 B   | 60          |
   |   3 | x | x | Proxy-Uri      | string | 1-270 B | (none)      |
   |   4 |   | x | ETag           | opaque | 1-8 B   | (none)      |
   |   5 | x |   | Uri-Host       | string | 1-270 B | (see below) |
   |   6 |   | x | Location-Path  | string | 0-270 B | (none)      |
   |   7 | x |   | Uri-Port       | uint   | 0-2 B   | (see below) |
   |   8 |   | x | Location-Query | string | 0-270 B | (none)      |
   |   9 | x | x | Uri-Path       | string | 0-270 B | (none)      |
   |  11 | x |   | Token          | opaque | 1-8 B   | (empty)     |
   |  12 |   | x | Accept         | uint   | 0-2 B   | (none)      |
   |  13 | x | x | If-Match       | opaque | 0-8 B   | (none)      |
   |  15 | x | x | Uri-Query      | string | 0-270 B | (none)      |
   |  21 | x |   | If-None-Match  | empty  | 0 B     | (none)      |
   +-----+---+---+----------------+--------+---------+-------------+
*/

type Option struct {
	ID    OptionID
	Value interface{}
}

func encodeInt(v uint32) []byte {
	switch {
	case v == 0:
		return []byte{}
	case v < 256:
		return []byte{byte(v)}
	case v < 65536:
		rv := []byte{0, 0}
		binary.BigEndian.PutUint16(rv, uint16(v))
		return rv
	case v < 16777216:
		rv := []byte{0, 0, 0, 0}
		binary.BigEndian.PutUint32(rv, uint32(v))
		return rv[1:]
	default:
		rv := []byte{0, 0, 0, 0}
		binary.BigEndian.PutUint32(rv, uint32(v))
		return rv
	}
}

func decodeInt(b []byte) uint32 {
	tmp := []byte{0, 0, 0, 0}
	copy(tmp[4-len(b):], b)
	return binary.BigEndian.Uint32(tmp)
}

func (o Option) toBytes() []byte {
	var v uint32

	switch i := o.Value.(type) {
	case string:
		return []byte(i)
	case []byte:
		return i
	case int:
		v = uint32(i)
	case int32:
		v = uint32(i)
	case uint:
		v = uint32(i)
	case uint32:
		v = i
	default:
		panic(fmt.Errorf("Invalid type for option %x: %T (%v)",
			o.ID, o.Value, o.Value))
	}

	return encodeInt(v)
}

type Options []Option

func (o Options) Len() int {
	return len(o)
}

func (o Options) Less(i, j int) bool {
	if o[i].ID == o[j].ID {
		return i < j
	}
	return o[i].ID < o[j].ID
}

func (o Options) Swap(i, j int) {
	o[i], o[j] = o[j], o[i]
}

func (o Options) Minus(oid OptionID) Options {
	rv := Options{}
	for _, opt := range o {
		if opt.ID != oid {
			rv = append(rv, opt)
		}
	}
	return rv
}

// A CoAP message.
type Message struct {
	Type      COAPType
	Code      uint8
	MessageID uint16

	Options Options

	Payload []byte
}

// Return True if this message is confirmable.
func (m Message) IsConfirmable() bool {
	return m.Type == Confirmable
}

// Get the Path set on this message if any.
func (m Message) Path() []string {
	rv := []string{}
	for _, o := range m.Options {
		if o.ID == URIPath {
			rv = append(rv, o.Value.(string))
		}
	}
	return rv
}

// Get a path as a / separated string.
func (m Message) PathString() string {
	return strings.Join(m.Path(), "/")
}

// Set a path by a / separated string.
func (m *Message) SetPathString(s string) {
	m.SetPath(strings.Split(s, "/"))
}

// Update or add a LocationPath attribute on this message.
func (m *Message) SetPath(s []string) {
	m.Options = m.Options.Minus(URIPath)
	for _, p := range s {
		m.Options = append(m.Options, Option{URIPath, p})
	}
}

func encodeMessage(r Message) ([]byte, error) {
	if len(r.Options) > 14 {
		return []byte{}, TooManyOptions
	}

	tmpbuf := []byte{0, 0}
	binary.BigEndian.PutUint16(tmpbuf, r.MessageID)

	/*
	     0                   1                   2                   3
	    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |Ver| T |  OC   |      Code     |          Message ID           |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |   Options (if any) ...
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |   Payload (if any) ...
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	*/

	buf := bytes.Buffer{}
	buf.Write([]byte{
		(1 << 6) | (uint8(r.Type) << 4) | uint8(0xf&len(r.Options)),
		byte(r.Code),
		tmpbuf[0], tmpbuf[1],
	})

	/*
	     0   1   2   3   4   5   6   7
	   +---+---+---+---+---+---+---+---+
	   | Option Delta  |    Length     | for 0..14
	   +---+---+---+---+---+---+---+---+
	   |   Option Value ...
	   +---+---+---+---+---+---+---+---+
	                                               for 15..270:
	   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
	   | Option Delta  | 1   1   1   1 |          Length - 15          |
	   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
	   |   Option Value ...
	   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
	*/

	sort.Sort(&r.Options)

	prev := 0
	for _, o := range r.Options {
		b := o.toBytes()
		if len(b) > 15 {
			buf.Write([]byte{
				byte(int(o.ID)-prev)<<4 | 15,
				byte(len(b) - 15),
			})
		} else {
			buf.Write([]byte{byte(int(o.ID)-prev)<<4 | byte(len(b))})
		}
		if int(o.ID)-prev > 15 {
			return []byte{}, errors.New("Gap too large")
		}

		buf.Write(b)
		prev = int(o.ID)
	}

	buf.Write(r.Payload)

	return buf.Bytes(), nil
}

func parseMessage(data []byte) (rv Message, err error) {
	if len(data) < 6 {
		return rv, errors.New("Short packet")
	}

	if data[0]>>6 != 1 {
		return rv, errors.New("Invalid version")
	}

	rv.Type = COAPType((data[0] >> 4) & 0x3)
	opCount := int(data[0] & 0xf)
	if opCount > 14 {
		return rv, TooManyOptions
	}

	rv.Code = data[1]
	rv.MessageID = binary.BigEndian.Uint16(data[2:4])

	b := data[4:]
	prev := 0
	for i := 0; i < opCount && len(b) > 0; i++ {
		oid := OptionID(prev + int(b[0]>>4))
		l := int(b[0] & 0xf)
		b = b[1:]
		if l > 14 {
			l += int(b[0])
			b = b[1:]
		}
		if len(b) < l {
			return rv, errors.New("Truncated")
		}
		var opval interface{} = b[:l]
		switch oid {
		case ContentType,
			MaxAge,
			URIPort,
			Accept:
			opval = decodeInt(b[:l])
		case ProxyURI, URIHost, LocationPath, LocationQuery, URIPath, UriQuery:
			opval = string(b[:l])
		}

		option := Option{
			ID:    oid,
			Value: opval,
		}
		b = b[l:]
		prev = int(option.ID)

		rv.Options = append(rv.Options, option)
	}

	rv.Payload = b
	return rv, nil
}
<|endoftext|>


<commit_before>package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/antonholmquist/jason"
	"github.com/bmizerany/pat"
	"github.com/julienschmidt/httprouter"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"golang.org/x/net/context"
)

type key int

var (
	userID  key = 0
	version key = 1
)

// API version middleware
func versionMiddleware(ctx context.Context, r *Req) (context.Context, error) {
	r.Response.Header().Set("X-Api-Version", "0.0.1")
	return context.WithValue(ctx, version, "0.0.1"), nil
}

// Login middleware
func login(ctx context.Context, r *Req) (context.Context, error) {
	c := context.WithValue(ctx, userID, "1")
	return c, nil
}

// Auth middleware
func auth(ctx context.Context, r *Req) (context.Context, error) {
	id, ok := ctx.Value(userID).(string)
	if !ok || id == "" {
		return ctx, NewError(http.StatusUnauthorized, "not authenticated")
	}
	return ctx, nil
}

type pet struct {
	ID   string `json:"id,omitempty"`
	Name string `json:"name,omitempty"`
}

func makeAPI() *API {
	api := New("/v1")

	api.Use(MiddlewareFunc(versionMiddleware))

	api.Add(Endpoint{
		Method:     "PUT",
		Path:       "/pets/:id",
		Middleware: []Middleware{MiddlewareFunc(login), MiddlewareFunc(auth)},
		Implementation: func(ctx context.Context, r *Req) {
			pet := pet{ID: r.Params.Get(":id")}
			err := r.Decode(&pet)

			b, err := json.Marshal(pet)
			if err != nil {
				panic(err)
			}

			r.Response.Header().Set("Content-Type", "application/json")
			r.Response.WriteHeader(200)
			r.Response.Write(b)
		},
	})

	api.Add(Endpoint{
		Method:     "POST",
		Path:       "/pets",
		Middleware: []Middleware{MiddlewareFunc(auth)},
		Implementation: func(ctx context.Context, r *Req) {
			pet := pet{}
			err := r.Decode(&pet)

			b, err := json.Marshal(pet)
			if err != nil {
				panic(err)
			}

			r.Response.Header().Set("Content-Type", "application/json")
			r.Response.WriteHeader(200)
			r.Response.Write(b)
		},
	})

	return api
}

var _ = Describe("api integration", func() {
	Context("httprouter", func() {
		FIt("handles a failing middleware", func() {
			router := httprouter.New()
			api := makeAPI()
			api.Activate(router)

			res := httptest.NewRecorder()
			req, _ := http.NewRequest("POST", "/v1/pets", strings.NewReader(`{"name":"moufassa","id":"king"}`))
			router.ServeHTTP(res, req)

			Expect(res.Header().Get("X-Api-Version")).To(Equal("0.0.1"))
			Expect(res.Code).To(Equal(401))

			data, err := jason.NewObjectFromReader(res.Body)
			Expect(err).ToNot(HaveOccurred())

			slice, err := data.GetObjectArray("errors")
			Expect(err).ToNot(HaveOccurred())
			Expect(slice).ToNot(BeNil())

			msg := slice[0]
			Expect(msg).ToNot(BeNil())

			i, err := msg.GetString("status")
			Expect(err).ToNot(HaveOccurred())
			Expect(i).To(Equal("401"))

			v, err := msg.GetString("title")
			Expect(err).ToNot(HaveOccurred())
			Expect(v).To(Equal("not authenticated"))
		})

		It("works", func() {
			router := httprouter.New()
			api := makeAPI()
			api.Activate(router)

			res := httptest.NewRecorder()
			req, _ := http.NewRequest("PUT", "/v1/pets/abc", strings.NewReader(`{"name":"simba"}`))
			fmt.Printf("ServeHTTP %v %v\n", req.Method, req.URL.String())
			router.ServeHTTP(res, req)

			Expect(res.Header().Get("X-Api-Version")).To(Equal("0.0.1"))
			Expect(res.Code).To(Equal(200))

			data, err := jason.NewObjectFromReader(res.Body)
			Expect(err).ToNot(HaveOccurred())

			name, err := data.GetString("name")
			Expect(err).ToNot(HaveOccurred())
			Expect(name).To(Equal("simba"))

			id, err := data.GetString("id")
			Expect(err).ToNot(HaveOccurred())
			Expect(id).To(Equal("abc"))
		})
	})

	Context("Gorilla Pat", func() {
		It("handles a failing middleware", func() {
			router := pat.New()
			api := makeAPI()
			api.Activate(router)

			res := httptest.NewRecorder()
			req, _ := http.NewRequest("POST", "/v1/pets", strings.NewReader(`{"name":"moufassa","id":"abc"}`))
			router.ServeHTTP(res, req)

			Expect(res.Header().Get("X-Api-Version")).To(Equal("0.0.1"))
			Expect(res.Code).To(Equal(401))

			data, err := jason.NewObjectFromReader(res.Body)
			Expect(err).ToNot(HaveOccurred())

			slice, err := data.GetObjectArray("errors")
			Expect(err).ToNot(HaveOccurred())
			Expect(slice).ToNot(BeNil())

			msg := slice[0]
			Expect(msg).ToNot(BeNil())

			i, err := msg.GetString("status")
			Expect(err).ToNot(HaveOccurred())
			Expect(i).To(Equal("401"))

			v, err := msg.GetString("title")
			Expect(err).ToNot(HaveOccurred())
			Expect(v).To(Equal("not authenticated"))
		})

		It("works", func() {
			router := pat.New()
			api := makeAPI()
			api.Activate(router)

			res := httptest.NewRecorder()
			req, _ := http.NewRequest("PUT", "/v1/pets/abc", strings.NewReader(`{"name":"simba"}`))
			router.ServeHTTP(res, req)

			Expect(res.Header().Get("X-Api-Version")).To(Equal("0.0.1"))
			Expect(res.Code).To(Equal(200))

			data, err := jason.NewObjectFromReader(res.Body)
			Expect(err).ToNot(HaveOccurred())

			name, err := data.GetString("name")
			Expect(err).ToNot(HaveOccurred())
			Expect(name).To(Equal("simba"))

			id, err := data.GetString("id")
			Expect(err).ToNot(HaveOccurred())
			Expect(id).To(Equal("abc"))
		})
	})
})
<commit_msg>remove focus on tests<commit_after>package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/antonholmquist/jason"
	"github.com/bmizerany/pat"
	"github.com/julienschmidt/httprouter"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"golang.org/x/net/context"
)

type key int

var (
	userID  key = 0
	version key = 1
)

// API version middleware
func versionMiddleware(ctx context.Context, r *Req) (context.Context, error) {
	r.Response.Header().Set("X-Api-Version", "0.0.1")
	return context.WithValue(ctx, version, "0.0.1"), nil
}

// Login middleware
func login(ctx context.Context, r *Req) (context.Context, error) {
	c := context.WithValue(ctx, userID, "1")
	return c, nil
}

// Auth middleware
func auth(ctx context.Context, r *Req) (context.Context, error) {
	id, ok := ctx.Value(userID).(string)
	if !ok || id == "" {
		return ctx, NewError(http.StatusUnauthorized, "not authenticated")
	}
	return ctx, nil
}

type pet struct {
	ID   string `json:"id,omitempty"`
	Name string `json:"name,omitempty"`
}

func makeAPI() *API {
	api := New("/v1")

	api.Use(MiddlewareFunc(versionMiddleware))

	api.Add(Endpoint{
		Method:     "PUT",
		Path:       "/pets/:id",
		Middleware: []Middleware{MiddlewareFunc(login), MiddlewareFunc(auth)},
		Implementation: func(ctx context.Context, r *Req) {
			pet := pet{ID: r.Params.Get(":id")}
			err := r.Decode(&pet)

			b, err := json.Marshal(pet)
			if err != nil {
				panic(err)
			}

			r.Response.Header().Set("Content-Type", "application/json")
			r.Response.WriteHeader(200)
			r.Response.Write(b)
		},
	})

	api.Add(Endpoint{
		Method:     "POST",
		Path:       "/pets",
		Middleware: []Middleware{MiddlewareFunc(auth)},
		Implementation: func(ctx context.Context, r *Req) {
			pet := pet{}
			err := r.Decode(&pet)

			b, err := json.Marshal(pet)
			if err != nil {
				panic(err)
			}

			r.Response.Header().Set("Content-Type", "application/json")
			r.Response.WriteHeader(200)
			r.Response.Write(b)
		},
	})

	return api
}

var _ = Describe("api integration", func() {
	Context("httprouter", func() {
		It("handles a failing middleware", func() {
			router := httprouter.New()
			api := makeAPI()
			api.Activate(router)

			res := httptest.NewRecorder()
			req, _ := http.NewRequest("POST", "/v1/pets", strings.NewReader(`{"name":"moufassa","id":"king"}`))
			router.ServeHTTP(res, req)

			Expect(res.Header().Get("X-Api-Version")).To(Equal("0.0.1"))
			Expect(res.Code).To(Equal(401))

			data, err := jason.NewObjectFromReader(res.Body)
			Expect(err).ToNot(HaveOccurred())

			slice, err := data.GetObjectArray("errors")
			Expect(err).ToNot(HaveOccurred())
			Expect(slice).ToNot(BeNil())

			msg := slice[0]
			Expect(msg).ToNot(BeNil())

			i, err := msg.GetString("status")
			Expect(err).ToNot(HaveOccurred())
			Expect(i).To(Equal("401"))

			v, err := msg.GetString("title")
			Expect(err).ToNot(HaveOccurred())
			Expect(v).To(Equal("not authenticated"))
		})

		It("works", func() {
			router := httprouter.New()
			api := makeAPI()
			api.Activate(router)

			res := httptest.NewRecorder()
			req, _ := http.NewRequest("PUT", "/v1/pets/abc", strings.NewReader(`{"name":"simba"}`))
			fmt.Printf("ServeHTTP %v %v\n", req.Method, req.URL.String())
			router.ServeHTTP(res, req)

			Expect(res.Header().Get("X-Api-Version")).To(Equal("0.0.1"))
			Expect(res.Code).To(Equal(200))

			data, err := jason.NewObjectFromReader(res.Body)
			Expect(err).ToNot(HaveOccurred())

			name, err := data.GetString("name")
			Expect(err).ToNot(HaveOccurred())
			Expect(name).To(Equal("simba"))

			id, err := data.GetString("id")
			Expect(err).ToNot(HaveOccurred())
			Expect(id).To(Equal("abc"))
		})
	})

	Context("Gorilla Pat", func() {
		It("handles a failing middleware", func() {
			router := pat.New()
			api := makeAPI()
			api.Activate(router)

			res := httptest.NewRecorder()
			req, _ := http.NewRequest("POST", "/v1/pets", strings.NewReader(`{"name":"moufassa","id":"abc"}`))
			router.ServeHTTP(res, req)

			Expect(res.Header().Get("X-Api-Version")).To(Equal("0.0.1"))
			Expect(res.Code).To(Equal(401))

			data, err := jason.NewObjectFromReader(res.Body)
			Expect(err).ToNot(HaveOccurred())

			slice, err := data.GetObjectArray("errors")
			Expect(err).ToNot(HaveOccurred())
			Expect(slice).ToNot(BeNil())

			msg := slice[0]
			Expect(msg).ToNot(BeNil())

			i, err := msg.GetString("status")
			Expect(err).ToNot(HaveOccurred())
			Expect(i).To(Equal("401"))

			v, err := msg.GetString("title")
			Expect(err).ToNot(HaveOccurred())
			Expect(v).To(Equal("not authenticated"))
		})

		It("works", func() {
			router := pat.New()
			api := makeAPI()
			api.Activate(router)

			res := httptest.NewRecorder()
			req, _ := http.NewRequest("PUT", "/v1/pets/abc", strings.NewReader(`{"name":"simba"}`))
			router.ServeHTTP(res, req)

			Expect(res.Header().Get("X-Api-Version")).To(Equal("0.0.1"))
			Expect(res.Code).To(Equal(200))

			data, err := jason.NewObjectFromReader(res.Body)
			Expect(err).ToNot(HaveOccurred())

			name, err := data.GetString("name")
			Expect(err).ToNot(HaveOccurred())
			Expect(name).To(Equal("simba"))

			id, err := data.GetString("id")
			Expect(err).ToNot(HaveOccurred())
			Expect(id).To(Equal("abc"))
		})
	})
})
<|endoftext|>


<commit_before>package speed

import (
	"errors"
	"fmt"
	"math"
	"sync"

	"github.com/performancecopilot/speed/bytebuffer"
)

// MetricType is an enumerated type representing all valid types for a metric
type MetricType int32

// Possible values for a MetricType
const (
	NoSupportType       MetricType = -1
	Int32Type           MetricType = 0
	Uint32Type          MetricType = 1
	Int64Type           MetricType = 2
	Uint64Type          MetricType = 3
	FloatType           MetricType = 4
	DoubleType          MetricType = 5
	StringType          MetricType = 6
	AggregateType       MetricType = 7
	AggregateStaticType MetricType = 8
	EventType           MetricType = 9
	HighresEventType    MetricType = 10
	UnknownType         MetricType = 255
)

//go:generate stringer -type=MetricType

// IsCompatible checks if the passed value is compatible with the current MetricType
func (m MetricType) IsCompatible(val interface{}) bool {
	switch val.(type) {
	case int:
		v := val.(int)
		switch {
		case v < 0:
			return m == Int32Type || m == Int64Type
		case v <= math.MaxInt32:
			return m == Int32Type || m == Int64Type || m == Uint32Type || m == Uint64Type
		case uint32(v) <= math.MaxUint32:
			return m == Int64Type || m == Uint32Type || m == Uint64Type
		case int64(v) <= math.MaxInt64:
			return m == Int64Type || m == Uint64Type
		default:
			return false
		}
	case int32:
		return m == Int32Type
	case int64:
		return m == Int64Type
	case uint:
		v := val.(uint)
		if v > math.MaxUint32 {
			return m == Uint64Type
		}
		return m == Uint32Type || m == Uint64Type
	case uint32:
		return m == Uint32Type
	case uint64:
		return m == Uint64Type
	default:
		return false
	}
}

// WriteVal implements value writer for the current MetricType to a buffer
func (m MetricType) WriteVal(val interface{}, b bytebuffer.Buffer) error {
	switch val.(type) {
	case int:
		switch m {
		case Int32Type:
			return b.WriteInt32(int32(val.(int)))
		case Int64Type:
			return b.WriteInt64(int64(val.(int)))
		case Uint32Type:
			return b.WriteUint32(uint32(val.(int)))
		case Uint64Type:
			return b.WriteUint64(uint64(val.(int)))
		}
	case int32:
		return b.WriteInt32(val.(int32))
	case int64:
		return b.WriteInt64(val.(int64))
	case uint:
		switch m {
		case Uint32Type:
			return b.WriteUint32(uint32(val.(uint)))
		case Uint64Type:
			return b.WriteUint64(uint64(val.(uint)))
		}
	case uint32:
		return b.WriteUint32(val.(uint32))
	case uint64:
		return b.WriteUint64(val.(uint64))
	}

	return errors.New("Invalid Type")
}

///////////////////////////////////////////////////////////////////////////////

// MetricUnit defines the interface for a unit type for speed
type MetricUnit interface {
	// return 32 bit PMAPI representation for the unit
	// see: https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/pmapi.h#L61-L101
	PMAPI() uint32
}

// SpaceUnit is an enumerated type representing all units for space
type SpaceUnit uint32

// Possible values for SpaceUnit
const (
	ByteUnit SpaceUnit = 1<<28 | iota<<16
	KilobyteUnit
	MegabyteUnit
	GigabyteUnit
	TerabyteUnit
	PetabyteUnit
	ExabyteUnit
)

//go:generate stringer -type=SpaceUnit

// PMAPI returns the PMAPI representation for a SpaceUnit
// for space units bits 0-3 are 1 and bits 13-16 are scale
func (s SpaceUnit) PMAPI() uint32 {
	return uint32(s)
}

// TimeUnit is an enumerated type representing all possible units for representing time
type TimeUnit uint32

// Possible Values for TimeUnit
// for time units bits 4-7 are 1 and bits 17-20 are scale
const (
	NanosecondUnit TimeUnit = 1<<24 | iota<<12
	MicrosecondUnit
	MillisecondUnit
	SecondUnit
	MinuteUnit
	HourUnit
)

//go:generate stringer -type=TimeUnit

// PMAPI returns the PMAPI representation for a TimeUnit
func (t TimeUnit) PMAPI() uint32 {
	return uint32(t)
}

// CountUnit is a type representing a counted quantity
type CountUnit uint32

// OneUnit represents the only CountUnit
// for count units bits 8-11 are 1 and bits 21-24 are scale
const OneUnit CountUnit = 1<<20 | iota<<8

//go:generate stringer -type=CountUnit

// PMAPI returns the PMAPI representation for a CountUnit
func (c CountUnit) PMAPI() uint32 {
	return uint32(c)
}

///////////////////////////////////////////////////////////////////////////////

// MetricSemantics represents an enumerated type representing the possible
// values for the semantics of a metric
type MetricSemantics int32

// Possible values for MetricSemantics
const (
	NoSemantics MetricSemantics = iota
	CounterSemantics
	InstantSemantics
	DiscreteSemantics
)

//go:generate stringer -type=MetricSemantics

///////////////////////////////////////////////////////////////////////////////

// Metric defines the general interface a type needs to implement to qualify
// as a valid PCP metric
type Metric interface {
	// gets the unique id generated for this metric
	ID() uint32

	// gets the name for the metric
	Name() string

	// gets the type of a metric
	Type() MetricType

	// gets the unit of a metric
	Unit() MetricUnit

	// gets the semantics for a metric
	Semantics() MetricSemantics

	// gets the description of a metric
	Description() string
}

///////////////////////////////////////////////////////////////////////////////

// SingletonMetric defines the interface for a metric that stores only one value
type SingletonMetric interface {
	Metric

	// gets the value of the metric
	Val() interface{}

	// Sets the value of the metric to a value, optionally returns an error on failure
	Set(interface{}) error
}

///////////////////////////////////////////////////////////////////////////////

// InstanceMetric defines the interface for a metric that stores multiple values
// in instances and instance domains
type InstanceMetric interface {
	Metric

	// gets the value of a particular instance
	ValInstance(string) (interface{}, error)

	// sets the value of a particular instance
	SetInstance(string, interface{}) error
}

///////////////////////////////////////////////////////////////////////////////

// PCPMetric defines the interface for a metric that is compatible with PCP
type PCPMetric interface {
	Metric

	// a PCPMetric will always have an instance domain, even if it is nil
	Indom() *PCPInstanceDomain

	ShortDescription() *PCPString

	LongDescription() *PCPString
}

///////////////////////////////////////////////////////////////////////////////

// PCPMetricItemBitLength is the maximum bit size of a PCP Metric id
//
// see: https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/impl.h#L102-L121
const PCPMetricItemBitLength = 10

// pcpMetricDesc is a metric metadata wrapper
// each metric type can wrap its metadata by containing a pcpMetricDesc type and only define its own
// specific properties assuming pcpMetricDesc will handle the rest
//
// when writing, this type is supposed to map directly to the pmDesc struct as defined in PCP core
type pcpMetricDesc struct {
	id                                uint32          // unique metric id
	name                              string          // the name
	t                                 MetricType      // the type of a metric
	sem                               MetricSemantics // the semantics
	u                                 MetricUnit      // the unit
	descoffset                        int             // memory storage offset for the metric description
	shortDescription, longDescription *PCPString
}

// newpcpMetricDesc creates a new Metric Description wrapper type
func newpcpMetricDesc(n string, t MetricType, s MetricSemantics, u MetricUnit, shortdesc, longdesc string) *pcpMetricDesc {
	return &pcpMetricDesc{
		getHash(n, PCPMetricItemBitLength),
		n, t, s, u, 0,
		NewPCPString(shortdesc), NewPCPString(longdesc),
	}
}

// ID returns the generated id for PCPMetric
func (md *pcpMetricDesc) ID() uint32 { return md.id }

// Name returns the generated id for PCPMetric
func (md *pcpMetricDesc) Name() string { return md.name }

// Semantics returns the current stored value for PCPMetric
func (md *pcpMetricDesc) Semantics() MetricSemantics { return md.sem }

// Unit returns the unit for PCPMetric
func (md *pcpMetricDesc) Unit() MetricUnit { return md.u }

// Type returns the type for PCPMetric
func (md *pcpMetricDesc) Type() MetricType { return md.t }

// ShortDescription returns the shortdesc value
func (md *pcpMetricDesc) ShortDescription() *PCPString { return md.shortDescription }

// LongDescription returns the longdesc value
func (md *pcpMetricDesc) LongDescription() *PCPString { return md.longDescription }

// Description returns the description for PCPMetric
func (md *pcpMetricDesc) Description() string {
	sd := md.shortDescription
	ld := md.longDescription
	if len(ld.val) > 0 {
		return sd.val + "\n\n" + ld.val
	}
	return sd.val
}

///////////////////////////////////////////////////////////////////////////////

// updateClosure is a closure that will write the modified value of a metric on disk
type updateClosure func(interface{}) error

// newupdateClosure creates a new update closure for an offset, type and buffer
func newupdateClosure(offset int, buffer bytebuffer.Buffer, t MetricType) updateClosure {
	return func(val interface{}) error {
		buffer.SetPos(offset)
		return t.WriteVal(val, buffer)
	}
}

///////////////////////////////////////////////////////////////////////////////

// PCPSingletonMetric defines a singleton metric with no instance domain
// only a value and a valueoffset
type PCPSingletonMetric struct {
	sync.RWMutex
	*pcpMetricDesc
	val         interface{}
	valueoffset int
	update      updateClosure
}

// NewPCPSingletonMetric creates a new instance of PCPSingletonMetric
func NewPCPSingletonMetric(val interface{}, name string, t MetricType, s MetricSemantics, u MetricUnit, shortdesc, longdesc string) (*PCPSingletonMetric, error) {
	if name == "" {
		return nil, errors.New("Metric name cannot be empty")
	}

	if !t.IsCompatible(val) {
		return nil, fmt.Errorf("type %v is not compatible with value %v", t, val)
	}

	return &PCPSingletonMetric{
		sync.RWMutex{},
		newpcpMetricDesc(name, t, s, u, shortdesc, longdesc),
		val, 0, nil,
	}, nil
}

// Val returns the current Set value of PCPSingletonMetric
func (m *PCPSingletonMetric) Val() interface{} {
	m.RLock()
	defer m.RUnlock()
	return m.val
}

// Set Sets the current value of PCPSingletonMetric
func (m *PCPSingletonMetric) Set(val interface{}) error {
	if !m.t.IsCompatible(val) {
		return errors.New("the value is incompatible with this metrics MetricType")
	}

	if val != m.val {
		m.Lock()
		defer m.Unlock()
		if m.update != nil {
			err := m.update(m.val)
			if err != nil {
				return err
			}
		}
		m.val = val
	}

	return nil
}

// Indom returns the instance domain for a PCPSingletonMetric
func (m *PCPSingletonMetric) Indom() *PCPInstanceDomain { return nil }

func (m *PCPSingletonMetric) String() string {
	return fmt.Sprintf("Val: %v\n%v", m.val, m.Description())
}

// TODO: implement PCPCounterMetric, PCPGaugeMetric ...

///////////////////////////////////////////////////////////////////////////////

type instanceValue struct {
	val    interface{}
	offset int
	update updateClosure
}

func newinstanceValue(val interface{}) *instanceValue {
	return &instanceValue{val, 0, nil}
}

// PCPInstanceMetric represents a PCPMetric that can have multiple values
// over multiple instances in an instance domain
type PCPInstanceMetric struct {
	sync.RWMutex
	*pcpMetricDesc
	indom *PCPInstanceDomain
	vals  map[string]*instanceValue
}

// NewPCPInstanceMetric creates a new instance of PCPSingletonMetric
func NewPCPInstanceMetric(vals map[string]interface{}, name string, indom *PCPInstanceDomain, t MetricType, s MetricSemantics, u MetricUnit, shortdesc, longdesc string) (*PCPInstanceMetric, error) {
	if name == "" {
		return nil, errors.New("Metric name cannot be empty")
	}

	if len(vals) != indom.InstanceCount() {
		return nil, errors.New("values for all instances in the instance domain only should be passed")
	}

	mvals := make(map[string]*instanceValue)

	for name := range indom.instances {
		val, present := vals[name]
		if !present {
			return nil, fmt.Errorf("Instance %v not initialized", name)
		}

		if !t.IsCompatible(val) {
			return nil, fmt.Errorf("value %v is incompatible with type %v for Instance %v", val, t, name)
		}

		mvals[name] = newinstanceValue(val)
	}

	return &PCPInstanceMetric{
		sync.RWMutex{},
		newpcpMetricDesc(name, t, s, u, shortdesc, longdesc),
		indom,
		mvals,
	}, nil
}

// Indom returns the instance domain for the metric
func (m *PCPInstanceMetric) Indom() *PCPInstanceDomain { return m.indom }

// ValInstance returns the value for a particular instance of the metric
func (m *PCPInstanceMetric) ValInstance(instance string) (interface{}, error) {
	if !m.indom.HasInstance(instance) {
		return nil, fmt.Errorf("%v is not an instance of this metric", instance)
	}

	m.RLock()
	defer m.RUnlock()

	return m.vals[instance], nil
}

// SetInstance sets the value for a particular instance of the metric
func (m *PCPInstanceMetric) SetInstance(instance string, val interface{}) error {
	if !m.indom.HasInstance(instance) {
		return fmt.Errorf("%v is not an instance of this metric", instance)
	}

	m.Lock()
	defer m.Unlock()

	if m.vals[instance].update != nil {
		err := m.vals[instance].update(val)
		if err != nil {
			return err
		}
	}

	m.vals[instance].val = val
	return nil
}
<commit_msg>metrics: make PCPMetricDesc public<commit_after>package speed

import (
	"errors"
	"fmt"
	"math"
	"sync"

	"github.com/performancecopilot/speed/bytebuffer"
)

// MetricType is an enumerated type representing all valid types for a metric
type MetricType int32

// Possible values for a MetricType
const (
	NoSupportType       MetricType = -1
	Int32Type           MetricType = 0
	Uint32Type          MetricType = 1
	Int64Type           MetricType = 2
	Uint64Type          MetricType = 3
	FloatType           MetricType = 4
	DoubleType          MetricType = 5
	StringType          MetricType = 6
	AggregateType       MetricType = 7
	AggregateStaticType MetricType = 8
	EventType           MetricType = 9
	HighresEventType    MetricType = 10
	UnknownType         MetricType = 255
)

//go:generate stringer -type=MetricType

// IsCompatible checks if the passed value is compatible with the current MetricType
func (m MetricType) IsCompatible(val interface{}) bool {
	switch val.(type) {
	case int:
		v := val.(int)
		switch {
		case v < 0:
			return m == Int32Type || m == Int64Type
		case v <= math.MaxInt32:
			return m == Int32Type || m == Int64Type || m == Uint32Type || m == Uint64Type
		case uint32(v) <= math.MaxUint32:
			return m == Int64Type || m == Uint32Type || m == Uint64Type
		case int64(v) <= math.MaxInt64:
			return m == Int64Type || m == Uint64Type
		default:
			return false
		}
	case int32:
		return m == Int32Type
	case int64:
		return m == Int64Type
	case uint:
		v := val.(uint)
		if v > math.MaxUint32 {
			return m == Uint64Type
		}
		return m == Uint32Type || m == Uint64Type
	case uint32:
		return m == Uint32Type
	case uint64:
		return m == Uint64Type
	default:
		return false
	}
}

// WriteVal implements value writer for the current MetricType to a buffer
func (m MetricType) WriteVal(val interface{}, b bytebuffer.Buffer) error {
	switch val.(type) {
	case int:
		switch m {
		case Int32Type:
			return b.WriteInt32(int32(val.(int)))
		case Int64Type:
			return b.WriteInt64(int64(val.(int)))
		case Uint32Type:
			return b.WriteUint32(uint32(val.(int)))
		case Uint64Type:
			return b.WriteUint64(uint64(val.(int)))
		}
	case int32:
		return b.WriteInt32(val.(int32))
	case int64:
		return b.WriteInt64(val.(int64))
	case uint:
		switch m {
		case Uint32Type:
			return b.WriteUint32(uint32(val.(uint)))
		case Uint64Type:
			return b.WriteUint64(uint64(val.(uint)))
		}
	case uint32:
		return b.WriteUint32(val.(uint32))
	case uint64:
		return b.WriteUint64(val.(uint64))
	}

	return errors.New("Invalid Type")
}

///////////////////////////////////////////////////////////////////////////////

// MetricUnit defines the interface for a unit type for speed
type MetricUnit interface {
	// return 32 bit PMAPI representation for the unit
	// see: https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/pmapi.h#L61-L101
	PMAPI() uint32
}

// SpaceUnit is an enumerated type representing all units for space
type SpaceUnit uint32

// Possible values for SpaceUnit
const (
	ByteUnit SpaceUnit = 1<<28 | iota<<16
	KilobyteUnit
	MegabyteUnit
	GigabyteUnit
	TerabyteUnit
	PetabyteUnit
	ExabyteUnit
)

//go:generate stringer -type=SpaceUnit

// PMAPI returns the PMAPI representation for a SpaceUnit
// for space units bits 0-3 are 1 and bits 13-16 are scale
func (s SpaceUnit) PMAPI() uint32 {
	return uint32(s)
}

// TimeUnit is an enumerated type representing all possible units for representing time
type TimeUnit uint32

// Possible Values for TimeUnit
// for time units bits 4-7 are 1 and bits 17-20 are scale
const (
	NanosecondUnit TimeUnit = 1<<24 | iota<<12
	MicrosecondUnit
	MillisecondUnit
	SecondUnit
	MinuteUnit
	HourUnit
)

//go:generate stringer -type=TimeUnit

// PMAPI returns the PMAPI representation for a TimeUnit
func (t TimeUnit) PMAPI() uint32 {
	return uint32(t)
}

// CountUnit is a type representing a counted quantity
type CountUnit uint32

// OneUnit represents the only CountUnit
// for count units bits 8-11 are 1 and bits 21-24 are scale
const OneUnit CountUnit = 1<<20 | iota<<8

//go:generate stringer -type=CountUnit

// PMAPI returns the PMAPI representation for a CountUnit
func (c CountUnit) PMAPI() uint32 {
	return uint32(c)
}

///////////////////////////////////////////////////////////////////////////////

// MetricSemantics represents an enumerated type representing the possible
// values for the semantics of a metric
type MetricSemantics int32

// Possible values for MetricSemantics
const (
	NoSemantics MetricSemantics = iota
	CounterSemantics
	InstantSemantics
	DiscreteSemantics
)

//go:generate stringer -type=MetricSemantics

///////////////////////////////////////////////////////////////////////////////

// Metric defines the general interface a type needs to implement to qualify
// as a valid PCP metric
type Metric interface {
	// gets the unique id generated for this metric
	ID() uint32

	// gets the name for the metric
	Name() string

	// gets the type of a metric
	Type() MetricType

	// gets the unit of a metric
	Unit() MetricUnit

	// gets the semantics for a metric
	Semantics() MetricSemantics

	// gets the description of a metric
	Description() string
}

///////////////////////////////////////////////////////////////////////////////

// SingletonMetric defines the interface for a metric that stores only one value
type SingletonMetric interface {
	Metric

	// gets the value of the metric
	Val() interface{}

	// Sets the value of the metric to a value, optionally returns an error on failure
	Set(interface{}) error
}

///////////////////////////////////////////////////////////////////////////////

// InstanceMetric defines the interface for a metric that stores multiple values
// in instances and instance domains
type InstanceMetric interface {
	Metric

	// gets the value of a particular instance
	ValInstance(string) (interface{}, error)

	// sets the value of a particular instance
	SetInstance(string, interface{}) error
}

///////////////////////////////////////////////////////////////////////////////

// PCPMetric defines the interface for a metric that is compatible with PCP
type PCPMetric interface {
	Metric

	// a PCPMetric will always have an instance domain, even if it is nil
	Indom() *PCPInstanceDomain

	ShortDescription() *PCPString

	LongDescription() *PCPString
}

///////////////////////////////////////////////////////////////////////////////

// PCPMetricItemBitLength is the maximum bit size of a PCP Metric id
//
// see: https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/impl.h#L102-L121
const PCPMetricItemBitLength = 10

// PCPMetricDesc is a metric metadata wrapper
// each metric type can wrap its metadata by containing a PCPMetricDesc type and only define its own
// specific properties assuming PCPMetricDesc will handle the rest
//
// when writing, this type is supposed to map directly to the pmDesc struct as defined in PCP core
type PCPMetricDesc struct {
	id                                uint32          // unique metric id
	name                              string          // the name
	t                                 MetricType      // the type of a metric
	sem                               MetricSemantics // the semantics
	u                                 MetricUnit      // the unit
	descoffset                        int             // memory storage offset for the metric description
	shortDescription, longDescription *PCPString
}

// newPCPMetricDesc creates a new Metric Description wrapper type
func newPCPMetricDesc(n string, t MetricType, s MetricSemantics, u MetricUnit, shortdesc, longdesc string) *PCPMetricDesc {
	return &PCPMetricDesc{
		getHash(n, PCPMetricItemBitLength),
		n, t, s, u, 0,
		NewPCPString(shortdesc), NewPCPString(longdesc),
	}
}

// ID returns the generated id for PCPMetric
func (md *PCPMetricDesc) ID() uint32 { return md.id }

// Name returns the generated id for PCPMetric
func (md *PCPMetricDesc) Name() string { return md.name }

// Semantics returns the current stored value for PCPMetric
func (md *PCPMetricDesc) Semantics() MetricSemantics { return md.sem }

// Unit returns the unit for PCPMetric
func (md *PCPMetricDesc) Unit() MetricUnit { return md.u }

// Type returns the type for PCPMetric
func (md *PCPMetricDesc) Type() MetricType { return md.t }

// ShortDescription returns the shortdesc value
func (md *PCPMetricDesc) ShortDescription() *PCPString { return md.shortDescription }

// LongDescription returns the longdesc value
func (md *PCPMetricDesc) LongDescription() *PCPString { return md.longDescription }

// Description returns the description for PCPMetric
func (md *PCPMetricDesc) Description() string {
	sd := md.shortDescription
	ld := md.longDescription
	if len(ld.val) > 0 {
		return sd.val + "\n\n" + ld.val
	}
	return sd.val
}

///////////////////////////////////////////////////////////////////////////////

// updateClosure is a closure that will write the modified value of a metric on disk
type updateClosure func(interface{}) error

// newupdateClosure creates a new update closure for an offset, type and buffer
func newupdateClosure(offset int, buffer bytebuffer.Buffer, t MetricType) updateClosure {
	return func(val interface{}) error {
		buffer.SetPos(offset)
		return t.WriteVal(val, buffer)
	}
}

///////////////////////////////////////////////////////////////////////////////

// PCPSingletonMetric defines a singleton metric with no instance domain
// only a value and a valueoffset
type PCPSingletonMetric struct {
	sync.RWMutex
	*PCPMetricDesc
	val         interface{}
	valueoffset int
	update      updateClosure
}

// NewPCPSingletonMetric creates a new instance of PCPSingletonMetric
func NewPCPSingletonMetric(val interface{}, name string, t MetricType, s MetricSemantics, u MetricUnit, shortdesc, longdesc string) (*PCPSingletonMetric, error) {
	if name == "" {
		return nil, errors.New("Metric name cannot be empty")
	}

	if !t.IsCompatible(val) {
		return nil, fmt.Errorf("type %v is not compatible with value %v", t, val)
	}

	return &PCPSingletonMetric{
		sync.RWMutex{},
		newPCPMetricDesc(name, t, s, u, shortdesc, longdesc),
		val, 0, nil,
	}, nil
}

// Val returns the current Set value of PCPSingletonMetric
func (m *PCPSingletonMetric) Val() interface{} {
	m.RLock()
	defer m.RUnlock()
	return m.val
}

// Set Sets the current value of PCPSingletonMetric
func (m *PCPSingletonMetric) Set(val interface{}) error {
	if !m.t.IsCompatible(val) {
		return errors.New("the value is incompatible with this metrics MetricType")
	}

	if val != m.val {
		m.Lock()
		defer m.Unlock()
		if m.update != nil {
			err := m.update(m.val)
			if err != nil {
				return err
			}
		}
		m.val = val
	}

	return nil
}

// Indom returns the instance domain for a PCPSingletonMetric
func (m *PCPSingletonMetric) Indom() *PCPInstanceDomain { return nil }

func (m *PCPSingletonMetric) String() string {
	return fmt.Sprintf("Val: %v\n%v", m.val, m.Description())
}

// TODO: implement PCPCounterMetric, PCPGaugeMetric ...

///////////////////////////////////////////////////////////////////////////////

type instanceValue struct {
	val    interface{}
	offset int
	update updateClosure
}

func newinstanceValue(val interface{}) *instanceValue {
	return &instanceValue{val, 0, nil}
}

// PCPInstanceMetric represents a PCPMetric that can have multiple values
// over multiple instances in an instance domain
type PCPInstanceMetric struct {
	sync.RWMutex
	*PCPMetricDesc
	indom *PCPInstanceDomain
	vals  map[string]*instanceValue
}

// NewPCPInstanceMetric creates a new instance of PCPSingletonMetric
func NewPCPInstanceMetric(vals map[string]interface{}, name string, indom *PCPInstanceDomain, t MetricType, s MetricSemantics, u MetricUnit, shortdesc, longdesc string) (*PCPInstanceMetric, error) {
	if name == "" {
		return nil, errors.New("Metric name cannot be empty")
	}

	if len(vals) != indom.InstanceCount() {
		return nil, errors.New("values for all instances in the instance domain only should be passed")
	}

	mvals := make(map[string]*instanceValue)

	for name := range indom.instances {
		val, present := vals[name]
		if !present {
			return nil, fmt.Errorf("Instance %v not initialized", name)
		}

		if !t.IsCompatible(val) {
			return nil, fmt.Errorf("value %v is incompatible with type %v for Instance %v", val, t, name)
		}

		mvals[name] = newinstanceValue(val)
	}

	return &PCPInstanceMetric{
		sync.RWMutex{},
		newPCPMetricDesc(name, t, s, u, shortdesc, longdesc),
		indom,
		mvals,
	}, nil
}

// Indom returns the instance domain for the metric
func (m *PCPInstanceMetric) Indom() *PCPInstanceDomain { return m.indom }

// ValInstance returns the value for a particular instance of the metric
func (m *PCPInstanceMetric) ValInstance(instance string) (interface{}, error) {
	if !m.indom.HasInstance(instance) {
		return nil, fmt.Errorf("%v is not an instance of this metric", instance)
	}

	m.RLock()
	defer m.RUnlock()

	return m.vals[instance], nil
}

// SetInstance sets the value for a particular instance of the metric
func (m *PCPInstanceMetric) SetInstance(instance string, val interface{}) error {
	if !m.indom.HasInstance(instance) {
		return fmt.Errorf("%v is not an instance of this metric", instance)
	}

	m.Lock()
	defer m.Unlock()

	if m.vals[instance].update != nil {
		err := m.vals[instance].update(val)
		if err != nil {
			return err
		}
	}

	m.vals[instance].val = val
	return nil
}
<|endoftext|>


<commit_before>package refresh

import (
	"crypto/x509"
	"errors"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"net/url"
	"reflect"
	"testing"
	"time"
)

func simplePasswordCheck(w http.ResponseWriter, r *http.Request) {
	u, p, k := r.BasicAuth()
	if !k {
		w.Header().Set("WWW-Authenticate", `Basic realm="test"`)
	}

	if !(u == "bob-bcrypt" && p == "secret") {
		http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		return
	}
}

func redirectPasswordCheck(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		http.Redirect(w, r, "/auth", http.StatusSeeOther)
		return
	}

	u, p, k := r.BasicAuth()
	if !k {
		w.Header().Set("WWW-Authenticate", `Basic realm="test"`)
	}

	if !(u == "bob-bcrypt" && p == "secret") {
		http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		return
	}
}

func simpleCookieCheck(w http.ResponseWriter, r *http.Request) {
	c, err := r.Cookie("test")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if c == nil || c.Value != "trustme" {
		http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		return
	}
}

func TestAuthenticateSimple(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(simplePasswordCheck))
	ssrv := httptest.NewTLSServer(http.HandlerFunc(simplePasswordCheck))
	defer func() {
		srv.Close()
		ssrv.Close()
	}()

	ssrv.Config.ErrorLog = log.New(ioutil.Discard, "", 0) // ಠ_ಠ

	uri, _ := url.Parse(srv.URL)
	suri, _ := url.Parse(ssrv.URL)

	us := Upstream{
		url:     uri,
		timeout: DefaultTimeout,
	}

	t.Log("Testing no credentials")
	r, _ := http.NewRequest("GET", "https://test.example.com", nil)
	ok, err := us.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if ok {
		t.Error("Authenticate should have failed")
	}

	t.Log("Testing wrong credentials")
	r.SetBasicAuth("fred", "blogs")
	ok, err = us.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if ok {
		t.Error("Authenticate should have failed")
	}

	t.Log("Testing correct credentials")
	r.SetBasicAuth("bob-bcrypt", "secret")
	ok, err = us.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if !ok {
		t.Error("Authenticate should have succeeded")
	}

	t.Log("Testing over https with bad cert")
	us.url = suri
	ok, err = us.Authenticate(r)
	if err == nil {
		t.Errorf("Expected an error, didn't get one")
	} else {
		uerr, ok := err.(*url.Error)
		if !ok {
			t.Errorf("Unexpected error `%v`", err)
		} else if _, ok := uerr.Err.(x509.UnknownAuthorityError); !ok {
			t.Errorf("Unexpected error `%v`", err)
		}
	}
	if ok {
		t.Error("Authenticate should have failed")
	}

	t.Log("Testing over https with skipverify")
	us.insecureSkipVerify = true
	ok, err = us.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if !ok {
		t.Error("Authenticate should have succeeded")
	}

}

func TestAuthenticateRedirects(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(redirectPasswordCheck))
	uri, _ := url.Parse(srv.URL)

	us := Upstream{
		url:     uri,
		timeout: DefaultTimeout,
	}

	r, _ := http.NewRequest("GET", "https://test.example.com", nil)
	r.SetBasicAuth("bob-bcrypt", "secret")

	ok, err := us.Authenticate(r)
	if err == nil {
		t.Error("Expected an error, didn't get one")
	} else if err.Error() != "Get /auth: follow redirects disabled" {
		t.Errorf("Unexpected error `%v`", err)
	}
	if ok {
		t.Error("Authenticate should not have succeeded")
	}

	us.followRedirects = true

	ok, err = us.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if !ok {
		t.Error("Authenticate should have succeeded")
	}
}

func TestAuthenticateCookie(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(simpleCookieCheck))
	defer srv.Close()

	uri, _ := url.Parse(srv.URL)

	us := Upstream{
		url:         uri,
		timeout:     DefaultTimeout,
		passCookies: true,
	}

	t.Log("Testing no credentials")
	r, _ := http.NewRequest("GET", "https://test.example.com", nil)
	ok, err := us.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if ok {
		t.Error("Authenticate should have failed")
	}

	t.Log("Testing wrong credentials")
	r.AddCookie(&http.Cookie{Name: "test", Value: "trustnoone"})
	ok, err = us.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if ok {
		t.Error("Authenticate should have failed")
	}

	t.Log("Testing correct credentials")
	r, _ = http.NewRequest("GET", "https://test.example.com", nil)
	r.AddCookie(&http.Cookie{Name: "test", Value: "trustme"})
	ok, err = us.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if !ok {
		t.Error("Authenticate should have succeeded")
	}
}

func TestAuthenticateConstructor(t *testing.T) {
	tests := []struct {
		desc   string
		config string
		expect *Upstream
		err    error
	}{
		{
			`Empty configuration`,
			``,
			nil,
			errors.New(`backend configuration has to be in form 'key1=value1,key2=..', but was `),
		}, {
			`URL only configuration`,
			`url=http://google.com`,
			&Upstream{url: &url.URL{Scheme: `http`, Host: `google.com`}, timeout: DefaultTimeout},
			nil,
		}, {
			`Invalid url configuration`,
			`url=!http://google.com`,
			nil,
			errors.New(`unable to parse url !http://google.com: parse !http://google.com: first path segment in URL cannot contain colon`),
		}, {
			`With valid arguments`,
			`url=http://google.com,timeout=5s,insecure=true,follow=true`,
			&Upstream{url: &url.URL{Scheme: `http`, Host: `google.com`}, timeout: 5 * time.Second, insecureSkipVerify: true, followRedirects: true},
			nil,
		}, {
			`With invalid timeout`,
			`url=http://google.com,timeout=5j`,
			nil,
			errors.New(`unable to parse timeout 5j: time: unknown unit j in duration 5j`),
		}, {
			`With invalid insecure`,
			`url=http://google.com,insecure=yesplease`,
			nil,
			errors.New(`unable to parse insecure yesplease: strconv.ParseBool: parsing "yesplease": invalid syntax`),
		}, {
			`With invalid follow`,
			`url=http://google.com,follow=yesplease`,
			nil,
			errors.New(`unable to parse follow yesplease: strconv.ParseBool: parsing "yesplease": invalid syntax`),
		}, {
			`With valid arguments, missing url`,
			`timeout=5s,insecure=true,follow=true`,
			nil,
			errors.New(`url is a required parameter`),
		}, {
			`With pass cookies`,
			`url=http://google.com,cookies=true`,
			&Upstream{url: &url.URL{Scheme: `http`, Host: `google.com`}, timeout: DefaultTimeout, passCookies: true},
			nil,
		}, {
			`With invalid pass cookies`,
			`url=http://google.com,cookies=yay`,
			nil,
			errors.New(`unable to parse cookies yay: strconv.ParseBool: parsing "yay": invalid syntax`),
		},
	}

	for i, tc := range tests {
		t.Logf("Testing configuration %d (%s)", i+1, tc.desc)
		be, err := constructor(tc.config)
		if tc.err != nil {
			if err == nil {
				t.Error("Expected error, got none")
			} else if err.Error() != tc.err.Error() {
				t.Errorf("Expected `%v` got `%v`", tc.err, err)
			}
		} else if err != nil {
			t.Errorf("Unexpected error `%v`", err)
		}

		if tc.expect == nil {
			if be != nil {
				t.Errorf("Expected nil rules, got %v", be)
			}
		} else {
			actual, ok := be.(*Upstream)
			if !ok {
				t.Errorf("Expected *Upstream, got %T", be)
			} else if !reflect.DeepEqual(tc.expect, actual) {
				t.Errorf("Expected %v got %v", tc.expect, actual)
			}
		}
	}
}
<commit_msg>started tests for refresh backend<commit_after>package refresh

import (
	"crypto/x509"
	"errors"
	"github.com/nicolasazrak/caddy-cache"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"net/url"
	"reflect"
	"testing"
	"time"
)

func simplePasswordCheck(w http.ResponseWriter, r *http.Request) {
	u, p, k := r.BasicAuth()
	if !k {
		w.Header().Set("WWW-Authenticate", `Basic realm="test"`)
	}

	if !(u == "bob-bcrypt" && p == "secret") {
		http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		return
	}
}

func redirectPasswordCheck(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		http.Redirect(w, r, "/auth", http.StatusSeeOther)
		return
	}

	u, p, k := r.BasicAuth()
	if !k {
		w.Header().Set("WWW-Authenticate", `Basic realm="test"`)
	}

	if !(u == "bob-bcrypt" && p == "secret") {
		http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		return
	}
}

func simpleCookieCheck(w http.ResponseWriter, r *http.Request) {
	c, err := r.Cookie("test")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if c == nil || c.Value != "trustme" {
		http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		return
	}
}

func TestAuthenticateSimple(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(simplePasswordCheck))
	ssrv := httptest.NewTLSServer(http.HandlerFunc(simplePasswordCheck))
	defer func() {
		srv.Close()
		ssrv.Close()
	}()

	ssrv.Config.ErrorLog = log.New(ioutil.Discard, "", 0) // ಠ_ಠ

	sref, _ := http.NewRequest("GET", ssrv.URL, nil)
	ref, _ := http.NewRequest("GET", srv.URL, nil)

	rf := Refresh{
		refreshRequest: ref,
		refreshCache:   cache.NewHTTPCache(),
		cacheConfig: &cache.Config{
			Path:        "tmp",
			LockTimeout: time.Duration(5) * time.Minute,
		},
		timeout: DefaultTimeout,
	}

	t.Log("Testing no credentials")
	r, _ := http.NewRequest("GET", "https://test.example.com", nil)
	ok, err := rf.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if ok {
		t.Error("Authenticate should have failed")
	}

	t.Log("Testing wrong credentials")
	r.SetBasicAuth("fred", "blogs")
	ok, err = rf.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if ok {
		t.Error("Authenticate should have failed")
	}

	t.Log("Testing correct credentials")
	r.SetBasicAuth("bob-bcrypt", "secret")
	ok, err = rf.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if !ok {
		t.Error("Authenticate should have succeeded")
	}

	t.Log("Testing over https with bad cert")
	rf.refreshRequest = sref
	ok, err = rf.Authenticate(r)
	if err == nil {
		t.Errorf("Expected an error, didn't get one")
	} else {
		uerr, ok := err.(*url.Error)
		if !ok {
			t.Errorf("Unexpected error `%v`", err)
		} else if _, ok := uerr.Err.(x509.UnknownAuthorityError); !ok {
			t.Errorf("Unexpected error `%v`", err)
		}
	}
	if ok {
		t.Error("Authenticate should have failed")
	}

	t.Log("Testing over https with skipverify")
	rf.insecureSkipVerify = true
	ok, err = rf.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if !ok {
		t.Error("Authenticate should have succeeded")
	}

}

func TestAuthenticateRedirects(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(redirectPasswordCheck))
	ref, _ := http.NewRequest("GET", srv.URL, nil)

	rf := Refresh{
		refreshRequest: ref,
		refreshCache:   cache.NewHTTPCache(),
		cacheConfig: &cache.Config{
			Path:        "tmp",
			LockTimeout: time.Duration(5) * time.Minute,
		},
		timeout: DefaultTimeout,
	}

	r, _ := http.NewRequest("GET", "https://test.example.com", nil)
	r.SetBasicAuth("bob-bcrypt", "secret")

	ok, err := rf.Authenticate(r)
	if err == nil {
		t.Error("Expected an error, didn't get one")
	} else if err.Error() != "Get /auth: follow redirects disabled" {
		t.Errorf("Unexpected error `%v`", err)
	}
	if ok {
		t.Error("Authenticate should not have succeeded")
	}

	rf.followRedirects = true

	ok, err = rf.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if !ok {
		t.Error("Authenticate should have succeeded")
	}
}

func TestAuthenticateCookie(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(simpleCookieCheck))
	defer srv.Close()
	ref, _ := http.NewRequest("GET", srv.URL, nil)

	rf := Refresh{
		refreshRequest: ref,
		refreshCache:   cache.NewHTTPCache(),
		cacheConfig: &cache.Config{
			Path:        "tmp",
			LockTimeout: time.Duration(5) * time.Minute,
		},
		timeout:     DefaultTimeout,
		passCookies: true,
	}

	t.Log("Testing no credentials")
	r, _ := http.NewRequest("GET", "https://test.example.com", nil)
	ok, err := rf.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if ok {
		t.Error("Authenticate should have failed")
	}

	t.Log("Testing wrong credentials")
	r.AddCookie(&http.Cookie{Name: "test", Value: "trustnoone"})
	ok, err = rf.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if ok {
		t.Error("Authenticate should have failed")
	}

	t.Log("Testing correct credentials")
	r, _ = http.NewRequest("GET", "https://test.example.com", nil)
	r.AddCookie(&http.Cookie{Name: "test", Value: "trustme"})
	ok, err = rf.Authenticate(r)
	if err != nil {
		t.Errorf("Unexpected error `%v`", err)
	}
	if !ok {
		t.Error("Authenticate should have succeeded")
	}
}

func TestAuthenticateConstructor(t *testing.T) {
	ref, _ := http.NewRequest("GET", "http://google.com", nil)

	tests := []struct {
		desc   string
		config string
		expect *Refresh
		err    error
	}{
		{
			`Empty configuration`,
			``,
			nil,
			errors.New(`backend configuration has to be in form 'key1=value1,key2=..', but was `),
		}, {
			`URL only configuration`,
			`url=http://google.com`,
			&Refresh{refreshRequest: ref, timeout: DefaultTimeout},
			nil,
		}, {
			`Invalid url configuration`,
			`url=!http://google.com`,
			nil,
			errors.New(`unable to parse url !http://google.com: parse !http://google.com: first path segment in URL cannot contain colon`),
		}, {
			`With valid arguments`,
			`url=http://google.com,timeout=5s,insecure=true,follow=true`,
			&Refresh{refreshRequest: ref, timeout: 5 * time.Second, insecureSkipVerify: true, followRedirects: true},
			nil,
		}, {
			`With invalid timeout`,
			`url=http://google.com,timeout=5j`,
			nil,
			errors.New(`unable to parse timeout 5j: time: unknown unit j in duration 5j`),
		}, {
			`With invalid insecure`,
			`url=http://google.com,insecure=yesplease`,
			nil,
			errors.New(`unable to parse insecure yesplease: strconv.ParseBool: parsing "yesplease": invalid syntax`),
		}, {
			`With invalid follow`,
			`url=http://google.com,follow=yesplease`,
			nil,
			errors.New(`unable to parse follow yesplease: strconv.ParseBool: parsing "yesplease": invalid syntax`),
		}, {
			`With valid arguments, missing url`,
			`timeout=5s,insecure=true,follow=true`,
			nil,
			errors.New(`url is a required parameter`),
		}, {
			`With pass cookies`,
			`url=http://google.com,cookies=true`,
			&Refresh{refreshRequest: ref, timeout: DefaultTimeout, passCookies: true},
			nil,
		}, {
			`With invalid pass cookies`,
			`url=http://google.com,cookies=yay`,
			nil,
			errors.New(`unable to parse cookies yay: strconv.ParseBool: parsing "yay": invalid syntax`),
		},
	}

	for i, tc := range tests {
		t.Logf("Testing configuration %d (%s)", i+1, tc.desc)
		be, err := constructor(tc.config)
		if tc.err != nil {
			if err == nil {
				t.Error("Expected error, got none")
			} else if err.Error() != tc.err.Error() {
				t.Errorf("Expected `%v` got `%v`", tc.err, err)
			}
		} else if err != nil {
			t.Errorf("Unexpected error `%v`", err)
		}

		if tc.expect == nil {
			if be != nil {
				t.Errorf("Expected nil rules, got %v", be)
			}
		} else {
			actual, ok := be.(*Refresh)
			if !ok {
				t.Errorf("Expected *Upstream, got %T", be)
			} else if !reflect.DeepEqual(tc.expect, actual) {
				t.Errorf("Expected %v got %v", tc.expect, actual)
			}
		}
	}
}
<|endoftext|>