<commit_before>package etf

import (
	"errors"
	"fmt"
	"math/big"
	r "reflect"
)

var (
	atomType     = r.ValueOf(Atom("")).Type()
	ErrBadFormat = errors.New("etf: bad format")
)

// OverflowError is returned when number cannot be represented by supplied type.
type OverflowError struct {
	Value interface{}
	Type  r.Type
}

// TypeError is returned when a type cannot be decoded.
type TypeError struct {
	Type r.Type
}

// VersionError is returned on invalid Erlang external format version number.
type VersionError struct {
	Version byte
}

func (err OverflowError) Error() string {
	return fmt.Sprintf(
		"overflow error: cannot represent %s by type %s",
		err.Value,
		err.Type,
	)
}

func (err TypeError) Error() string {
	return fmt.Sprintf("type error: cannot represent type %s", err.Type)
}

func (err VersionError) Error() string {
	return fmt.Sprintf("version error: version %d is not supported", err.Version)
}

// decodeStruct decodes a structure from tuple.
func decodeStruct(b []byte, ptr r.Value) (size uint, err error) {
	var arity int

	v := ptr.Elem()

	switch erlType(b[0]) {
	case erlSmallTuple:
		// $hA…
		if len(b) >= 2 {
			arity = int(b[1])
			size = 2
			goto decode
		} else {
			err = StructuralError{
				fmt.Sprintf("invalid tuple length (%d)", len(b)),
			}
		}

	case erlLargeTuple:
		// $iAAAA…
		if len(b) >= 5 {
			arity = int(be.Uint32(b[1:5]))
			size = 5
			goto decode
		} else {
			err = StructuralError{
				fmt.Sprintf("invalid tuple length (%d)", len(b)),
			}
		}

	default:
		err = SyntaxError{"not a tuple"}
	}

	return

decode:
	if arity != v.NumField() {
		err = StructuralError{
			fmt.Sprintf(
				"different number of fields (%d, should be %d)",
				v.NumField(),
				arity,
			),
		}
		return
	}

	for i := 0; i < arity; i++ {
		var s uint
		f := v.Field(i).Addr()

		s, err = decode(b[size:], f)
		size += s

		if err != nil {
			break
		}
	}

	return
}

// decodePtr decodes a specific type of data.
func decodePtr(b []byte, ptr r.Value) (size uint, err error) {
	v := ptr.Elem()

	switch v.Interface().(type) {
	case *big.Int:
		var result *big.Int
		if result, size, err = parseBigInt(b); err == nil {
			v.Set(r.ValueOf(result))
		}

	default:
		err = TypeError{v.Type()}
	}

	return
}

// decodeSlice decodes a slice.
func decodeSlice(b []byte, ptr r.Value) (size uint, err error) {
	v := ptr.Elem()

	switch v.Interface().(type) {
	case []byte:
		var result []byte
		if result, size, err = parseBinary(b); err == nil {
			v.SetBytes(result)
		}

	default:
		err = TypeError{v.Type()}
	}

	return
}

// decodeArray decodes an array.
func decodeArray(b []byte, ptr r.Value) (size uint, err error) {
	v := ptr.Elem()
	length := v.Len()

	switch v.Type().Elem().Kind() {
	case r.Uint8:
		var result []byte
		if result, size, err = parseBinary(b); err == nil {
			if length == len(result) {
				for i := range result {
					v.Index(i).SetUint(uint64(result[i]))
				}
			} else {
				err = OverflowError{result, v.Type()}
			}
		}

	default:
		err = TypeError{v.Type()}
	}

	return
}

func decode(b []byte, ptr r.Value) (size uint, err error) {
	v := ptr.Elem()

	switch v.Kind() {
	case r.Bool:
		var result bool
		if result, size, err = parseBool(b); err == nil {
			v.SetBool(result)
		}

	case r.Int, r.Int8, r.Int16, r.Int32, r.Int64:
		var result int64
		if result, size, err = parseInt64(b); err == nil {
			if v.OverflowInt(result) {
				err = OverflowError{result, v.Type()}
			} else {
				v.SetInt(result)
			}
		}

	case r.Uint, r.Uint8, r.Uint16, r.Uint32, r.Uint64:
		var result uint64
		if result, size, err = parseUint64(b); err == nil {
			if v.OverflowUint(result) {
				err = OverflowError{result, v.Type()}
			} else {
				v.SetUint(result)
			}
		}

	case r.Float32:
		var result float64
		if result, size, err = parseFloat64(b); err == nil {
			if v.OverflowFloat(result) {
				err = OverflowError{result, v.Type()}
			} else {
				v.SetFloat(result)
			}
		}

	case r.Float64:
		var result float64
		if result, size, err = parseFloat64(b); err == nil {
			v.SetFloat(result)
		}

	case r.String:
		if v.Type() == atomType {
			var result Atom
			if result, size, err = parseAtom(b); err == nil {
				v.Set(r.ValueOf(result))
			}
		} else {
			var result string
			if result, size, err = parseString(b); err == nil {
				v.Set(r.ValueOf(result))
			}
		}

	case r.Struct:
		size, err = decodeStruct(b, ptr)

	case r.Ptr:
		size, err = decodePtr(b, ptr)

	case r.Slice:
		size, err = decodeSlice(b, ptr)

	case r.Array:
		size, err = decodeArray(b, ptr)

	default:
		err = TypeError{v.Type()}
	}

	return
}

// Decode unmarshals a value and stores it to a variable pointer by ptr.
func Decode(b []byte, ptr interface{}) (size uint, err error) {
	if len(b) < 1 {
		return 0, ErrBadFormat
	} else if b[0] != erlFormatVersion {
		err = VersionError{b[0]}
	} else {
		p := r.ValueOf(ptr)
		size, err = decode(b[1:], p)
		size++
	}

	return
}
<commit_msg>decode erlang list as slice<commit_after>package etf

import (
	"errors"
	"fmt"
	"math/big"
	r "reflect"
)

var (
	atomType     = r.ValueOf(Atom("")).Type()
	ErrBadFormat = errors.New("etf: bad format")
)

// OverflowError is returned when number cannot be represented by supplied type.
type OverflowError struct {
	Value interface{}
	Type  r.Type
}

// TypeError is returned when a type cannot be decoded.
type TypeError struct {
	Type r.Type
}

// VersionError is returned on invalid Erlang external format version number.
type VersionError struct {
	Version byte
}

func (err OverflowError) Error() string {
	return fmt.Sprintf(
		"overflow error: cannot represent %s by type %s",
		err.Value,
		err.Type,
	)
}

func (err TypeError) Error() string {
	return fmt.Sprintf("type error: cannot represent type %s", err.Type)
}

func (err VersionError) Error() string {
	return fmt.Sprintf("version error: version %d is not supported", err.Version)
}

// decodeStruct decodes a structure from tuple.
func decodeStruct(b []byte, ptr r.Value) (size uint, err error) {
	var arity int

	v := ptr.Elem()

	switch erlType(b[0]) {
	case erlSmallTuple:
		// $hA…
		if len(b) >= 2 {
			arity = int(b[1])
			size = 2
			goto decode
		} else {
			err = StructuralError{
				fmt.Sprintf("invalid tuple length (%d)", len(b)),
			}
		}

	case erlLargeTuple:
		// $iAAAA…
		if len(b) >= 5 {
			arity = int(be.Uint32(b[1:5]))
			size = 5
			goto decode
		} else {
			err = StructuralError{
				fmt.Sprintf("invalid tuple length (%d)", len(b)),
			}
		}

	default:
		err = SyntaxError{"not a tuple"}
	}

	return

decode:
	if arity != v.NumField() {
		err = StructuralError{
			fmt.Sprintf(
				"different number of fields (%d, should be %d)",
				v.NumField(),
				arity,
			),
		}
		return
	}

	for i := 0; i < arity; i++ {
		var s uint
		f := v.Field(i).Addr()

		s, err = decode(b[size:], f)
		size += s

		if err != nil {
			break
		}
	}

	return
}

// decodePtr decodes a specific type of data.
func decodePtr(b []byte, ptr r.Value) (size uint, err error) {
	v := ptr.Elem()

	switch v.Interface().(type) {
	case *big.Int:
		var result *big.Int
		if result, size, err = parseBigInt(b); err == nil {
			v.Set(r.ValueOf(result))
		}

	default:
		err = TypeError{v.Type()}
	}

	return
}

// decodeSlice decodes a slice.
func decodeSlice(b []byte, ptr r.Value) (size uint, err error) {
	v := ptr.Elem()

	switch v.Interface().(type) {
	case []byte:
		var result []byte
		if result, size, err = parseBinary(b); err == nil {
			v.SetBytes(result)
		}

	default:
		size, err = decodeList(b, ptr)
	}

	return
}

// decodeList decodes an Erlang list elements.
func decodeList(b []byte, ptr r.Value) (size uint, err error) {
	v := ptr.Elem()

	switch v.Kind() {
	case r.Slice, r.Array:
		switch b[0] {
		case erlList:
			// $lLLLL…$j
			if len(b) <= 5 {
				err = StructuralError{
					fmt.Sprintf("invalid list length (%d)", len(b)),
				}
				break
			}

			listLen := uint(be.Uint32(b[1:5]))
			size = 5
			b = b[size:]

			slice := r.MakeSlice(v.Type(), int(listLen), int(listLen))
			for i := uint(0); i < listLen; i++ {
				if elemSize, err := decode(b, slice.Index(int(i)).Addr()); err == nil {
					size += elemSize
					b = b[elemSize:]
				} else {
					break
				}
			}

			if len(b) < 1 || erlType(b[0]) != erlNil {
				err = StructuralError{"got improper list"}
			} else {
				size++
				v.Set(slice)
			}

		case erlNil:
			// empty slice -- do not touch it
			return 1, nil
		}

	default:
		err = TypeError{v.Type()}
	}

	return
}

// decodeArray decodes an array.
func decodeArray(b []byte, ptr r.Value) (size uint, err error) {
	v := ptr.Elem()
	length := v.Len()

	switch v.Type().Elem().Kind() {
	case r.Uint8:
		var result []byte
		if result, size, err = parseBinary(b); err == nil {
			if length == len(result) {
				for i := range result {
					v.Index(i).SetUint(uint64(result[i]))
				}
			} else {
				err = OverflowError{result, v.Type()}
			}
		}

	default:
		err = TypeError{v.Type()}
	}

	return
}

func decode(b []byte, ptr r.Value) (size uint, err error) {
	v := ptr.Elem()

	switch v.Kind() {
	case r.Bool:
		var result bool
		if result, size, err = parseBool(b); err == nil {
			v.SetBool(result)
		}

	case r.Int, r.Int8, r.Int16, r.Int32, r.Int64:
		var result int64
		if result, size, err = parseInt64(b); err == nil {
			if v.OverflowInt(result) {
				err = OverflowError{result, v.Type()}
			} else {
				v.SetInt(result)
			}
		}

	case r.Uint, r.Uint8, r.Uint16, r.Uint32, r.Uint64:
		var result uint64
		if result, size, err = parseUint64(b); err == nil {
			if v.OverflowUint(result) {
				err = OverflowError{result, v.Type()}
			} else {
				v.SetUint(result)
			}
		}

	case r.Float32:
		var result float64
		if result, size, err = parseFloat64(b); err == nil {
			if v.OverflowFloat(result) {
				err = OverflowError{result, v.Type()}
			} else {
				v.SetFloat(result)
			}
		}

	case r.Float64:
		var result float64
		if result, size, err = parseFloat64(b); err == nil {
			v.SetFloat(result)
		}

	case r.String:
		if v.Type() == atomType {
			var result Atom
			if result, size, err = parseAtom(b); err == nil {
				v.Set(r.ValueOf(result))
			}
		} else {
			var result string
			if result, size, err = parseString(b); err == nil {
				v.Set(r.ValueOf(result))
			}
		}

	case r.Struct:
		size, err = decodeStruct(b, ptr)

	case r.Ptr:
		size, err = decodePtr(b, ptr)

	case r.Slice:
		size, err = decodeSlice(b, ptr)

	case r.Array:
		size, err = decodeArray(b, ptr)

	default:
		err = TypeError{v.Type()}
	}

	return
}

// Decode unmarshals a value and stores it to a variable pointer by ptr.
func Decode(b []byte, ptr interface{}) (size uint, err error) {
	if len(b) < 1 {
		return 0, ErrBadFormat
	} else if b[0] != erlFormatVersion {
		err = VersionError{b[0]}
	} else {
		p := r.ValueOf(ptr)
		size, err = decode(b[1:], p)
		size++
	}

	return
}
<|endoftext|>


<commit_before>package common

import (
	"github.com/stretchr/testify/assert"
	"golang.org/x/net/context"

	"errors"
	"sync"
	"testing"
	"time"
)

func TestRunningTaskReturnsError(t *testing.T) {
	ok := false
	assert.Error(t, recurring(nil,
		Task{
			Name: "test",
			Job: func([]interface{}) error {
				ok = true

				return errors.New("expected :)")
			},
			Interval: 0},
	))
	assert.True(t, ok)
}

func TestStopRunningTask(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	var wg sync.WaitGroup
	wg.Add(1)
	ok := false
	go func() {
		defer wg.Done()
		recurring(ctx, Task{
			Name: "test",
			Job: func(params []interface{}) error {
				ok = true
				assert.Equal(t, "check me", params[0])

				return nil
			},
			JobParams: []interface{}{"check me"},
			Interval:  time.Second * 10,
		})
	}()
	cancel()
	wg.Wait()
	assert.True(t, ok)
}

