<commit_before>package main

import (
	"encoding/binary"
	"fmt"
	ent "github.com/fclairamb/m2mp/go/m2mp-db/entities"
	"log"
	"net"
	"strings"
	"time"
)

type MessagePing struct {
	data byte
}

type MessageData struct {
	channelName string
}

type MessageDataSimple struct {
	MessageData
	data []byte
}

type MessageDataArray struct {
	MessageData
	data [][]byte
}

type ClientHandler struct {
	Id             int
	Conn           net.Conn
	daddy          *Server
	connectionTime time.Time
	device         *ent.Device
	sendChannels   map[string]int
	recvChannels   map[int]string
}

func NewClientHandler(daddy *Server, id int, conn net.Conn) *ClientHandler {
	ch := &ClientHandler{
		daddy:          daddy,
		Id:             id,
		Conn:           conn,
		connectionTime: time.Now().UTC(),
		sendChannels:   make(map[string]int),
		recvChannels:   make(map[int]string)}

	return ch
}

func (ch *ClientHandler) Start() {
	if par.LogLevel >= 3 {
		log.Print("Added ", ch, " / ", ch.daddy.NbClients())
	}
	go ch.handleConnection()
}

func (ch *ClientHandler) end() {
	ch.daddy.removeClientHandler(ch)
	if par.LogLevel >= 3 {
		log.Print("Removed ", ch)
	}
}

func (ch *ClientHandler) sendData(channel string, data []byte) error {
	if par.LogLevel >= 7 {
		log.Print(ch, " <-- \"", channel, "\" : ", data)
	}

	var channelId int

	if id, ok := ch.sendChannels[channel]; ok {
		channelId = id
	} else {
		channelId = len(ch.sendChannels)
		if channelId >= 255 {
			ch.sendChannels = make(map[string]int)
			channelId = 0
		}
		frame := []byte{0x20, byte(1 + len(channel)), byte(channelId)}
		frame = append(frame, []byte(channel)...)

		ch.sendChannels[channel] = channelId

		if par.LogLevel >= 7 {
			log.Print(ch, " --> \"", channel, "\" created on ", channelId)
		}

		_, err := ch.Conn.Write(frame)
		return err
	}

	if par.LogLevel >= 7 {
		log.Print(ch, " --> \"", channel, "\" : ", data)
	}

	return nil
}

func (ch *ClientHandler) handleData(channel string, data []byte) error {
	if par.LogLevel >= 7 {
		log.Print(ch, " --> \"", channel, "\" : ", data)
	}

	tokens := strings.SplitN(channel, ":", 2)

	switch tokens[0] {
	case "_set": // settings have their own logic
		{

		}

	case "_cmd": // commands have their own logic
		{

		}
	case "echo": // echo is just replied
		{
			ch.sendData(channel, data)
		}

	case "sen": // sensor is just stored
		{
			if ch.device != nil {
				ch.device.SaveTSTime(channel, time.Now().UTC(), string(data))
			}
		}
	}

	return nil
}

func (ch *ClientHandler) handleDataArray(channel string, data [][]byte) error {
	if par.LogLevel >= 7 {
		log.Print(ch, " --> \"", channel, "\" : ", data)
	}

	return nil
}

const BUFFER_SIZE = 1024

func (ch *ClientHandler) handleConnection() {
	// When things will go wrong, we will end it properly
	defer ch.end()
	buffer := make([]byte, BUFFER_SIZE)
	for {
		if cap(buffer) > BUFFER_SIZE*2 {
			buffer = make([]byte, BUFFER_SIZE)
		}

		_, err := ch.Conn.Read(buffer[0:2])

		if err != nil {
			log.Print("Problem with ", ch, " : ", err)
			break
		}

		switch buffer[0] {

		case 0x01: // Identification
			{
				size := buffer[1]
				ch.Conn.Read(buffer[:size])
				ident := string(buffer[:size])
				if par.LogLevel >= 5 {
					log.Print(ch, " --> Identification: ", ident)
					ch.device, err = ent.NewDeviceByIdentCreate(ident)
					if err != nil {
						log.Print("Problem with ", ch, " : ", err)
					}
				}

				// OK
				if err == nil {
					ch.Conn.Write([]byte{0x01, 0x01})
				} else {
					ch.Conn.Write([]byte{0x01, 0x00})
				}
			}

		case 0x02: // Ping
			{
				if par.LogLevel >= 7 {
					log.Print(ch, " - Ping: ", buffer[1])
				}
				ch.Conn.Write(buffer[0:2])
			}

		case 0x20: // Named channel
			{
				size := buffer[1]
				ch.Conn.Read(buffer[:size])
				channelId := int(buffer[0])
				channelName := string(buffer[1:size])
				ch.recvChannels[channelId] = channelName
				if par.LogLevel >= 7 {
					log.Print(ch, " --> \"", channelName, "\" created on ", channelId)
				}
			}
		// All the data messages
		case 0x21, 0x41, 0x61, 0x22, 0x42, 0x62:
			{
				// We handle all size of messages at the same place
				var sizeLength int
				switch buffer[0] {
				case 0x21, 0x22:
					sizeLength = 1 // 1 byte sized (0 to 255)
				case 0x41, 0x42:
					sizeLength = 2 // 2 bytes sized (0 to 64K)
				case 0x61, 0x62:
					sizeLength = 4 // 4 bytes sized (0 to 4G)
				}
				// We get the necessary remaining bytes of the size
				ch.Conn.Read(buffer[2 : sizeLength+1])

				// We convert this to a size
				var size int
				{
					s, _ := binary.Uvarint(buffer[1 : sizeLength+1])
					size = int(s)
				}

				// We might have to increase the buffer size
				if size > cap(buffer) {
					if par.LogLevel >= 7 {
						log.Printf("%s - Increasing buffer size to %d bytes.", ch, size)
					}
					buffer = make([]byte, size)
				}

				// We read everything (we get rid of existing buffer content)
				ch.Conn.Read(buffer[:size])

				// We get the channel id
				channelId := int(buffer[0])
				channelName := ch.recvChannels[channelId]
				switch buffer[0] {

				case 0x21, 0x41, 0x61: // Single byte array messages
					{
						err = ch.handleData(channelName, buffer[1:size-1])
					}
				case 0x22, 0x42, 0x62: // Array of byte arrays messages
					{
						offset := 1
						data := make([][]byte, 5)
						for offset < size {
							s, _ := binary.Uvarint(buffer[offset : offset+sizeLength])
							subSize := int(s)
							offset += sizeLength
							ch.Conn.Read(buffer[offset : offset+subSize])
							data = append(data, buffer[offset:offset+subSize])
							offset += subSize
						}
						err = ch.handleDataArray(channelName, data)
					}
				}
				// And we finally handle the data

			}
		default:
			{
				log.Printf("%s - Unhandled header: 0x%02X", ch, buffer[0:1])
				ch.Conn.Close()
				return
			}
		}
	}
}

func (ch *ClientHandler) String() string {
	return fmt.Sprintf("CH{Id=%d,Conn=%v}", ch.Id, ch.Conn.RemoteAddr())
}
<commit_msg>Backup commit before adding a new package.<commit_after>package main

import (
	"encoding/binary"
	"fmt"
	ent "github.com/fclairamb/m2mp/go/m2mp-db/entities"
	"log"
	"net"
	"strings"
	"time"
)

type MessagePing struct {
	Data byte
}

type MessageDataSimple struct {
	ChannelName string
	Data        []byte
}

type MessageDataArray struct {
	ChannelName string
	Data        [][]byte
}

type ClientHandler struct {
	Id             int
	Conn           net.Conn
	daddy          *Server
	connectionTime time.Time
	device         *ent.Device
	sendChannels   map[string]int
	recvChannels   map[int]string
}

func NewClientHandler(daddy *Server, id int, conn net.Conn) *ClientHandler {
	ch := &ClientHandler{
		daddy:          daddy,
		Id:             id,
		Conn:           conn,
		connectionTime: time.Now().UTC(),
		sendChannels:   make(map[string]int),
		recvChannels:   make(map[int]string)}

	return ch
}

func (ch *ClientHandler) Start() {
	if par.LogLevel >= 3 {
		log.Print("Added ", ch, " / ", ch.daddy.NbClients())
	}
	go ch.handleConnection()
}

func (ch *ClientHandler) end() {
	ch.daddy.removeClientHandler(ch)
	if par.LogLevel >= 3 {
		log.Print("Removed ", ch)
	}
}

func (ch *ClientHandler) sendData(channel string, data []byte) error {
	if par.LogLevel >= 7 {
		log.Print(ch, " <-- \"", channel, "\" : ", data)
	}

	var channelId int

	if id, ok := ch.sendChannels[channel]; ok {
		channelId = id
	} else {
		channelId = len(ch.sendChannels)
		if channelId >= 255 {
			ch.sendChannels = make(map[string]int)
			channelId = 0
		}
		frame := []byte{0x20, byte(1 + len(channel)), byte(channelId)}
		frame = append(frame, []byte(channel)...)

		ch.sendChannels[channel] = channelId

		if par.LogLevel >= 7 {
			log.Print(ch, " --> \"", channel, "\" created on ", channelId)
		}

		_, err := ch.Conn.Write(frame)
		return err
	}

	if par.LogLevel >= 7 {
		log.Print(ch, " --> \"", channel, "\" : ", data)
	}

	return nil
}

func (ch *ClientHandler) handlePing(msg *MessagePing) error {
	_, err := ch.Conn.Write([]byte{0x02, msg.Data})
	return err
}

func (ch *ClientHandler) handleData(msg *MessageDataSimple) error {
	if par.LogLevel >= 7 {
		log.Print(ch, " --> \"", msg.ChannelName, "\" : ", msg.Data)
	}

	tokens := strings.SplitN(msg.ChannelName, ":", 2)

	switch tokens[0] {
	case "_set": // settings have their own logic
		{

		}

	case "_cmd": // commands have their own logic
		{

		}
	case "_sta": // status have their own logic
		{

		}
	case "echo": // echo is just replied
		{
			ch.sendData(msg.ChannelName, msg.Data)
		}

	case "sen": // sensor is just stored
		{
			if ch.device != nil {
				ch.device.SaveTSTime(msg.ChannelName, time.Now().UTC(), string(msg.Data))
			}
		}
	}

	return nil
}

func (ch *ClientHandler) handleDataArray(msg *MessageDataArray) error {
	if par.LogLevel >= 7 {
		log.Print(ch, " --> \"", msg.ChannelName, "\" : ", msg.Data)
	}

	return nil
}

const BUFFER_SIZE = 1024

func (ch *ClientHandler) handleConnection() {
	// When things will go wrong, we will end it properly
	defer ch.end()
	buffer := make([]byte, BUFFER_SIZE)
	for {
		if cap(buffer) > BUFFER_SIZE*2 {
			buffer = make([]byte, BUFFER_SIZE)
		}

		_, err := ch.Conn.Read(buffer[0:2])

		if err != nil {
			log.Print("Problem with ", ch, " : ", err)
			break
		}

		switch buffer[0] {

		case 0x01: // Identification
			{
				size := buffer[1]
				ch.Conn.Read(buffer[:size])
				ident := string(buffer[:size])
				if par.LogLevel >= 5 {
					log.Print(ch, " --> Identification: ", ident)
					ch.device, err = ent.NewDeviceByIdentCreate(ident)
					if err != nil {
						log.Print("Problem with ", ch, " : ", err)
					}
				}

				// OK
				if err == nil {
					ch.Conn.Write([]byte{0x01, 0x01})
				} else {
					ch.Conn.Write([]byte{0x01, 0x00})
				}
			}

		case 0x02: // Ping
			{
				if par.LogLevel >= 7 {
					log.Print(ch, " - Ping: ", buffer[1])
				}
				ch.Conn.Write(buffer[0:2])
			}

		case 0x20: // Named channel
			{
				size := buffer[1]
				ch.Conn.Read(buffer[:size])
				channelId := int(buffer[0])
				channelName := string(buffer[1:size])
				ch.recvChannels[channelId] = channelName
				if par.LogLevel >= 7 {
					log.Print(ch, " --> \"", channelName, "\" created on ", channelId)
				}
			}
		// All the data messages
		case 0x21, 0x41, 0x61, 0x22, 0x42, 0x62:
			{
				// We handle all size of messages at the same place
				var sizeLength int
				switch buffer[0] {
				case 0x21, 0x22:
					sizeLength = 1 // 1 byte sized (0 to 255)
				case 0x41, 0x42:
					sizeLength = 2 // 2 bytes sized (0 to 64K)
				case 0x61, 0x62:
					sizeLength = 4 // 4 bytes sized (0 to 4G)
				}
				// We get the necessary remaining bytes of the size
				ch.Conn.Read(buffer[2 : sizeLength+1])

				// We convert this to a size
				var size int
				{
					s, _ := binary.Uvarint(buffer[1 : sizeLength+1])
					size = int(s)
				}

				// We might have to increase the buffer size
				if size > cap(buffer) {
					if par.LogLevel >= 7 {
						log.Printf("%s - Increasing buffer size to %d bytes.", ch, size)
					}
					buffer = make([]byte, size)
				}

				// We read everything (we get rid of existing buffer content)
				ch.Conn.Read(buffer[:size])

				// We get the channel id
				channelId := int(buffer[0])
				channelName := ch.recvChannels[channelId]
				switch buffer[0] {

				case 0x21, 0x41, 0x61: // Single byte array messages
					{
						err = ch.handleData(&MessageDataSimple{ChannelName: channelName, Data: buffer[1 : size-1]})
					}
				case 0x22, 0x42, 0x62: // Array of byte arrays messages
					{
						offset := 1
						data := make([][]byte, 5)
						for offset < size {
							s, _ := binary.Uvarint(buffer[offset : offset+sizeLength])
							subSize := int(s)
							offset += sizeLength
							ch.Conn.Read(buffer[offset : offset+subSize])
							data = append(data, buffer[offset:offset+subSize])
							offset += subSize
						}
						err = ch.handleDataArray(&MessageDataArray{ChannelName: channelName, Data: data})
					}
				}
				// And we finally handle the data

			}
		default:
			{
				log.Printf("%s - Unhandled header: 0x%02X", ch, buffer[0:1])
				ch.Conn.Close()
				return
			}
		}
	}
}