func TestTaskManagerRunTasks(t *testing.T) {
	var wg sync.WaitGroup
	wg.Add(2)
	manager := NewTaskManager()
	ok1, ok2 := false, false
	assert.True(t, manager.RunNewRecurringTask(Task{
		ID:   "t1-id",
		Name: "t1",
		Job: func([]interface{}) error {
			defer wg.Done()
			ok1 = true

			return nil
		},
		Interval: time.Second * 10,
	}))
	assert.True(t, manager.RunNewRecurringTask(Task{
		ID:   "t2-id",
		Name: "t2",
		Job: func([]interface{}) error {
			defer wg.Done()
			ok2 = true

			return nil
		},
		Interval: time.Second * 10,
	}))
	wg.Wait()
	manager.StopAllTasks()
	assert.True(t, ok1)
	assert.True(t, ok2)
}

func TestTaskManagerRunTasks_TaskAlreadyExist(t *testing.T) {
	var wg sync.WaitGroup
	wg.Add(1)
	manager := NewTaskManager()
	ok1 := false
	assert.True(t, manager.RunNewRecurringTask(Task{
		ID:   "t1-id",
		Name: "t1",
		Job: func([]interface{}) error {
			defer wg.Done()
			ok1 = true

			return nil
		},
		Interval: time.Second * 10,
	}))
	assert.False(t, manager.RunNewRecurringTask(Task{
		ID: "t1-id",
	}))
	wg.Wait()
	manager.StopAllTasks()
	assert.True(t, ok1)
}

func TestTaskManagerRefresh(t *testing.T) {
	var wg sync.WaitGroup
	wg.Add(2)
	manager := NewTaskManager()
	ok1, ok2 := false, false
	t1 := Task{
		ID:   "t1-id",
		Name: "t1",
		Job: func([]interface{}) error {
			defer wg.Done()
			ok1 = true

			return nil
		},
		Interval: time.Second * 10,
	}
	t2 := Task{
		ID:   "t2-id",
		Name: "t2",
		Job: func([]interface{}) error {
			defer wg.Done()
			ok2 = true

			return nil
		},
		Interval: time.Second * 10,
	}
	assert.True(t, manager.RunNewRecurringTask(t1))
	assert.True(t, manager.RunNewRecurringTask(t2))
	wg.Wait()
	assert.True(t, ok1)
	assert.True(t, ok2)

	// removing t1
	manager.Refresh([]string{t2.ID})
	wg.Add(1)
	assert.True(t, manager.RunNewRecurringTask(t1))
	assert.False(t, manager.RunNewRecurringTask(t2))
	wg.Wait()
	manager.StopAllTasks()
}

func TestTaskManagerRefresh_NilIds(t *testing.T) {
	manager := NewTaskManager()
	manager.Refresh(nil)
}

func TestTaskManagerRefresh_EmptyIds(t *testing.T) {
	manager := NewTaskManager()
	manager.Refresh([]string{})
}
<commit_msg>improve stop all tasks;<commit_after>package common

import (
	"github.com/stretchr/testify/assert"
	"golang.org/x/net/context"

	"errors"
	"sync"
	"testing"
	"time"
)

func TestRunningTaskRecurringReturnsError(t *testing.T) {
	ok := false
	assert.Error(t, recurring(nil,
		Task{
			Name: "test",
			Job: func([]interface{}) error {
				ok = true

				return errors.New("expected :)")
			},
			Interval: 0},
	))
	assert.True(t, ok)
}

func TestStopRecurringTask(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	var wg sync.WaitGroup
	wg.Add(1)
	ok := false
	const param = "check me"
	go func() {
		defer wg.Done()
		recurring(ctx, Task{
			Name: "test",
			Job: func(params []interface{}) error {
				ok = true
				assert.Equal(t, param, params[0])

				return nil
			},
			JobParams: []interface{}{param},
			Interval:  time.Second * 10,
		})
	}()
	cancel()
	wg.Wait()
	assert.True(t, ok)
}

func TestTaskManagerRunTasks(t *testing.T) {
	var wg sync.WaitGroup
	wg.Add(2)
	manager := NewTaskManager()
	ok1, ok2 := false, false
	assert.True(t, manager.RunNewRecurringTask(Task{
		ID:   "t1-id",
		Name: "t1",
		Job: func([]interface{}) error {
			defer wg.Done()
			ok1 = true

			return nil
		},
		Interval: time.Second * 10,
	}))
	assert.True(t, manager.RunNewRecurringTask(Task{
		ID:   "t2-id",
		Name: "t2",
		Job: func([]interface{}) error {
			defer wg.Done()
			ok2 = true

			return nil
		},
		Interval: time.Second * 10,
	}))
	wg.Wait()
	manager.StopAllTasks()
	assert.True(t, ok1)
	assert.True(t, ok2)
}

func TestTaskManagerRunTasks_TaskAlreadyExist(t *testing.T) {
	var wg sync.WaitGroup
	wg.Add(1)
	manager := NewTaskManager()
	ok1 := false
	assert.True(t, manager.RunNewRecurringTask(Task{
		ID:   "t1-id",
		Name: "t1",
		Job: func([]interface{}) error {
			defer wg.Done()
			ok1 = true

			return nil
		},
		Interval: time.Second * 10,
	}))
	assert.False(t, manager.RunNewRecurringTask(Task{
		ID: "t1-id",
	}))
	wg.Wait()
	manager.StopAllTasks()
	assert.True(t, ok1)
}

func TestTaskManagerRefresh(t *testing.T) {
	var wg sync.WaitGroup
	wg.Add(2)
	manager := NewTaskManager()
	ok1, ok2 := false, false
	t1 := Task{
		ID:   "t1-id",
		Name: "t1",
		Job: func([]interface{}) error {
			defer wg.Done()
			ok1 = true

			return nil
		},
		Interval: time.Second * 10,
	}
	t2 := Task{
		ID:   "t2-id",
		Name: "t2",
		Job: func([]interface{}) error {
			defer wg.Done()
			ok2 = true

			return nil
		},
		Interval: time.Second * 10,
	}
	assert.True(t, manager.RunNewRecurringTask(t1))
	assert.True(t, manager.RunNewRecurringTask(t2))
	wg.Wait()
	assert.True(t, ok1)
	assert.True(t, ok2)

	// removing t1
	manager.Refresh([]string{t2.ID})
	wg.Add(1)
	assert.True(t, manager.RunNewRecurringTask(t1))
	assert.False(t, manager.RunNewRecurringTask(t2))
	wg.Wait()
	manager.StopAllTasks()
}

func TestTaskManagerRefresh_NilIds(t *testing.T) {
	manager := NewTaskManager()
	manager.Refresh(nil)
}

func TestTaskManagerRefresh_EmptyIds(t *testing.T) {
	manager := NewTaskManager()
	manager.Refresh([]string{})
}
<|endoftext|>


<commit_before>// Copyright 2012 Andreas Louca. All rights reserved.
// Use of this source code is goverend by a BSD-style
// license that can be found in the LICENSE file.

package gosnmp

import (
	"fmt"
	"net"
)

type Asn1BER byte

// SNMP Data Types
const (
	Integer          Asn1BER = 0x02
	BitString                = 0x03
	OctetString              = 0x04
	Null                     = 0x05
	ObjectIdentifier         = 0x06
	Sequence                 = 0x30
	IpAddress                = 0x40
	Counter32                = 0x41
	Gauge32                  = 0x42
	TimeTicks                = 0x43
	Opaque                   = 0x44
	NsapAddress              = 0x45
	Counter64                = 0x46
	Uinteger32               = 0x47
	NoSuchObject             = 0x80
	NoSuchInstance           = 0x81
	GetRequest               = 0xa0
	GetNextRequest           = 0xa1
	GetResponse              = 0xa2
	SetRequest               = 0xa3
	Trap                     = 0xa4
	GetBulkRequest           = 0xa5
)

// String representations of each SNMP Data Type
var dataTypeStrings = map[Asn1BER]string{
	Integer:          "Integer",
	BitString:        "BitString",
	OctetString:      "OctetString",
	Null:             "Null",
	ObjectIdentifier: "ObjectIdentifier",
	IpAddress:        "IpAddress",
	Sequence:         "Sequence",
	Counter32:        "Counter32",
	Gauge32:          "Gauge32",
	TimeTicks:        "TimeTicks",
	Opaque:           "Opaque",
	NsapAddress:      "NsapAddress",
	Counter64:        "Counter64",
	Uinteger32:       "Uinteger32",
	NoSuchObject:     "NoSuchObject",
	NoSuchInstance:   "NoSuchInstance",
	GetRequest:       "GetRequest",
	GetNextRequest:   "GetNextRequest",
	GetResponse:      "GetResponse",
	SetRequest:       "SetRequest",
	Trap:             "Trap",
	GetBulkRequest:   "GetBulkRequest",
}

func (dataType Asn1BER) String() string {
	str, ok := dataTypeStrings[dataType]

	if !ok {
		str = "Unknown"
	}

	return str
}

type Variable struct {
	Name  []int
	Type  Asn1BER
	Size  uint64
	Value interface{}
}

func decodeValue(valueType Asn1BER, data []byte) (retVal *Variable, err error) {
	retVal = new(Variable)
	retVal.Size = uint64(len(data))

	switch Asn1BER(valueType) {

	// Integer
	case Integer:
		ret, err := parseInt(data)
		if err != nil {
			break
		}
		retVal.Type = Integer
		retVal.Value = ret
	// Octet
	case OctetString:
		retVal.Type = OctetString
		retVal.Value = data
	case ObjectIdentifier:
		retVal.Type = ObjectIdentifier
		retVal.Value, _ = parseObjectIdentifier(data)
	// IpAddress
	case IpAddress:
		retVal.Type = IpAddress
		retVal.Value = net.IP{data[0], data[1], data[2], data[3]}
	// Counter32
	case Counter32:
		ret, err := parseInt(data)
		if err != nil {
			break
		}
		retVal.Type = Counter32
		retVal.Value = ret
	case TimeTicks:
		ret, err := parseInt(data)
		if err != nil {
			break
		}
		retVal.Type = TimeTicks
		retVal.Value = ret
	// Gauge32
	case Gauge32:
		ret, err := parseInt(data)
		if err != nil {
			break
		}
		retVal.Type = Gauge32
		retVal.Value = ret
	case Counter64:
		ret, err := parseInt64(data)

		// Decode it
		if err != nil {
			break
		}

		retVal.Type = Counter64
		retVal.Value = ret
	case Sequence:
		// NOOP
		retVal.Value = data
	case GetResponse:
		// NOOP
		retVal.Value = data
	case NoSuchInstance:
		return nil, fmt.Errorf("No such instance")
	case NoSuchObject:
		return nil, fmt.Errorf("No such object")
	default:
		err = fmt.Errorf("Unable to decode %x - not implemented", valueType)
	}

	return
}

// Parses UINT16
func ParseUint16(content []byte) int {
	number := uint8(content[1]) | uint8(content[0])<<8
	//fmt.Printf("\t%d\n", number)

	return int(number)
}
<commit_msg>Make error when decoding an object doesn't work more helpful<commit_after>// Copyright 2012 Andreas Louca. All rights reserved.
// Use of this source code is goverend by a BSD-style
// license that can be found in the LICENSE file.

package gosnmp

import (
	"fmt"
	"net"
)

type Asn1BER byte

// SNMP Data Types
const (
	Integer          Asn1BER = 0x02
	BitString                = 0x03
	OctetString              = 0x04
	Null                     = 0x05
	ObjectIdentifier         = 0x06
	Sequence                 = 0x30
	IpAddress                = 0x40
	Counter32                = 0x41
	Gauge32                  = 0x42
	TimeTicks                = 0x43
	Opaque                   = 0x44
	NsapAddress              = 0x45
	Counter64                = 0x46
	Uinteger32               = 0x47
	NoSuchObject             = 0x80
	NoSuchInstance           = 0x81
	GetRequest               = 0xa0
	GetNextRequest           = 0xa1
	GetResponse              = 0xa2
	SetRequest               = 0xa3
	Trap                     = 0xa4
	GetBulkRequest           = 0xa5
)

// String representations of each SNMP Data Type
var dataTypeStrings = map[Asn1BER]string{
	Integer:          "Integer",
	BitString:        "BitString",
	OctetString:      "OctetString",
	Null:             "Null",
	ObjectIdentifier: "ObjectIdentifier",
	IpAddress:        "IpAddress",
	Sequence:         "Sequence",
	Counter32:        "Counter32",
	Gauge32:          "Gauge32",
	TimeTicks:        "TimeTicks",
	Opaque:           "Opaque",
	NsapAddress:      "NsapAddress",
	Counter64:        "Counter64",
	Uinteger32:       "Uinteger32",
	NoSuchObject:     "NoSuchObject",
	NoSuchInstance:   "NoSuchInstance",
	GetRequest:       "GetRequest",
	GetNextRequest:   "GetNextRequest",
	GetResponse:      "GetResponse",
	SetRequest:       "SetRequest",
	Trap:             "Trap",
	GetBulkRequest:   "GetBulkRequest",
}

func (dataType Asn1BER) String() string {
	str, ok := dataTypeStrings[dataType]

	if !ok {
		str = "Unknown"
	}

	return str
}

type Variable struct {
	Name  []int
	Type  Asn1BER
	Size  uint64
	Value interface{}
}

func decodeValue(valueType Asn1BER, data []byte) (retVal *Variable, err error) {
	retVal = new(Variable)
	retVal.Size = uint64(len(data))

	switch Asn1BER(valueType) {

	// Integer
	case Integer:
		ret, err := parseInt(data)
		if err != nil {
			break
		}
		retVal.Type = Integer
		retVal.Value = ret
	// Octet
	case OctetString:
		retVal.Type = OctetString
		retVal.Value = data
	case ObjectIdentifier:
		retVal.Type = ObjectIdentifier
		retVal.Value, _ = parseObjectIdentifier(data)
	// IpAddress
	case IpAddress:
		retVal.Type = IpAddress
		retVal.Value = net.IP{data[0], data[1], data[2], data[3]}
	// Counter32
	case Counter32:
		ret, err := parseInt(data)
		if err != nil {
			break
		}
		retVal.Type = Counter32
		retVal.Value = ret
	case TimeTicks:
		ret, err := parseInt(data)
		if err != nil {
			break
		}
		retVal.Type = TimeTicks
		retVal.Value = ret
	// Gauge32
	case Gauge32:
		ret, err := parseInt(data)
		if err != nil {
			break
		}
		retVal.Type = Gauge32
		retVal.Value = ret
	case Counter64:
		ret, err := parseInt64(data)

		// Decode it
		if err != nil {
			break
		}

		retVal.Type = Counter64
		retVal.Value = ret
	case Sequence:
		// NOOP
		retVal.Value = data
	case GetResponse:
		// NOOP
		retVal.Value = data
	case NoSuchInstance:
		return nil, fmt.Errorf("No such instance")
	case NoSuchObject:
		return nil, fmt.Errorf("No such object")
	default:
		err = fmt.Errorf("Unable to decode %#v - not implemented", valueType)
	}

	return
}

// Parses UINT16
func ParseUint16(content []byte) int {
	number := uint8(content[1]) | uint8(content[0])<<8
	//fmt.Printf("\t%d\n", number)

	return int(number)
}
<|endoftext|>


<commit_before>// Package cq provides tools for interacting with the CQ tools.
package cq

import (
	"bytes"
	"fmt"
	"sync"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/skia-dev/glog"

	"go.skia.org/infra/go/buildbucket"
	"go.skia.org/infra/go/gerrit"
	"go.skia.org/infra/go/gitiles"
	"go.skia.org/infra/go/metrics2"
	"go.skia.org/infra/go/util"
)

const (
	CQ_CFG_FILE_PATH = "infra/branch-config/cq.cfg"
	SKIA_REPO        = "https://skia.googlesource.com/skia"

	// Constants for in-flight metrics.
	INFLIGHT_METRIC_NAME     = "in_flight"
	INFLIGHT_TRYBOT_DURATION = "trybot_duration"
	INFLIGHT_TRYBOT_NUM      = "trybot_num"
	INFLIGHT_WAITING_IN_CQ   = "waiting_in_cq"

	// Constants for landed metrics.
	LANDED_METRIC_NAME     = "after_commit"
	LANDED_TRYBOT_DURATION = "trybot_duration"
	LANDED_TOTAL_DURATION  = "total_duration"
)

var (
	// Slice of all known presubmit bot names.
	PRESUBMIT_BOTS = []string{"skia_presubmit-Trybot"}

	// Mutext to control access to the slice of CQ trybots.
	cqTryBotsMutex sync.RWMutex
)

// NewClient creates a new client for interacting with CQ tools.
func NewClient(gerritClient *gerrit.Gerrit, cqTryBotsFunc GetCQTryBots, metricName string) (*Client, error) {
	cqTryBots, err := cqTryBotsFunc()
	if err != nil {
		return nil, err
	}
	return &Client{gerritClient, cqTryBots, cqTryBotsFunc, metricName}, err
}

// GetCQTryBots is an interface for returing the CQ trybots of a project.
type GetCQTryBots func() ([]string, error)

type Client struct {
	gerritClient  *gerrit.Gerrit
	cqTryBots     []string
	cqTryBotsFunc GetCQTryBots
	metricName    string
}

// GetSkiaCQTryBots is a Skia implementation of GetCQTryBots.
func GetSkiaCQTryBots() ([]string, error) {
	var buf bytes.Buffer
	if err := gitiles.NewRepo(SKIA_REPO).ReadFile(CQ_CFG_FILE_PATH, &buf); err != nil {
		return nil, err
	}
	var cqCfg Config
	if err := proto.UnmarshalText(buf.String(), &cqCfg); err != nil {
		return nil, err
	}
	tryJobs := []string{}
	for _, bucket := range cqCfg.Verifiers.GetTryJob().GetBuckets() {
		for _, builder := range bucket.GetBuilders() {
			if builder.GetExperimentPercentage() > 0 {
				// Exclude experimental builders.
				continue
			}
			if util.ContainsAny(builder.GetName(), PRESUBMIT_BOTS) {
				// Exclude presubmit bots because they could fail or be delayed
				// due to factors such as owners approval and other project
				// specific checks.
				continue
			}
			tryJobs = append(tryJobs, builder.GetName())
		}
	}
	glog.Infof("The list of CQ trybots is: %s", tryJobs)
	return tryJobs, nil
}

// RefreshCQTryBots refreshes the slice of CQ trybots on the instance. Access
// to the trybots is protected by a RWMutex.
func (c *Client) RefreshCQTryBots() error {
	tryBots, err := c.cqTryBotsFunc()
	if err != nil {
		return err
	}

	cqTryBotsMutex.Lock()
	defer cqTryBotsMutex.Unlock()
	c.cqTryBots = tryBots
	return nil
}

// ReportCQStats reports all relevant stats for the specified Gerrit change.
// Note: Different stats are reported depending on whether the change has been
// merged or not.
func (c *Client) ReportCQStats(change int64) error {
	changeInfo, err := c.gerritClient.GetIssueProperties(change)
	if err != nil {
		return err
	}
	patchsetIds := changeInfo.GetPatchsetIDs()
	latestPatchsetId := patchsetIds[len(patchsetIds)-1]
	if changeInfo.Committed {
		// TODO(rmistry): The last patchset in Gerrit does not contain trybot
		// information so we have to look at the one immediately before it.
		// This will be fixed with crbug.com/634944.
		latestPatchsetId = patchsetIds[len(patchsetIds)-2]
	}

	builds, err := c.gerritClient.GetTrybotResults(change, latestPatchsetId)
	if err != nil {
		return err
	}
	// Consider only CQ bots.
	cqBuilds := []*buildbucket.Build{}
	for _, b := range builds {
		if c.isCQTryBot(b.Parameters.BuilderName) {
			cqBuilds = append(cqBuilds, b)
		}
	}
	gerritURL := fmt.Sprintf("%s/c/%d/%d", gerrit.GERRIT_SKIA_URL, change, latestPatchsetId)
	if len(cqBuilds) == 0 {
		glog.Infof("No trybot results were found for %s", gerritURL)
		return nil
	}

	glog.Infof("Starting processing %s. Merged status: %t", gerritURL, changeInfo.Committed)

	if changeInfo.Committed {
		c.ReportCQStatsForLandedCL(change, latestPatchsetId, cqBuilds)
	} else {
		c.ReportCQStatsForInFlightCL(change, latestPatchsetId, cqBuilds)
	}
	return nil
}

// ReportCQStatsForLandedCL reports the following metrics for the specified
// change and patchsetID:
// * The total time the change spent waiting for CQ trybots to complete.
// * The time each CQ trybot took to complete.
func (c *Client) ReportCQStatsForLandedCL(change, patchsetId int64, cqBuilds []*buildbucket.Build) {
	endTimeOfCQBots := time.Time{}
	maximumTrybotDuration := int64(0)
	for _, b := range cqBuilds {
		createdTime := time.Time(b.Created).UTC()
		completedTime := time.Time(b.Completed).UTC()
		if endTimeOfCQBots.Before(completedTime) {
			endTimeOfCQBots = completedTime
		}

		durationTags := map[string]string{
			"issue":    fmt.Sprintf("%d", change),
			"patchset": fmt.Sprintf("%d", patchsetId),
			"trybot":   b.Parameters.BuilderName,
		}
		duration := int64(completedTime.Sub(createdTime).Seconds())
		glog.Infof("\t%s was created at %s and completed at %s. Total duration: %d", b.Parameters.BuilderName, createdTime, completedTime, duration)
		metrics2.RawAddInt64PointAtTime(fmt.Sprintf("%s.%s.%s", c.metricName, LANDED_METRIC_NAME, LANDED_TRYBOT_DURATION), durationTags, duration, completedTime)

		if duration > maximumTrybotDuration {
			maximumTrybotDuration = duration
		}
	}

	glog.Infof("\tMaximum trybot duration: %d", maximumTrybotDuration)
	glog.Infof("\tFurthest completion time: %s", endTimeOfCQBots)
	totalDurationTags := map[string]string{
		"issue":    fmt.Sprintf("%d", change),
		"patchset": fmt.Sprintf("%d", patchsetId)}
	metrics2.RawAddInt64PointAtTime(fmt.Sprintf("%s.%s.%s", c.metricName, LANDED_METRIC_NAME, LANDED_TOTAL_DURATION), totalDurationTags, maximumTrybotDuration, endTimeOfCQBots)
}

// ReportCQStatsForInFlightCL reports the following metrics for the specified
// change and patchsetID:
// * How long CQ trybots have been running for.
// * How many CQ trybots have been triggered.
func (c *Client) ReportCQStatsForInFlightCL(issue, patchsetId int64, cqBuilds []*buildbucket.Build) {
	totalTriggeredCQBots := int(0)
	currentTime := time.Now()
	for _, b := range cqBuilds {
		totalTriggeredCQBots++

		createdTime := time.Time(b.Created).UTC()
		completedTime := time.Time(b.Completed).UTC()
		if (completedTime != time.Time{}.UTC()) {
			// If build has completed then move on.
			continue
		}

		duration := int64(currentTime.Sub(createdTime).Seconds())
		glog.Infof("\t%s was created at %s and is running after %d seconds", b.Parameters.BuilderName, createdTime, duration)
		durationTags := map[string]string{
			"issue":    fmt.Sprintf("%d", issue),
			"patchset": fmt.Sprintf("%d", patchsetId),
			"trybot":   b.Parameters.BuilderName,
		}
		metrics2.RawAddInt64PointAtTime(fmt.Sprintf("%s.%s.%s", c.metricName, INFLIGHT_METRIC_NAME, INFLIGHT_TRYBOT_DURATION), durationTags, duration, currentTime)
	}
	cqTryBotsMutex.RLock()
	glog.Infof("\t%d CQ bots have been triggered by %d/%d. There are %d CQ bots in cq.cfg", totalTriggeredCQBots, issue, patchsetId, len(c.cqTryBots))
	cqTryBotsMutex.RUnlock()
	numTags := map[string]string{
		"issue":    fmt.Sprintf("%d", issue),
		"patchset": fmt.Sprintf("%d", patchsetId),
	}
	metrics2.RawAddInt64PointAtTime(fmt.Sprintf("%s.%s.%s", c.metricName, INFLIGHT_METRIC_NAME, INFLIGHT_TRYBOT_NUM), numTags, int64(totalTriggeredCQBots), currentTime)
}

func (c *Client) isCQTryBot(builderName string) bool {
	cqTryBotsMutex.RLock()
	isCQTrybot := util.ContainsAny(builderName, c.cqTryBots)
	cqTryBotsMutex.RUnlock()
	return isCQTrybot
}
<commit_msg>Fix for incorrect completed time from buildbucket<commit_after>// Package cq provides tools for interacting with the CQ tools.
package cq

import (
	"bytes"
	"fmt"
	"sync"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/skia-dev/glog"

	"go.skia.org/infra/go/buildbucket"
	"go.skia.org/infra/go/gerrit"
	"go.skia.org/infra/go/gitiles"
	"go.skia.org/infra/go/metrics2"
	"go.skia.org/infra/go/util"
)

const (
	CQ_CFG_FILE_PATH = "infra/branch-config/cq.cfg"
	SKIA_REPO        = "https://skia.googlesource.com/skia"

	// Constants for in-flight metrics.
	INFLIGHT_METRIC_NAME     = "in_flight"
	INFLIGHT_TRYBOT_DURATION = "trybot_duration"
	INFLIGHT_TRYBOT_NUM      = "trybot_num"
	INFLIGHT_WAITING_IN_CQ   = "waiting_in_cq"

	// Constants for landed metrics.
	LANDED_METRIC_NAME     = "after_commit"
	LANDED_TRYBOT_DURATION = "trybot_duration"
	LANDED_TOTAL_DURATION  = "total_duration"
)

var (
	// Slice of all known presubmit bot names.
	PRESUBMIT_BOTS = []string{"skia_presubmit-Trybot"}

	// Mutext to control access to the slice of CQ trybots.
	cqTryBotsMutex sync.RWMutex
)

// NewClient creates a new client for interacting with CQ tools.
func NewClient(gerritClient *gerrit.Gerrit, cqTryBotsFunc GetCQTryBots, metricName string) (*Client, error) {
	cqTryBots, err := cqTryBotsFunc()
	if err != nil {
		return nil, err
	}
	return &Client{gerritClient, cqTryBots, cqTryBotsFunc, metricName}, err
}

// GetCQTryBots is an interface for returing the CQ trybots of a project.
type GetCQTryBots func() ([]string, error)

type Client struct {
	gerritClient  *gerrit.Gerrit
	cqTryBots     []string
	cqTryBotsFunc GetCQTryBots
	metricName    string
}