func (ch *ClientHandler) String() string {
	return fmt.Sprintf("CH{Id=%d,Conn=%v}", ch.Id, ch.Conn.RemoteAddr())
}
<|endoftext|>


<commit_before>package cipher

import (
	"strings"
	"unicode"
)

type caesar struct{}
type shift struct {
	distance int
}
type vigenere struct {
	key string
}

func NewCaesar() Cipher {
	return caesar{}
}

func (c caesar) Encode(s string) (encoded string) {
	for _, r := range strings.ToLower(s) {
		if unicode.IsLetter(r) {
			encodedR := ((r-97)+3)%26 + 97
			encoded += string(encodedR)
		}
	}
	return encoded
}

func (c caesar) Decode(s string) (decoded string) {
	for _, r := range strings.ToLower(s) {
		if unicode.IsLetter(r) {
			// HACKHACK go % operator returns a negative number for -2 % 26 so add 26 then modulo 26
			// https://stackoverflow.com/questions/43018206/modulo-of-negative-integers-in-go
			decodedR := (((r-97)-3)%26+26)%26 + 97
			decoded += string(decodedR)
		}
	}
	return decoded
}

func NewShift(distance int) Cipher {
	if distance >= 26 || distance == 0 || distance <= -26 {
		return nil
	}
	return shift{distance: distance}
}

func (c shift) Encode(input string) (encoded string) {
	for _, r := range strings.ToLower(input) {
		if unicode.IsLetter(r) {
			encoded += string(shiftLetter(r, c.distance))
		}
	}
	return encoded
}

func (c shift) Decode(input string) (decoded string) {
	for _, r := range strings.ToLower(input) {
		if unicode.IsLetter(r) {
			decoded += string(shiftLetter(r, -c.distance))
		}
	}
	return decoded
}

func NewVigenere(key string) Cipher {
	return vigenere{
		key: key,
	}
}

func (v vigenere) Encode(input string) string {
	panic("Please implement the Encode function")
}

func (v vigenere) Decode(input string) string {
	panic("Please implement the Decode function")
}

func shiftLetter(letter rune, distance int) (shifted rune) {
	d := rune(((int(letter-97)+distance)%26)+26)%26 + 97
	return rune(d)
}
<commit_msg>Use shiftLetter everywhere<commit_after>package cipher

import (
	"strings"
	"unicode"
)

type caesar struct{}
type shift struct {
	distance int
}
type vigenere struct {
	key string
}

func NewCaesar() Cipher {
	return caesar{}
}

func (c caesar) Encode(s string) (encoded string) {
	for _, r := range strings.ToLower(s) {
		if unicode.IsLetter(r) {
			encoded += string(shiftLetter(r, 3))
		}
	}
	return encoded
}

func (c caesar) Decode(s string) (decoded string) {
	for _, r := range strings.ToLower(s) {
		if unicode.IsLetter(r) {
			decoded += string(shiftLetter(r, -3))
		}
	}
	return decoded
}

func NewShift(distance int) Cipher {
	if distance >= 26 || distance == 0 || distance <= -26 {
		return nil
	}
	return shift{distance: distance}
}

func (c shift) Encode(input string) (encoded string) {
	for _, r := range strings.ToLower(input) {
		if unicode.IsLetter(r) {
			encoded += string(shiftLetter(r, c.distance))
		}
	}
	return encoded
}

func (c shift) Decode(input string) (decoded string) {
	for _, r := range strings.ToLower(input) {
		if unicode.IsLetter(r) {
			decoded += string(shiftLetter(r, -c.distance))
		}
	}
	return decoded
}

func NewVigenere(key string) Cipher {
	return vigenere{
		key: key,
	}
}

func (v vigenere) Encode(input string) string {
	panic("Please implement the Encode function")
}

func (v vigenere) Decode(input string) string {
	panic("Please implement the Decode function")
}

func shiftLetter(letter rune, distance int) (shifted rune) {
	// HACKHACK go % operator returns a negative number for -2 % 26 so add 26 then modulo 26
	// https://stackoverflow.com/questions/43018206/modulo-of-negative-integers-in-go
	d := rune(((int(letter-97)+distance)%26)+26)%26 + 97
	return rune(d)
}
<|endoftext|>