// GetSkiaCQTryBots is a Skia implementation of GetCQTryBots.
func GetSkiaCQTryBots() ([]string, error) {
	var buf bytes.Buffer
	if err := gitiles.NewRepo(SKIA_REPO).ReadFile(CQ_CFG_FILE_PATH, &buf); err != nil {
		return nil, err
	}
	var cqCfg Config
	if err := proto.UnmarshalText(buf.String(), &cqCfg); err != nil {
		return nil, err
	}
	tryJobs := []string{}
	for _, bucket := range cqCfg.Verifiers.GetTryJob().GetBuckets() {
		for _, builder := range bucket.GetBuilders() {
			if builder.GetExperimentPercentage() > 0 {
				// Exclude experimental builders.
				continue
			}
			if util.ContainsAny(builder.GetName(), PRESUBMIT_BOTS) {
				// Exclude presubmit bots because they could fail or be delayed
				// due to factors such as owners approval and other project
				// specific checks.
				continue
			}
			tryJobs = append(tryJobs, builder.GetName())
		}
	}
	glog.Infof("The list of CQ trybots is: %s", tryJobs)
	return tryJobs, nil
}

// RefreshCQTryBots refreshes the slice of CQ trybots on the instance. Access
// to the trybots is protected by a RWMutex.
func (c *Client) RefreshCQTryBots() error {
	tryBots, err := c.cqTryBotsFunc()
	if err != nil {
		return err
	}

	cqTryBotsMutex.Lock()
	defer cqTryBotsMutex.Unlock()
	c.cqTryBots = tryBots
	return nil
}

// ReportCQStats reports all relevant stats for the specified Gerrit change.
// Note: Different stats are reported depending on whether the change has been
// merged or not.
func (c *Client) ReportCQStats(change int64) error {
	changeInfo, err := c.gerritClient.GetIssueProperties(change)
	if err != nil {
		return err
	}
	patchsetIds := changeInfo.GetPatchsetIDs()
	latestPatchsetId := patchsetIds[len(patchsetIds)-1]
	if changeInfo.Committed {
		// TODO(rmistry): The last patchset in Gerrit does not contain trybot
		// information so we have to look at the one immediately before it.
		// This will be fixed with crbug.com/634944.
		latestPatchsetId = patchsetIds[len(patchsetIds)-2]
	}

	builds, err := c.gerritClient.GetTrybotResults(change, latestPatchsetId)
	if err != nil {
		return err
	}
	// Consider only CQ bots.
	cqBuilds := []*buildbucket.Build{}
	for _, b := range builds {
		if c.isCQTryBot(b.Parameters.BuilderName) {
			cqBuilds = append(cqBuilds, b)
		}
	}
	gerritURL := fmt.Sprintf("%s/c/%d/%d", gerrit.GERRIT_SKIA_URL, change, latestPatchsetId)
	if len(cqBuilds) == 0 {
		glog.Infof("No trybot results were found for %s", gerritURL)
		return nil
	}

	glog.Infof("Starting processing %s. Merged status: %t", gerritURL, changeInfo.Committed)

	if changeInfo.Committed {
		c.ReportCQStatsForLandedCL(change, latestPatchsetId, cqBuilds)
	} else {
		c.ReportCQStatsForInFlightCL(change, latestPatchsetId, cqBuilds)
	}
	return nil
}

// ReportCQStatsForLandedCL reports the following metrics for the specified
// change and patchsetID:
// * The total time the change spent waiting for CQ trybots to complete.
// * The time each CQ trybot took to complete.
func (c *Client) ReportCQStatsForLandedCL(change, patchsetId int64, cqBuilds []*buildbucket.Build) {
	endTimeOfCQBots := time.Time{}
	maximumTrybotDuration := int64(0)
	for _, b := range cqBuilds {
		createdTime := time.Time(b.Created).UTC()
		completedTime := time.Time(b.Completed).UTC()
		if (completedTime == time.Time{}.UTC()) {
			// The correct completed time has not shown up in Buildbucket yet.
			continue
		}
		if endTimeOfCQBots.Before(completedTime) {
			endTimeOfCQBots = completedTime
		}

		durationTags := map[string]string{
			"issue":    fmt.Sprintf("%d", change),
			"patchset": fmt.Sprintf("%d", patchsetId),
			"trybot":   b.Parameters.BuilderName,
		}
		duration := int64(completedTime.Sub(createdTime).Seconds())
		glog.Infof("\t%s was created at %s and completed at %s. Total duration: %d", b.Parameters.BuilderName, createdTime, completedTime, duration)
		metrics2.RawAddInt64PointAtTime(fmt.Sprintf("%s.%s.%s", c.metricName, LANDED_METRIC_NAME, LANDED_TRYBOT_DURATION), durationTags, duration, completedTime)

		if duration > maximumTrybotDuration {
			maximumTrybotDuration = duration
		}
	}

	glog.Infof("\tMaximum trybot duration: %d", maximumTrybotDuration)
	glog.Infof("\tFurthest completion time: %s", endTimeOfCQBots)
	totalDurationTags := map[string]string{
		"issue":    fmt.Sprintf("%d", change),
		"patchset": fmt.Sprintf("%d", patchsetId)}
	metrics2.RawAddInt64PointAtTime(fmt.Sprintf("%s.%s.%s", c.metricName, LANDED_METRIC_NAME, LANDED_TOTAL_DURATION), totalDurationTags, maximumTrybotDuration, endTimeOfCQBots)
}

// ReportCQStatsForInFlightCL reports the following metrics for the specified
// change and patchsetID:
// * How long CQ trybots have been running for.
// * How many CQ trybots have been triggered.
func (c *Client) ReportCQStatsForInFlightCL(issue, patchsetId int64, cqBuilds []*buildbucket.Build) {
	totalTriggeredCQBots := int(0)
	currentTime := time.Now()
	for _, b := range cqBuilds {
		totalTriggeredCQBots++

		createdTime := time.Time(b.Created).UTC()
		completedTime := time.Time(b.Completed).UTC()
		if (completedTime != time.Time{}.UTC()) {
			// If build has completed then move on.
			continue
		}

		duration := int64(currentTime.Sub(createdTime).Seconds())
		glog.Infof("\t%s was created at %s and is running after %d seconds", b.Parameters.BuilderName, createdTime, duration)
		durationTags := map[string]string{
			"issue":    fmt.Sprintf("%d", issue),
			"patchset": fmt.Sprintf("%d", patchsetId),
			"trybot":   b.Parameters.BuilderName,
		}
		metrics2.RawAddInt64PointAtTime(fmt.Sprintf("%s.%s.%s", c.metricName, INFLIGHT_METRIC_NAME, INFLIGHT_TRYBOT_DURATION), durationTags, duration, currentTime)
	}
	cqTryBotsMutex.RLock()
	glog.Infof("\t%d CQ bots have been triggered by %d/%d. There are %d CQ bots in cq.cfg", totalTriggeredCQBots, issue, patchsetId, len(c.cqTryBots))
	cqTryBotsMutex.RUnlock()
	numTags := map[string]string{
		"issue":    fmt.Sprintf("%d", issue),
		"patchset": fmt.Sprintf("%d", patchsetId),
	}
	metrics2.RawAddInt64PointAtTime(fmt.Sprintf("%s.%s.%s", c.metricName, INFLIGHT_METRIC_NAME, INFLIGHT_TRYBOT_NUM), numTags, int64(totalTriggeredCQBots), currentTime)
}

func (c *Client) isCQTryBot(builderName string) bool {
	cqTryBotsMutex.RLock()
	isCQTrybot := util.ContainsAny(builderName, c.cqTryBots)
	cqTryBotsMutex.RUnlock()
	return isCQTrybot
}
<|endoftext|>


<commit_before>// Copyright 2018 Google 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 buffer provides the implementation of a buffer view.
package buffer

// View is a slice of a buffer, with convenience methods.
type View []byte

// NewView allocates a new buffer and returns an initialized view that covers
// the whole buffer.
func NewView(size int) View {
	return make(View, size)
}

// NewViewFromBytes allocates a new buffer and copies in the given bytes.
func NewViewFromBytes(b []byte) View {
	return append(View(nil), b...)
}

// TrimFront removes the first "count" bytes from the visible section of the
// buffer.
func (v *View) TrimFront(count int) {
	*v = (*v)[count:]
}

// CapLength irreversibly reduces the length of the visible section of the
// buffer to the value specified.
func (v *View) CapLength(length int) {
	// We also set the slice cap because if we don't, one would be able to
	// expand the view back to include the region just excluded. We want to
	// prevent that to avoid potential data leak if we have uninitialized
	// data in excluded region.
	*v = (*v)[:length:length]
}

// ToVectorisedView transforms a View in a VectorisedView from an
// already-allocated slice of View.
func (v *View) ToVectorisedView(views [1]View) VectorisedView {
	views[0] = *v
	return NewVectorisedView(len(*v), views[:])
}

// VectorisedView is a vectorised version of View using non contigous memory.
// It supports all the convenience methods supported by View.
//
// +stateify savable
type VectorisedView struct {
	views []View
	size  int
}

// NewVectorisedView creates a new vectorised view from an already-allocated slice
// of View and sets its size.
func NewVectorisedView(size int, views []View) VectorisedView {
	return VectorisedView{views: views, size: size}
}

// TrimFront removes the first "count" bytes of the vectorised view.
func (vv *VectorisedView) TrimFront(count int) {
	for count > 0 && len(vv.views) > 0 {
		if count < len(vv.views[0]) {
			vv.size -= count
			vv.views[0].TrimFront(count)
			return
		}
		count -= len(vv.views[0])
		vv.RemoveFirst()
	}
}

// CapLength irreversibly reduces the length of the vectorised view.
func (vv *VectorisedView) CapLength(length int) {
	if length < 0 {
		length = 0
	}
	if vv.size < length {
		return
	}
	vv.size = length
	for i := range vv.views {
		v := &vv.views[i]
		if len(*v) >= length {
			if length == 0 {
				vv.views = vv.views[:i]
			} else {
				v.CapLength(length)
				vv.views = vv.views[:i+1]
			}
			return
		}
		length -= len(*v)
	}
}

// Clone returns a clone of this VectorisedView.
// If the buffer argument is large enough to contain all the Views of this VectorisedView,
// the method will avoid allocations and use the buffer to store the Views of the clone.
func (vv *VectorisedView) Clone(buffer []View) VectorisedView {
	var views []View
	if len(buffer) >= len(vv.views) {
		views = buffer[:len(vv.views)]
	} else {
		views = make([]View, len(vv.views))
	}
	for i, v := range vv.views {
		views[i] = v
	}
	return VectorisedView{views: views, size: vv.size}
}

// First returns the first view of the vectorised view.
func (vv *VectorisedView) First() View {
	if len(vv.views) == 0 {
		return nil
	}
	return vv.views[0]
}

// RemoveFirst removes the first view of the vectorised view.
func (vv *VectorisedView) RemoveFirst() {
	if len(vv.views) == 0 {
		return
	}
	vv.size -= len(vv.views[0])
	vv.views = vv.views[1:]
}

// SetSize unsafely sets the size of the VectorisedView.
func (vv *VectorisedView) SetSize(size int) {
	vv.size = size
}

// SetViews unsafely sets the views of the VectorisedView.
func (vv *VectorisedView) SetViews(views []View) {
	vv.views = views
}

// Size returns the size in bytes of the entire content stored in the vectorised view.
func (vv *VectorisedView) Size() int {
	return vv.size
}

// ToView returns a single view containing the content of the vectorised view.
func (vv *VectorisedView) ToView() View {
	v := make([]byte, vv.size)
	u := v
	for i := range vv.views {
		n := copy(u, vv.views[i])
		u = u[n:]
	}
	return v
}

// Views returns the slice containing the all views.
func (vv *VectorisedView) Views() []View {
	return vv.views
}

// ByteSlice returns a slice containing the all views as a []byte.
func (vv *VectorisedView) ByteSlice() [][]byte {
	s := make([][]byte, len(vv.views))
	for i := range vv.views {
		s[i] = []byte(vv.views[i])
	}
	return s
}

// copy returns a deep-copy of the vectorised view.
// It is an expensive method that should be used only in tests.
func (vv *VectorisedView) copy() *VectorisedView {
	uu := &VectorisedView{
		views: make([]View, len(vv.views)),
		size:  vv.size,
	}
	for i, v := range vv.views {
		uu.views[i] = make(View, len(v))
		copy(uu.views[i], v)
	}
	return uu
}
<commit_msg>Simplify some code in VectorisedView#ToView.<commit_after>// Copyright 2018 Google 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 buffer provides the implementation of a buffer view.
package buffer

// View is a slice of a buffer, with convenience methods.
type View []byte

// NewView allocates a new buffer and returns an initialized view that covers
// the whole buffer.
func NewView(size int) View {
	return make(View, size)
}

// NewViewFromBytes allocates a new buffer and copies in the given bytes.
func NewViewFromBytes(b []byte) View {
	return append(View(nil), b...)
}

// TrimFront removes the first "count" bytes from the visible section of the
// buffer.
func (v *View) TrimFront(count int) {
	*v = (*v)[count:]
}

// CapLength irreversibly reduces the length of the visible section of the
// buffer to the value specified.
func (v *View) CapLength(length int) {
	// We also set the slice cap because if we don't, one would be able to
	// expand the view back to include the region just excluded. We want to
	// prevent that to avoid potential data leak if we have uninitialized
	// data in excluded region.
	*v = (*v)[:length:length]
}

// ToVectorisedView transforms a View in a VectorisedView from an
// already-allocated slice of View.
func (v *View) ToVectorisedView(views [1]View) VectorisedView {
	views[0] = *v
	return NewVectorisedView(len(*v), views[:])
}

// VectorisedView is a vectorised version of View using non contigous memory.
// It supports all the convenience methods supported by View.
//
// +stateify savable
type VectorisedView struct {
	views []View
	size  int
}

// NewVectorisedView creates a new vectorised view from an already-allocated slice
// of View and sets its size.
func NewVectorisedView(size int, views []View) VectorisedView {
	return VectorisedView{views: views, size: size}
}

// TrimFront removes the first "count" bytes of the vectorised view.
func (vv *VectorisedView) TrimFront(count int) {
	for count > 0 && len(vv.views) > 0 {
		if count < len(vv.views[0]) {
			vv.size -= count
			vv.views[0].TrimFront(count)
			return
		}
		count -= len(vv.views[0])
		vv.RemoveFirst()
	}
}

// CapLength irreversibly reduces the length of the vectorised view.
func (vv *VectorisedView) CapLength(length int) {
	if length < 0 {
		length = 0
	}
	if vv.size < length {
		return
	}
	vv.size = length
	for i := range vv.views {
		v := &vv.views[i]
		if len(*v) >= length {
			if length == 0 {
				vv.views = vv.views[:i]
			} else {
				v.CapLength(length)
				vv.views = vv.views[:i+1]
			}
			return
		}
		length -= len(*v)
	}
}

// Clone returns a clone of this VectorisedView.
// If the buffer argument is large enough to contain all the Views of this VectorisedView,
// the method will avoid allocations and use the buffer to store the Views of the clone.
func (vv *VectorisedView) Clone(buffer []View) VectorisedView {
	var views []View
	if len(buffer) >= len(vv.views) {
		views = buffer[:len(vv.views)]
	} else {
		views = make([]View, len(vv.views))
	}
	for i, v := range vv.views {
		views[i] = v
	}
	return VectorisedView{views: views, size: vv.size}
}

// First returns the first view of the vectorised view.
func (vv *VectorisedView) First() View {
	if len(vv.views) == 0 {
		return nil
	}
	return vv.views[0]
}

// RemoveFirst removes the first view of the vectorised view.
func (vv *VectorisedView) RemoveFirst() {
	if len(vv.views) == 0 {
		return
	}
	vv.size -= len(vv.views[0])
	vv.views = vv.views[1:]
}

// SetSize unsafely sets the size of the VectorisedView.
func (vv *VectorisedView) SetSize(size int) {
	vv.size = size
}

// SetViews unsafely sets the views of the VectorisedView.
func (vv *VectorisedView) SetViews(views []View) {
	vv.views = views
}

// Size returns the size in bytes of the entire content stored in the vectorised view.
func (vv *VectorisedView) Size() int {
	return vv.size
}

// ToView returns a single view containing the content of the vectorised view.
func (vv *VectorisedView) ToView() View {
	u := make([]byte, 0, vv.size)
	for _, v := range vv.views {
		u = append(u, v...)
	}
	return u
}

// Views returns the slice containing the all views.
func (vv *VectorisedView) Views() []View {
	return vv.views
}

// ByteSlice returns a slice containing the all views as a []byte.
func (vv *VectorisedView) ByteSlice() [][]byte {
	s := make([][]byte, len(vv.views))
	for i := range vv.views {
		s[i] = []byte(vv.views[i])
	}
	return s
}

// copy returns a deep-copy of the vectorised view.
// It is an expensive method that should be used only in tests.
func (vv *VectorisedView) copy() *VectorisedView {
	uu := &VectorisedView{
		views: make([]View, len(vv.views)),
		size:  vv.size,
	}
	for i, v := range vv.views {
		uu.views[i] = make(View, len(v))
		copy(uu.views[i], v)
	}
	return uu
}
<|endoftext|>


<commit_before>package pod

import (
	"fmt"

	"github.com/hyperhq/hypercontainer-utils/hlog"
	apitypes "github.com/hyperhq/hyperd/types"
	runv "github.com/hyperhq/runv/api"
	"github.com/hyperhq/runv/hypervisor/network"
)

const DEFAULT_INTERFACE_NAME = "eth-default"

type Interface struct {
	p *XPod

	spec     *apitypes.UserInterface
	descript *runv.InterfaceDescription
}

func newInterface(p *XPod, spec *apitypes.UserInterface) *Interface {
	if spec.Ifname == "" {
		spec.Ifname = DEFAULT_INTERFACE_NAME
	}
	return &Interface{p: p, spec: spec}
}

func (inf *Interface) LogPrefix() string {
	return fmt.Sprintf("%sNic[%s] ", inf.p.LogPrefix(), inf.spec.Ifname)
}

func (inf *Interface) Log(level hlog.LogLevel, args ...interface{}) {
	hlog.HLog(level, inf, 1, args...)
}

func (inf *Interface) prepare() error {

	defer inf.Log(DEBUG, "prepare inf info: %#v", inf.descript)

	if inf.spec.Ip == "" && inf.spec.Bridge != "" {
		err := fmt.Errorf("if configured a bridge, must specify the IP address")
		inf.Log(ERROR, err)
		return err
	}

	if inf.spec.Ip == "" {
		setting, err := network.AllocateAddr("")
		if err != nil {
			inf.Log(ERROR, "failed to allocate IP: %v", err)
			return err
		}
		inf.descript = &runv.InterfaceDescription{
			Id:     setting.IPAddress,
			Lo:     false,
			Bridge: setting.Bridge,
			Ip:     setting.IPAddress,
			Mac:    setting.Mac,
			Gw:     setting.Gateway,
		}
		return nil
	}

	inf.descript = &runv.InterfaceDescription{
		Id:      inf.spec.Ifname,
		Lo:      false,
		Bridge:  inf.spec.Bridge,
		Ip:      inf.spec.Ip,
		Mac:     inf.spec.Mac,
		Gw:      inf.spec.Gateway,
		TapName: inf.spec.Tap,
	}

	return nil
}

func (inf *Interface) add() error {
	if inf.descript == nil || inf.descript.Ip == "" {
		err := fmt.Errorf("interfice has not ready %#v", inf.descript)
		inf.Log(ERROR, err)
		return err
	}
	err := inf.p.sandbox.AddNic(inf.descript)
	if err != nil {
		inf.Log(ERROR, "failed to add NIC: %v", err)
	}
	return err
}

func (inf *Interface) cleanup() error {
	if inf.spec.Ip != "" || inf.descript == nil || inf.descript.Ip == "" {
		return nil
	}

	inf.Log(DEBUG, "release IP address: %s", inf.descript.Ip)
	err := network.ReleaseAddr(inf.descript.Ip)
	if err != nil {
		inf.Log(ERROR, "failed to release IP %s: %v", inf.descript.Ip, nil)
	}
	return err
}
<commit_msg>fix debug message<commit_after>package pod

import (
	"fmt"

	"github.com/hyperhq/hypercontainer-utils/hlog"
	apitypes "github.com/hyperhq/hyperd/types"
	runv "github.com/hyperhq/runv/api"
	"github.com/hyperhq/runv/hypervisor/network"
)

const DEFAULT_INTERFACE_NAME = "eth-default"

type Interface struct {
	p *XPod

	spec     *apitypes.UserInterface
	descript *runv.InterfaceDescription
}

func newInterface(p *XPod, spec *apitypes.UserInterface) *Interface {
	if spec.Ifname == "" {
		spec.Ifname = DEFAULT_INTERFACE_NAME
	}
	return &Interface{p: p, spec: spec}
}

func (inf *Interface) LogPrefix() string {
	return fmt.Sprintf("%sNic[%s] ", inf.p.LogPrefix(), inf.spec.Ifname)
}

func (inf *Interface) Log(level hlog.LogLevel, args ...interface{}) {
	hlog.HLog(level, inf, 1, args...)
}

func (inf *Interface) prepare() error {
	defer func() {
		inf.Log(DEBUG, "prepare inf info: %#v", inf.descript)
	}()

	if inf.spec.Ip == "" && inf.spec.Bridge != "" {
		err := fmt.Errorf("if configured a bridge, must specify the IP address")
		inf.Log(ERROR, err)
		return err
	}

	if inf.spec.Ip == "" {
		setting, err := network.AllocateAddr("")
		if err != nil {
			inf.Log(ERROR, "failed to allocate IP: %v", err)
			return err
		}
		inf.descript = &runv.InterfaceDescription{
			Id:     setting.IPAddress,
			Lo:     false,
			Bridge: setting.Bridge,
			Ip:     setting.IPAddress,
			Mac:    setting.Mac,
			Gw:     setting.Gateway,
		}
		return nil
	}

	inf.descript = &runv.InterfaceDescription{
		Id:      inf.spec.Ifname,
		Lo:      false,
		Bridge:  inf.spec.Bridge,
		Ip:      inf.spec.Ip,
		Mac:     inf.spec.Mac,
		Gw:      inf.spec.Gateway,
		TapName: inf.spec.Tap,
	}

	return nil
}

func (inf *Interface) add() error {
	if inf.descript == nil || inf.descript.Ip == "" {
		err := fmt.Errorf("interfice has not ready %#v", inf.descript)
		inf.Log(ERROR, err)
		return err
	}
	err := inf.p.sandbox.AddNic(inf.descript)
	if err != nil {
		inf.Log(ERROR, "failed to add NIC: %v", err)
	}
	return err
}

func (inf *Interface) cleanup() error {
	if inf.spec.Ip != "" || inf.descript == nil || inf.descript.Ip == "" {
		return nil
	}

	inf.Log(DEBUG, "release IP address: %s", inf.descript.Ip)
	err := network.ReleaseAddr(inf.descript.Ip)
	if err != nil {
		inf.Log(ERROR, "failed to release IP %s: %v", inf.descript.Ip, nil)
	}
	return err
}
<|endoftext|>


<commit_before>package template
<commit_msg>implement simple go template engine<commit_after>package template

import (
	"io"
	gtemplate "text/template"
)

var _ Engine = (*goEngine)(nil)

//Engine represents a template engine
type Engine interface {
	Parse(text string) (Engine, error)
	Execute(writer io.Writer, data interface{}) error
}

type goEngine struct {
	template *gtemplate.Template
}

//NewGoEngine returns a new instance of a go template engine
func NewGoEngine(name string) Engine {
	template := gtemplate.New(name)
	return &goEngine{template}
}

func (g *goEngine) Parse(text string) (Engine, error) {
	var err error
	g.template, err = g.template.Parse(text)
	if err != nil {
		return nil, err
	}

	return g, nil
}

func (g *goEngine) Execute(writer io.Writer, data interface{}) error {
	return g.template.Execute(writer, data)
}
<|endoftext|>


<commit_before>package gogetter

import (
	"bitbucket.org/pkg/inflect"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"sync"
)

type Dream interface{}
type Goal func() Dream
type Lesson map[string]Dream

// TODO: Support Special Type
// type Inspiration func() Dream

type GoGetter struct {
	dreams map[string][]Dream
	db     Database
}

func NewGoGetter(db Database) *GoGetter {
	return &GoGetter{
		db:     db,
		dreams: map[string][]Dream{},
	}
}

var ErrGetterNotExist = errors.New("Getter Not Exist")
var defaultGetter = NewGoGetter(nil)
var goalMap = map[string]Goal{}
var tableNameMap = map[string]string{}

// Setting table name is optional, if table name is not specifically setted, gogetter
// will use the pluralization and lower case form of the name as table name, it will
// also replace all spaces with underscores.
func SetTableName(name, table string) {
	tableNameMap[name] = table
}

func GetTableName(name string) (table string, err error) {
	var ok bool
	table, ok = tableNameMap[name]
	if ok {
		return
	}

	if GetGoal(name) == nil {
		return "", ErrGetterNotExist
	}

	table = inflect.Pluralize(strings.ToLower(name))
	table = strings.Replace(table, " ", "_", -1)
	tableNameMap[name] = table

	return
}

// var mux = sync.Mutex{}

// SetGoal will save the Goal globally, then all gogetter values could share
// the same set of goals.
// Note:
// 	1. Leading asterisk (*) in name is saved for gogetter.
// 	2. The return value of goal must be a Struct, map or anything else is not supported.
func SetGoal(name string, goal Goal) {
	// mux.Lock()
	// defer mux.Unlock()

	goalMap[name] = goal
}

func GetGoal(name string) Goal {
	goal, ok := goalMap[name]
	if !ok {
		return nil
	}

	return goal
}

func SetDefaultGetterDb(db Database) {
	defaultGetter.db = db
}

// func Raise(children, parent string, lessons ...Lesson) {
// }

func Grow(name string, lessons ...Lesson) (dreams Dream, err error) {
	return defaultGetter.Grow(name, lessons...)
}

// Realize is similar to Grow, except for inserting records/docs in a provided database.
func Realize(name string, lessons ...Lesson) (dreams Dream, err error) {
	return defaultGetter.Realize(name, lessons...)
}

func AllInVain(name string, dreams ...Dream) (err error) {
	return defaultGetter.AllInVain(name, dreams...)
}

// Could use a leading asterisk (*) in name to get pointer value.
//
// 	TODO:
// 	1. Support anonymous type, e,g, custom struct, map, etc
// 	2. Tags specification in custom structs, provided that gogetter will support struct
func (gg *GoGetter) Grow(name string, lessons ...Lesson) (dreams Dream, err error) {
	return gg.makeDreams(name, false, lessons...)
}