<commit_before>package hey

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestRecentActivity(t *testing.T) {
	chrono, err := New()
	assert.NoError(t, err)
	assert.NotNil(t, chrono)

	thread := &Thread{
		ThreadID: "ud1",
	}
	err = chrono.NewThread(thread)
	assert.NoError(t, err)

	user1 := &User{
		UserID: "user1",
	}
	err = chrono.NewUser(user1)
	assert.NoError(t, err)

	err = chrono.Observe(user1.UserID, thread.ThreadID)
	assert.NoError(t, err)

	lastTs := time.Now()

	event1 := &Event{
		EventID:   "ev1",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event2 := &Event{
		EventID:   "ev2",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event3 := &Event{
		EventID:   "ev3",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event4 := &Event{
		EventID:   "ev4",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}

	for _, ev := range []*Event{event1, event2, event3, event4} {
		err = chrono.NewEvent(ev)
		assert.NoError(t, err)
	}

	events, err := chrono.RecentActivityByLastTS(user1.UserID, thread.ThreadID, 3, lastTs)
	assert.NoError(t, err)
	assert.Equal(t, 3, len(events))
	assert.Equal(t, "ev4", events[0].EventID)
	assert.Equal(t, "ev3", events[1].EventID)
	assert.Equal(t, "ev2", events[2].EventID)

}

func TestRecentActivityThreadline(t *testing.T) {
	chrono, err := New()
	assert.NoError(t, err)
	assert.NotNil(t, chrono)

	thread := &Thread{
		ThreadID:          "ud2",
		ThreadlineEnabled: true,
	}
	err = chrono.NewThread(thread)
	assert.NoError(t, err)

	user1 := &User{
		UserID: "user2",
	}
	err = chrono.NewUser(user1)
	assert.NoError(t, err)

	err = chrono.Observe(user1.UserID, thread.ThreadID)
	assert.NoError(t, err)

	lastTs := time.Now()

	event1 := &Event{
		EventID:   "ev5",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event2 := &Event{
		EventID:   "ev6",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event3 := &Event{
		EventID:   "ev7",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event4 := &Event{
		EventID:   "ev8",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}

	for _, ev := range []*Event{event1, event2, event3, event4} {
		err = chrono.NewEvent(ev)
		assert.NoError(t, err)
	}

	events, err := chrono.RecentActivityByLastTS(user1.UserID, thread.ThreadID, 3, lastTs)
	assert.NoError(t, err)
	assert.Equal(t, 3, len(events))
	assert.Equal(t, "ev8", events[0].EventID)
	assert.Equal(t, "ev7", events[1].EventID)
	assert.Equal(t, "ev6", events[2].EventID)

}
<commit_msg>add test RecentActivity<commit_after>package hey

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestRecentActivity(t *testing.T) {
	chrono, err := New()
	assert.NoError(t, err)
	assert.NotNil(t, chrono)

	thread := &Thread{
		ThreadID: "ud3",
	}
	err = chrono.NewThread(thread)
	assert.NoError(t, err)

	user1 := &User{
		UserID: "user11",
	}
	err = chrono.NewUser(user1)
	assert.NoError(t, err)

	err = chrono.Observe(user1.UserID, thread.ThreadID)
	assert.NoError(t, err)

	event1 := &Event{
		EventID:   "ev100",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event2 := &Event{
		EventID:   "ev101",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event3 := &Event{
		EventID:   "ev102",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event4 := &Event{
		EventID:   "ev103",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event5 := &Event{
		EventID:   "ev104",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}

	for _, ev := range []*Event{event1, event2, event3, event4, event5} {
		err = chrono.NewEvent(ev)
		assert.NoError(t, err)
	}

	events, err := chrono.RecentActivity(user1.UserID, thread.ThreadID, 1, 3)
	assert.NoError(t, err)
	assert.Equal(t, 3, len(events))
	assert.Equal(t, "ev103", events[0].EventID)
	assert.Equal(t, "ev102", events[1].EventID)
	assert.Equal(t, "ev101", events[2].EventID)
}

func TestRecentActivityByLastTs(t *testing.T) {
	chrono, err := New()
	assert.NoError(t, err)
	assert.NotNil(t, chrono)

	thread := &Thread{
		ThreadID: "ud1",
	}
	err = chrono.NewThread(thread)
	assert.NoError(t, err)

	user1 := &User{
		UserID: "user1",
	}
	err = chrono.NewUser(user1)
	assert.NoError(t, err)

	err = chrono.Observe(user1.UserID, thread.ThreadID)
	assert.NoError(t, err)

	lastTs := time.Now()

	event1 := &Event{
		EventID:   "ev1",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event2 := &Event{
		EventID:   "ev2",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event3 := &Event{
		EventID:   "ev3",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event4 := &Event{
		EventID:   "ev4",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}

	for _, ev := range []*Event{event1, event2, event3, event4} {
		err = chrono.NewEvent(ev)
		assert.NoError(t, err)
	}

	events, err := chrono.RecentActivityByLastTS(user1.UserID, thread.ThreadID, 3, lastTs)
	assert.NoError(t, err)
	assert.Equal(t, 3, len(events))
	assert.Equal(t, "ev4", events[0].EventID)
	assert.Equal(t, "ev3", events[1].EventID)
	assert.Equal(t, "ev2", events[2].EventID)

}

func TestRecentActivityThreadline(t *testing.T) {
	chrono, err := New()
	assert.NoError(t, err)
	assert.NotNil(t, chrono)

	thread := &Thread{
		ThreadID:          "ud2",
		ThreadlineEnabled: true,
	}
	err = chrono.NewThread(thread)
	assert.NoError(t, err)

	user1 := &User{
		UserID: "user2",
	}
	err = chrono.NewUser(user1)
	assert.NoError(t, err)

	err = chrono.Observe(user1.UserID, thread.ThreadID)
	assert.NoError(t, err)

	lastTs := time.Now()

	event1 := &Event{
		EventID:   "ev5",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event2 := &Event{
		EventID:   "ev6",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event3 := &Event{
		EventID:   "ev7",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}
	event4 := &Event{
		EventID:   "ev8",
		ThreadID:  thread.ThreadID,
		CreatedAt: time.Now(),
	}

	for _, ev := range []*Event{event1, event2, event3, event4} {
		err = chrono.NewEvent(ev)
		assert.NoError(t, err)
	}

	events, err := chrono.RecentActivityByLastTS(user1.UserID, thread.ThreadID, 3, lastTs)
	assert.NoError(t, err)
	assert.Equal(t, 3, len(events))
	assert.Equal(t, "ev8", events[0].EventID)
	assert.Equal(t, "ev7", events[1].EventID)
	assert.Equal(t, "ev6", events[2].EventID)

}
<|endoftext|>


<commit_before>package task_test

import (
	"fmt"
	"github.com/anupcshan/neoism"
	"github.com/anupcshan/sciforme/task"
	"github.com/jmcvetta/randutil"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"testing"
)

const DESC_LEN = 20

type MockGraphDB struct {
	mock.Mock
}

func (m *MockGraphDB) CreateNode(props neoism.Props) (*neoism.Node, error) {
	args := m.Mock.Called(props)
	node, ok := args.Get(0).(*neoism.Node)
	if !ok {
		return nil, args.Error(1)
	}
	return node, args.Error(1)
}

func (m *MockGraphDB) NodesByLabel(label string) ([]*neoism.Node, error) {
	args := m.Mock.Called(label)
	nodes, ok := args.Get(0).([]*neoism.Node)
	if !ok {
		return nil, args.Error(1)
	}
	return nodes, args.Error(1)
}

func TestAddTaskNoDB(t *testing.T) {
	tm := task.TaskManager{}
	err, tsk := tm.AddTask("foo")

	assert.Error(t, err, "Error expected when DB instance was nil")
	assert.Nil(t, tsk, "Expected nil value for task")
}

func TestAddTaskDBNotConnected(t *testing.T) {
	mdb := new(MockGraphDB)
	tm := task.TaskManager{Database: mdb}

	str, _ := randutil.AlphaString(DESC_LEN)
	mdb.On("CreateNode", neoism.Props{"name": str}).Return(nil, fmt.Errorf(""))
	err, tsk := tm.AddTask(str)

	assert.Error(t, err, "Error expected when non-working DB instance was provided")
	assert.Nil(t, tsk, "Expected nil value for task")
}

func TestAddTaskSuccess(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	db, _ := neoism.Connect("http://localhost:7474/db/data")
	tm := task.TaskManager{Database: db}

	str, _ := randutil.AlphaString(DESC_LEN)
	err, tsk := tm.AddTask(str)

	assert.NoError(t, err, nil, "No error expected while adding a new node")
	assert.NotNil(t, tsk, "Expected non-nil value for task")

	node, err := db.Node(tsk.Id)
	assert.NoError(t, err, nil, "Could not find newly created node in DB")

	// Cleanup
	defer node.Delete()

	name, err := node.Property("name")
	assert.NoError(t, err, nil, "'name' property not set")
	assert.Equal(t, name, str, "Testing task name")

	labels, _ := node.Labels()
	assert.Equal(t, labels[0], task.TASK_LABEL, "Testing task label")
}

func TestListTasksNoDB(t *testing.T) {
	tm := task.TaskManager{}
	err, tasks := tm.ListTasks()

	assert.Error(t, err, "Error expected when DB instance was nil")
	assert.Nil(t, tasks, "Expected nil value for list of tasks")
}

func TestListTasksDBNotConnected(t *testing.T) {
	mdb := new(MockGraphDB)
	tm := task.TaskManager{Database: mdb}

	mdb.On("NodesByLabel", task.TASK_LABEL).Return(nil, fmt.Errorf(""))
	err, tasks := tm.ListTasks()

	assert.Error(t, err, "Error expected when non-working DB instance was provided")
	assert.Nil(t, tasks, "Expected nil value for list of tasks")
}

func TestListTasksEmpty(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	db, _ := neoism.Connect("http://localhost:7474/db/data")
	tm := task.TaskManager{Database: db}

	err, tasks := tm.ListTasks()

	assert.NoError(t, err, nil, "No error expected while fetching empty list")
	assert.Empty(t, tasks, "Expected empty list of tasks")
}

func TestListTasksSingle(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	db, _ := neoism.Connect("http://localhost:7474/db/data")
	tm := task.TaskManager{Database: db}

	str, _ := randutil.AlphaString(DESC_LEN)
	err, tsk := tm.AddTask(str)
	node, err := db.Node(tsk.Id)
	// Cleanup
	defer node.Delete()

	err, tasks := tm.ListTasks()

	assert.NoError(t, err, nil, "No error expected while listing tasks")
	assert.Equal(t, len(tasks), 1, "Expected list of size 1")
}
<commit_msg>Fix tests.<commit_after>package task_test

import (
	"fmt"
	"github.com/anupcshan/neoism"
	"github.com/anupcshan/sciforme/task"
	"github.com/jmcvetta/randutil"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"testing"
)

const DESC_LEN = 20

type MockGraphDB struct {
	mock.Mock
}

func (m *MockGraphDB) CreateNode(props neoism.Props) (*neoism.Node, error) {
	args := m.Mock.Called(props)
	node, ok := args.Get(0).(*neoism.Node)
	if !ok {
		return nil, args.Error(1)
	}
	return node, args.Error(1)
}

func (m *MockGraphDB) NodesByLabel(label string) ([]*neoism.Node, error) {
	args := m.Mock.Called(label)
	nodes, ok := args.Get(0).([]*neoism.Node)
	if !ok {
		return nil, args.Error(1)
	}
	return nodes, args.Error(1)
}

func TestAddTaskNoDB(t *testing.T) {
	tm := task.TaskManager{}
	err, tsk := tm.AddTask("foo")

	assert.Error(t, err, "Error expected when DB instance was nil")
	assert.Nil(t, tsk, "Expected nil value for task")
}

func TestAddTaskDBNotConnected(t *testing.T) {
	mdb := new(MockGraphDB)
	tm := task.TaskManager{Database: mdb}

	str, _ := randutil.AlphaString(DESC_LEN)
	mdb.On("CreateNode", neoism.Props{"name": str}).Return(nil, fmt.Errorf(""))
	err, tsk := tm.AddTask(str)

	assert.Error(t, err, "Error expected when non-working DB instance was provided")
	assert.Nil(t, tsk, "Expected nil value for task")
}

func TestAddTaskSuccess(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	db, _ := neoism.Connect("http://localhost:7474/db/data")
	tm := task.TaskManager{Database: db}

	str, _ := randutil.AlphaString(DESC_LEN)
	err, tsk := tm.AddTask(str)

	assert.NoError(t, err, nil, "No error expected while adding a new node")
	assert.NotNil(t, tsk, "Expected non-nil value for task")

	node, err := db.Node(tsk.Id)
	assert.NoError(t, err, nil, "Could not find newly created node in DB")

	// Cleanup
	defer func() {
		node.Delete()
	}()

	name, err := node.Property("name")
	assert.NoError(t, err, nil, "'name' property not set")
	assert.Equal(t, name, str, "Testing task name")

	labels, _ := node.Labels()
	assert.True(t, len(labels) > 0, task.TASK_LABEL, "Testing task label")
	assert.Equal(t, labels[0], task.TASK_LABEL, "Testing task label")
}

func TestListTasksNoDB(t *testing.T) {
	tm := task.TaskManager{}
	err, tasks := tm.ListTasks()

	assert.Error(t, err, "Error expected when DB instance was nil")
	assert.Nil(t, tasks, "Expected nil value for list of tasks")
}

func TestListTasksDBNotConnected(t *testing.T) {
	mdb := new(MockGraphDB)
	tm := task.TaskManager{Database: mdb}

	mdb.On("NodesByLabel", task.TASK_LABEL).Return(nil, fmt.Errorf(""))
	err, tasks := tm.ListTasks()

	assert.Error(t, err, "Error expected when non-working DB instance was provided")
	assert.Nil(t, tasks, "Expected nil value for list of tasks")
}

func TestListTasksEmpty(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	db, _ := neoism.Connect("http://localhost:7474/db/data")
	tm := task.TaskManager{Database: db}

	err, tasks := tm.ListTasks()

	assert.NoError(t, err, nil, "No error expected while fetching empty list")
	assert.Empty(t, tasks, "Expected empty list of tasks")
}

func TestListTasksSingle(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	db, _ := neoism.Connect("http://localhost:7474/db/data")
	tm := task.TaskManager{Database: db}

	str, _ := randutil.AlphaString(DESC_LEN)
	err, tsk := tm.AddTask(str)
	node, err := db.Node(tsk.Id)

	// Cleanup
	defer func() {
		node.Delete()
	}()

	err, tasks := tm.ListTasks()

	assert.NoError(t, err, nil, "No error expected while listing tasks")
	assert.Equal(t, len(tasks), 1, "Expected list of size 1")
}
<|endoftext|>


<commit_before>/*

Package golden is a package designed to make it possible to compare a game to a
golden run for testing purposes. It takes a record saved in storage/filesystem
format and compares it.

Typical Use

Typically you generate a new set of goldens by cd'ing into the package
containing the game you want to test. Then you run `boardgame-util
create-golden`, which generates a stub server for that game. As you play and
create games, it will save the records as filesystem.record.Record, a format
that is designed to be easy to read and modify as JSON. `create-golden` also
will save a `golden_test.go` which will, when tests are run, compare the current
GameManager's operation to the states and moves saved in the golden json blobs.
These tests are a great way to verify that the behavior of your game does not
accidentlaly change.

The format of filesystem.record.Record is optimized to be able to be
hand-edited. It does a number of tricks to make sure that states and moves can
be spliced in easily. First and foremost, it typically stores subsequent states
not as full blobs but as diffs from the state before. This means that changing
one state doesn't require also modifying all subsequent states to have the same
values. The format also "relativizes" moves, setting their Version to -1,
signifying that when fetched via record.Move(version), it should just use the
version number it said it was. In addition, the Initiator field is stored as a
relative number for how many moves back in history to go to. Finally, the
Timestamp field is stored in a format that is as many seconds past the Unix
epoch as the move is from the Game's Created timestamp. All of these properties
mean that the format is (relatively) easy to tweak manually to add or remove
moves.

You can also add a "Description" top-level field in the json to describe what
the game is testing, which is useful to keep track of goldens that test various
edge cases.

Remastering Goldens

Typically you record a golden, and then every time you test the game package, it
will just verify the game logic still applies the same moves in the right order,
with the right state modifications. But every so often you want to 'remaster'
your golden. For example, perhaps the game logic has changed to have slightly
different behavior, or you want to update the format of the golden to ensure
it's canonical and up-to-date, to match changes in the underlying library.

It's possible to 'remaster' goldens, which means to re-record them and overwrite
the original. You do this by passing true as the last value to Compare or
CompareFolder. The `golden_test.go` that is generated for you will also
automatically add a flag that will be passed in, so the canonical way to
remaster is to run `go test -update-golden`, which instead of comparing the
golden, will remaster it, overwriting the original.

After you remaster, it's a good idea to sanity check by doing a normal test (`go
test`) to verify the game logic matches the new golden. You should also visually
inspect the diff of the golden before commiting to make sure there aren't any
unintended changes. The remastering process is designed to ensure that wherever
possible content doesn't change. The design of filesystem.record.Record
(described above) helps, but the remastering process also seeks to, for example,
use existing timestamps wherever possible and generate reasonable intermediate
timestamps for new moves that have been added.

*/
package golden

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"reflect"
	"strings"

	"github.com/go-test/deep"
	"github.com/jkomoros/boardgame"
	"github.com/jkomoros/boardgame/storage/filesystem/record"
	"github.com/sirupsen/logrus"
	"github.com/yudai/gojsondiff"
	"github.com/yudai/gojsondiff/formatter"
)

//Compare is the primary method in the package. It takes a game delegate and a
//filename denoting a record to compare against. delegate shiould be a fresh
//delegate not yet affiliated with a manager. It compares every version and move
//in the history (ignoring things that shouldn't be the same, like timestamps)
//and reports the first place they divrge. Any time it finds a move not proposed
//by AdminPlayerIndex it will propose that move. As long as your game uses
//state.Rand() for all randomness and is otherwise deterministic then everything
//should work. If updateOnDifferent is true, instead of erroring, it will
//instead overwrite the existing golden with a new one. The boardgame-util
//create-goldens tool will output a test that will look for a `-update-golden`
//flag and pass in that variable here.
func Compare(delegate boardgame.GameDelegate, recFilename string, updateOnDifferent bool) error {

	storage := newStorageManager()

	manager, err := boardgame.NewGameManager(delegate, storage)

	if err != nil {
		return errors.New("Couldn't create new manager: " + err.Error())
	}

	storage.manager = manager

	rec, err := record.New(recFilename)

	if err != nil {
		return errors.New("Couldn't create record: " + err.Error())
	}

	return compare(manager, rec, storage, updateOnDifferent)

}

//CompareFolder is like Compare, except it will iterate through any file in
//recFolder that ends in .json. Errors if any of those files cannot be parsed
//into recs. See Compare for more documentation.
func CompareFolder(delegate boardgame.GameDelegate, recFolder string, updateOnDifferent bool) error {

	storage := newStorageManager()

	manager, err := boardgame.NewGameManager(delegate, storage)

	if err != nil {
		return errors.New("Couldn't create new manager: " + err.Error())
	}

	storage.manager = manager

	infos, err := ioutil.ReadDir(recFolder)

	if err != nil {
		return errors.New("Couldn't read folder: " + err.Error())
	}

	for _, info := range infos {
		if info.IsDir() {
			continue
		}

		if filepath.Ext(info.Name()) != ".json" {
			continue
		}

		rec, err := record.New(filepath.Join(recFolder, info.Name()))

		if err != nil {
			return errors.New("File with name " + info.Name() + " couldn't be loaded into rec: " + err.Error())
		}

		if err := compare(manager, rec, storage, updateOnDifferent); err != nil {
			return errors.New("File named " + info.Name() + " had compare error: " + err.Error())
		}

	}

	return nil
}

func newLogger() (*logrus.Logger, *bytes.Buffer) {
	result := logrus.New()
	buf := &bytes.Buffer{}
	result.Out = buf
	result.SetLevel(logrus.DebugLevel)
	return result, buf
}

func compare(manager *boardgame.GameManager, rec *record.Record, storage *storageManager, updateOnDifferent bool) error {

	//TODO: get rid of this function once refactored
	comparer, err := newComparer(manager, rec, storage)

	if err != nil {
		return errors.New("Couldn't create comparer: " + err.Error())
	}

	if updateOnDifferent {
		fmt.Println("WARNING: overwriting old goldens, verify the diff looks sane before committing!")
		newGolden, err := comparer.RegenerateGolden()
		if err != nil {
			comparer.PrintDebug()
			return err
		}
		if err := newGolden.Save(rec.Path(), false); err != nil {
			return errors.New("Could not overwrite " + rec.Path() + ": " + err.Error())
		}
	} else {

		if err := comparer.Compare(); err != nil {
			comparer.PrintDebug()
			return err
		}
	}

	return nil
}

var differ = gojsondiff.New()

func compareJSONBlobs(one, two []byte) error {

	diff, err := differ.Compare(one, two)

	if err != nil {
		return errors.New("Couldn't diff: " + err.Error())
	}

	if diff.Modified() {

		var oneJSON map[string]interface{}

		if err := json.Unmarshal(one, &oneJSON); err != nil {
			return errors.New("Couldn't unmarshal left")
		}

		diffformatter := formatter.NewAsciiFormatter(oneJSON, formatter.AsciiFormatterConfig{
			Coloring: true,
		})

		str, err := diffformatter.Format(diff)

		if err != nil {
			return errors.New("Couldn't format diff: " + err.Error())
		}

		return errors.New("Diff: " + str)
	}

	return nil

}

func compareMoveStorageRecords(one, two boardgame.MoveStorageRecord, skipAbsoluteVersions bool) error {

	oneBlob := one.Blob
	twoBlob := two.Blob

	//Set the fields we know might differ to known values
	one.Blob = nil
	two.Blob = nil

	two.Timestamp = one.Timestamp

	if skipAbsoluteVersions {
		if one.Version >= 0 || two.Version >= 0 {
			two.Version = one.Version
			two.Initiator = one.Initiator
		}
	}

	if !reflect.DeepEqual(one, two) {
		return errors.New("Move storage records differed in base fields: " + strings.Join(deep.Equal(one, two), ", "))
	}

	return compareJSONBlobs(oneBlob, twoBlob)

}
<commit_msg>Added package doc about the pattern for how to remaster when changing game logic. Fixes #701. Part of #755.<commit_after>/*

Package golden is a package designed to make it possible to compare a game to a
golden run for testing purposes. It takes a record saved in storage/filesystem
format and compares it.

Typical Use

Typically you generate a new set of goldens by cd'ing into the package
containing the game you want to test. Then you run `boardgame-util
create-golden`, which generates a stub server for that game. As you play and
create games, it will save the records as filesystem.record.Record, a format
that is designed to be easy to read and modify as JSON. `create-golden` also
will save a `golden_test.go` which will, when tests are run, compare the current
GameManager's operation to the states and moves saved in the golden json blobs.
These tests are a great way to verify that the behavior of your game does not
accidentlaly change.

The format of filesystem.record.Record is optimized to be able to be
hand-edited. It does a number of tricks to make sure that states and moves can
be spliced in easily. First and foremost, it typically stores subsequent states
not as full blobs but as diffs from the state before. This means that changing
one state doesn't require also modifying all subsequent states to have the same
values. The format also "relativizes" moves, setting their Version to -1,
signifying that when fetched via record.Move(version), it should just use the
version number it said it was. In addition, the Initiator field is stored as a
relative number for how many moves back in history to go to. Finally, the
Timestamp field is stored in a format that is as many seconds past the Unix
epoch as the move is from the Game's Created timestamp. All of these properties
mean that the format is (relatively) easy to tweak manually to add or remove
moves.

You can also add a "Description" top-level field in the json to describe what
the game is testing, which is useful to keep track of goldens that test various
edge cases.

Remastering Goldens

Typically you record a golden, and then every time you test the game package, it
will just verify the game logic still applies the same moves in the right order,
with the right state modifications. But every so often you want to 'remaster'
your golden. For example, perhaps the game logic has changed to have slightly
different behavior, or you want to update the format of the golden to ensure
it's canonical and up-to-date, to match changes in the underlying library.

It's possible to 'remaster' goldens, which means to re-record them and overwrite
the original. You do this by passing true as the last value to Compare or
CompareFolder. The `golden_test.go` that is generated for you will also
automatically add a flag that will be passed in, so the canonical way to
remaster is to run `go test -update-golden`, which instead of comparing the
golden, will remaster it, overwriting the original.

After you remaster, it's a good idea to sanity check by doing a normal test (`go
test`) to verify the game logic matches the new golden. You should also visually
inspect the diff of the golden before commiting to make sure there aren't any
unintended changes. The remastering process is designed to ensure that wherever
possible content doesn't change. The design of filesystem.record.Record
(described above) helps, but the remastering process also seeks to, for example,
use existing timestamps wherever possible and generate reasonable intermediate
timestamps for new moves that have been added.

If you have changed your game logic (for example, adding a new FixUp move that
didn't exist before), then your old goldens will no longer match, so `go test`
will fail, and you'll need to remaster your goldens. In the common case where
the new FixUp move occurs within a run of already-existing FixUp moves, then you
don't need to do anything special; `go test -update-golden`, combined with a
manual sanity check of the diff, should be sufficient.

In the case where you have added a new Player move, or a move like SeatPlayer
that occurs on its own, you'll need to do a bit of manual surgery. Before
remastering, splice in a move record corresponding to the new move into the
right place in the golden's Move section. Note that you'll need to update the
Initiator fields of any other moves that come in the immediate fixup run
immediately after it, to verify that their relative value points to the new
initator. You do not need to update the state patches to match. Then, run `go
test -update-golden` and verify the output looks correct. Note that your manual
splice will now have been overwritten, which means that if the output was wrong,
you'll need to reset it, manually modify again, and then remaster.

*/
package golden

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"reflect"
	"strings"

	"github.com/go-test/deep"
	"github.com/jkomoros/boardgame"
	"github.com/jkomoros/boardgame/storage/filesystem/record"
	"github.com/sirupsen/logrus"
	"github.com/yudai/gojsondiff"
	"github.com/yudai/gojsondiff/formatter"
)

//Compare is the primary method in the package. It takes a game delegate and a
//filename denoting a record to compare against. delegate shiould be a fresh
//delegate not yet affiliated with a manager. It compares every version and move
//in the history (ignoring things that shouldn't be the same, like timestamps)
//and reports the first place they divrge. Any time it finds a move not proposed
//by AdminPlayerIndex it will propose that move. As long as your game uses
//state.Rand() for all randomness and is otherwise deterministic then everything
//should work. If updateOnDifferent is true, instead of erroring, it will
//instead overwrite the existing golden with a new one. The boardgame-util
//create-goldens tool will output a test that will look for a `-update-golden`
//flag and pass in that variable here.
func Compare(delegate boardgame.GameDelegate, recFilename string, updateOnDifferent bool) error {

	storage := newStorageManager()

	manager, err := boardgame.NewGameManager(delegate, storage)

	if err != nil {
		return errors.New("Couldn't create new manager: " + err.Error())
	}

	storage.manager = manager

	rec, err := record.New(recFilename)

	if err != nil {
		return errors.New("Couldn't create record: " + err.Error())
	}

	return compare(manager, rec, storage, updateOnDifferent)

}

//CompareFolder is like Compare, except it will iterate through any file in
//recFolder that ends in .json. Errors if any of those files cannot be parsed
//into recs. See Compare for more documentation.
func CompareFolder(delegate boardgame.GameDelegate, recFolder string, updateOnDifferent bool) error {

	storage := newStorageManager()

	manager, err := boardgame.NewGameManager(delegate, storage)

	if err != nil {
		return errors.New("Couldn't create new manager: " + err.Error())
	}

	storage.manager = manager

	infos, err := ioutil.ReadDir(recFolder)

	if err != nil {
		return errors.New("Couldn't read folder: " + err.Error())
	}

	for _, info := range infos {
		if info.IsDir() {
			continue
		}

		if filepath.Ext(info.Name()) != ".json" {
			continue
		}

		rec, err := record.New(filepath.Join(recFolder, info.Name()))

		if err != nil {
			return errors.New("File with name " + info.Name() + " couldn't be loaded into rec: " + err.Error())
		}

		if err := compare(manager, rec, storage, updateOnDifferent); err != nil {
			return errors.New("File named " + info.Name() + " had compare error: " + err.Error())
		}

	}

	return nil
}

func newLogger() (*logrus.Logger, *bytes.Buffer) {
	result := logrus.New()
	buf := &bytes.Buffer{}
	result.Out = buf
	result.SetLevel(logrus.DebugLevel)
	return result, buf
}

func compare(manager *boardgame.GameManager, rec *record.Record, storage *storageManager, updateOnDifferent bool) error {

	//TODO: get rid of this function once refactored
	comparer, err := newComparer(manager, rec, storage)

	if err != nil {
		return errors.New("Couldn't create comparer: " + err.Error())
	}

	if updateOnDifferent {
		fmt.Println("WARNING: overwriting old goldens, verify the diff looks sane before committing!")
		newGolden, err := comparer.RegenerateGolden()
		if err != nil {
			comparer.PrintDebug()
			return err
		}
		if err := newGolden.Save(rec.Path(), false); err != nil {
			return errors.New("Could not overwrite " + rec.Path() + ": " + err.Error())
		}
	} else {

		if err := comparer.Compare(); err != nil {
			comparer.PrintDebug()
			return err
		}
	}

	return nil
}

var differ = gojsondiff.New()

func compareJSONBlobs(one, two []byte) error {

	diff, err := differ.Compare(one, two)

	if err != nil {
		return errors.New("Couldn't diff: " + err.Error())
	}

	if diff.Modified() {

		var oneJSON map[string]interface{}

		if err := json.Unmarshal(one, &oneJSON); err != nil {
			return errors.New("Couldn't unmarshal left")
		}

		diffformatter := formatter.NewAsciiFormatter(oneJSON, formatter.AsciiFormatterConfig{
			Coloring: true,
		})

		str, err := diffformatter.Format(diff)

		if err != nil {
			return errors.New("Couldn't format diff: " + err.Error())
		}

		return errors.New("Diff: " + str)
	}

	return nil

}

func compareMoveStorageRecords(one, two boardgame.MoveStorageRecord, skipAbsoluteVersions bool) error {

	oneBlob := one.Blob
	twoBlob := two.Blob

	//Set the fields we know might differ to known values
	one.Blob = nil
	two.Blob = nil

	two.Timestamp = one.Timestamp

	if skipAbsoluteVersions {
		if one.Version >= 0 || two.Version >= 0 {
			two.Version = one.Version
			two.Initiator = one.Initiator
		}
	}

	if !reflect.DeepEqual(one, two) {
		return errors.New("Move storage records differed in base fields: " + strings.Join(deep.Equal(one, two), ", "))
	}

	return compareJSONBlobs(oneBlob, twoBlob)

}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"os"
	"text/tabwriter"
)
import (
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/fatih/color"
)

func display(item interface{}, err error, format ...string) {
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	if len(format) > 0 {
		switch format[0] {
		case "raw":
			fmt.Println(item)
		default:
			lineDisplay(item)
		}
	} else {
		lineDisplay(item)
	}
}

var simpleDay = "Mon, Jan 2, 2006"

func lineDisplay(item interface{}) {
	w := tabwriter.NewWriter(os.Stdout, 20, 1, 1, ' ', 0)

	switch item.(type) {
	case *iam.ListUsersOutput:
		for _, user := range item.(*iam.ListUsersOutput).Users {
			fmt.Fprintln(w, fmt.Sprintf("name: %s\tid: %s\tcreated: %s\t", *user.UserName, *user.UserId, (*user.CreateDate).Format(simpleDay)))
		}
	case *iam.ListGroupsOutput:
		for _, group := range item.(*iam.ListGroupsOutput).Groups {
			fmt.Fprintln(w, fmt.Sprintf("name:%s\tid:%s\tcreated: %s\t", *group.GroupName, *group.GroupId, (*group.CreateDate).Format(simpleDay)))
		}
	case *iam.ListRolesOutput:
		for _, role := range item.(*iam.ListRolesOutput).Roles {
			fmt.Fprintln(w, fmt.Sprintf("name: %s\tid: %s\tcreated: %s\t", *role.RoleName, *role.RoleId, (*role.CreateDate).Format(simpleDay)))
		}
	case *iam.ListPoliciesOutput:
		for _, policy := range item.(*iam.ListPoliciesOutput).Policies {
			fmt.Fprintln(w, fmt.Sprintf("name: %s\tid: %s\tcreated: %s\t", *policy.PolicyName, *policy.PolicyId, (*policy.CreateDate).Format(simpleDay)))
		}
	case *ec2.DescribeInstancesOutput:
		for _, reserv := range item.(*ec2.DescribeInstancesOutput).Reservations {
			for _, inst := range reserv.Instances {
				var pubIp string
				if inst.PublicIpAddress != nil {
					pubIp = *inst.PublicIpAddress
				}
				fmt.Fprintln(w, fmt.Sprintf("id: %s\ttype: %s\tstate: %s\tpriv-ip: %s\tpub-ip: %s\tlaunched: %s\t", *inst.InstanceId, *inst.State.Name, *inst.InstanceType, *inst.PrivateIpAddress, pubIp, (*inst.LaunchTime).Format(simpleDay)))
			}
		}
	case *ec2.Reservation:
		for _, inst := range item.(*ec2.Reservation).Instances {
			var pubIp string
			if inst.PublicIpAddress != nil {
				pubIp = *inst.PublicIpAddress
			}
			fmt.Fprintln(w, fmt.Sprintf("id: %s\ttype: %s\tstate: %s\tpriv-ip: %s\tpub-ip: %s\tlaunched: %s\t", *inst.InstanceId, *inst.State.Name, *inst.InstanceType, *inst.PrivateIpAddress, pubIp, (*inst.LaunchTime).Format(simpleDay)))
		}
	case *ec2.DescribeVpcsOutput:
		for _, vpc := range item.(*ec2.DescribeVpcsOutput).Vpcs {
			fmt.Fprintln(w, fmt.Sprintf("id: %s\tdefault: %s\tstate: %s\tcidr: %s\t", *vpc.VpcId, printColorIf(*vpc.IsDefault), *vpc.State, *vpc.CidrBlock))
		}
	case *ec2.DescribeSubnetsOutput:
		for _, subnet := range item.(*ec2.DescribeSubnetsOutput).Subnets {
			fmt.Fprintln(w, fmt.Sprintf("id: %s\tpublic-vms: %s\tstate: %s\tcidr: %s\t", *subnet.SubnetId, printColorIf(*subnet.MapPublicIpOnLaunch, color.FgRed), *subnet.State, *subnet.CidrBlock))
		}
	default:
		fmt.Println(item)
		return
	}

	w.Flush()
}

func printColorIf(cond bool, c ...color.Attribute) string {
	col := color.FgGreen
	if len(c) > 0 {
		col = c[0]
	}

	var fn func(string, ...interface{}) string
	if cond {
		fn = color.New(col).SprintfFunc()
	} else {
		fn = color.New().SprintfFunc()
	}

	return fn(fmt.Sprintf("%t", cond))
}
<commit_msg>Using aws.StringValue instead of defensive code<commit_after>package cmd

import (
	"fmt"
	"os"
	"text/tabwriter"
)
import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/fatih/color"
)

func display(item interface{}, err error, format ...string) {
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	if len(format) > 0 {
		switch format[0] {
		case "raw":
			fmt.Println(item)
		default:
			lineDisplay(item)
		}
	} else {
		lineDisplay(item)
	}
}

var simpleDay = "Mon, Jan 2, 2006"

func lineDisplay(item interface{}) {
	w := tabwriter.NewWriter(os.Stdout, 20, 1, 1, ' ', 0)

	switch item.(type) {
	case *iam.ListUsersOutput:
		for _, user := range item.(*iam.ListUsersOutput).Users {
			fmt.Fprintln(w, fmt.Sprintf("name: %s\tid: %s\tcreated: %s\t", *user.UserName, *user.UserId, (*user.CreateDate).Format(simpleDay)))
		}
	case *iam.ListGroupsOutput:
		for _, group := range item.(*iam.ListGroupsOutput).Groups {
			fmt.Fprintln(w, fmt.Sprintf("name:%s\tid:%s\tcreated: %s\t", *group.GroupName, *group.GroupId, (*group.CreateDate).Format(simpleDay)))
		}
	case *iam.ListRolesOutput:
		for _, role := range item.(*iam.ListRolesOutput).Roles {
			fmt.Fprintln(w, fmt.Sprintf("name: %s\tid: %s\tcreated: %s\t", *role.RoleName, *role.RoleId, (*role.CreateDate).Format(simpleDay)))
		}
	case *iam.ListPoliciesOutput:
		for _, policy := range item.(*iam.ListPoliciesOutput).Policies {
			fmt.Fprintln(w, fmt.Sprintf("name: %s\tid: %s\tcreated: %s\t", *policy.PolicyName, *policy.PolicyId, (*policy.CreateDate).Format(simpleDay)))
		}
	case *ec2.DescribeInstancesOutput:
		for _, reserv := range item.(*ec2.DescribeInstancesOutput).Reservations {
			for _, inst := range reserv.Instances {
				fmt.Fprintln(w, fmt.Sprintf("id: %s\ttype: %s\tstate: %s\tpriv-ip: %s\tpub-ip: %s\tlaunched: %s\t", aws.StringValue(inst.InstanceId), aws.StringValue(inst.State.Name), aws.StringValue(inst.InstanceType), aws.StringValue(inst.PrivateIpAddress), aws.StringValue(inst.PublicIpAddress), (*inst.LaunchTime).Format(simpleDay)))
			}
		}
	case *ec2.Reservation:
		for _, inst := range item.(*ec2.Reservation).Instances {
			fmt.Fprintln(w, fmt.Sprintf("id: %s\ttype: %s\tstate: %s\tpriv-ip: %s\tpub-ip: %s\tlaunched: %s\t", aws.StringValue(inst.InstanceId), aws.StringValue(inst.State.Name), aws.StringValue(inst.InstanceType), aws.StringValue(inst.PrivateIpAddress), aws.StringValue(inst.PublicIpAddress), (*inst.LaunchTime).Format(simpleDay)))
		}
	case *ec2.DescribeVpcsOutput:
		for _, vpc := range item.(*ec2.DescribeVpcsOutput).Vpcs {
			fmt.Fprintln(w, fmt.Sprintf("id: %s\tdefault: %s\tstate: %s\tcidr: %s\t", *vpc.VpcId, printColorIf(*vpc.IsDefault), *vpc.State, *vpc.CidrBlock))
		}
	case *ec2.DescribeSubnetsOutput:
		for _, subnet := range item.(*ec2.DescribeSubnetsOutput).Subnets {
			fmt.Fprintln(w, fmt.Sprintf("id: %s\tpublic-vms: %s\tstate: %s\tcidr: %s\t", *subnet.SubnetId, printColorIf(*subnet.MapPublicIpOnLaunch, color.FgRed), *subnet.State, *subnet.CidrBlock))
		}
	default:
		fmt.Println(item)
		return
	}

	w.Flush()
}

func printColorIf(cond bool, c ...color.Attribute) string {
	col := color.FgGreen
	if len(c) > 0 {
		col = c[0]
	}

	var fn func(string, ...interface{}) string
	if cond {
		fn = color.New(col).SprintfFunc()
	} else {
		fn = color.New().SprintfFunc()
	}

	return fn(fmt.Sprintf("%t", cond))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/constabulary/gb"
	"github.com/constabulary/gb/cmd"
)

func init() {
	registerCommand(&cmd.Command{
		Name:      "info",
		UsageLine: `info`,
		Short:     "info returns information about this project",
		Long: `info returns information about this project.

info returns 0 if the project is well formed, and non zero otherwise.
`,
		Run:      info,
		AddFlags: addBuildFlags,
	})
}

func info(ctx *gb.Context, args []string) error {
	fmt.Printf("GB_PROJECT_DIR=%q\n", ctx.Projectdir())
	fmt.Printf("GB_SRC_PATH=%q\n", joinlist(ctx.Srcdirs()...))
	fmt.Printf("GB_PKG_DIR=%q\n", ctx.Pkgdir())
	fmt.Printf("GB_BIN_SUFFIX=%q\n", ctx.Suffix())
	return nil
}

// joinlist joins path elements using the os specific separator.
// TODO(dfc) it probably gets this wrong on windows in some circumstances.
func joinlist(paths ...string) string {
	return strings.Join(paths, string(filepath.ListSeparator))
}
<commit_msg>cmd/gb: gb info reports GOROOT that build gb<commit_after>package main

import (
	"fmt"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/constabulary/gb"
	"github.com/constabulary/gb/cmd"
)

func init() {
	registerCommand(&cmd.Command{
		Name:      "info",
		UsageLine: `info`,
		Short:     "info returns information about this project",
		Long: `info returns information about this project.

info returns 0 if the project is well formed, and non zero otherwise.
`,
		Run:      info,
		AddFlags: addBuildFlags,
	})
}

func info(ctx *gb.Context, args []string) error {
	fmt.Printf("GB_PROJECT_DIR=%q\n", ctx.Projectdir())
	fmt.Printf("GB_SRC_PATH=%q\n", joinlist(ctx.Srcdirs()...))
	fmt.Printf("GB_PKG_DIR=%q\n", ctx.Pkgdir())
	fmt.Printf("GB_BIN_SUFFIX=%q\n", ctx.Suffix())
	fmt.Printf("GB_GOROOT=%q\n", runtime.GOROOT())
	return nil
}

// joinlist joins path elements using the os specific separator.
// TODO(dfc) it probably gets this wrong on windows in some circumstances.
func joinlist(paths ...string) string {
	return strings.Join(paths, string(filepath.ListSeparator))
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/alecthomas/chroma/quick"
)

// pageln invokes the systems pager with the supplied data
// falls back to fmt.Println() when paging fails or non-interactive
func pageln(i ...interface{}) {
	// no paging in non-interactive mode
	if !interactive {
		fmt.Println(i...)
		return
	}

	// get system pager, fallback to `more`
	pager := os.Getenv("PAGER")
	if pager == "" {
		pager = "more"
	}

	// invoke pager
	cmd := exec.Command(pager)
	cmd.Stdin = strings.NewReader(fmt.Sprintln(i...))
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	// if this fails, just print it
	if err := cmd.Run(); err != nil {
		fmt.Println(i...)
	}
}

func highlight(lang, s string) string {
	var buf bytes.Buffer
	if err := quick.Highlight(&buf, s, lang, "terminal", "vim"); err != nil {
		log.Fatalln("Highlighting:", err)
	}
	return buf.String()
}
<commit_msg>Improve PAGER usage. (#38)<commit_after>package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/alecthomas/chroma/quick"
)

// pageln invokes the systems pager with the supplied data
// falls back to fmt.Println() when paging fails or non-interactive
func pageln(i ...interface{}) {
	// no paging in non-interactive mode
	if !interactive {
		fmt.Println(i...)
		return
	}

	// get system pager, fallback to `more`
	pager := os.Getenv("PAGER")
	var args []string
	if pager == "" {
		// --raw-control-chars  Honors colors from diff.
		// --quit-if-one-screen Closer to the git experience.
		// --no-init            Don't clear the screen when exiting.
		pager = "less"
		args = []string{"--raw-control-chars", "--quit-if-one-screen", "--no-init"}
	}

	// invoke pager
	cmd := exec.Command(pager, args...)
	cmd.Stdin = strings.NewReader(fmt.Sprintln(i...))
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	// if this fails, just print it
	if err := cmd.Run(); err != nil {
		fmt.Println(i...)
	}
}

func highlight(lang, s string) string {
	var buf bytes.Buffer
	if err := quick.Highlight(&buf, s, lang, "terminal", "vim"); err != nil {
		log.Fatalln("Highlighting:", err)
	}
	return buf.String()
}
<|endoftext|>


<commit_before>package iocontrol

import (
	"github.com/benbjohnson/clock"
	"sync"
	"time"
)

type rateCounter struct {
	time  clock.Clock
	mu    sync.RWMutex
	count int

	lastCount int
	lastCheck time.Time
}

func newCounter() *rateCounter {
	return &rateCounter{
		time: clock.New(),
	}
}

func (c *rateCounter) Add(n int) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.count += n
	if c.lastCheck.IsZero() {
		c.lastCheck = time.Now()
	}
}

func (c *rateCounter) Total() int {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.count
}

func (c *rateCounter) Rate(perPeriod time.Duration) float64 {
	c.mu.RLock()
	defer c.mu.RUnlock()

	now := c.time.Now()

	between := now.Sub(c.lastCheck)

	changed := c.count - c.lastCount
	rate := float64(changed*int(perPeriod)) / float64(between)

	c.lastCount = c.count
	c.lastCheck = now
	return rate
}
<commit_msg>modify field so take write lock<commit_after>package iocontrol

import (
	"github.com/benbjohnson/clock"
	"sync"
	"time"
)

type rateCounter struct {
	time  clock.Clock
	mu    sync.RWMutex
	count int

	lastCount int
	lastCheck time.Time
}

func newCounter() *rateCounter {
	return &rateCounter{
		time: clock.New(),
	}
}

func (c *rateCounter) Add(n int) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.count += n
	if c.lastCheck.IsZero() {
		c.lastCheck = time.Now()
	}
}

func (c *rateCounter) Total() int {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.count
}

func (c *rateCounter) Rate(perPeriod time.Duration) float64 {
	c.mu.Lock()
	defer c.mu.Unlock()

	now := c.time.Now()

	between := now.Sub(c.lastCheck)

	changed := c.count - c.lastCount
	rate := float64(changed*int(perPeriod)) / float64(between)

	c.lastCount = c.count
	c.lastCheck = now
	return rate
}
<|endoftext|>


<commit_before>// Copyright (c) 2020 Tigera, Inc. All rights reserved.
//
// 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 conntrack

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

	log "github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"

	"github.com/projectcalico/felix/bpf"
)

// struct calico_ct_key {
//   uint32_t protocol;
//   __be32 addr_a, addr_b; // NBO
//   uint16_t port_a, port_b; // HBO
// };
const conntrackKeySize = 16
const conntrackValueSize = 64

type Key [conntrackKeySize]byte

func (k Key) AsBytes() []byte {
	return k[:]
}

func (k Key) Proto() uint8 {
	return uint8(binary.LittleEndian.Uint32(k[:4]))
}

func (k Key) AddrA() net.IP {
	return k[4:8]
}

func (k Key) PortA() uint16 {
	return binary.LittleEndian.Uint16(k[12:14])
}

func (k Key) AddrB() net.IP {
	return k[8:12]
}

func (k Key) PortB() uint16 {
	return binary.LittleEndian.Uint16(k[14:16])
}

func (k Key) String() string {
	return fmt.Sprintf("ConntrackKey{proto=%v %v:%v <-> %v:%v}",
		k.Proto(), k.AddrA(), k.PortA(), k.AddrB(), k.PortB())
}

func NewKey(proto uint8, ipA net.IP, portA uint16, ipB net.IP, portB uint16) Key {
	var k Key
	binary.LittleEndian.PutUint32(k[:4], uint32(proto))
	copy(k[4:8], ipA)
	copy(k[8:12], ipB)
	binary.LittleEndian.PutUint16(k[12:14], portA)
	binary.LittleEndian.PutUint16(k[14:16], portB)
	return k
}

// struct calico_ct_value {
//  __u64 created;
//  __u64 last_seen; // 8
//  __u8 type;     // 16
//  __u8 flags;     // 17
//
//  // Important to use explicit padding, otherwise the compiler can decide
//  // not to zero the padding bytes, which upsets the verifier.  Worse than
//  // that, debug logging often prevents such optimisation resulting in
//  // failures when debug logging is compiled out only :-).
//  __u8 pad0[6];
//  union {
//    // CALI_CT_TYPE_NORMAL and CALI_CT_TYPE_NAT_REV.
//    struct {
//      struct calico_ct_leg a_to_b; // 24
//      struct calico_ct_leg b_to_a; // 36
//
//      // CALI_CT_TYPE_NAT_REV only.
//      __u32 orig_dst;                    // 44
//      __u16 orig_port;                   // 48
//      __u8 pad1[2];                      // 50
//      __u32 tun_ip;                      // 52
//      __u32 pad3;                        // 56
//    };
//
//    // CALI_CT_TYPE_NAT_FWD; key for the CALI_CT_TYPE_NAT_REV entry.
//    struct {
//      struct calico_ct_key nat_rev_key;  // 24
//      __u8 pad2[8];
//    };
//  };
// };
type Value [conntrackValueSize]byte

func (e Value) Created() int64 {
	return int64(binary.LittleEndian.Uint64(e[:8]))
}

func (e Value) LastSeen() int64 {
	return int64(binary.LittleEndian.Uint64(e[8:16]))
}

func (e Value) Type() uint8 {
	return e[16]
}

func (e Value) Flags() uint8 {
	return e[17]
}

const (
	TypeNormal uint8 = iota
	TypeNATForward
	TypeNATReverse

	FlagNATOut    uint8 = (1 << 0)
	FlagNATFwdDsr uint8 = (1 << 1)
	FlagNATNPFwd  uint8 = (1 << 2)
)

func (e Value) ReverseNATKey() Key {
	var ret Key

	l := len(Key{})
	copy(ret[:l], e[24:24+l])

	return ret
}

type Leg struct {
	Seqno       uint32
	SynSeen     bool
	AckSeen     bool
	FinSeen     bool
	RstSeen     bool
	Whitelisted bool
	Opener      bool
	Ifindex     uint32
}

func (leg Leg) Flags() uint32 {
	var flags uint32
	if leg.SynSeen {
		flags |= 1
	}
	if leg.AckSeen {
		flags |= 1 << 1
	}
	if leg.FinSeen {
		flags |= 1 << 2
	}
	if leg.RstSeen {
		flags |= 1 << 3
	}
	if leg.Whitelisted {
		flags |= 1 << 4
	}
	if leg.Opener {
		flags |= 1 << 5
	}
	return flags
}

func bitSet(bits uint32, bit uint8) bool {
	return (bits & (1 << bit)) != 0
}

func readConntrackLeg(b []byte) Leg {
	bits := binary.LittleEndian.Uint32(b[4:8])
	return Leg{
		Seqno:       binary.BigEndian.Uint32(b[0:4]),
		SynSeen:     bitSet(bits, 0),
		AckSeen:     bitSet(bits, 1),
		FinSeen:     bitSet(bits, 2),
		RstSeen:     bitSet(bits, 3),
		Whitelisted: bitSet(bits, 4),
		Opener:      bitSet(bits, 5),
		Ifindex:     binary.LittleEndian.Uint32(b[8:12]),
	}
}

type EntryData struct {
	A2B      Leg
	B2A      Leg
	OrigDst  net.IP
	OrigPort uint16
	TunIP    net.IP
}

func (data EntryData) Established() bool {
	return data.A2B.SynSeen && data.A2B.AckSeen && data.B2A.SynSeen && data.B2A.AckSeen
}

func (data EntryData) RSTSeen() bool {
	return data.A2B.RstSeen || data.B2A.RstSeen
}

func (data EntryData) FINsSeen() bool {
	return data.A2B.FinSeen && data.B2A.FinSeen
}

func (data EntryData) FINsSeenDSR() bool {
	return data.A2B.FinSeen || data.B2A.FinSeen
}

func (e Value) Data() EntryData {
	ip := e[48:52]
	tip := e[56:60]
	return EntryData{
		A2B:      readConntrackLeg(e[24:36]),
		B2A:      readConntrackLeg(e[36:48]),
		OrigDst:  net.IPv4(ip[0], ip[1], ip[2], ip[3]),
		OrigPort: binary.LittleEndian.Uint16(e[52:54]),
		TunIP:    net.IPv4(tip[0], tip[1], tip[2], tip[3]),
	}
}

func (e Value) String() string {
	flagsStr := ""
	flags := e.Flags()

	if flags == 0 {
		flagsStr = " <none>"
	} else {
		if flags&FlagNATOut != 0 {
			flagsStr += " nat-out"
		}

		if flags&FlagNATFwdDsr != 0 {
			flagsStr += " fwd-dsr"
		}
	}

	ret := fmt.Sprintf("Entry{Type:%d, Created:%d, LastSeen:%d, Flags:%s ",
		e.Type(), e.Created(), e.LastSeen(), flagsStr)

	switch e.Type() {
	case TypeNATForward:
		ret += fmt.Sprintf("REVKey : %s", e.ReverseNATKey().String())
	case TypeNormal, TypeNATReverse:
		ret += fmt.Sprintf("Data: %+v", e.Data())
	default:
		ret += "TYPE INVALID"
	}

	return ret + "}"
}

func (e Value) IsForwardDSR() bool {
	return e.Flags()&FlagNATFwdDsr != 0
}

var MapParams = bpf.MapParameters{
	Filename:   "/sys/fs/bpf/tc/globals/cali_v4_ct",
	Type:       "hash",
	KeySize:    conntrackKeySize,
	ValueSize:  conntrackValueSize,
	MaxEntries: 512000,
	Name:       "cali_v4_ct",
	Flags:      unix.BPF_F_NO_PREALLOC,
	Version:    2,
}

func Map(mc *bpf.MapContext) bpf.Map {
	return mc.NewPinnedMap(MapParams)
}

const (
	ProtoICMP = 1
	ProtoTCP  = 6
	ProtoUDP  = 17
)

func keyFromBytes(k []byte) Key {
	var ctKey Key
	if len(k) != len(ctKey) {
		log.Panic("Key has unexpected length")
	}
	copy(ctKey[:], k[:])
	return ctKey
}

func entryFromBytes(v []byte) Value {
	var ctVal Value
	if len(v) != len(ctVal) {
		log.Panic("Value has unexpected length")
	}
	copy(ctVal[:], v[:])
	return ctVal
}

type MapMem map[Key]Value

// LoadMapMem loads ConntrackMap into memory
func LoadMapMem(m bpf.Map) (MapMem, error) {
	ret := make(MapMem)

	err := m.Iter(func(k, v []byte) {
		ks := len(Key{})
		vs := len(Value{})

		var key Key
		copy(key[:ks], k[:ks])

		var val Value
		copy(val[:vs], v[:vs])

		ret[key] = val
	})

	return ret, err
}

// MapMemIter returns bpf.MapIter that loads the provided MapMem
func MapMemIter(m MapMem) bpf.MapIter {
	ks := len(Key{})
	vs := len(Value{})

	return func(k, v []byte) {
		var key Key
		copy(key[:ks], k[:ks])

		var val Value
		copy(val[:vs], v[:vs])

		m[key] = val
	}
}
<commit_msg>bpf: functions for creating various conntrack values<commit_after>// Copyright (c) 2020 Tigera, Inc. All rights reserved.
//
// 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 conntrack

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

	log "github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"

	"github.com/projectcalico/felix/bpf"
)

// struct calico_ct_key {
//   uint32_t protocol;
//   __be32 addr_a, addr_b; // NBO
//   uint16_t port_a, port_b; // HBO
// };
const conntrackKeySize = 16
const conntrackValueSize = 64

type Key [conntrackKeySize]byte

func (k Key) AsBytes() []byte {
	return k[:]
}

func (k Key) Proto() uint8 {
	return uint8(binary.LittleEndian.Uint32(k[:4]))
}

func (k Key) AddrA() net.IP {
	return k[4:8]
}

func (k Key) PortA() uint16 {
	return binary.LittleEndian.Uint16(k[12:14])
}

func (k Key) AddrB() net.IP {
	return k[8:12]
}

func (k Key) PortB() uint16 {
	return binary.LittleEndian.Uint16(k[14:16])
}

func (k Key) String() string {
	return fmt.Sprintf("ConntrackKey{proto=%v %v:%v <-> %v:%v}",
		k.Proto(), k.AddrA(), k.PortA(), k.AddrB(), k.PortB())
}

func NewKey(proto uint8, ipA net.IP, portA uint16, ipB net.IP, portB uint16) Key {
	var k Key
	binary.LittleEndian.PutUint32(k[:4], uint32(proto))
	copy(k[4:8], ipA.To4())
	copy(k[8:12], ipB.To4())
	binary.LittleEndian.PutUint16(k[12:14], portA)
	binary.LittleEndian.PutUint16(k[14:16], portB)
	return k
}

// struct calico_ct_value {
//  __u64 created;
//  __u64 last_seen; // 8
//  __u8 type;     // 16
//  __u8 flags;     // 17
//
//  // Important to use explicit padding, otherwise the compiler can decide
//  // not to zero the padding bytes, which upsets the verifier.  Worse than
//  // that, debug logging often prevents such optimisation resulting in
//  // failures when debug logging is compiled out only :-).
//  __u8 pad0[6];
//  union {
//    // CALI_CT_TYPE_NORMAL and CALI_CT_TYPE_NAT_REV.
//    struct {
//      struct calico_ct_leg a_to_b; // 24
//      struct calico_ct_leg b_to_a; // 36
//
//      // CALI_CT_TYPE_NAT_REV only.
//      __u32 orig_dst;                    // 48
//      __u16 orig_port;                   // 52
//      __u8 pad1[2];                      // 54
//      __u32 tun_ip;                      // 56
//      __u32 pad3;                        // 60
//    };
//
//    // CALI_CT_TYPE_NAT_FWD; key for the CALI_CT_TYPE_NAT_REV entry.
//    struct {
//      struct calico_ct_key nat_rev_key;  // 24
//      __u8 pad2[8];
//    };
//  };
// };
type Value [conntrackValueSize]byte

func (e Value) Created() int64 {
	return int64(binary.LittleEndian.Uint64(e[:8]))
}

func (e Value) LastSeen() int64 {
	return int64(binary.LittleEndian.Uint64(e[8:16]))
}

func (e Value) Type() uint8 {
	return e[16]
}

func (e Value) Flags() uint8 {
	return e[17]
}

// OrigIP returns the original destination IP, valid only if Type() is TypeNormal or TypeNATReverse
func (e Value) OrigIP() net.IP {
	return e[48:52]
}

const (
	TypeNormal uint8 = iota
	TypeNATForward
	TypeNATReverse

	FlagNATOut    uint8 = (1 << 0)
	FlagNATFwdDsr uint8 = (1 << 1)
	FlagNATNPFwd  uint8 = (1 << 2)
)

func (e Value) ReverseNATKey() Key {
	var ret Key

	l := len(Key{})
	copy(ret[:l], e[24:24+l])

	return ret
}

// AsBytes returns the value as slice of bytes
func (e Value) AsBytes() []byte {
	return e[:]
}

func initValue(v *Value, created, lastSeen time.Duration, typ, flags uint8) {
	binary.LittleEndian.PutUint64(v[:8], uint64(created))
	binary.LittleEndian.PutUint64(v[8:16], uint64(lastSeen))
	v[16] = typ
	v[17] = flags
}

// NewValueNormal creates a new Value of type TypeNormal based on the given parameters
func NewValueNormal(created, lastSeen time.Duration, flags uint8, legA, legB Leg) Value {
	v := Value{}

	initValue(&v, created, lastSeen, TypeNormal, flags)

	copy(v[24:36], legA.AsBytes())
	copy(v[36:48], legA.AsBytes())

	return v
}

// NewValueNATForward creates a new Value of type TypeNATForward for the given
// arguments and the reverse key
func NewValueNATForward(created, lastSeen time.Duration, flags uint8, revKey Key) Value {
	v := Value{}

	initValue(&v, created, lastSeen, TypeNATForward, flags)

	copy(v[24:24+conntrackKeySize], revKey.AsBytes())

	return v
}

// NewValueNATReverse creates a new Value of type TypeNATReverse for the given
// arguments and reverse parameters
func NewValueNATReverse(created, lastSeen time.Duration, flags uint8, legA, legB Leg,
	tunnelIP, origIP net.IP, origPort uint16) Value {
	v := Value{}

	initValue(&v, created, lastSeen, TypeNATReverse, flags)

	copy(v[24:36], legA.AsBytes())
	copy(v[36:48], legA.AsBytes())

	copy(v[48:52], origIP.To4())
	binary.LittleEndian.PutUint16(v[52:54], origPort)

	copy(v[56:60], tunnelIP.To4())

	return v
}

type Leg struct {
	Seqno       uint32
	SynSeen     bool
	AckSeen     bool
	FinSeen     bool
	RstSeen     bool
	Whitelisted bool
	Opener      bool
	Ifindex     uint32
}

func setBit(bits *uint32, bit uint8, val bool) {
	if val {
		*bits |= (1 << bit)
	}
}

// AsBytes returns Leg serialized as a slice of bytes
func (leg Leg) AsBytes() []byte {
	bytes := make([]byte, 12)

	bits := uint32(0)

	setBit(&bits, 0, leg.SynSeen)
	setBit(&bits, 1, leg.AckSeen)
	setBit(&bits, 2, leg.FinSeen)
	setBit(&bits, 3, leg.RstSeen)
	setBit(&bits, 4, leg.Whitelisted)
	setBit(&bits, 5, leg.Opener)

	binary.LittleEndian.PutUint32(bytes[0:4], leg.Seqno)
	binary.LittleEndian.PutUint32(bytes[4:8], bits)
	binary.LittleEndian.PutUint32(bytes[8:12], leg.Ifindex)

	return bytes
}

func (leg Leg) Flags() uint32 {
	var flags uint32
	if leg.SynSeen {
		flags |= 1
	}
	if leg.AckSeen {
		flags |= 1 << 1
	}
	if leg.FinSeen {
		flags |= 1 << 2
	}
	if leg.RstSeen {
		flags |= 1 << 3
	}
	if leg.Whitelisted {
		flags |= 1 << 4
	}
	if leg.Opener {
		flags |= 1 << 5
	}
	return flags
}

func bitSet(bits uint32, bit uint8) bool {
	return (bits & (1 << bit)) != 0
}

func readConntrackLeg(b []byte) Leg {
	bits := binary.LittleEndian.Uint32(b[4:8])
	return Leg{
		Seqno:       binary.BigEndian.Uint32(b[0:4]),
		SynSeen:     bitSet(bits, 0),
		AckSeen:     bitSet(bits, 1),
		FinSeen:     bitSet(bits, 2),
		RstSeen:     bitSet(bits, 3),
		Whitelisted: bitSet(bits, 4),
		Opener:      bitSet(bits, 5),
		Ifindex:     binary.LittleEndian.Uint32(b[8:12]),
	}
}

type EntryData struct {
	A2B      Leg
	B2A      Leg
	OrigDst  net.IP
	OrigPort uint16
	TunIP    net.IP
}

func (data EntryData) Established() bool {
	return data.A2B.SynSeen && data.A2B.AckSeen && data.B2A.SynSeen && data.B2A.AckSeen
}

func (data EntryData) RSTSeen() bool {
	return data.A2B.RstSeen || data.B2A.RstSeen
}

func (data EntryData) FINsSeen() bool {
	return data.A2B.FinSeen && data.B2A.FinSeen
}

func (data EntryData) FINsSeenDSR() bool {
	return data.A2B.FinSeen || data.B2A.FinSeen
}

func (e Value) Data() EntryData {
	ip := e[48:52]
	tip := e[56:60]
	return EntryData{
		A2B:      readConntrackLeg(e[24:36]),
		B2A:      readConntrackLeg(e[36:48]),
		OrigDst:  net.IPv4(ip[0], ip[1], ip[2], ip[3]),
		OrigPort: binary.LittleEndian.Uint16(e[52:54]),
		TunIP:    net.IPv4(tip[0], tip[1], tip[2], tip[3]),
	}
}

func (e Value) String() string {
	flagsStr := ""
	flags := e.Flags()

	if flags == 0 {
		flagsStr = " <none>"
	} else {
		if flags&FlagNATOut != 0 {
			flagsStr += " nat-out"
		}

		if flags&FlagNATFwdDsr != 0 {
			flagsStr += " fwd-dsr"
		}
	}

	ret := fmt.Sprintf("Entry{Type:%d, Created:%d, LastSeen:%d, Flags:%s ",
		e.Type(), e.Created(), e.LastSeen(), flagsStr)

	switch e.Type() {
	case TypeNATForward:
		ret += fmt.Sprintf("REVKey : %s", e.ReverseNATKey().String())
	case TypeNormal, TypeNATReverse:
		ret += fmt.Sprintf("Data: %+v", e.Data())
	default:
		ret += "TYPE INVALID"
	}

	return ret + "}"
}

func (e Value) IsForwardDSR() bool {
	return e.Flags()&FlagNATFwdDsr != 0
}

var MapParams = bpf.MapParameters{
	Filename:   "/sys/fs/bpf/tc/globals/cali_v4_ct",
	Type:       "hash",
	KeySize:    conntrackKeySize,
	ValueSize:  conntrackValueSize,
	MaxEntries: 512000,
	Name:       "cali_v4_ct",
	Flags:      unix.BPF_F_NO_PREALLOC,
	Version:    2,
}

func Map(mc *bpf.MapContext) bpf.Map {
	return mc.NewPinnedMap(MapParams)
}

const (
	ProtoICMP = 1
	ProtoTCP  = 6
	ProtoUDP  = 17
)

func keyFromBytes(k []byte) Key {
	var ctKey Key
	if len(k) != len(ctKey) {
		log.Panic("Key has unexpected length")
	}
	copy(ctKey[:], k[:])
	return ctKey
}

func entryFromBytes(v []byte) Value {
	var ctVal Value
	if len(v) != len(ctVal) {
		log.Panic("Value has unexpected length")
	}
	copy(ctVal[:], v[:])
	return ctVal
}

type MapMem map[Key]Value

// LoadMapMem loads ConntrackMap into memory
func LoadMapMem(m bpf.Map) (MapMem, error) {
	ret := make(MapMem)

	err := m.Iter(func(k, v []byte) {
		ks := len(Key{})
		vs := len(Value{})

		var key Key
		copy(key[:ks], k[:ks])

		var val Value
		copy(val[:vs], v[:vs])

		ret[key] = val
	})

	return ret, err
}

// MapMemIter returns bpf.MapIter that loads the provided MapMem
func MapMemIter(m MapMem) bpf.MapIter {
	ks := len(Key{})
	vs := len(Value{})

	return func(k, v []byte) {
		var key Key
		copy(key[:ks], k[:ks])

		var val Value
		copy(val[:vs], v[:vs])

		m[key] = val
	}
}

// BytesToKey turns a slice of bytes into a Key
func BytesToKey(bytes []byte) Key {
	var k Key

	if len(bytes) >= len(k) {
		copy(k[:], bytes[:len(k)])
	} else {
		copy(k[:], bytes[:])
	}

	return k
}

// StringToKey turns a string into a Key
func StringToKey(str string) Key {
	return BytesToKey([]byte(str))
}

// BytesToValue turns a slice of bytes into a Key
func BytesToValue(bytes []byte) Value {
	var v Value

	copy(v[:], bytes[:len(v)])

	return v
}

// StringToValue turns a string into a Value
func StringToValue(str string) Value {
	return BytesToValue([]byte(str))
}
<|endoftext|>


<commit_before>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package time

import (
	"errors"
	"sync"
	"syscall"
)

//go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go

// A Location maps time instants to the zone in use at that time.
// Typically, the Location represents the collection of time offsets
// in use in a geographical area, such as CEST and CET for central Europe.
type Location struct {
	name string
	zone []zone
	tx   []zoneTrans

	// Most lookups will be for the current time.
	// To avoid the binary search through tx, keep a
	// static one-element cache that gives the correct
	// zone for the time when the Location was created.
	// if cacheStart <= t < cacheEnd,
	// lookup can return cacheZone.
	// The units for cacheStart and cacheEnd are seconds
	// since January 1, 1970 UTC, to match the argument
	// to lookup.
	cacheStart int64
	cacheEnd   int64
	cacheZone  *zone
}

// A zone represents a single time zone such as CEST or CET.
type zone struct {
	name   string // abbreviated name, "CET"
	offset int    // seconds east of UTC
	isDST  bool   // is this zone Daylight Savings Time?
}

// A zoneTrans represents a single time zone transition.
type zoneTrans struct {
	when         int64 // transition time, in seconds since 1970 GMT
	index        uint8 // the index of the zone that goes into effect at that time
	isstd, isutc bool  // ignored - no idea what these mean
}

// alpha and omega are the beginning and end of time for zone
// transitions.
const (
	alpha = -1 << 63  // math.MinInt64
	omega = 1<<63 - 1 // math.MaxInt64
)

// UTC represents Universal Coordinated Time (UTC).
var UTC *Location = &utcLoc

// utcLoc is separate so that get can refer to &utcLoc
// and ensure that it never returns a nil *Location,
// even if a badly behaved client has changed UTC.
var utcLoc = Location{name: "UTC"}

// Local represents the system's local time zone.
var Local *Location = &localLoc

// localLoc is separate so that initLocal can initialize
// it even if a client has changed Local.
var localLoc Location
var localOnce sync.Once

func (l *Location) get() *Location {
	if l == nil {
		return &utcLoc
	}
	if l == &localLoc {
		localOnce.Do(initLocal)
	}
	return l
}

// String returns a descriptive name for the time zone information,
// corresponding to the name argument to LoadLocation or FixedZone.
func (l *Location) String() string {
	return l.get().name
}

// FixedZone returns a Location that always uses
// the given zone name and offset (seconds east of UTC).
func FixedZone(name string, offset int) *Location {
	l := &Location{
		name:       name,
		zone:       []zone{{name, offset, false}},
		tx:         []zoneTrans{{alpha, 0, false, false}},
		cacheStart: alpha,
		cacheEnd:   omega,
	}
	l.cacheZone = &l.zone[0]
	return l
}

// lookup returns information about the time zone in use at an
// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
//
// The returned information gives the name of the zone (such as "CET"),
// the start and end times bracketing sec when that zone is in effect,
// the offset in seconds east of UTC (such as -5*60*60), and whether
// the daylight savings is being observed at that time.
func (l *Location) lookup(sec int64) (name string, offset int, start, end int64) {
	l = l.get()

	if len(l.zone) == 0 {
		name = "UTC"
		offset = 0
		start = alpha
		end = omega
		return
	}

	if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
		name = zone.name
		offset = zone.offset
		start = l.cacheStart
		end = l.cacheEnd
		return
	}

	if len(l.tx) == 0 || sec < l.tx[0].when {
		zone := &l.zone[l.lookupFirstZone()]
		name = zone.name
		offset = zone.offset
		start = alpha
		if len(l.tx) > 0 {
			end = l.tx[0].when
		} else {
			end = omega
		}
		return
	}

	// Binary search for entry with largest time <= sec.
	// Not using sort.Search to avoid dependencies.
	tx := l.tx
	end = omega
	lo := 0
	hi := len(tx)
	for hi-lo > 1 {
		m := lo + (hi-lo)/2
		lim := tx[m].when
		if sec < lim {
			end = lim
			hi = m
		} else {
			lo = m
		}
	}
	zone := &l.zone[tx[lo].index]
	name = zone.name
	offset = zone.offset
	start = tx[lo].when
	// end = maintained during the search
	return
}

// lookupFirstZone returns the index of the time zone to use for times
// before the first transition time, or when there are no transition
// times.
//
// The reference implementation in localtime.c from
// https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
// implements the following algorithm for these cases:
// 1) If the first zone is unused by the transitions, use it.
// 2) Otherwise, if there are transition times, and the first
//    transition is to a zone in daylight time, find the first
//    non-daylight-time zone before and closest to the first transition
//    zone.
// 3) Otherwise, use the first zone that is not daylight time, if
//    there is one.
// 4) Otherwise, use the first zone.
func (l *Location) lookupFirstZone() int {
	// Case 1.
	if !l.firstZoneUsed() {
		return 0
	}

	// Case 2.
	if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
		for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
			if !l.zone[zi].isDST {
				return zi
			}
		}
	}

	// Case 3.
	for zi := range l.zone {
		if !l.zone[zi].isDST {
			return zi
		}
	}

	// Case 4.
	return 0
}

// firstZoneUsed returns whether the first zone is used by some
// transition.
func (l *Location) firstZoneUsed() bool {
	for _, tx := range l.tx {
		if tx.index == 0 {
			return true
		}
	}
	return false
}

// lookupName returns information about the time zone with
// the given name (such as "EST") at the given pseudo-Unix time
// (what the given time of day would be in UTC).
func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
	l = l.get()

	// First try for a zone with the right name that was actually
	// in effect at the given time. (In Sydney, Australia, both standard
	// and daylight-savings time are abbreviated "EST". Using the
	// offset helps us pick the right one for the given time.
	// It's not perfect: during the backward transition we might pick
	// either one.)
	for i := range l.zone {
		zone := &l.zone[i]
		if zone.name == name {
			nam, offset, _, _ := l.lookup(unix - int64(zone.offset))
			if nam == zone.name {
				return offset, true
			}
		}
	}

	// Otherwise fall back to an ordinary name match.
	for i := range l.zone {
		zone := &l.zone[i]
		if zone.name == name {
			return zone.offset, true
		}
	}

	// Otherwise, give up.
	return
}

// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
// syntax too, but I don't feel like implementing it today.

var errLocation = errors.New("time: invalid location name")

var zoneinfo *string
var zoneinfoOnce sync.Once

// LoadLocation returns the Location with the given name.
//
// If the name is "" or "UTC", LoadLocation returns UTC.
// If the name is "Local", LoadLocation returns Local.
//
// Otherwise, the name is taken to be a location name corresponding to a file
// in the IANA Time Zone database, such as "America/New_York".
//
// The time zone database needed by LoadLocation may not be
// present on all systems, especially non-Unix systems.
// LoadLocation looks in the directory or uncompressed zip file
// named by the ZONEINFO environment variable, if any, then looks in
// known installation locations on Unix systems,
// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
func LoadLocation(name string) (*Location, error) {
	if name == "" || name == "UTC" {
		return UTC, nil
	}
	if name == "Local" {
		return Local, nil
	}
	if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
		// No valid IANA Time Zone name contains a single dot,
		// much less dot dot. Likewise, none begin with a slash.
		return nil, errLocation
	}
	zoneinfoOnce.Do(func() {
		env, _ := syscall.Getenv("ZONEINFO")
		zoneinfo = &env
	})
	if *zoneinfo != "" {
		if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
			if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
				return z, nil
			}
		}
	}
	return loadLocation(name, zoneSources)
}

// containsDotDot reports whether s contains "..".
func containsDotDot(s string) bool {
	if len(s) < 2 {
		return false
	}
	for i := 0; i < len(s)-1; i++ {
		if s[i] == '.' && s[i+1] == '.' {
			return true
		}
	}
	return false
}
<commit_msg>time: return first error in unsuccessful calls to LoadLocation<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package time

import (
	"errors"
	"sync"
	"syscall"
)

//go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go

// A Location maps time instants to the zone in use at that time.
// Typically, the Location represents the collection of time offsets
// in use in a geographical area, such as CEST and CET for central Europe.
type Location struct {
	name string
	zone []zone
	tx   []zoneTrans

	// Most lookups will be for the current time.
	// To avoid the binary search through tx, keep a
	// static one-element cache that gives the correct
	// zone for the time when the Location was created.
	// if cacheStart <= t < cacheEnd,
	// lookup can return cacheZone.
	// The units for cacheStart and cacheEnd are seconds
	// since January 1, 1970 UTC, to match the argument
	// to lookup.
	cacheStart int64
	cacheEnd   int64
	cacheZone  *zone
}

// A zone represents a single time zone such as CEST or CET.
type zone struct {
	name   string // abbreviated name, "CET"
	offset int    // seconds east of UTC
	isDST  bool   // is this zone Daylight Savings Time?
}

// A zoneTrans represents a single time zone transition.
type zoneTrans struct {
	when         int64 // transition time, in seconds since 1970 GMT
	index        uint8 // the index of the zone that goes into effect at that time
	isstd, isutc bool  // ignored - no idea what these mean
}

// alpha and omega are the beginning and end of time for zone
// transitions.
const (
	alpha = -1 << 63  // math.MinInt64
	omega = 1<<63 - 1 // math.MaxInt64
)

// UTC represents Universal Coordinated Time (UTC).
var UTC *Location = &utcLoc

// utcLoc is separate so that get can refer to &utcLoc
// and ensure that it never returns a nil *Location,
// even if a badly behaved client has changed UTC.
var utcLoc = Location{name: "UTC"}

// Local represents the system's local time zone.
var Local *Location = &localLoc

// localLoc is separate so that initLocal can initialize
// it even if a client has changed Local.
var localLoc Location
var localOnce sync.Once

func (l *Location) get() *Location {
	if l == nil {
		return &utcLoc
	}
	if l == &localLoc {
		localOnce.Do(initLocal)
	}
	return l
}

// String returns a descriptive name for the time zone information,
// corresponding to the name argument to LoadLocation or FixedZone.
func (l *Location) String() string {
	return l.get().name
}

// FixedZone returns a Location that always uses
// the given zone name and offset (seconds east of UTC).
func FixedZone(name string, offset int) *Location {
	l := &Location{
		name:       name,
		zone:       []zone{{name, offset, false}},
		tx:         []zoneTrans{{alpha, 0, false, false}},
		cacheStart: alpha,
		cacheEnd:   omega,
	}
	l.cacheZone = &l.zone[0]
	return l
}

// lookup returns information about the time zone in use at an
// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
//
// The returned information gives the name of the zone (such as "CET"),
// the start and end times bracketing sec when that zone is in effect,
// the offset in seconds east of UTC (such as -5*60*60), and whether
// the daylight savings is being observed at that time.
func (l *Location) lookup(sec int64) (name string, offset int, start, end int64) {
	l = l.get()

	if len(l.zone) == 0 {
		name = "UTC"
		offset = 0
		start = alpha
		end = omega
		return
	}

	if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
		name = zone.name
		offset = zone.offset
		start = l.cacheStart
		end = l.cacheEnd
		return
	}

	if len(l.tx) == 0 || sec < l.tx[0].when {
		zone := &l.zone[l.lookupFirstZone()]
		name = zone.name
		offset = zone.offset
		start = alpha
		if len(l.tx) > 0 {
			end = l.tx[0].when
		} else {
			end = omega
		}
		return
	}

	// Binary search for entry with largest time <= sec.
	// Not using sort.Search to avoid dependencies.
	tx := l.tx
	end = omega
	lo := 0
	hi := len(tx)
	for hi-lo > 1 {
		m := lo + (hi-lo)/2
		lim := tx[m].when
		if sec < lim {
			end = lim
			hi = m
		} else {
			lo = m
		}
	}
	zone := &l.zone[tx[lo].index]
	name = zone.name
	offset = zone.offset
	start = tx[lo].when
	// end = maintained during the search
	return
}

// lookupFirstZone returns the index of the time zone to use for times
// before the first transition time, or when there are no transition
// times.
//
// The reference implementation in localtime.c from
// https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
// implements the following algorithm for these cases:
// 1) If the first zone is unused by the transitions, use it.
// 2) Otherwise, if there are transition times, and the first
//    transition is to a zone in daylight time, find the first
//    non-daylight-time zone before and closest to the first transition
//    zone.
// 3) Otherwise, use the first zone that is not daylight time, if
//    there is one.
// 4) Otherwise, use the first zone.
func (l *Location) lookupFirstZone() int {
	// Case 1.
	if !l.firstZoneUsed() {
		return 0
	}

	// Case 2.
	if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
		for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
			if !l.zone[zi].isDST {
				return zi
			}
		}
	}

	// Case 3.
	for zi := range l.zone {
		if !l.zone[zi].isDST {
			return zi
		}
	}

	// Case 4.
	return 0
}

// firstZoneUsed returns whether the first zone is used by some
// transition.
func (l *Location) firstZoneUsed() bool {
	for _, tx := range l.tx {
		if tx.index == 0 {
			return true
		}
	}
	return false
}

// lookupName returns information about the time zone with
// the given name (such as "EST") at the given pseudo-Unix time
// (what the given time of day would be in UTC).
func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
	l = l.get()

	// First try for a zone with the right name that was actually
	// in effect at the given time. (In Sydney, Australia, both standard
	// and daylight-savings time are abbreviated "EST". Using the
	// offset helps us pick the right one for the given time.
	// It's not perfect: during the backward transition we might pick
	// either one.)
	for i := range l.zone {
		zone := &l.zone[i]
		if zone.name == name {
			nam, offset, _, _ := l.lookup(unix - int64(zone.offset))
			if nam == zone.name {
				return offset, true
			}
		}
	}

	// Otherwise fall back to an ordinary name match.
	for i := range l.zone {
		zone := &l.zone[i]
		if zone.name == name {
			return zone.offset, true
		}
	}

	// Otherwise, give up.
	return
}

// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
// syntax too, but I don't feel like implementing it today.

var errLocation = errors.New("time: invalid location name")

var zoneinfo *string
var zoneinfoOnce sync.Once

// LoadLocation returns the Location with the given name.
//
// If the name is "" or "UTC", LoadLocation returns UTC.
// If the name is "Local", LoadLocation returns Local.
//
// Otherwise, the name is taken to be a location name corresponding to a file
// in the IANA Time Zone database, such as "America/New_York".
//
// The time zone database needed by LoadLocation may not be
// present on all systems, especially non-Unix systems.
// LoadLocation looks in the directory or uncompressed zip file
// named by the ZONEINFO environment variable, if any, then looks in
// known installation locations on Unix systems,
// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
func LoadLocation(name string) (*Location, error) {
	if name == "" || name == "UTC" {
		return UTC, nil
	}
	if name == "Local" {
		return Local, nil
	}
	if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
		// No valid IANA Time Zone name contains a single dot,
		// much less dot dot. Likewise, none begin with a slash.
		return nil, errLocation
	}
	zoneinfoOnce.Do(func() {
		env, _ := syscall.Getenv("ZONEINFO")
		zoneinfo = &env
	})
	var firstErr error
	if *zoneinfo != "" {
		if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
			if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
				return z, nil
			}
			firstErr = err
		} else if err != syscall.ENOENT {
			firstErr = err
		}
	}
	if z, err := loadLocation(name, zoneSources); err == nil {
		return z, nil
	} else if firstErr == nil {
		firstErr = err
	}
	return nil, firstErr
}

// containsDotDot reports whether s contains "..".
func containsDotDot(s string) bool {
	if len(s) < 2 {
		return false
	}
	for i := 0; i < len(s)-1; i++ {
		if s[i] == '.' && s[i+1] == '.' {
			return true
		}
	}
	return false
}
<|endoftext|>