func (gg *GoGetter) makeDreams(name string, saveInDb bool, lessons ...Lesson) (dreams Dream, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%+v", r)
		}
	}()

	inPointer := len(name) > 1 && name[0] == '*'
	if inPointer {
		name = name[1:]
	}
	goal := GetGoal(name)
	if goal == nil {
		return nil, ErrGetterNotExist
	}

	// Start Produce Dreams
	firstD := reflect.ValueOf(goal())
	dType := firstD.Type()
	if inPointer {
		dType = reflect.PtrTo(dType)
	}
	goalChan := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, dType), 1)
	if len(lessons) == 0 {
		lessons = append(lessons, nil)
	}
	for i, _ := range lessons {
		go func(index int) {
			defer func() {
				if r := recover(); r != nil {
					err = fmt.Errorf("%+v", r)
					goalChan.Send(reflect.Zero(dType))
				}
			}()

			var forebear reflect.Value
			if index == 0 {
				forebear = firstD
			} else {
				forebear = reflect.ValueOf(goal())
			}

			// TODO: refactor
			var dst, src reflect.Value
			theone := reflect.New(dType)
			if inPointer {
				if forebear.Kind() == reflect.Ptr {
					src = forebear.Elem()
					v := reflect.New(src.Type())
					v.Elem().Set(reflect.New(src.Type()).Elem())
					vv := reflect.New(v.Type())
					vv.Elem().Set(v)
					theone.Elem().Set(vv)
					dst = theone.Elem().Elem().Elem()
				} else {
					src = forebear
					v := reflect.New(src.Type())
					v.Elem().Set(reflect.New(src.Type()).Elem())
					theone.Elem().Set(v)
					dst = theone.Elem().Elem()
				}
			} else {
				if dType.Kind() == reflect.Ptr {
					src = forebear.Elem()
					v := reflect.New(src.Type())
					v.Elem().Set(reflect.New(src.Type()).Elem())
					theone.Elem().Set(v)
					dst = theone.Elem().Elem()
				} else {
					src = forebear
					dst = theone.Elem()
				}
			}

			for j := 0; j < src.NumField(); j++ {
				fIndex := []int{j}
				v := src.FieldByIndex(fIndex)
				dst.FieldByIndex(fIndex).Set(v)
			}

			lesson := lessons[index]
			for k, v := range lesson {
				dst.FieldByName(k).Set(reflect.ValueOf(v))
			}

			goalChan.Send(theone.Elem())
		}(i)
	}

	// Receive Dreams
	goals := reflect.MakeSlice(reflect.SliceOf(dType), 0, 0)
	for i := 0; i < len(lessons); i++ {
		goal, _ := goalChan.Recv()
		goals = reflect.Append(goals, goal)
		gg.dreams[name] = append(gg.dreams[name], goal.Interface())
	}

	table := ""
	if saveInDb && gg.db != nil {
		table, err = GetTableName(name)
		if err != nil {
			return
		}
	}

	if saveInDb && gg.db != nil {
		records := []interface{}{}
		for i := 0; i < goals.Len(); i++ {
			records = append(records, goals.Index(i).Interface())
		}
		err = gg.db.Create(table, records...)
	}

	// Return userful/handy results
	if goals.Len() == 0 {
		dreams = reflect.Zero(dType).Interface()
	} else if goals.Len() == 1 {
		dreams = goals.Index(0).Interface()
	} else {
		dreams = goals.Interface()
	}

	return
}

// Grow and Create a Record in Database
func (gg *GoGetter) Realize(name string, lessons ...Lesson) (dreams Dream, err error) {
	return gg.makeDreams(name, true, lessons...)
}

var allInVainMutex = sync.Mutex{}

// Remove from database (Do not use leading * in name with this function)
// TODO: enable field tag configuration
func (gg *GoGetter) AllInVain(name string, dreams ...Dream) (err error) {
	allInVainMutex.Lock()
	defer allInVainMutex.Unlock()

	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%+v", r)
		}
	}()

	if len(dreams) == 1 && reflect.TypeOf(dreams[0]).Kind() == reflect.Slice {
		packedDreams := reflect.ValueOf(dreams[0])
		dreams = []Dream{}
		for i := 0; i < packedDreams.Len(); i++ {
			dreams = append(dreams, packedDreams.Index(i).Interface())
		}
	}

	table, err := GetTableName(name)
	if err != nil {
		return
	}

	if len(dreams) == 0 {
		dreams = gg.dreams[name]
		if len(dreams) == 0 {
			return
		}
	}

	idField := getDreamIdField(name)
	if idField == "" {
		err = errors.New("Id Field is Not Exist")
		return
	}

	// records := []Record{}
	ids := []interface{}{}
	for i, _ := range dreams {
		// records = append(records, gg.retrieveRecord(dreams[i]))
		ids = append(ids, gg.retrieveDreamId(dreams[i], idField))
	}

	survivedDreams := []Dream{}
	for _, dream := range gg.dreams[name] {
		// dreamRecord := gg.retrieveRecord(dream)
		dreamId := gg.retrieveDreamId(dream, idField)
		// for _, dyingDream := range records {
		for _, id := range ids {
			// if reflect.DeepEqual(dreamRecord.Identity(), dyingDream.Identity()) {
			if reflect.DeepEqual(id, dreamId) {
				goto hell
			}
		}

		survivedDreams = append(survivedDreams, dream)

	hell:
	}
	gg.dreams[name] = survivedDreams

	if gg.db != nil {
		err = gg.db.Remove(table, ids...)
	}

	return
}

func (gg *GoGetter) retrieveDreamId(dream Dream, idField string) (id interface{}) {
	dv := reflect.ValueOf(dream)

retriving:
	// TODO: refactor
	if dv.Type().Kind() == reflect.Ptr {
		dv = dv.Elem()
		goto retriving
	} else {
		idFieldV := dv.FieldByName(idField)
		if idFieldV.IsValid() {
			id = idFieldV.Interface()
		}
	}

	return
}

var defaultTableId = "Id"

// Table Id is used when gogetter is trying remove records from table, using a simple
// sql/mongo statement to remove the data.
// Default Table Id is "Id", its value must be comparable via reflect.DeepEqual.
func SetDefaultTableId(name string) {
	defaultTableId = name
}

var dreamIdFieldMap = map[string]string{}

func getDreamIdField(name string) (id string) {
	var ok bool
	if id, ok = dreamIdFieldMap[name]; ok {
		return
	}

	// Validation of Goal must make before calling this method
	dType := reflect.TypeOf(GetGoal(name)())
	for {
		// TODO: refactor
		if dType.Kind() == reflect.Ptr {
			dType = dType.Elem()
		} else {
			break
		}
	}

	for i := 0; i < dType.NumField(); i++ {
		field := dType.Field(i)
		gogetterTag := field.Tag.Get("gogetter")
		if gogetterTag == "id" {
			id = field.Name
			break
		}
	}

	if id == "" {
		if _, ok := dType.FieldByName(defaultTableId); ok {
			id = defaultTableId
		}
	}

	dreamIdFieldMap[name] = id
	return
}

func Apocalypse(names ...string) (err error) {
	return defaultGetter.Apocalypse(names...)
}

func (gg *GoGetter) Apocalypse(names ...string) (err error) {
	if len(names) == 0 {
		for k, _ := range gg.dreams {
			names = append(names, k)
		}
	}

	errchan := make(chan error)
	for i, _ := range names {
		name := names[i]
		go func() {
			errchan <- gg.AllInVain(name)
		}()
	}
	for i := 0; i < len(names); i++ {
		er := <-errchan
		if er == nil {
			continue
		}
		err = errors.New(err.Error() + er.Error())
	}

	return
}

// func (gg *GoGetter) Raise(children, parent string, lessons ...Lesson) {
// }
<commit_msg>better documentation<commit_after>package gogetter

import (
	"bitbucket.org/pkg/inflect"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"sync"
)

type Dream interface{}
type Goal func() Dream
type Lesson map[string]Dream

// TODO: Support Special Type
// type Inspiration func() Dream

type GoGetter struct {
	dreams map[string][]Dream
	db     Database
}

func NewGoGetter(db Database) *GoGetter {
	return &GoGetter{
		db:     db,
		dreams: map[string][]Dream{},
	}
}

var ErrGetterNotExist = errors.New("Getter Not Exist")
var defaultGetter = NewGoGetter(nil)
var goalMap = map[string]Goal{}
var tableNameMap = map[string]string{}

// Setting table name is optional, if table name is not specifically setted, gogetter
// will use the pluralization and lower case form of the name as table name, it will
// also replace all spaces with underscores.
func SetTableName(name, table string) {
	tableNameMap[name] = table
}

func GetTableName(name string) (table string, err error) {
	var ok bool
	table, ok = tableNameMap[name]
	if ok {
		return
	}

	if GetGoal(name) == nil {
		return "", ErrGetterNotExist
	}

	table = inflect.Pluralize(strings.ToLower(name))
	table = strings.Replace(table, " ", "_", -1)
	tableNameMap[name] = table

	return
}

// var mux = sync.Mutex{}

// SetGoal will save the Goal globally, then all gogetter values could share
// the same set of goals.
// Note:
// 	1. Leading asterisk (*) in name is saved for gogetter.
// 	2. The return value of goal must be a Struct, map or anything else is not supported.
func SetGoal(name string, goal Goal) {
	// mux.Lock()
	// defer mux.Unlock()

	goalMap[name] = goal
}

func GetGoal(name string) Goal {
	goal, ok := goalMap[name]
	if !ok {
		return nil
	}

	return goal
}

func SetDefaultGetterDb(db Database) {
	defaultGetter.db = db
}

// func Raise(children, parent string, lessons ...Lesson) {
// }

// See (gg *GoGetter) Grow.
func Grow(name string, lessons ...Lesson) (dreams Dream, err error) {
	return defaultGetter.Grow(name, lessons...)
}

// Realize is similar to Grow, except for inserting records/docs in a provided database.
func Realize(name string, lessons ...Lesson) (dreams Dream, err error) {
	return defaultGetter.Realize(name, lessons...)
}

// See (gg *GoGetter) AllInVain.
func AllInVain(name string, dreams ...Dream) (err error) {
	return defaultGetter.AllInVain(name, dreams...)
}

// Could use a leading asterisk (*) in name to get pointer value.
//
// 	TODO:
// 	1. Support anonymous type, e,g, custom struct, map, etc
// 	2. Tags specification in custom structs, provided that gogetter will support struct
func (gg *GoGetter) Grow(name string, lessons ...Lesson) (dreams Dream, err error) {
	return gg.makeDreams(name, false, lessons...)
}

func (gg *GoGetter) makeDreams(name string, saveInDb bool, lessons ...Lesson) (dreams Dream, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%+v", r)
		}
	}()

	inPointer := len(name) > 1 && name[0] == '*'
	if inPointer {
		name = name[1:]
	}
	goal := GetGoal(name)
	if goal == nil {
		return nil, ErrGetterNotExist
	}

	// Start Produce Dreams
	firstD := reflect.ValueOf(goal())
	dType := firstD.Type()
	if inPointer {
		dType = reflect.PtrTo(dType)
	}
	goalChan := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, dType), 1)
	if len(lessons) == 0 {
		lessons = append(lessons, nil)
	}
	for i, _ := range lessons {
		go func(index int) {
			defer func() {
				if r := recover(); r != nil {
					err = fmt.Errorf("%+v", r)
					goalChan.Send(reflect.Zero(dType))
				}
			}()

			var forebear reflect.Value
			if index == 0 {
				forebear = firstD
			} else {
				forebear = reflect.ValueOf(goal())
			}

			// TODO: refactor
			var dst, src reflect.Value
			theone := reflect.New(dType)
			if inPointer {
				if forebear.Kind() == reflect.Ptr {
					src = forebear.Elem()
					v := reflect.New(src.Type())
					v.Elem().Set(reflect.New(src.Type()).Elem())
					vv := reflect.New(v.Type())
					vv.Elem().Set(v)
					theone.Elem().Set(vv)
					dst = theone.Elem().Elem().Elem()
				} else {
					src = forebear
					v := reflect.New(src.Type())
					v.Elem().Set(reflect.New(src.Type()).Elem())
					theone.Elem().Set(v)
					dst = theone.Elem().Elem()
				}
			} else {
				if dType.Kind() == reflect.Ptr {
					src = forebear.Elem()
					v := reflect.New(src.Type())
					v.Elem().Set(reflect.New(src.Type()).Elem())
					theone.Elem().Set(v)
					dst = theone.Elem().Elem()
				} else {
					src = forebear
					dst = theone.Elem()
				}
			}

			for j := 0; j < src.NumField(); j++ {
				fIndex := []int{j}
				v := src.FieldByIndex(fIndex)
				dst.FieldByIndex(fIndex).Set(v)
			}

			lesson := lessons[index]
			for k, v := range lesson {
				dst.FieldByName(k).Set(reflect.ValueOf(v))
			}

			goalChan.Send(theone.Elem())
		}(i)
	}

	// Receive Dreams
	goals := reflect.MakeSlice(reflect.SliceOf(dType), 0, 0)
	for i := 0; i < len(lessons); i++ {
		goal, _ := goalChan.Recv()
		goals = reflect.Append(goals, goal)
		gg.dreams[name] = append(gg.dreams[name], goal.Interface())
	}

	table := ""
	if saveInDb && gg.db != nil {
		table, err = GetTableName(name)
		if err != nil {
			return
		}
	}

	if saveInDb && gg.db != nil {
		records := []interface{}{}
		for i := 0; i < goals.Len(); i++ {
			records = append(records, goals.Index(i).Interface())
		}
		err = gg.db.Create(table, records...)
	}

	// Return userful/handy results
	if goals.Len() == 0 {
		dreams = reflect.Zero(dType).Interface()
	} else if goals.Len() == 1 {
		dreams = goals.Index(0).Interface()
	} else {
		dreams = goals.Interface()
	}

	return
}

// Grow and Create a Record in Database
func (gg *GoGetter) Realize(name string, lessons ...Lesson) (dreams Dream, err error) {
	return gg.makeDreams(name, true, lessons...)
}

var allInVainMutex = sync.Mutex{}

// TODO: [AllInVain] enable field tag configuration

//
// Destroy data created by Grow/Realize (Do not use leading * in name with this function).
//
// Usage:
//
// 	gogetter.AllInVain("Users") // Will destory all "Users"
// 	gogetter.AllInVain("Users", user1, user2) // Only destory user1 and user2
// 	gogetter.AllInVain("users", users) // users is a slice of User
//
func (gg *GoGetter) AllInVain(name string, dreams ...Dream) (err error) {
	allInVainMutex.Lock()
	defer allInVainMutex.Unlock()

	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%+v", r)
		}
	}()

	if len(dreams) == 1 && reflect.TypeOf(dreams[0]).Kind() == reflect.Slice {
		packedDreams := reflect.ValueOf(dreams[0])
		dreams = []Dream{}
		for i := 0; i < packedDreams.Len(); i++ {
			dreams = append(dreams, packedDreams.Index(i).Interface())
		}
	}

	table, err := GetTableName(name)
	if err != nil {
		return
	}

	if len(dreams) == 0 {
		dreams = gg.dreams[name]
		if len(dreams) == 0 {
			return
		}
	}

	idField := getDreamIdField(name)
	if idField == "" {
		err = errors.New("Id Field is Not Exist")
		return
	}

	// records := []Record{}
	ids := []interface{}{}
	for i, _ := range dreams {
		// records = append(records, gg.retrieveRecord(dreams[i]))
		ids = append(ids, gg.retrieveDreamId(dreams[i], idField))
	}

	survivedDreams := []Dream{}
	for _, dream := range gg.dreams[name] {
		// dreamRecord := gg.retrieveRecord(dream)
		dreamId := gg.retrieveDreamId(dream, idField)
		// for _, dyingDream := range records {
		for _, id := range ids {
			// if reflect.DeepEqual(dreamRecord.Identity(), dyingDream.Identity()) {
			if reflect.DeepEqual(id, dreamId) {
				goto hell
			}
		}

		survivedDreams = append(survivedDreams, dream)

	hell:
	}
	gg.dreams[name] = survivedDreams

	if gg.db != nil {
		err = gg.db.Remove(table, ids...)
	}

	return
}

func (gg *GoGetter) retrieveDreamId(dream Dream, idField string) (id interface{}) {
	dv := reflect.ValueOf(dream)

retriving:
	// TODO: refactor
	if dv.Type().Kind() == reflect.Ptr {
		dv = dv.Elem()
		goto retriving
	} else {
		idFieldV := dv.FieldByName(idField)
		if idFieldV.IsValid() {
			id = idFieldV.Interface()
		}
	}

	return
}

var defaultTableId = "Id"

// Table Id is used when gogetter is trying remove records from table, using a simple
// sql/mongo statement to remove the data.
// Default Table Id is "Id", its value must be comparable via reflect.DeepEqual.
func SetDefaultTableId(name string) {
	defaultTableId = name
}

var dreamIdFieldMap = map[string]string{}

func getDreamIdField(name string) (id string) {
	var ok bool
	if id, ok = dreamIdFieldMap[name]; ok {
		return
	}

	// Validation of Goal must make before calling this method
	dType := reflect.TypeOf(GetGoal(name)())
	for {
		// TODO: refactor
		if dType.Kind() == reflect.Ptr {
			dType = dType.Elem()
		} else {
			break
		}
	}

	for i := 0; i < dType.NumField(); i++ {
		field := dType.Field(i)
		gogetterTag := field.Tag.Get("gogetter")
		if gogetterTag == "id" {
			id = field.Name
			break
		}
	}

	if id == "" {
		if _, ok := dType.FieldByName(defaultTableId); ok {
			id = defaultTableId
		}
	}

	dreamIdFieldMap[name] = id
	return
}

// See (gg *GoGetter) Apocalypse.
func Apocalypse(names ...string) (err error) {
	return defaultGetter.Apocalypse(names...)
}

// Apocalypse is designed as a handy method to replace AllInVain in cases like
// simply wipe out all data created by Grow/Realize.
//
// Usage:
//
// 	gogetter.Apocalypse("Users") // Will remove all "Users" data
// 	gogetter.Apocalypse("Users", "Another Goals") // Will remove all "Users" and "Another Goals" data
// 	gogetter.Apocalypse() // Will destroy all data
//
func (gg *GoGetter) Apocalypse(names ...string) (err error) {
	if len(names) == 0 {
		for k, _ := range gg.dreams {
			names = append(names, k)
		}
	}

	errchan := make(chan error)
	for i, _ := range names {
		name := names[i]
		go func() {
			errchan <- gg.AllInVain(name)
		}()
	}
	for i := 0; i < len(names); i++ {
		er := <-errchan
		if er == nil {
			continue
		}
		err = errors.New(err.Error() + er.Error())
	}

	return
}

// func (gg *GoGetter) Raise(children, parent string, lessons ...Lesson) {
// }
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"os"
	"strings"
	"sync"
	"time"
)

type peerKind int

const (
	client peerKind = iota
	server
)

type peerMsg struct {
	FromID  string
	Message string
}

type peerInfo struct {
	Kind          peerKind
	Name          string
	ID            string
	Channel       chan *peerMsg
	ConnectedWith string
	LastContact   time.Time
	Waiting       bool
}

func (m peerInfo) DisplayName() string {
	return fmt.Sprintf("%s@%s[%s]", m.Name, m.ID, m.ConnectedWith)
}

func (m peerInfo) InfoString() string {
	return fmt.Sprintf("%s,%s,1", m.Name, m.ID)
}

const peerIDParamName string = "peer_id"
const toParamName string = "to"

const peerMessageBufferSize int = 100

var peers = make(map[string]*peerInfo)

var peerIDCount uint
var peerMutex sync.Mutex

func printReqHandler(res http.ResponseWriter, req *http.Request) {
	reqDump, err := httputil.DumpRequest(req, true)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(reqDump))
}

func registerHandler(path string, handlerFunc http.Handler) {
	if path != "" {
		fmt.Printf("Registering handler for %s", path)
		fmt.Println()
		http.Handle(path, handlerFunc)
	}
}

func setConnectionHeader(header http.Header, close bool) {
	if close {
		header.Set("Connection", "close")
	} else {
		header.Set("Connection", "keep-alive")
	}
}

func setVersionHeader(header http.Header) {
	header.Set("Server", "PeerConnectionTestServer/0.1g")
}

func setNoCacheHeader(header http.Header) {
	header.Set("Cache-Control", "no-cache")
}

func addCorsHeaders(header http.Header) {
	header.Set("Access-Control-Allow-Origin", "*")
	header.Set("Access-Control-Allow-Credentials", "true")
	header.Set("Access-Control-Allow-Methods", strings.Join([]string{"GET", "POST", "OPTIONS"}, ","))
	header.Set("Access-Control-Allow-Headers", strings.Join([]string{"Content-Type", "Content-Length", "Cache-Control", "Connection"}, ","))
	header.Set("Access-Control-Expose-Headers", strings.Join([]string{"Content-Length", "X-Peer-Id"}, ","))
}

func setPragmaHeader(header http.Header, peerID string) {
	header.Set("Pragma", peerID)
}

func printStats() {
	var serverCount int
	var clientCount int
	for _, v := range peers {
		if v.Kind == server {
			serverCount++
		} else {
			clientCount++
		}
	}
	fmt.Printf("TotalPeers: %d, Servers: %d, Clients: %d\n", len(peers), serverCount, clientCount)
}

func commonHeaderMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		setNoCacheHeader(res.Header())
		setVersionHeader(res.Header())
		addCorsHeaders(res.Header())
		setConnectionHeader(res.Header(), true)
		next.ServeHTTP(res, req)
	})
}

func signinHandler(res http.ResponseWriter, req *http.Request) {

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	var name string
	// Parse out peer name
	for k, v := range req.URL.Query() {
		// Pick the first query param without a value
		//  e.g. /sign_in?notname=foo&name should pick 'name'
		if v[0] == "" {
			name = k
			break
		}
	}

	if name == "" {
		http.Error(res, "No name", http.StatusBadRequest)
		return
	}

	var peerInfo peerInfo
	peerInfo.Name = name
	peerInfo.Channel = make(chan *peerMsg, peerMessageBufferSize)
	peerInfo.LastContact = time.Now().UTC()

	// Determine peer type
	if strings.Index(name, "renderingserver_") == 0 {
		peerInfo.Kind = server
	}

	// Generate id
	peerMutex.Lock()
	peerIDCount++
	peerInfo.ID = fmt.Sprintf("%d", peerIDCount)
	peerMutex.Unlock()

	peers[peerInfo.ID] = &peerInfo

	setPragmaHeader(res.Header(), peerInfo.ID)

	peerInfoString := peerInfo.InfoString()
	peerInfoString += fmt.Sprintln()
	responseString := peerInfoString

	// Return above + current peers (filtered for oppositing type)
	for pID, pInfo := range peers {
		if pID != peerInfo.ID && pInfo.Kind != peerInfo.Kind && pInfo.ConnectedWith == "" {
			responseString += pInfo.InfoString()
			responseString += fmt.Sprintln()

			// Also notify these peers that the new one exists
			if len(pInfo.Channel) < cap(pInfo.Channel) {
				pInfo.Channel <- &peerMsg{pInfo.ID, peerInfoString}
			} else {
				fmt.Printf("WARNING: Dropped message for peer %s", pInfo.DisplayName())
				// TODO: Figure out what to do when peeer message buffer fills up
			}
		}
	}
	res.WriteHeader(http.StatusOK)
	_, err := fmt.Fprintf(res, responseString)
	if err != nil {
		fmt.Printf("ERROR: %v\n", err)
	}
	fmt.Printf("sign-in - Peer: %s\n", peerInfo.DisplayName())
	printStats()
}

func signoutHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	var peerID string
	// Parse out peers id
	peerIDValues, peerIDExists := req.URL.Query()[peerIDParamName]
	if peerIDExists {
		peerID = peerIDValues[0]
	}

	peer, exists := peers[peerID]
	if !exists || peer == nil {
		http.Error(res, "Unknown peer", http.StatusBadRequest)
		return
	}

	if peer.ConnectedWith != "" {
		connectedPeer, connectionExists := peers[peer.ConnectedWith]
		if connectionExists && connectedPeer != nil {
			connectedPeer.ConnectedWith = ""
		}
	}

	setPragmaHeader(res.Header(), peerID)
	delete(peers, peerID)
	res.WriteHeader(http.StatusOK)

	fmt.Printf("sign-out - Peer: %s\n", peer.DisplayName())
	printStats()
}

func messageHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "POST" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out from id
	// Parse out to id
	peerIDValues, peerExists := req.URL.Query()[peerIDParamName]
	toIDValues, toExists := req.URL.Query()[toParamName]

	if !peerExists || !toExists {
		http.Error(res, "Missing Peer or To ID", http.StatusBadRequest)
		return
	}

	peerID := peerIDValues[0]
	toID := toIDValues[0]

	from, peerInfoExists := peers[peerID]
	to, toInfoExists := peers[toID]

	if !peerInfoExists || !toInfoExists || from == nil || to == nil {
		http.Error(res, "Invalid Peer or To ID", http.StatusBadRequest)
		return
	}

	from.LastContact = time.Now().UTC()

	if from.ConnectedWith == "" {
		fmt.Printf("Connecting %s with %s\n", from.DisplayName(), to.DisplayName())
		from.ConnectedWith = to.ID
	}

	if to.ConnectedWith == "" {
		fmt.Printf("Connecting %s with %s\n", to.DisplayName(), from.DisplayName())
		to.ConnectedWith = from.ID
	}

	if from.ConnectedWith != to.ID {
		fmt.Printf("WARNING: Peer sending message to recipient outside room\n")
	}

	setPragmaHeader(res.Header(), peerID)

	requestData, err := ioutil.ReadAll(req.Body)
	if err != nil {
		http.Error(res, err.Error(), http.StatusInternalServerError)
	}
	requestString := string(requestData)
	defer req.Body.Close()
	// Look up channel for to id
	if len(to.Channel) == cap(to.Channel) {
		http.Error(res, "Peer is backed up", http.StatusServiceUnavailable)
		return
	}
	to.Channel <- &peerMsg{peerID, requestString}

	// Send message to channel for to id
	res.WriteHeader(http.StatusOK)
	fmt.Printf("message: %s -> %s: \n\t%s\n", from.DisplayName(), to.DisplayName(), requestString)
}

func waitHandler(res http.ResponseWriter, req *http.Request) {

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out peer id
	peerIDValues, peerExists := req.URL.Query()[peerIDParamName]

	if !peerExists {
		http.Error(res, "Missing Peer ID", http.StatusBadRequest)
		return
	}

	peerID := peerIDValues[0]

	peerInfo, peerInfoExists := peers[peerID]

	if !peerInfoExists || peerInfo == nil {
		http.Error(res, "Unknown peer", http.StatusBadRequest)
		return
	}
	peerInfo.LastContact = time.Now().UTC()
	peerInfo.Waiting = true

	fmt.Printf("wait: Peer %s waiting...\n", peerInfo.DisplayName())
	// Look up message channel for peers id
	// Wait for message to reply
	var peerMsg *peerMsg
	var cancelled bool
	select {
	case peerMsg = <-(peerInfo.Channel):
	case <-req.Context().Done():
		cancelled = true
	}
	peerInfo.Waiting = false
	if cancelled {
		fmt.Printf("Peer (%s) cancelled/closed connection. Terminating wait call.\n", peerInfo.DisplayName())
		return
	}
	if peerMsg == nil {
		fmt.Printf("Error: nil peerMsg in channel")
		http.Error(res, "Bad message", http.StatusInternalServerError)
		return
	}

	peerInfo.LastContact = time.Now().UTC()

	setPragmaHeader(res.Header(), peerMsg.FromID)
	res.WriteHeader(http.StatusOK)
	_, err := fmt.Fprint(res, peerMsg.Message)
	if err != nil {
		fmt.Printf("ERROR: %v\n", err)
	}

	fmt.Printf("wait: Peer %s recieved message from ID %s\n\t%s\n\n", peerInfo.DisplayName(), peerMsg.FromID, peerMsg.Message)
}

func peerCleanupRoutine() {
	tickerChan := time.NewTicker(time.Second * 30).C

	for {
		<-tickerChan
		fmt.Printf("Checking for stale peers\n")
		printStats()
		for k, v := range peers {
			if !v.Waiting && (time.Now().UTC().Sub(v.LastContact) > time.Minute*1) {
				fmt.Printf("Removing stale peer %s\n", v.DisplayName())
				connectedWithPeer := peers[v.ConnectedWith]
				if connectedWithPeer != nil {
					fmt.Printf("Disconnecting peer %s with id %s\n", v.DisplayName(), connectedWithPeer.DisplayName())
					connectedWithPeer.ConnectedWith = ""
				}
				delete(peers, k)
			}
		}
	}
}

func main() {

	fmt.Println("gosigsrv starting")
	fmt.Println()

	port := os.Getenv("PORT")
	if port == "" {
		port = "8087"
	}

	fmt.Printf("Will listen on port %s\n\n", port)

	// Register handlers
	registerHandler("/sign_in", commonHeaderMiddleware(http.HandlerFunc(signinHandler)))
	registerHandler("/sign_out", commonHeaderMiddleware(http.HandlerFunc(signoutHandler)))
	registerHandler("/message", commonHeaderMiddleware(http.HandlerFunc(messageHandler)))
	registerHandler("/wait", commonHeaderMiddleware(http.HandlerFunc(waitHandler)))
	registerHandler("/", commonHeaderMiddleware(http.HandlerFunc(printReqHandler)))

	// Start peer cleenup timer routine
	go peerCleanupRoutine()

	// Start listening
	err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
	if err != nil {
		fmt.Println("Error:")
		fmt.Println(err)
	}
	fmt.Println()
	fmt.Println("gosigsrv exiting")
	if err != nil {
		os.Exit(2)
	} else {
		os.Exit(0)
	}
}
<commit_msg>Embrace Stringer interface<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"os"
	"strings"
	"sync"
	"time"
)

type peerKind int

const (
	client peerKind = iota
	server
)

type peerMsg struct {
	FromID  string
	Message string
}

type peerInfo struct {
	Kind          peerKind
	Name          string
	ID            string
	Channel       chan *peerMsg
	ConnectedWith string
	LastContact   time.Time
	Waiting       bool
}

func (m peerInfo) String() string {
	return fmt.Sprintf("%s@%s[%s]", m.Name, m.ID, m.ConnectedWith)
}

func (m peerInfo) InfoString() string {
	return fmt.Sprintf("%s,%s,1", m.Name, m.ID)
}

const peerIDParamName string = "peer_id"
const toParamName string = "to"

const peerMessageBufferSize int = 100

var peers = make(map[string]*peerInfo)

var peerIDCount uint
var peerMutex sync.Mutex

func printReqHandler(res http.ResponseWriter, req *http.Request) {
	reqDump, err := httputil.DumpRequest(req, true)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(reqDump))
}

func registerHandler(path string, handlerFunc http.Handler) {
	if path != "" {
		fmt.Printf("Registering handler for %s", path)
		fmt.Println()
		http.Handle(path, handlerFunc)
	}
}

func setConnectionHeader(header http.Header, close bool) {
	if close {
		header.Set("Connection", "close")
	} else {
		header.Set("Connection", "keep-alive")
	}
}

func setVersionHeader(header http.Header) {
	header.Set("Server", "PeerConnectionTestServer/0.1g")
}

func setNoCacheHeader(header http.Header) {
	header.Set("Cache-Control", "no-cache")
}

func addCorsHeaders(header http.Header) {
	header.Set("Access-Control-Allow-Origin", "*")
	header.Set("Access-Control-Allow-Credentials", "true")
	header.Set("Access-Control-Allow-Methods", strings.Join([]string{"GET", "POST", "OPTIONS"}, ","))
	header.Set("Access-Control-Allow-Headers", strings.Join([]string{"Content-Type", "Content-Length", "Cache-Control", "Connection"}, ","))
	header.Set("Access-Control-Expose-Headers", strings.Join([]string{"Content-Length", "X-Peer-Id"}, ","))
}

func setPragmaHeader(header http.Header, peerID string) {
	header.Set("Pragma", peerID)
}

func printStats() {
	var serverCount int
	var clientCount int
	for _, v := range peers {
		if v.Kind == server {
			serverCount++
		} else {
			clientCount++
		}
	}
	fmt.Printf("TotalPeers: %d, Servers: %d, Clients: %d\n", len(peers), serverCount, clientCount)
}

func commonHeaderMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		setNoCacheHeader(res.Header())
		setVersionHeader(res.Header())
		addCorsHeaders(res.Header())
		setConnectionHeader(res.Header(), true)
		next.ServeHTTP(res, req)
	})
}

func signinHandler(res http.ResponseWriter, req *http.Request) {

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	var name string
	// Parse out peer name
	for k, v := range req.URL.Query() {
		// Pick the first query param without a value
		//  e.g. /sign_in?notname=foo&name should pick 'name'
		if v[0] == "" {
			name = k
			break
		}
	}

	if name == "" {
		http.Error(res, "No name", http.StatusBadRequest)
		return
	}

	var peerInfo peerInfo
	peerInfo.Name = name
	peerInfo.Channel = make(chan *peerMsg, peerMessageBufferSize)
	peerInfo.LastContact = time.Now().UTC()

	// Determine peer type
	if strings.Index(name, "renderingserver_") == 0 {
		peerInfo.Kind = server
	}

	// Generate id
	peerMutex.Lock()
	peerIDCount++
	peerInfo.ID = fmt.Sprintf("%d", peerIDCount)
	peerMutex.Unlock()

	peers[peerInfo.ID] = &peerInfo

	setPragmaHeader(res.Header(), peerInfo.ID)

	peerInfoString := peerInfo.InfoString()
	peerInfoString += fmt.Sprintln()
	responseString := peerInfoString

	// Return above + current peers (filtered for oppositing type)
	for pID, pInfo := range peers {
		if pID != peerInfo.ID && pInfo.Kind != peerInfo.Kind && pInfo.ConnectedWith == "" {
			responseString += pInfo.InfoString()
			responseString += fmt.Sprintln()

			// Also notify these peers that the new one exists
			if len(pInfo.Channel) < cap(pInfo.Channel) {
				pInfo.Channel <- &peerMsg{pInfo.ID, peerInfoString}
			} else {
				fmt.Printf("WARNING: Dropped message for peer %s", pInfo)
				// TODO: Figure out what to do when peeer message buffer fills up
			}
		}
	}
	res.WriteHeader(http.StatusOK)
	_, err := fmt.Fprintf(res, responseString)
	if err != nil {
		fmt.Printf("ERROR: %v\n", err)
	}
	fmt.Printf("sign-in - Peer: %s\n", peerInfo)
	printStats()
}

func signoutHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	var peerID string
	// Parse out peers id
	peerIDValues, peerIDExists := req.URL.Query()[peerIDParamName]
	if peerIDExists {
		peerID = peerIDValues[0]
	}

	peer, exists := peers[peerID]
	if !exists || peer == nil {
		http.Error(res, "Unknown peer", http.StatusBadRequest)
		return
	}

	if peer.ConnectedWith != "" {
		connectedPeer, connectionExists := peers[peer.ConnectedWith]
		if connectionExists && connectedPeer != nil {
			connectedPeer.ConnectedWith = ""
		}
	}

	setPragmaHeader(res.Header(), peerID)
	delete(peers, peerID)
	res.WriteHeader(http.StatusOK)

	fmt.Printf("sign-out - Peer: %s\n", peer)
	printStats()
}

func messageHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "POST" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out from id
	// Parse out to id
	peerIDValues, peerExists := req.URL.Query()[peerIDParamName]
	toIDValues, toExists := req.URL.Query()[toParamName]

	if !peerExists || !toExists {
		http.Error(res, "Missing Peer or To ID", http.StatusBadRequest)
		return
	}

	peerID := peerIDValues[0]
	toID := toIDValues[0]

	from, peerInfoExists := peers[peerID]
	to, toInfoExists := peers[toID]

	if !peerInfoExists || !toInfoExists || from == nil || to == nil {
		http.Error(res, "Invalid Peer or To ID", http.StatusBadRequest)
		return
	}

	from.LastContact = time.Now().UTC()

	if from.ConnectedWith == "" {
		fmt.Printf("Connecting %s with %s\n", from, to)
		from.ConnectedWith = to.ID
	}

	if to.ConnectedWith == "" {
		fmt.Printf("Connecting %s with %s\n", to, from)
		to.ConnectedWith = from.ID
	}

	if from.ConnectedWith != to.ID {
		fmt.Printf("WARNING: Peer sending message to recipient outside room\n")
	}

	setPragmaHeader(res.Header(), peerID)

	requestData, err := ioutil.ReadAll(req.Body)
	if err != nil {
		http.Error(res, err.Error(), http.StatusInternalServerError)
	}
	requestString := string(requestData)
	defer req.Body.Close()
	// Look up channel for to id
	if len(to.Channel) == cap(to.Channel) {
		http.Error(res, "Peer is backed up", http.StatusServiceUnavailable)
		return
	}
	to.Channel <- &peerMsg{peerID, requestString}

	// Send message to channel for to id
	res.WriteHeader(http.StatusOK)
	fmt.Printf("message: %s -> %s: \n\t%s\n", from, to, requestString)
}

func waitHandler(res http.ResponseWriter, req *http.Request) {

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out peer id
	peerIDValues, peerExists := req.URL.Query()[peerIDParamName]

	if !peerExists {
		http.Error(res, "Missing Peer ID", http.StatusBadRequest)
		return
	}

	peerID := peerIDValues[0]

	peerInfo, peerInfoExists := peers[peerID]

	if !peerInfoExists || peerInfo == nil {
		http.Error(res, "Unknown peer", http.StatusBadRequest)
		return
	}
	peerInfo.LastContact = time.Now().UTC()
	peerInfo.Waiting = true

	fmt.Printf("wait: Peer %s waiting...\n", peerInfo)
	// Look up message channel for peers id
	// Wait for message to reply
	var peerMsg *peerMsg
	var cancelled bool
	select {
	case peerMsg = <-(peerInfo.Channel):
	case <-req.Context().Done():
		cancelled = true
	}
	peerInfo.Waiting = false
	if cancelled {
		fmt.Printf("Peer (%s) cancelled/closed connection. Terminating wait call.\n", peerInfo)
		return
	}
	if peerMsg == nil {
		fmt.Printf("Error: nil peerMsg in channel")
		http.Error(res, "Bad message", http.StatusInternalServerError)
		return
	}

	peerInfo.LastContact = time.Now().UTC()

	setPragmaHeader(res.Header(), peerMsg.FromID)
	res.WriteHeader(http.StatusOK)
	_, err := fmt.Fprint(res, peerMsg.Message)
	if err != nil {
		fmt.Printf("ERROR: %v\n", err)
	}

	fmt.Printf("wait: Peer %s recieved message from ID %s\n\t%s\n\n", peerInfo, peerMsg.FromID, peerMsg.Message)
}

func peerCleanupRoutine() {
	tickerChan := time.NewTicker(time.Second * 30).C

	for {
		<-tickerChan
		fmt.Printf("Checking for stale peers\n")
		printStats()
		for k, v := range peers {
			if !v.Waiting && (time.Now().UTC().Sub(v.LastContact) > time.Minute*1) {
				fmt.Printf("Removing stale peer %s\n", v)
				connectedWithPeer := peers[v.ConnectedWith]
				if connectedWithPeer != nil {
					fmt.Printf("Disconnecting peer %s with id %s\n", v, connectedWithPeer)
					connectedWithPeer.ConnectedWith = ""
				}
				delete(peers, k)
			}
		}
	}
}

func main() {

	fmt.Println("gosigsrv starting")
	fmt.Println()

	port := os.Getenv("PORT")
	if port == "" {
		port = "8087"
	}

	fmt.Printf("Will listen on port %s\n\n", port)

	// Register handlers
	registerHandler("/sign_in", commonHeaderMiddleware(http.HandlerFunc(signinHandler)))
	registerHandler("/sign_out", commonHeaderMiddleware(http.HandlerFunc(signoutHandler)))
	registerHandler("/message", commonHeaderMiddleware(http.HandlerFunc(messageHandler)))
	registerHandler("/wait", commonHeaderMiddleware(http.HandlerFunc(waitHandler)))
	registerHandler("/", commonHeaderMiddleware(http.HandlerFunc(printReqHandler)))

	// Start peer cleenup timer routine
	go peerCleanupRoutine()

	// Start listening
	err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
	if err != nil {
		fmt.Println("Error:")
		fmt.Println(err)
	}
	fmt.Println()
	fmt.Println("gosigsrv exiting")
	if err != nil {
		os.Exit(2)
	} else {
		os.Exit(0)
	}
}
<|endoftext|>