<commit_before>package properrandom

import (
	cryptrand "crypto/rand"
	"encoding/binary"
	"io"
	mathrand "math/rand"
)

var defaultSource cryptoSource
var globalRand *mathrand.Rand

func init() {
	defaultSource = cryptoSource{
		source: cryptrand.Reader,
	}

	globalRand = mathrand.New(defaultSource)
}

type cryptoSource struct {
	source io.Reader
}

func (c cryptoSource) Uint64() uint64 {
	buf := make([]byte, 8)
	n, err := io.ReadFull(c.source, buf)
	if err != nil {
		// Fall back on the default random reader
		_, err = io.ReadFull(cryptrand.Reader, buf[n:])
		if err != nil {
			panic(err)
		}
	}

	return binary.BigEndian.Uint64(buf)
}

func (c cryptoSource) Int63() int64 {
	return int64(c.Uint64() & 0x7fffffffffffffff)
}

func (c cryptoSource) Seed(seed int64) {
	// Don't.
}

// Int63 returns a non-negative pseudo-random 63-bit integer as an int64
// from the default Source.
func Int63() int64 { return globalRand.Int63() }

// Uint32 returns a pseudo-random 32-bit value as a uint32
// from the default Source.
func Uint32() uint32 { return globalRand.Uint32() }

// Uint64 returns a pseudo-random 64-bit value as a uint64
// from the default Source.
func Uint64() uint64 { return globalRand.Uint64() }

// Int31 returns a non-negative pseudo-random 31-bit integer as an int32
// from the default Source.
func Int31() int32 { return globalRand.Int31() }

// Int returns a non-negative pseudo-random int from the default Source.
func Int() int { return globalRand.Int() }

// Int63n returns, as an int64, a non-negative pseudo-random number in [0,n)
// from the default Source.
// It panics if n <= 0.
func Int63n(n int64) int64 { return globalRand.Int63n(n) }

// Int31n returns, as an int32, a non-negative pseudo-random number in [0,n)
// from the default Source.
// It panics if n <= 0.
func Int31n(n int32) int32 { return globalRand.Int31n(n) }

// Intn returns, as an int, a non-negative pseudo-random number in [0,n)
// from the default Source.
// It panics if n <= 0.
func Intn(n int) int { return globalRand.Intn(n) }

// Float64 returns, as a float64, a pseudo-random number in [0.0,1.0)
// from the default Source.
func Float64() float64 { return globalRand.Float64() }

// Float32 returns, as a float32, a pseudo-random number in [0.0,1.0)
// from the default Source.
func Float32() float32 { return globalRand.Float32() }

// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n)
// from the default Source.
func Perm(n int) []int { return globalRand.Perm(n) }

// Read generates len(p) random bytes from the default Source and
// writes them into p. It always returns len(p) and a nil error.
// Read, unlike the Rand.Read method, is safe for concurrent use.
func Read(p []byte) (n int, err error) { return globalRand.Read(p) }

// NormFloat64 returns a normally distributed float64 in the range
// [-math.MaxFloat64, +math.MaxFloat64] with
// standard normal distribution (mean = 0, stddev = 1)
// from the default Source.
// To produce a different normal distribution, callers can
// adjust the output using:
//
//  sample = NormFloat64() * desiredStdDev + desiredMean
//
func NormFloat64() float64 { return globalRand.NormFloat64() }

// ExpFloat64 returns an exponentially distributed float64 in the range
// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter
// (lambda) is 1 and whose mean is 1/lambda (1) from the default Source.
// To produce a distribution with a different rate parameter,
// callers can adjust the output using:
//
//  sample = ExpFloat64() / desiredRateParameter
//
func ExpFloat64() float64 { return globalRand.ExpFloat64() }
<commit_msg>Fix compatibility with Go < 1.8<commit_after>package properrandom

import (
	cryptrand "crypto/rand"
	"encoding/binary"
	"io"
	mathrand "math/rand"
)

var defaultSource cryptoSource
var globalRand *mathrand.Rand

func init() {
	defaultSource = cryptoSource{
		source: cryptrand.Reader,
	}

	globalRand = mathrand.New(defaultSource)
}

type cryptoSource struct {
	source io.Reader
}

func (c cryptoSource) Uint64() uint64 {
	buf := make([]byte, 8)
	n, err := io.ReadFull(c.source, buf)
	if err != nil {
		// Fall back on the default random reader
		_, err = io.ReadFull(cryptrand.Reader, buf[n:])
		if err != nil {
			panic(err)
		}
	}

	return binary.BigEndian.Uint64(buf)
}

func (c cryptoSource) Int63() int64 {
	return int64(c.Uint64() & 0x7fffffffffffffff)
}

func (c cryptoSource) Seed(seed int64) {
	// Don't.
}

// Int63 returns a non-negative pseudo-random 63-bit integer as an int64
// from the default Source.
func Int63() int64 { return globalRand.Int63() }

// Uint32 returns a pseudo-random 32-bit value as a uint32
// from the default Source.
func Uint32() uint32 { return globalRand.Uint32() }

// Uint64 returns a pseudo-random 64-bit value as a uint64
// from the default Source.
func Uint64() uint64 { return defaultSource.Uint64() }

// Int31 returns a non-negative pseudo-random 31-bit integer as an int32
// from the default Source.
func Int31() int32 { return globalRand.Int31() }

// Int returns a non-negative pseudo-random int from the default Source.
func Int() int { return globalRand.Int() }

// Int63n returns, as an int64, a non-negative pseudo-random number in [0,n)
// from the default Source.
// It panics if n <= 0.
func Int63n(n int64) int64 { return globalRand.Int63n(n) }

// Int31n returns, as an int32, a non-negative pseudo-random number in [0,n)
// from the default Source.
// It panics if n <= 0.
func Int31n(n int32) int32 { return globalRand.Int31n(n) }

// Intn returns, as an int, a non-negative pseudo-random number in [0,n)
// from the default Source.
// It panics if n <= 0.
func Intn(n int) int { return globalRand.Intn(n) }

// Float64 returns, as a float64, a pseudo-random number in [0.0,1.0)
// from the default Source.
func Float64() float64 { return globalRand.Float64() }

// Float32 returns, as a float32, a pseudo-random number in [0.0,1.0)
// from the default Source.
func Float32() float32 { return globalRand.Float32() }

// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n)
// from the default Source.
func Perm(n int) []int { return globalRand.Perm(n) }

// Read generates len(p) random bytes from the default Source and
// writes them into p. It always returns len(p) and a nil error.
// Read, unlike the Rand.Read method, is safe for concurrent use.
func Read(p []byte) (n int, err error) { return globalRand.Read(p) }

// NormFloat64 returns a normally distributed float64 in the range
// [-math.MaxFloat64, +math.MaxFloat64] with
// standard normal distribution (mean = 0, stddev = 1)
// from the default Source.
// To produce a different normal distribution, callers can
// adjust the output using:
//
//  sample = NormFloat64() * desiredStdDev + desiredMean
//
func NormFloat64() float64 { return globalRand.NormFloat64() }

// ExpFloat64 returns an exponentially distributed float64 in the range
// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter
// (lambda) is 1 and whose mean is 1/lambda (1) from the default Source.
// To produce a distribution with a different rate parameter,
// callers can adjust the output using:
//
//  sample = ExpFloat64() / desiredRateParameter
//
func ExpFloat64() float64 { return globalRand.ExpFloat64() }
<|endoftext|>


<commit_before>package queuesender

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"io"
	"net"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"
)

const (
	kNetworkTimeout = 15 * time.Second
)

var (
	kErrNoConnection = errors.New("queuesender: No connection")
	kErrInterrupted  = errors.New("queuesender: interrupted")
)

type connManagerType struct {
	refresh       func() (net.Conn, error)
	lock          sync.Mutex
	active        *connType
	connRefreshes uint64
}

func newConnManager(urlStr string) (*connManagerType, error) {
	scheme, endpoint, err := extractEndpoint(urlStr)
	if err != nil {
		return nil, err
	}
	var refresh func() (net.Conn, error)
	if scheme == "https" {
		refresh = func() (net.Conn, error) {
			return tls.Dial("tcp", endpoint, nil)
		}
	}
	if refresh == nil {
		return nil, errors.New("Unsupported scheme")
	}
	return &connManagerType{refresh: refresh}, nil
}

func (c *connManagerType) Get() (*connType, error) {
	c.lock.Lock()
	defer c.lock.Unlock()
	if c.active != nil && !c.active.isBad() {
		return c.active.get(), nil
	}
	conn, err := c.refresh()
	if err != nil {
		return nil, err
	}
	c.active = newConn(conn)
	c.connRefreshes++
	return c.active.get(), nil
}

func (c *connManagerType) Refreshes() uint64 {
	c.lock.Lock()
	defer c.lock.Unlock()
	return c.connRefreshes
}

// connType represents a single connection.
type connType struct {
	conn      net.Conn
	br        *bufio.Reader
	lock      sync.Mutex
	userCount int
	bad       bool
}

func newConn(conn net.Conn) *connType {
	return &connType{conn: conn, br: bufio.NewReader(conn)}
}

func (c *connType) Put() {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.userCount--
	if c.userCount < 1 && c.bad {
		c.conn.Close()
	}
}

func (c *connType) MarkBad() {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.bad = true
}

func (c *connType) isBad() bool {
	c.lock.Lock()
	defer c.lock.Unlock()
	return c.bad
}

func (c *connType) get() *connType {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.userCount++
	return c
}

func (c *connType) Send(req requestType) error {
	buffer, err := encodeJSON(req.Json)
	if err != nil {
		return err
	}
	httpreq, err := http.NewRequest("POST", req.Endpoint, buffer)
	if err != nil {
		return err
	}
	httpreq.Header.Set("Content-Type", "application/json")
	return httpreq.Write(c.conn)
}

func (c *connType) Read() (bool, error) {
	resp, err := http.ReadResponse(c.br, nil)
	if err != nil {
		return false, err
	}
	defer resp.Body.Close()
	if resp.StatusCode/100 != 2 {
		var buffer bytes.Buffer
		io.Copy(&buffer, resp.Body)
		// 408 is a special error meaning that the connection timed out
		// because we didn't send any request. 408 errors don't go with
		// any particular request. When we get one of these, assume the
		// connection is bad.
		return resp.StatusCode != 408, errors.New(resp.Status + ": " + buffer.String())
	}
	return true, nil
}

func encodeJSON(payload interface{}) (*bytes.Buffer, error) {
	result := &bytes.Buffer{}
	encoder := json.NewEncoder(result)
	if err := encoder.Encode(payload); err != nil {
		return nil, err
	}
	return result, nil
}

func extractEndpoint(urlStr string) (scheme, endpoint string, err error) {
	u, err := url.Parse(urlStr)
	if err != nil {
		return "", "", err
	}
	host := u.Host
	if u.Scheme == "https" && !strings.Contains(u.Host, ":") {
		host = host + ":443"
	}
	if u.Scheme == "http" && !strings.Contains(u.Host, ":") {
		host = host + ":80"
	}
	return u.Scheme, host, nil
}
<commit_msg>Add more documentation. Remove unused code.<commit_after>package queuesender

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"io"
	"net"
	"net/http"
	"net/url"
	"strings"
	"sync"
)

type connManagerType struct {
	refresh       func() (net.Conn, error)
	lock          sync.Mutex
	active        *connType
	connRefreshes uint64
}

// newConnManager returns a connection manager that creates connections to
// urlStr.
func newConnManager(urlStr string) (*connManagerType, error) {
	scheme, endpoint, err := extractEndpoint(urlStr)
	if err != nil {
		return nil, err
	}
	var refresh func() (net.Conn, error)
	if scheme == "https" {
		refresh = func() (net.Conn, error) {
			return tls.Dial("tcp", endpoint, nil)
		}
	}
	if refresh == nil {
		return nil, errors.New("Unsupported scheme")
	}
	return &connManagerType{refresh: refresh}, nil
}

// Get returns the current connection. If the current connection is bad,
// Get replaces it with a new connection.
func (c *connManagerType) Get() (*connType, error) {
	c.lock.Lock()
	defer c.lock.Unlock()
	if c.active != nil && !c.active.isBad() {
		return c.active.get(), nil
	}
	conn, err := c.refresh()
	if err != nil {
		return nil, err
	}
	c.active = newConn(conn)
	c.connRefreshes++
	return c.active.get(), nil
}

// Refreshes returns the number of connection refreshes. That is, the
// number of times Get() created a new connection to replace a bad connection.
func (c *connManagerType) Refreshes() uint64 {
	c.lock.Lock()
	defer c.lock.Unlock()
	return c.connRefreshes
}

// connType represents a single connection.
type connType struct {
	conn      net.Conn
	br        *bufio.Reader
	lock      sync.Mutex
	userCount int
	bad       bool
}

func newConn(conn net.Conn) *connType {
	return &connType{conn: conn, br: bufio.NewReader(conn)}
}

// Put marks this connection as unused by the caller. Each time the caller
// calls Get() on the connection manager to get a connection, it must call
// Put() on the returned connection when done with it. The caller must not
// use a connection after calling Put on it.
func (c *connType) Put() {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.userCount--
	if c.userCount < 1 && c.bad {
		c.conn.Close()
	}
}

// MarkBad marks this connection as bad. Caller must call MarkBad before
// calling Put.
func (c *connType) MarkBad() {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.bad = true
}

func (c *connType) isBad() bool {
	c.lock.Lock()
	defer c.lock.Unlock()
	return c.bad
}

func (c *connType) get() *connType {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.userCount++
	return c
}

// Send sends given request on this connection.
func (c *connType) Send(req requestType) error {
	buffer, err := encodeJSON(req.Json)
	if err != nil {
		return err
	}
	httpreq, err := http.NewRequest("POST", req.Endpoint, buffer)
	if err != nil {
		return err
	}
	httpreq.Header.Set("Content-Type", "application/json")
	return httpreq.Write(c.conn)
}

// Read reads response from the connection returning true if the connection
// is good or false if the connection is bad. If the connection is good,
// err will be non-nil if response read is something other than a 2XX
// response.
func (c *connType) Read() (bool, error) {
	resp, err := http.ReadResponse(c.br, nil)
	if err != nil {
		return false, err
	}
	defer resp.Body.Close()
	if resp.StatusCode/100 != 2 {
		var buffer bytes.Buffer
		io.Copy(&buffer, resp.Body)
		// 408 is a special error meaning that the connection timed out
		// because we didn't send any request. 408 errors don't go with
		// any particular request. When we get one of these, assume the
		// connection is bad.
		return resp.StatusCode != 408, errors.New(resp.Status + ": " + buffer.String())
	}
	return true, nil
}

func encodeJSON(payload interface{}) (*bytes.Buffer, error) {
	result := &bytes.Buffer{}
	encoder := json.NewEncoder(result)
	if err := encoder.Encode(payload); err != nil {
		return nil, err
	}
	return result, nil
}

func extractEndpoint(urlStr string) (scheme, endpoint string, err error) {
	u, err := url.Parse(urlStr)
	if err != nil {
		return "", "", err
	}
	host := u.Host
	if u.Scheme == "https" && !strings.Contains(u.Host, ":") {
		host = host + ":443"
	}
	if u.Scheme == "http" && !strings.Contains(u.Host, ":") {
		host = host + ":80"
	}
	return u.Scheme, host, nil
}
<|endoftext|>


<commit_before>package server

import (
	"github.com/trasa/watchmud-message"
	"github.com/trasa/watchmud-message/slot"
	"github.com/trasa/watchmud/client"
	"github.com/trasa/watchmud/db"
	"github.com/trasa/watchmud/gameserver"
	"github.com/trasa/watchmud/mudtime"
	player2 "github.com/trasa/watchmud/player"
	"github.com/trasa/watchmud/world"
	"log"
	"time"
)

type GameServer struct {
	incomingBuffer chan *gameserver.HandlerParameter
	World          *world.World
	tickInterval   time.Duration
}

func NewGameServer(worldFilesDirectory string) (gs *GameServer, err error) {
	w, err := world.New(worldFilesDirectory)
	gs = &GameServer{
		incomingBuffer: make(chan *gameserver.HandlerParameter),
		World:          w,
	}
	return
}

//noinspection SpellCheckingInspection
func (gs *GameServer) Run() {
	// this is the loop that handles incoming requests
	// needs to be organized around PULSEs
	tstart := time.Now().UnixNano()
	ticker := time.NewTicker(mudtime.PULSE_INTERVAL)
	pulse := mudtime.PulseCount(0)

	for {
		select {
		case <-ticker.C:
			now := time.Now().UnixNano()
			deltaSeconds := float64(now-tstart) / 1000000000
			tstart = now
			pulse++
			gs.heartbeat(pulse, deltaSeconds)
		}
	}
}

// runs the heartbeat of the game. Use pulse to determine intervals
// between things (ex. reset zones every 15 minutes...)
// delta is the amount of time since the last heartbeat was run.
func (gs *GameServer) heartbeat(pulse mudtime.PulseCount, delta float64) {
	//log.Printf("pulse %d hb %d", pulse, delta)
	// mobs, scripts, ...

	// pulse zone
	// (zone reset ...)
	if pulse.CheckInterval(mudtime.PULSE_ZONE) {
		gs.World.DoZoneActivity()
	}

	// pulse mobs
	// (mobs walk around, initiate attack?)
	if pulse.CheckInterval(mudtime.PULSE_MOBILE) {
		gs.World.DoMobileActivity()
	}

	// perform violence
	// do the attacking (players and mobs and everybody)
	if pulse.CheckInterval(mudtime.PULSE_VIOLENCE) {
		gs.World.DoViolence(pulse)
	}

	// mud-hour ("player tick")
	// affect weather, regen ..

	// handle an incoming message if one exists
	// TODO tick time: figure out how many incoming messages we can handle
	// see issue #4
	// for now, just process until buffer is empty...

	// not really infinite as the method will return false if there was
	// nothing to do.
	//noinspection GoInfiniteFor
	for gs.processIncomingMessage() {
	}
}

// read a message off of incomingMessageBuffer and do it
// this doesn't block so if the buffer is empty, the method returns immediately
// If a message was procssed (even in error) return true.
// Otherwise return false.
func (gs *GameServer) processIncomingMessage() bool {
	received := false
	select {
	case msg := <-gs.incomingBuffer:
		received = true
		switch msg.Message.Inner.(type) {
		case *message.GameMessage_LoginRequest:
			gs.handleLogin(msg) // TODO error handling

		case *message.GameMessage_CreatePlayerRequest:
			gs.handleCreatePlayer(msg)

		default:
			gs.World.HandleIncomingMessage(msg)
		}
	default:
		// do nothing
	}
	return received
}

func (gs *GameServer) Receive(msg *gameserver.HandlerParameter) {
	gs.incomingBuffer <- msg
}

func (gs *GameServer) Logout(c client.Client, cause string) {
	gm, err := message.NewGameMessage(message.LogoutRequest{Cause: cause})
	if err != nil {
		log.Printf("Error creating GameMessage for LogoutRequest: %v", err)
	} else {
		gs.Receive(gameserver.NewHandlerParameter(c, gm))
	}
}

func (gs *GameServer) handleLogin(msg *gameserver.HandlerParameter) { // TODO error handling
	// is this connection already authenticated?
	// see if we can find an existing player ..
	if msg.Client.GetPlayer() != nil {
		// you've already got one
		msg.Client.Send(message.LoginResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_ATTACHED",
		})
		return
	}
	// what if player is logged in on a different client?
	// TODO
	/*
		p := FindPlayerByClient(message.Client)
		if p != nil {
			// already authenticated, can't login again
			// TODO
			// note that this isn't really working; the same username can log on twice
			// instead the old player should be kicked and the new player take over
			p.Send(LoginResponse{
				Response: Response{
					MessageType: "login_response",
					Successful:  false,
					ResultCode:  "ALREADY_AUTHENTICATED",
				},
			})
			return
		}
	*/

	// todo authentication and stuff - does GRPC have a built in authentication method?

	playerName := msg.Message.GetLoginRequest().PlayerName
	playerData, err := db.GetPlayerData(playerName)
	if err != nil {
		log.Printf("Error trying to retrieve playerData for %s: %v", playerName, err)
		msg.Client.Send(message.LoginResponse{
			Success:    false,
			ResultCode: "PLAYER_DATA_ERROR",
		})
		return
	}
	player := NewClientPlayerFromPlayerData(msg.Message.GetLoginRequest().PlayerName, &playerData, msg.Client)

	// load inventory: have to convert playerinventorydata into
	// instances and definitions here, because we need 'the world' to do it.
	for _, inv := range playerData.Inventory {
		inst, err := gs.World.CreateObjectInstance(inv.ZoneId, inv.DefinitionId, inv.InstanceId)
		if err != nil {
			log.Printf("Error trying to load player %d (%s) inventory instance (%s-%s-%s) -- %s", playerData.Id, playerName, inv.ZoneId, inv.DefinitionId, inv.InstanceId, err)
			msg.Client.Send(message.LoginResponse{
				Success:    false,
				ResultCode: "PLAYER_INVENTORY_DATA_ERROR",
			})
			return
		}
		player.LoadInventory(inst)

	}
	// slots - need inventory before we can set slots
	for _, sd := range playerData.Slots.Slots {
		inst, exists := player.GetInventory().GetByInstanceId(sd.InstanceId)
		if !exists {
			log.Printf("Error trying to load player %d (%s) slot: %d object instance doesn't exist in inventory: %s",
				playerData.Id, playerName, sd.Location, sd.InstanceId)
		} else {
			player.Slots().Set(slot.Location(sd.Location), inst)
		}
	}

	msg.Client.SetPlayer(player)
	msg.Player = player

	// add player to world
	gs.World.AddPlayer(player)

	player.Send(message.LoginResponse{
		Success:    true,
		ResultCode: "OK",
		PlayerName: player.GetName(),
	})
}

func (gs *GameServer) handleCreatePlayer(msg *gameserver.HandlerParameter) { // TODO error handling
	if msg.Client.GetPlayer() != nil {
		// you've already got one
		msg.Client.Send(message.CreatePlayerResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_ATTACHED",
		})
		return
	}
	req := msg.Message.GetCreatePlayerRequest()
	playerName := req.PlayerName

	// create player data for playerName
	// TODO for now, just set abilities to some defaults
	abilities := player2.Abilities{
		Strength:     15,
		Dexterity:    15,
		Constitution: 15,
		Intelligence: 15,
		Wisdom:       15,
		Charisma:     15,
	}
	playerData, err := db.CreatePlayerData(playerName, req.Race, req.Class, gs.World.StartRoom.Zone.Id, gs.World.StartRoom.Id, abilities)
	if err != nil {
		log.Printf("Error trying to create player for %s: %v", playerName, err)
		msg.Client.Send(message.CreatePlayerResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_EXISTS",
		})
		return
	}
	player := NewClientPlayerFromPlayerData(playerName, playerData, msg.Client)
	msg.Client.SetPlayer(player)
	msg.Player = player

	gs.World.AddPlayer(player) // TODO destination
	player.Send(message.CreatePlayerResponse{
		Success:    true,
		ResultCode: "OK",
		PlayerName: player.GetName(),
	})
}
<commit_msg>better error handling for gameserver message handlers<commit_after>package server

import (
	"github.com/trasa/watchmud-message"
	"github.com/trasa/watchmud-message/slot"
	"github.com/trasa/watchmud/client"
	"github.com/trasa/watchmud/db"
	"github.com/trasa/watchmud/gameserver"
	"github.com/trasa/watchmud/mudtime"
	player2 "github.com/trasa/watchmud/player"
	"github.com/trasa/watchmud/world"
	"log"
	"time"
)

type GameServer struct {
	incomingBuffer chan *gameserver.HandlerParameter
	World          *world.World
	tickInterval   time.Duration
}

func NewGameServer(worldFilesDirectory string) (gs *GameServer, err error) {
	w, err := world.New(worldFilesDirectory)
	gs = &GameServer{
		incomingBuffer: make(chan *gameserver.HandlerParameter),
		World:          w,
	}
	return
}

//noinspection SpellCheckingInspection
func (gs *GameServer) Run() {
	// this is the loop that handles incoming requests
	// needs to be organized around PULSEs
	tstart := time.Now().UnixNano()
	ticker := time.NewTicker(mudtime.PULSE_INTERVAL)
	pulse := mudtime.PulseCount(0)

	for {
		select {
		case <-ticker.C:
			now := time.Now().UnixNano()
			deltaSeconds := float64(now-tstart) / 1000000000
			tstart = now
			pulse++
			gs.heartbeat(pulse, deltaSeconds)
		}
	}
}

// runs the heartbeat of the game. Use pulse to determine intervals
// between things (ex. reset zones every 15 minutes...)
// delta is the amount of time since the last heartbeat was run.
func (gs *GameServer) heartbeat(pulse mudtime.PulseCount, delta float64) {
	//log.Printf("pulse %d hb %d", pulse, delta)
	// mobs, scripts, ...

	// pulse zone
	// (zone reset ...)
	if pulse.CheckInterval(mudtime.PULSE_ZONE) {
		gs.World.DoZoneActivity()
	}

	// pulse mobs
	// (mobs walk around, initiate attack?)
	if pulse.CheckInterval(mudtime.PULSE_MOBILE) {
		gs.World.DoMobileActivity()
	}

	// perform violence
	// do the attacking (players and mobs and everybody)
	if pulse.CheckInterval(mudtime.PULSE_VIOLENCE) {
		gs.World.DoViolence(pulse)
	}

	// mud-hour ("player tick")
	// affect weather, regen ..

	// handle an incoming message if one exists
	// TODO tick time: figure out how many incoming messages we can handle
	// see issue #4
	// for now, just process until buffer is empty...

	// not really infinite as the method will return false if there was
	// nothing to do.
	//noinspection GoInfiniteFor
	for gs.processIncomingMessage() {
	}
}

// read a message off of incomingMessageBuffer and do it
// this doesn't block so if the buffer is empty, the method returns immediately
// If a message was procssed (even in error) return true.
// Otherwise return false.
func (gs *GameServer) processIncomingMessage() bool {
	received := false
	select {
	case msg := <-gs.incomingBuffer:
		received = true
		switch msg.Message.Inner.(type) {
		case *message.GameMessage_LoginRequest:
			err := gs.handleLogin(msg) // TODO error handling
			if err != nil {
				log.Printf("Error from handleLogin: %v", err)
			}

		case *message.GameMessage_CreatePlayerRequest:
			err := gs.handleCreatePlayer(msg)
			if err != nil {
				log.Printf("Error from handleCreatePlayer: %v", err)
			}

		case *message.GameMessage_DataRequest:
			err := gs.handleDataRequest(msg)
			if err != nil {
				log.Printf("Error from handleDataRequest: %v", err)
			}

		default:
			gs.World.HandleIncomingMessage(msg)
		}
	default:
		// do nothing
	}
	return received
}

func (gs *GameServer) Receive(msg *gameserver.HandlerParameter) {
	gs.incomingBuffer <- msg
}

func (gs *GameServer) Logout(c client.Client, cause string) {
	gm, err := message.NewGameMessage(message.LogoutRequest{Cause: cause})
	if err != nil {
		log.Printf("Error creating GameMessage for LogoutRequest: %v", err)
	} else {
		gs.Receive(gameserver.NewHandlerParameter(c, gm))
	}
}

func (gs *GameServer) handleLogin(msg *gameserver.HandlerParameter) (err error) {
	// is this connection already authenticated?
	// see if we can find an existing player ..
	if msg.Client.GetPlayer() != nil {
		// you've already got one
		err = msg.Client.Send(message.LoginResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_ATTACHED",
		})
		return
	}
	// what if player is logged in on a different client?
	// TODO
	/*
		p := FindPlayerByClient(message.Client)
		if p != nil {
			// already authenticated, can't login again
			// TODO
			// note that this isn't really working; the same username can log on twice
			// instead the old player should be kicked and the new player take over
			p.Send(LoginResponse{
				Response: Response{
					MessageType: "login_response",
					Successful:  false,
					ResultCode:  "ALREADY_AUTHENTICATED",
				},
			})
			return
		}
	*/

	// todo authentication and stuff - does GRPC have a built in authentication method?

	playerName := msg.Message.GetLoginRequest().PlayerName
	playerData, err := db.GetPlayerData(playerName)
	if err != nil {
		log.Printf("Error trying to retrieve playerData for %s: %v", playerName, err)
		clientErr := msg.Client.Send(message.LoginResponse{
			Success:    false,
			ResultCode: "PLAYER_DATA_ERROR",
		})
		if clientErr != nil {
			log.Printf("client error trying to send PLAYER_DATA_ERROR on login: %v", clientErr)
		}
		return // err
	}
	player := NewClientPlayerFromPlayerData(msg.Message.GetLoginRequest().PlayerName, &playerData, msg.Client)

	// load inventory: have to convert playerinventorydata into
	// instances and definitions here, because we need 'the world' to do it.
	for _, inv := range playerData.Inventory {
		inst, err := gs.World.CreateObjectInstance(inv.ZoneId, inv.DefinitionId, inv.InstanceId)
		if err != nil {
			log.Printf("Error trying to load player %d (%s) inventory instance (%s-%s-%s) -- %s", playerData.Id, playerName, inv.ZoneId, inv.DefinitionId, inv.InstanceId, err)
			clientErr := msg.Client.Send(message.LoginResponse{
				Success:    false,
				ResultCode: "PLAYER_INVENTORY_DATA_ERROR",
			})
			if clientErr != nil {
				log.Printf("client error trying to send PLAYER_INVENTORY_DATA_ERROR on login: %v", clientErr)
			}
			return err
		}
		player.LoadInventory(inst)

	}
	// slots - need inventory before we can set slots
	for _, sd := range playerData.Slots.Slots {
		inst, exists := player.GetInventory().GetByInstanceId(sd.InstanceId)
		if !exists {
			log.Printf("Error trying to load player %d (%s) slot: %d object instance doesn't exist in inventory: %s",
				playerData.Id, playerName, sd.Location, sd.InstanceId)
		} else {
			player.Slots().Set(slot.Location(sd.Location), inst)
		}
	}

	msg.Client.SetPlayer(player)
	msg.Player = player

	// add player to world
	gs.World.AddPlayer(player)

	err = player.Send(message.LoginResponse{
		Success:    true,
		ResultCode: "OK",
		PlayerName: player.GetName(),
	})
	return
}

func (gs *GameServer) handleCreatePlayer(msg *gameserver.HandlerParameter) (err error) {
	if msg.Client.GetPlayer() != nil {
		// you've already got one
		err = msg.Client.Send(message.CreatePlayerResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_ATTACHED",
		})
		return
	}
	req := msg.Message.GetCreatePlayerRequest()
	playerName := req.PlayerName

	// create player data for playerName
	// TODO for now, just set abilities to some defaults
	abilities := player2.Abilities{
		Strength:     15,
		Dexterity:    15,
		Constitution: 15,
		Intelligence: 15,
		Wisdom:       15,
		Charisma:     15,
	}
	playerData, err := db.CreatePlayerData(playerName, req.Race, req.Class, gs.World.StartRoom.Zone.Id, gs.World.StartRoom.Id, abilities)
	if err != nil {
		log.Printf("Error trying to create player for %s: %v", playerName, err)
		clientErr := msg.Client.Send(message.CreatePlayerResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_EXISTS",
		})
		if clientErr != nil {
			log.Printf("Client error trying to send PLAYER_ALREADY_EXISTS message: %v", clientErr)
		}
		return // err
	}
	player := NewClientPlayerFromPlayerData(playerName, playerData, msg.Client)
	msg.Client.SetPlayer(player)
	msg.Player = player

	gs.World.AddPlayer(player) // TODO destination
	err = player.Send(message.CreatePlayerResponse{
		Success:    true,
		ResultCode: "OK",
		PlayerName: player.GetName(),
	})
	return
}

// The client is requesting game data: races, class definitions, something like that.
func (gs *GameServer) handleDataRequest(msg *gameserver.HandlerParameter) (err error) {
	//_ := msg.Message.GetDataRequest()

	return // todo
}
<|endoftext|>


<commit_before>package view

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"sync"
)

type updateType string

const (
	Join  updateType = "+"
	Leave updateType = "-"
)

type Process struct {
	Addr string
}

type Update struct {
	Type    updateType
	Process Process
}

type View struct {
	Entries map[Update]bool
	Members map[Process]bool
	mu      sync.RWMutex
}

func New() View {
	v := View{}
	v.Entries = make(map[Update]bool)
	v.Members = make(map[Process]bool)
	return v
}

func (v View) String() string {
	v.mu.RLock()
	defer v.mu.RUnlock()

	var b bytes.Buffer
	fmt.Fprintf(&b, "{")
	first := true
	fmt.Fprintf(&b, "{")
	for k, _ := range v.Entries {
		if !first {
			fmt.Fprintf(&b, ", ")
		}
		fmt.Fprintf(&b, "<%v, %v>", k.Type, k.Process)
		first = false
	}
	fmt.Fprintf(&b, "}")
	fmt.Fprintf(&b, "{")
	first = true
	for k, _ := range v.Members {
		if !first {
			fmt.Fprintf(&b, ", ")
		}
		fmt.Fprintf(&b, "%v", k.Addr)
		first = false
	}
	fmt.Fprintf(&b, "}")
	fmt.Fprintf(&b, "}")
	return b.String()
}

func (v *View) Set(v2 View) {
	v.mu.Lock()
	defer v.mu.Unlock()

	v2.mu.RLock()
	defer v2.mu.RUnlock()

	v.Entries = v2.Entries
	v.Members = v2.Members
}

func (v View) Contains(v2 View) bool {
	v.mu.RLock()
	defer v.mu.RUnlock()

	v2.mu.RLock()
	defer v2.mu.RUnlock()

	for k2, _ := range v2.Entries {
		if _, ok := v.Entries[k2]; !ok {
			return false
		}
	}
	return true
}

func (v View) Equal(v2 View) bool {
	return v.Contains(v2) && v2.Contains(v)
}

func (v *View) AddUpdate(u Update) {
	v.mu.Lock()
	defer v.mu.Unlock()

	v.Entries[u] = true

	switch u.Type {
	case Join:
		v.Members[u.Process] = true
	case Leave:
		delete(v.Members, u.Process)
	}
}

func (v View) GetMembers() []Process {
	v.mu.RLock()
	defer v.mu.RUnlock()

	l := make([]Process, 0)
	for k, _ := range v.Members {
		l = append(l, k)
	}
	return l
}

func (v View) QuorunSize() int {
	v.mu.RLock()
	defer v.mu.RUnlock()

	n := len(v.Members) + 1
	return n/2 + n%2
}

func (v View) N() int {
	v.mu.RLock()
	defer v.mu.RUnlock()

	return len(v.Members)
}

func (v View) F() int {
	v.mu.RLock()
	defer v.mu.RUnlock()

	n := len(v.Members) + 1
	// N() - QuorunSize()
	return (n - 1) - (n/2 + n%2)
}

// ----- ERRORS -----

type OldViewError struct {
	OldView View
	NewView View
}

func (e OldViewError) Error() string {
	return fmt.Sprint("OLD_VIEW")
}

type WriteOlderError struct {
	WriteTimestamp  int
	ServerTimestamp int
}

func (e WriteOlderError) Error() string {
	return fmt.Sprintf("error: write request has timestamp %v but server has more updated timestamp %v", e.WriteTimestamp, e.ServerTimestamp)
}

func init() {
	gob.Register(new(OldViewError))
	gob.Register(new(WriteOlderError))
}
<commit_msg>Add GetProcessPosition to view<commit_after>package view

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"sync"
)

type updateType string

const (
	Join  updateType = "+"
	Leave updateType = "-"
)

type Process struct {
	Addr string
}

type Update struct {
	Type    updateType
	Process Process
}

type View struct {
	Entries map[Update]bool
	mu      sync.RWMutex

	// Cache
	Members map[Process]bool
}

func New() View {
	v := View{}
	v.Entries = make(map[Update]bool)
	v.Members = make(map[Process]bool)
	return v
}

func (v View) String() string {
	v.mu.RLock()
	defer v.mu.RUnlock()

	var b bytes.Buffer
	fmt.Fprintf(&b, "{")
	first := true
	fmt.Fprintf(&b, "{")
	for k, _ := range v.Entries {
		if !first {
			fmt.Fprintf(&b, ", ")
		}
		fmt.Fprintf(&b, "<%v, %v>", k.Type, k.Process)
		first = false
	}
	fmt.Fprintf(&b, "}")
	fmt.Fprintf(&b, "{")
	first = true
	for k, _ := range v.Members {
		if !first {
			fmt.Fprintf(&b, ", ")
		}
		fmt.Fprintf(&b, "%v", k.Addr)
		first = false
	}
	fmt.Fprintf(&b, "}")
	fmt.Fprintf(&b, "}")
	return b.String()
}

func (v *View) Set(v2 View) {
	v.mu.Lock()
	defer v.mu.Unlock()

	v2.mu.RLock()
	defer v2.mu.RUnlock()

	v.Entries = v2.Entries
	v.Members = v2.Members
}

func (v View) Contains(v2 View) bool {
	v.mu.RLock()
	defer v.mu.RUnlock()

	v2.mu.RLock()
	defer v2.mu.RUnlock()

	for k2, _ := range v2.Entries {
		if _, ok := v.Entries[k2]; !ok {
			return false
		}
	}
	return true
}

func (v View) Equal(v2 View) bool {
	return v.Contains(v2) && v2.Contains(v)
}

func (v *View) AddUpdate(u Update) {
	v.mu.Lock()
	defer v.mu.Unlock()

	v.Entries[u] = true

	switch u.Type {
	case Join:
		if !v.Entries[Update{Leave, u.Process}] {
			v.Members[u.Process] = true
		}
	case Leave:
		delete(v.Members, u.Process)
	}
}

func (v View) GetMembers() []Process {
	v.mu.RLock()
	defer v.mu.RUnlock()

	l := make([]Process, 0)
	for k, _ := range v.Members {
		l = append(l, k)
	}
	return l
}

func (v View) GetProcessPosition(process Process) int {
	v.mu.RLock()
	defer v.mu.RUnlock()

	i := 0
	for k, _ := range v.Members {
		if k.Addr < process.Addr {
			i++
		}
	}
	return i
}

func (v View) QuorunSize() int {
	v.mu.RLock()
	defer v.mu.RUnlock()

	n := len(v.Members) + 1
	return n/2 + n%2
}

func (v View) N() int {
	v.mu.RLock()
	defer v.mu.RUnlock()

	return len(v.Members)
}

func (v View) F() int {
	v.mu.RLock()
	defer v.mu.RUnlock()

	n := len(v.Members) + 1
	// N() - QuorunSize()
	return (n - 1) - (n/2 + n%2)
}

// ----- ERRORS -----

type OldViewError struct {
	OldView View
	NewView View
}

func (e OldViewError) Error() string {
	return fmt.Sprint("OLD_VIEW")
}

type WriteOlderError struct {
	WriteTimestamp  int
	ServerTimestamp int
}

func (e WriteOlderError) Error() string {
	return fmt.Sprintf("error: write request has timestamp %v but server has more updated timestamp %v", e.WriteTimestamp, e.ServerTimestamp)
}

func init() {
	gob.Register(new(OldViewError))
	gob.Register(new(WriteOlderError))
}
<|endoftext|>


<commit_before>package server

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
)

var prometheusHomeViewsTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "home_views_total",
	Help: "Total number of homepage views",
})

var prometheusCallJoinTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "call_join_total",
	Help: "Total number of new call requests",
})

var prometheusCallViewsTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "call_views_total",
	Help: "Total number of homepage views",
})

var prometheusWSConnTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "ws_conn_total",
	Help: "Total number of opened websocket connections",
})

var prometheusWSConnActive = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "ws_conn_active",
	Help: "Total number of active websocket connections",
})

var prometheusWSConnErrTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "ws_conn_err_total",
	Help: "Total number of errored out websocket connections",
})

var prometheusWSConnDuration = promauto.NewHistogram(prometheus.HistogramOpts{
	Name: "ws_conn_duration",
	Help: "Duration of websocket connections",
})

var prometheusWebRTCConnTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "webrtc_conn_total",
	Help: "Total number of opened webrtc connections",
})

var prometheusWebRTCConnActive = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "webrtc_conn_active",
	Help: "Total number of active webrtc connections",
})

// var prometheusWebRTCConnErrTotal = promauto.NewCounter(prometheus.CounterOpts{
// 	Name: "webrtc_conn_err_total",
// 	Help: "Total number of errored out webrtc connections",
// })

var prometheusWebRTCConnDuration = promauto.NewHistogram(prometheus.HistogramOpts{
	Name: "webrtc_conn_duration_seconds",
	Help: "Duration of webrtc connections",
})

var prometheusWebRTCTracksTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "webrtc_tracks_total",
	Help: "Total number of incoming webrtc tracks",
})

var prometheusWebRTCTracksActive = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "webrtc_tracks_active",
	Help: "Total number of incoming webrtc tracks",
})

var prometheusWebRTCTracksDuration = promauto.NewHistogram(prometheus.HistogramOpts{
	Name: "webrtc_tracks_duration_seconds",
	Help: "Duration of webrtc tracks",
})

var prometheusRTCPPacketsReceived = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "rtcp_packets_received_total",
	Help: "Total number of received RTCP packets",
})

var prometheusRTCPPacketsSent = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "rtcp_packets_sent_total",
	Help: "Total number of sent RTP packets",
})

var prometheusRTPPacketsReceived = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "rtp_packets_received_total",
	Help: "Total number of received RTP packets",
})
<commit_msg>Change histogram buckets and variable names<commit_after>package server

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
)

var prometheusHomeViewsTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "pageviews_index_total",
	Help: "Total number of homepage views",
})

var prometheusCallJoinTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "pageviews_call_join_total",
	Help: "Total number of new call requests",
})

var prometheusCallViewsTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "pageviews_call_total",
	Help: "Total number of homepage views",
})

var prometheusWSConnTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "ws_conn_total",
	Help: "Total number of opened websocket connections",
})

var prometheusWSConnActive = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "ws_conn_active",
	Help: "Total number of active websocket connections",
})

var prometheusWSConnErrTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "ws_conn_err_total",
	Help: "Total number of errored out websocket connections",
})

var prometheusWSConnDuration = promauto.NewHistogram(prometheus.HistogramOpts{
	Name:    "ws_conn_duration",
	Help:    "Duration of websocket connections",
	Buckets: []float64{1, 60, 5 * 60, 15 * 60, 30 * 60, 45 * 60, 60 * 60, 120 * 60},
})

var prometheusWebRTCConnTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "webrtc_conn_total",
	Help: "Total number of opened webrtc connections",
})

var prometheusWebRTCConnActive = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "webrtc_conn_active",
	Help: "Total number of active webrtc connections",
})

// var prometheusWebRTCConnErrTotal = promauto.NewCounter(prometheus.CounterOpts{
// 	Name: "webrtc_conn_err_total",
// 	Help: "Total number of errored out webrtc connections",
// })

var prometheusWebRTCConnDuration = promauto.NewHistogram(prometheus.HistogramOpts{
	Name:    "webrtc_conn_duration_seconds",
	Help:    "Duration of webrtc connections",
	Buckets: []float64{1, 60, 5 * 60, 15 * 60, 30 * 60, 45 * 60, 60 * 60, 120 * 60},
})

var prometheusWebRTCTracksTotal = promauto.NewCounter(prometheus.CounterOpts{
	Name: "webrtc_tracks_total",
	Help: "Total number of incoming webrtc tracks",
})

var prometheusWebRTCTracksActive = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "webrtc_tracks_active",
	Help: "Total number of incoming webrtc tracks",
})

var prometheusWebRTCTracksDuration = promauto.NewHistogram(prometheus.HistogramOpts{
	Name:    "webrtc_tracks_duration_seconds",
	Help:    "Duration of webrtc tracks",
	Buckets: []float64{1, 60, 5 * 60, 15 * 60, 30 * 60, 45 * 60, 60 * 60, 120 * 60},
})

var prometheusRTCPPacketsReceived = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "rtcp_packets_received_total",
	Help: "Total number of received RTCP packets",
})

var prometheusRTCPPacketsSent = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "rtcp_packets_sent_total",
	Help: "Total number of sent RTP packets",
})

var prometheusRTPPacketsReceived = promauto.NewGauge(prometheus.GaugeOpts{
	Name: "rtp_packets_received_total",
	Help: "Total number of received RTP packets",
})
<|endoftext|>


<commit_before>package mail

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime"
	"mime/multipart"
	"mime/quotedprintable"
	"net/http"
	"net/smtp"
	"net/textproto"
	"strings"
	"time"
	"xorkevin.dev/governor"
	"xorkevin.dev/governor/service/msgqueue"
	"xorkevin.dev/governor/service/template"
)

const (
	govmailchannelid = "gov.mail"
	govmailworker    = "gov.mail.worker"
)

type (
	// Mail is a service wrapper around a mailer instance
	Mail interface {
		Send(from, fromname string, to []string, tpl string, emdata interface{}) error
	}

	// Service is a Mail and governor.Service
	Service interface {
		governor.Service
		Mail
	}

	mailOp struct {
		from string
		to   []string
		msg  []byte
		res  chan<- error
	}

	mailmsg struct {
		From        string   `json:"from"`
		FromName    string   `json:"fromname"`
		To          []string `json:"to"`
		Subjecttpl  string   `json:"subjecttpl"`
		Bodytpl     string   `json:"bodytpl"`
		HTMLBodytpl string   `json:"htmlbodytpl"`
		Emdata      string   `json:"emdata"`
	}

	msgbuilder struct {
		headers  []string
		body     []byte
		htmlbody []byte
	}

	service struct {
		tpl         template.Template
		queue       msgqueue.Msgqueue
		config      governor.SecretReader
		logger      governor.Logger
		host        string
		addr        string
		fromAddress string
		fromName    string
		insecure    bool
		outbox      chan mailOp
		done        <-chan struct{}
	}

	ctxKeyMail struct{}
)

// GetCtxMail returns a Mail service from the context
func GetCtxMail(inj governor.Injector) Mail {
	v := inj.Get(ctxKeyMail{})
	if v == nil {
		return nil
	}
	return v.(Mail)
}

// setCtxMail sets a Mail service in the context
func setCtxMail(inj governor.Injector, m Mail) {
	inj.Set(ctxKeyMail{}, m)
}

// NewCtx creates a new Mail service from a context
func NewCtx(inj governor.Injector) Service {
	tpl := template.GetCtxTemplate(inj)
	queue := msgqueue.GetCtxMsgqueue(inj)
	return New(tpl, queue)
}

// New creates a new mailer service
func New(tpl template.Template, queue msgqueue.Msgqueue) Service {
	return &service{
		tpl:    tpl,
		queue:  queue,
		outbox: make(chan mailOp),
	}
}

func (s *service) Register(inj governor.Injector, r governor.ConfigRegistrar, jr governor.JobRegistrar) {
	setCtxMail(inj, s)

	r.SetDefault("auth", "")
	r.SetDefault("host", "localhost")
	r.SetDefault("port", "587")
	r.SetDefault("fromaddress", "")
	r.SetDefault("fromname", "")
	r.SetDefault("insecure", false)
}

func (s *service) Init(ctx context.Context, c governor.Config, r governor.ConfigReader, l governor.Logger, m governor.Router) error {
	s.logger = l
	l = s.logger.WithData(map[string]string{
		"phase": "init",
	})

	s.config = r

	s.host = r.GetStr("host")
	s.addr = fmt.Sprintf("%s:%s", r.GetStr("host"), r.GetStr("port"))
	s.fromAddress = r.GetStr("fromaddress")
	s.fromName = r.GetStr("fromname")
	s.insecure = r.GetBool("insecure")

	done := make(chan struct{})
	go s.execute(ctx, done)
	s.done = done

	l.Info("initialize mail options", map[string]string{
		"smtp server addr": s.addr,
		"sender address":   s.fromAddress,
		"sender name":      s.fromName,
	})
	return nil
}

func (s *service) Setup(req governor.ReqSetup) error {
	return nil
}

func (s *service) Start(ctx context.Context) error {
	l := s.logger.WithData(map[string]string{
		"phase": "start",
	})

	if _, err := s.queue.Subscribe(govmailchannelid, govmailworker, 15*time.Second, 2, s.mailSubscriber); err != nil {
		return governor.NewError("Failed to subscribe to mail queue", http.StatusInternalServerError, err)
	}
	l.Info("subscribed to mail queue", nil)
	return nil
}

func (s *service) Stop(ctx context.Context) {
	l := s.logger.WithData(map[string]string{
		"phase": "stop",
	})
	select {
	case <-s.done:
		return
	case <-ctx.Done():
		l.Warn("failed to stop", nil)
	}
}

func (s *service) Health() error {
	return nil
}

func (s *service) execute(ctx context.Context, done chan<- struct{}) {
	defer close(done)
	for {
		select {
		case <-ctx.Done():
			return
		case op := <-s.outbox:
			op.res <- s.handleSendMail(op.from, op.to, op.msg)
			close(op.res)
		}
	}
}

type plainAuth struct {
	identity, username, password string
	host                         string
}

func newPlainAuth(identity, username, password, host string) smtp.Auth {
	return &plainAuth{identity, username, password, host}

}

func (a *plainAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	if server.Name != a.host {
		return "", nil, governor.NewError(fmt.Sprintf("Wrong host name: expected %s, have %s", a.host, server.Name), http.StatusInternalServerError, nil)
	}
	resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
	return "PLAIN", resp, nil
}

func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		return nil, governor.NewError("Unexpected server challenge", http.StatusInternalServerError, nil)
	}
	return nil, nil
}

func (s *service) handleSendMail(from string, to []string, msg []byte) error {
	authsecret, err := s.config.GetSecret("auth")
	if err != nil {
		return err
	}

	username, ok := authsecret["username"].(string)
	if !ok {
		return governor.NewError("Invalid secret", http.StatusInternalServerError, nil)
	}
	password, ok := authsecret["password"].(string)
	if !ok {
		return governor.NewError("Invalid secret", http.StatusInternalServerError, nil)
	}
	var smtpauth smtp.Auth
	if s.insecure {
		smtpauth = newPlainAuth("", username, password, s.host)
	} else {
		smtpauth = smtp.PlainAuth("", username, password, s.host)
	}
	if err := smtp.SendMail(s.addr, smtpauth, from, to, msg); err != nil {
		return err
	}
	s.logger.Debug("mail sent", map[string]string{
		"actiontype": "sendmail",
		"addr":       s.addr,
		"username":   username,
		"from":       from,
		"to":         strings.Join(to, ","),
	})
	return nil
}

func (s *service) mailSubscriber(msgdata []byte) error {
	emmsg := &mailmsg{}
	if err := json.Unmarshal(msgdata, emmsg); err != nil {
		return governor.NewError("Failed to decode mail message", http.StatusInternalServerError, err)
	}
	emdata := map[string]string{}
	if err := json.Unmarshal([]byte(emmsg.Emdata), &emdata); err != nil {
		return governor.NewError("Failed to decode mail data", http.StatusInternalServerError, err)
	}

	subject, err := s.tpl.Execute(emmsg.Subjecttpl, emdata)
	if err != nil {
		return governor.NewError("Failed to execute mail subject template", http.StatusInternalServerError, err)
	}
	body, err := s.tpl.Execute(emmsg.Bodytpl, emdata)
	if err != nil {
		return governor.NewError("Failed to execute mail body template", http.StatusInternalServerError, err)
	}
	htmlbody, err := s.tpl.ExecuteHTML(emmsg.HTMLBodytpl, emdata)
	if err != nil {
		s.logger.Error("failed to execute mail html body template", map[string]string{
			"error":      err.Error(),
			"actiontype": "executehtmlbody",
			"bodytpl":    emmsg.HTMLBodytpl,
		})
		htmlbody = nil
	}

	msg, err := msgToBytes(string(subject), emmsg.From, emmsg.FromName, emmsg.To, body, htmlbody)
	if err != nil {
		return err
	}

	res := make(chan error)
	op := mailOp{
		from: emmsg.From,
		to:   emmsg.To,
		msg:  msg,
		res:  res,
	}
	select {
	case <-s.done:
		return governor.NewError("Mail service shutdown", http.StatusInternalServerError, err)
	case s.outbox <- op:
		return <-res
	}
}

func msgToBytes(subject string, from, fromname string, to []string, body []byte, htmlbody []byte) ([]byte, error) {
	msg := newMsgBuilder()
	msg.addHeader("Mime-Version", "1.0")
	msg.addHeader("Date", time.Now().Round(0).Format(time.RFC1123Z))
	msg.addHeader("Subject", mime.QEncoding.Encode("utf-8", subject))
	if fromname == "" {
		msg.addHeader("From", from)
	} else {
		msg.addAddrHeader("From", mime.QEncoding.Encode("utf-8", fromname), from)
	}
	msg.addHeader("To", strings.Join(to, ",\r\n\t"))
	if body != nil {
		msg.addBody(body)
	}
	if htmlbody != nil {
		msg.addHTMLBody(htmlbody)
	}
	buf, err := msg.build()
	if err != nil {
		return nil, governor.NewError("Failed to write mail", http.StatusInternalServerError, err)
	}
	return buf.Bytes(), nil
}

func newMsgBuilder() *msgbuilder {
	return &msgbuilder{
		headers: []string{},
		body:    nil,
	}
}

func (b *msgbuilder) addHeader(key, val string) {
	b.headers = append(b.headers, fmt.Sprintf("%s: %s", key, val))
}

func (b *msgbuilder) addAddrHeader(key, name, addr string) {
	b.addHeader(key, fmt.Sprintf("%s <%s>", name, addr))
}

func (b *msgbuilder) addBody(body []byte) {
	b.body = body
}

func (b *msgbuilder) addHTMLBody(body []byte) {
	b.htmlbody = body
}

func (b *msgbuilder) writeHeaders(buf *bytes.Buffer) {
	for _, h := range b.headers {
		buf.WriteString(h)
		buf.WriteString("\r\n")
	}
}

func (b *msgbuilder) writePart(w io.Writer, data []byte) error {
	qw := quotedprintable.NewWriter(w)
	defer qw.Close()
	if _, err := qw.Write(data); err != nil {
		return err
	}
	return nil
}

func (b *msgbuilder) writeBody(w *multipart.Writer) error {
	defer w.Close()
	if len(b.body) != 0 {
		header := textproto.MIMEHeader{
			"Content-Type":              {mime.FormatMediaType("text/plain", map[string]string{"charset": "utf-8"})},
			"Content-Transfer-Encoding": {"quoted-printable"},
		}
		w, err := w.CreatePart(header)
		if err != nil {
			return err
		}
		if err := b.writePart(w, b.body); err != nil {
			return err
		}
	}
	if len(b.htmlbody) != 0 {
		header := textproto.MIMEHeader{
			"Content-Type":              {mime.FormatMediaType("text/html", map[string]string{"charset": "utf-8"})},
			"Content-Transfer-Encoding": {"quoted-printable"},
		}
		w, err := w.CreatePart(header)
		if err != nil {
			return err
		}
		if err := b.writePart(w, b.htmlbody); err != nil {
			return err
		}
	}
	return nil
}

func genBoundary() string {
	return multipart.NewWriter(&bytes.Buffer{}).Boundary()
}

func createPart(m *multipart.Writer, contenttype string) (*multipart.Writer, error) {
	boundary := genBoundary()

	header := textproto.MIMEHeader{
		"Content-Type": {mime.FormatMediaType(contenttype, map[string]string{"boundary": boundary})},
	}
	part, err := m.CreatePart(header)
	if err != nil {
		return nil, err
	}
	w := multipart.NewWriter(part)
	if err := w.SetBoundary(boundary); err != nil {
		return nil, err
	}
	return w, nil
}

func (b *msgbuilder) build() (*bytes.Buffer, error) {
	buf := &bytes.Buffer{}
	b.writeHeaders(buf)

	m := multipart.NewWriter(buf)
	defer m.Close()
	fmt.Fprintf(buf, "Content-Type: %s\r\n\r\n", mime.FormatMediaType("multipart/mixed", map[string]string{"boundary": m.Boundary(), "charset": "utf-8"}))

	part, err := createPart(m, "multipart/alternative")
	if err != nil {
		return nil, err
	}
	if err := b.writeBody(part); err != nil {
		return nil, err
	}

	return buf, nil
}

// Send creates and enqueues a new message to be sent
func (s *service) Send(from, fromname string, to []string, tpl string, emdata interface{}) error {
	if len(to) == 0 {
		return governor.NewError("Email must have at least one recipient", http.StatusBadRequest, nil)
	}
	datastring, err := json.Marshal(emdata)
	if err != nil {
		return governor.NewError("Failed to encode email data to JSON", http.StatusInternalServerError, err)
	}

	msg := mailmsg{
		From:        from,
		FromName:    fromname,
		To:          to,
		Subjecttpl:  tpl + "_subject.txt",
		Bodytpl:     tpl + ".txt",
		HtmlBodytpl: tpl + ".html",
		Emdata:      string(datastring),
	}
	if msg.From == "" {
		msg.From = s.fromAddress
	}
	if msg.FromName == "" {
		msg.FromName = s.fromName
	}

	b, err := json.Marshal(msg)
	if err != nil {
		return governor.NewError("Failed to encode email to json", http.StatusInternalServerError, err)
	}
	if err := s.queue.Publish(govmailchannelid, b); err != nil {
		return governor.NewError("Failed to publish new email to message queue", http.StatusInternalServerError, err)
	}
	return nil
}
<commit_msg>Bugfix field rename<commit_after>package mail

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime"
	"mime/multipart"
	"mime/quotedprintable"
	"net/http"
	"net/smtp"
	"net/textproto"
	"strings"
	"time"
	"xorkevin.dev/governor"
	"xorkevin.dev/governor/service/msgqueue"
	"xorkevin.dev/governor/service/template"
)

const (
	govmailchannelid = "gov.mail"
	govmailworker    = "gov.mail.worker"
)

type (
	// Mail is a service wrapper around a mailer instance
	Mail interface {
		Send(from, fromname string, to []string, tpl string, emdata interface{}) error
	}

	// Service is a Mail and governor.Service
	Service interface {
		governor.Service
		Mail
	}

	mailOp struct {
		from string
		to   []string
		msg  []byte
		res  chan<- error
	}

	mailmsg struct {
		From        string   `json:"from"`
		FromName    string   `json:"fromname"`
		To          []string `json:"to"`
		Subjecttpl  string   `json:"subjecttpl"`
		Bodytpl     string   `json:"bodytpl"`
		HTMLBodytpl string   `json:"htmlbodytpl"`
		Emdata      string   `json:"emdata"`
	}

	msgbuilder struct {
		headers  []string
		body     []byte
		htmlbody []byte
	}

	service struct {
		tpl         template.Template
		queue       msgqueue.Msgqueue
		config      governor.SecretReader
		logger      governor.Logger
		host        string
		addr        string
		fromAddress string
		fromName    string
		insecure    bool
		outbox      chan mailOp
		done        <-chan struct{}
	}

	ctxKeyMail struct{}
)

// GetCtxMail returns a Mail service from the context
func GetCtxMail(inj governor.Injector) Mail {
	v := inj.Get(ctxKeyMail{})
	if v == nil {
		return nil
	}
	return v.(Mail)
}

// setCtxMail sets a Mail service in the context
func setCtxMail(inj governor.Injector, m Mail) {
	inj.Set(ctxKeyMail{}, m)
}

// NewCtx creates a new Mail service from a context
func NewCtx(inj governor.Injector) Service {
	tpl := template.GetCtxTemplate(inj)
	queue := msgqueue.GetCtxMsgqueue(inj)
	return New(tpl, queue)
}

// New creates a new mailer service
func New(tpl template.Template, queue msgqueue.Msgqueue) Service {
	return &service{
		tpl:    tpl,
		queue:  queue,
		outbox: make(chan mailOp),
	}
}

func (s *service) Register(inj governor.Injector, r governor.ConfigRegistrar, jr governor.JobRegistrar) {
	setCtxMail(inj, s)

	r.SetDefault("auth", "")
	r.SetDefault("host", "localhost")
	r.SetDefault("port", "587")
	r.SetDefault("fromaddress", "")
	r.SetDefault("fromname", "")
	r.SetDefault("insecure", false)
}

func (s *service) Init(ctx context.Context, c governor.Config, r governor.ConfigReader, l governor.Logger, m governor.Router) error {
	s.logger = l
	l = s.logger.WithData(map[string]string{
		"phase": "init",
	})

	s.config = r

	s.host = r.GetStr("host")
	s.addr = fmt.Sprintf("%s:%s", r.GetStr("host"), r.GetStr("port"))
	s.fromAddress = r.GetStr("fromaddress")
	s.fromName = r.GetStr("fromname")
	s.insecure = r.GetBool("insecure")

	done := make(chan struct{})
	go s.execute(ctx, done)
	s.done = done

	l.Info("initialize mail options", map[string]string{
		"smtp server addr": s.addr,
		"sender address":   s.fromAddress,
		"sender name":      s.fromName,
	})
	return nil
}

func (s *service) Setup(req governor.ReqSetup) error {
	return nil
}

func (s *service) Start(ctx context.Context) error {
	l := s.logger.WithData(map[string]string{
		"phase": "start",
	})

	if _, err := s.queue.Subscribe(govmailchannelid, govmailworker, 15*time.Second, 2, s.mailSubscriber); err != nil {
		return governor.NewError("Failed to subscribe to mail queue", http.StatusInternalServerError, err)
	}
	l.Info("subscribed to mail queue", nil)
	return nil
}

func (s *service) Stop(ctx context.Context) {
	l := s.logger.WithData(map[string]string{
		"phase": "stop",
	})
	select {
	case <-s.done:
		return
	case <-ctx.Done():
		l.Warn("failed to stop", nil)
	}
}

func (s *service) Health() error {
	return nil
}

func (s *service) execute(ctx context.Context, done chan<- struct{}) {
	defer close(done)
	for {
		select {
		case <-ctx.Done():
			return
		case op := <-s.outbox:
			op.res <- s.handleSendMail(op.from, op.to, op.msg)
			close(op.res)
		}
	}
}

type plainAuth struct {
	identity, username, password string
	host                         string
}

func newPlainAuth(identity, username, password, host string) smtp.Auth {
	return &plainAuth{identity, username, password, host}

}

func (a *plainAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	if server.Name != a.host {
		return "", nil, governor.NewError(fmt.Sprintf("Wrong host name: expected %s, have %s", a.host, server.Name), http.StatusInternalServerError, nil)
	}
	resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
	return "PLAIN", resp, nil
}

func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		return nil, governor.NewError("Unexpected server challenge", http.StatusInternalServerError, nil)
	}
	return nil, nil
}

func (s *service) handleSendMail(from string, to []string, msg []byte) error {
	authsecret, err := s.config.GetSecret("auth")
	if err != nil {
		return err
	}

	username, ok := authsecret["username"].(string)
	if !ok {
		return governor.NewError("Invalid secret", http.StatusInternalServerError, nil)
	}
	password, ok := authsecret["password"].(string)
	if !ok {
		return governor.NewError("Invalid secret", http.StatusInternalServerError, nil)
	}
	var smtpauth smtp.Auth
	if s.insecure {
		smtpauth = newPlainAuth("", username, password, s.host)
	} else {
		smtpauth = smtp.PlainAuth("", username, password, s.host)
	}
	if err := smtp.SendMail(s.addr, smtpauth, from, to, msg); err != nil {
		return err
	}
	s.logger.Debug("mail sent", map[string]string{
		"actiontype": "sendmail",
		"addr":       s.addr,
		"username":   username,
		"from":       from,
		"to":         strings.Join(to, ","),
	})
	return nil
}

func (s *service) mailSubscriber(msgdata []byte) error {
	emmsg := &mailmsg{}
	if err := json.Unmarshal(msgdata, emmsg); err != nil {
		return governor.NewError("Failed to decode mail message", http.StatusInternalServerError, err)
	}
	emdata := map[string]string{}
	if err := json.Unmarshal([]byte(emmsg.Emdata), &emdata); err != nil {
		return governor.NewError("Failed to decode mail data", http.StatusInternalServerError, err)
	}

	subject, err := s.tpl.Execute(emmsg.Subjecttpl, emdata)
	if err != nil {
		return governor.NewError("Failed to execute mail subject template", http.StatusInternalServerError, err)
	}
	body, err := s.tpl.Execute(emmsg.Bodytpl, emdata)
	if err != nil {
		return governor.NewError("Failed to execute mail body template", http.StatusInternalServerError, err)
	}
	htmlbody, err := s.tpl.ExecuteHTML(emmsg.HTMLBodytpl, emdata)
	if err != nil {
		s.logger.Error("failed to execute mail html body template", map[string]string{
			"error":      err.Error(),
			"actiontype": "executehtmlbody",
			"bodytpl":    emmsg.HTMLBodytpl,
		})
		htmlbody = nil
	}

	msg, err := msgToBytes(string(subject), emmsg.From, emmsg.FromName, emmsg.To, body, htmlbody)
	if err != nil {
		return err
	}

	res := make(chan error)
	op := mailOp{
		from: emmsg.From,
		to:   emmsg.To,
		msg:  msg,
		res:  res,
	}
	select {
	case <-s.done:
		return governor.NewError("Mail service shutdown", http.StatusInternalServerError, err)
	case s.outbox <- op:
		return <-res
	}
}

func msgToBytes(subject string, from, fromname string, to []string, body []byte, htmlbody []byte) ([]byte, error) {
	msg := newMsgBuilder()
	msg.addHeader("Mime-Version", "1.0")
	msg.addHeader("Date", time.Now().Round(0).Format(time.RFC1123Z))
	msg.addHeader("Subject", mime.QEncoding.Encode("utf-8", subject))
	if fromname == "" {
		msg.addHeader("From", from)
	} else {
		msg.addAddrHeader("From", mime.QEncoding.Encode("utf-8", fromname), from)
	}
	msg.addHeader("To", strings.Join(to, ",\r\n\t"))
	if body != nil {
		msg.addBody(body)
	}
	if htmlbody != nil {
		msg.addHTMLBody(htmlbody)
	}
	buf, err := msg.build()
	if err != nil {
		return nil, governor.NewError("Failed to write mail", http.StatusInternalServerError, err)
	}
	return buf.Bytes(), nil
}

func newMsgBuilder() *msgbuilder {
	return &msgbuilder{
		headers: []string{},
		body:    nil,
	}
}

func (b *msgbuilder) addHeader(key, val string) {
	b.headers = append(b.headers, fmt.Sprintf("%s: %s", key, val))
}

func (b *msgbuilder) addAddrHeader(key, name, addr string) {
	b.addHeader(key, fmt.Sprintf("%s <%s>", name, addr))
}

func (b *msgbuilder) addBody(body []byte) {
	b.body = body
}

func (b *msgbuilder) addHTMLBody(body []byte) {
	b.htmlbody = body
}

func (b *msgbuilder) writeHeaders(buf *bytes.Buffer) {
	for _, h := range b.headers {
		buf.WriteString(h)
		buf.WriteString("\r\n")
	}
}

func (b *msgbuilder) writePart(w io.Writer, data []byte) error {
	qw := quotedprintable.NewWriter(w)
	defer qw.Close()
	if _, err := qw.Write(data); err != nil {
		return err
	}
	return nil
}

func (b *msgbuilder) writeBody(w *multipart.Writer) error {
	defer w.Close()
	if len(b.body) != 0 {
		header := textproto.MIMEHeader{
			"Content-Type":              {mime.FormatMediaType("text/plain", map[string]string{"charset": "utf-8"})},
			"Content-Transfer-Encoding": {"quoted-printable"},
		}
		w, err := w.CreatePart(header)
		if err != nil {
			return err
		}
		if err := b.writePart(w, b.body); err != nil {
			return err
		}
	}
	if len(b.htmlbody) != 0 {
		header := textproto.MIMEHeader{
			"Content-Type":              {mime.FormatMediaType("text/html", map[string]string{"charset": "utf-8"})},
			"Content-Transfer-Encoding": {"quoted-printable"},
		}
		w, err := w.CreatePart(header)
		if err != nil {
			return err
		}
		if err := b.writePart(w, b.htmlbody); err != nil {
			return err
		}
	}
	return nil
}

func genBoundary() string {
	return multipart.NewWriter(&bytes.Buffer{}).Boundary()
}

func createPart(m *multipart.Writer, contenttype string) (*multipart.Writer, error) {
	boundary := genBoundary()

	header := textproto.MIMEHeader{
		"Content-Type": {mime.FormatMediaType(contenttype, map[string]string{"boundary": boundary})},
	}
	part, err := m.CreatePart(header)
	if err != nil {
		return nil, err
	}
	w := multipart.NewWriter(part)
	if err := w.SetBoundary(boundary); err != nil {
		return nil, err
	}
	return w, nil
}

func (b *msgbuilder) build() (*bytes.Buffer, error) {
	buf := &bytes.Buffer{}
	b.writeHeaders(buf)

	m := multipart.NewWriter(buf)
	defer m.Close()
	fmt.Fprintf(buf, "Content-Type: %s\r\n\r\n", mime.FormatMediaType("multipart/mixed", map[string]string{"boundary": m.Boundary(), "charset": "utf-8"}))

	part, err := createPart(m, "multipart/alternative")
	if err != nil {
		return nil, err
	}
	if err := b.writeBody(part); err != nil {
		return nil, err
	}

	return buf, nil
}

// Send creates and enqueues a new message to be sent
func (s *service) Send(from, fromname string, to []string, tpl string, emdata interface{}) error {
	if len(to) == 0 {
		return governor.NewError("Email must have at least one recipient", http.StatusBadRequest, nil)
	}
	datastring, err := json.Marshal(emdata)
	if err != nil {
		return governor.NewError("Failed to encode email data to JSON", http.StatusInternalServerError, err)
	}

	msg := mailmsg{
		From:        from,
		FromName:    fromname,
		To:          to,
		Subjecttpl:  tpl + "_subject.txt",
		Bodytpl:     tpl + ".txt",
		HTMLBodytpl: tpl + ".html",
		Emdata:      string(datastring),
	}
	if msg.From == "" {
		msg.From = s.fromAddress
	}
	if msg.FromName == "" {
		msg.FromName = s.fromName
	}

	b, err := json.Marshal(msg)
	if err != nil {
		return governor.NewError("Failed to encode email to json", http.StatusInternalServerError, err)
	}
	if err := s.queue.Publish(govmailchannelid, b); err != nil {
		return governor.NewError("Failed to publish new email to message queue", http.StatusInternalServerError, err)
	}
	return nil
}
<|endoftext|>


<commit_before>package sflow

import (
	"errors"
	"fmt"
	"io"

	"git.edgecastcdn.net/vflow/packet"
)

const (
	SFDataRawHeader      = 1
	SFDataEthernetHeader = 2
	SFDataIPV4Header     = 3
	SFDataIPV6Header     = 4

	SFDataExtSwitch     = 1001
	SFDataExtRouter     = 1002
	SFDataExtGateway    = 1003
	SFDataExtUser       = 1004
	SFDataExtURL        = 1005
	SFDataExtMPLS       = 1006
	SFDataExtNAT        = 1007
	SFDataExtMPLSTunnel = 1008
	SFDataExtMPLSVC     = 1009
	SFDataExtMPLSFEC    = 1010
	SFDataExtMPLSLVPFEC = 1011
	SFDataExtVLANTunnel = 1012
)

type FlowSample struct {
	SequenceNo   uint32 // Incremented with each flow sample
	SourceId     byte   // fsSourceId
	SamplingRate uint32 // fsPacketSamplingRate
	SamplePool   uint32 // Total number of packets that could have been sampled
	Drops        uint32 // Number of times a packet was dropped due to lack of resources
	Input        uint32 // SNMP ifIndex of input interface
	Output       uint32 // SNMP ifIndex of input interface
	RecordsNo    uint32 // Number of records to follow
}

type SampledHeader struct {
	Protocol     uint32 // (enum SFLHeader_protocol)
	FrameLength  uint32 // Original length of packet before sampling
	Stripped     uint32 // Header/trailer bytes stripped by sender
	HeaderLength uint32 // Length of sampled header bytes to follow
	Header       []byte // Header bytes
}

var (
	maxOutEthernetLength = errors.New("the ethernet lenght is greater than 1500")
)

func decodeFlowSample(r io.ReadSeeker) error {
	var (
		fs          = new(FlowSample)
		rTypeFormat uint32
		rTypeLength uint32
		err         error
	)

	if err = read(r, &fs.SequenceNo); err != nil {
		return err
	}

	if err = read(r, &fs.SourceId); err != nil {
		return err
	}

	r.Seek(3, 1) // skip counter sample decoding

	if err = read(r, &fs.SamplingRate); err != nil {
		return err
	}

	if err = read(r, &fs.SamplePool); err != nil {
		return err
	}

	if err = read(r, &fs.Drops); err != nil {
		return err
	}

	if err = read(r, &fs.Input); err != nil {
		return err
	}

	if err = read(r, &fs.Output); err != nil {
		return err
	}

	if err = read(r, &fs.RecordsNo); err != nil {
		return err
	}

	fmt.Printf("%#v\n", fs) // just for test

	for i := uint32(0); i < fs.RecordsNo; i++ {
		if err = read(r, &rTypeFormat); err != nil {
			return err
		}
		if err = read(r, &rTypeLength); err != nil {
			return err
		}

		switch rTypeFormat {
		case SFDataRawHeader:
			decodeSampledHeader(r)
			r.Seek(int64(rTypeLength), 1)
		case SFDataExtSwitch:
			decodeSampledExtSwitch(r)
			r.Seek(int64(rTypeLength), 1)
		default:
			r.Seek(int64(rTypeLength), 1)
		}
	}

	return nil
}

func decodeSampledHeader(r io.Reader) error {
	// TODO
	var (
		h   = new(SampledHeader)
		err error
	)
	if err = read(r, &h.Protocol); err != nil {
		return err
	}

	if err = read(r, &h.FrameLength); err != nil {
		return err
	}

	if err = read(r, &h.Stripped); err != nil {
		return err
	}

	if err = read(r, &h.HeaderLength); err != nil {
		return err
	}

	println("Header Len:", h.HeaderLength)

	if h.HeaderLength > 1500 {
		return maxOutEthernetLength
	}

	// TODO: make sure the padding works!!
	// cut off a header length mod 4 == 0 number of bytes
	tmp := (4 - h.HeaderLength) % 4
	if tmp < 0 {
		tmp += 4
	}

	h.Header = make([]byte, h.HeaderLength+tmp)
	if _, err = r.Read(h.Header); err != nil {
		return err
	}

	h.Header = h.Header[:h.HeaderLength]

	//fmt.Printf("%#v\n", h)
	d, err := packet.Decoder(h.Header)
	if err != nil {
		return err
	}
	fmt.Printf("%#v\n", d)

	return nil
}

func decodeSampledExtSwitch(r io.Reader) {
	// TODO
}

func decodeSampledIPv4() {
	// TODO
}

func decodeSampledIPv6() {
	// TODO
}
<commit_msg>change to method, debug err<commit_after>package sflow

import (
	"errors"
	"fmt"
	"io"

	"git.edgecastcdn.net/vflow/packet"
)

const (
	SFDataRawHeader      = 1
	SFDataEthernetHeader = 2
	SFDataIPV4Header     = 3
	SFDataIPV6Header     = 4

	SFDataExtSwitch     = 1001
	SFDataExtRouter     = 1002
	SFDataExtGateway    = 1003
	SFDataExtUser       = 1004
	SFDataExtURL        = 1005
	SFDataExtMPLS       = 1006
	SFDataExtNAT        = 1007
	SFDataExtMPLSTunnel = 1008
	SFDataExtMPLSVC     = 1009
	SFDataExtMPLSFEC    = 1010
	SFDataExtMPLSLVPFEC = 1011
	SFDataExtVLANTunnel = 1012
)

type FlowSample struct {
	SequenceNo   uint32 // Incremented with each flow sample
	SourceId     byte   // fsSourceId
	SamplingRate uint32 // fsPacketSamplingRate
	SamplePool   uint32 // Total number of packets that could have been sampled
	Drops        uint32 // Number of times a packet was dropped due to lack of resources
	Input        uint32 // SNMP ifIndex of input interface
	Output       uint32 // SNMP ifIndex of input interface
	RecordsNo    uint32 // Number of records to follow
}

type SampledHeader struct {
	Protocol     uint32 // (enum SFLHeader_protocol)
	FrameLength  uint32 // Original length of packet before sampling
	Stripped     uint32 // Header/trailer bytes stripped by sender
	HeaderLength uint32 // Length of sampled header bytes to follow
	Header       []byte // Header bytes
}

var (
	maxOutEthernetLength = errors.New("the ethernet lenght is greater than 1500")
)

func decodeFlowSample(r io.ReadSeeker) error {
	var (
		fs          = new(FlowSample)
		rTypeFormat uint32
		rTypeLength uint32
		err         error
	)

	if err = read(r, &fs.SequenceNo); err != nil {
		return err
	}

	if err = read(r, &fs.SourceId); err != nil {
		return err
	}

	r.Seek(3, 1) // skip counter sample decoding

	if err = read(r, &fs.SamplingRate); err != nil {
		return err
	}

	if err = read(r, &fs.SamplePool); err != nil {
		return err
	}

	if err = read(r, &fs.Drops); err != nil {
		return err
	}

	if err = read(r, &fs.Input); err != nil {
		return err
	}

	if err = read(r, &fs.Output); err != nil {
		return err
	}

	if err = read(r, &fs.RecordsNo); err != nil {
		return err
	}

	fmt.Printf("%#v\n", fs) // just for test

	for i := uint32(0); i < fs.RecordsNo; i++ {
		if err = read(r, &rTypeFormat); err != nil {
			return err
		}
		if err = read(r, &rTypeLength); err != nil {
			return err
		}

		switch rTypeFormat {
		case SFDataRawHeader:
			decodeSampledHeader(r)
			r.Seek(int64(rTypeLength), 1)
		case SFDataExtSwitch:
			decodeSampledExtSwitch(r)
			r.Seek(int64(rTypeLength), 1)
		default:
			r.Seek(int64(rTypeLength), 1)
		}
	}

	return nil
}

func decodeSampledHeader(r io.Reader) error {
	// TODO
	var (
		h   = new(SampledHeader)
		err error
	)
	if err = read(r, &h.Protocol); err != nil {
		return err
	}

	if err = read(r, &h.FrameLength); err != nil {
		return err
	}

	if err = read(r, &h.Stripped); err != nil {
		return err
	}

	if err = read(r, &h.HeaderLength); err != nil {
		return err
	}

	println("Header Len:", h.HeaderLength)

	if h.HeaderLength > 1500 {
		return maxOutEthernetLength
	}

	// TODO: make sure the padding works!!
	// cut off a header length mod 4 == 0 number of bytes
	tmp := (4 - h.HeaderLength) % 4
	if tmp < 0 {
		tmp += 4
	}

	h.Header = make([]byte, h.HeaderLength+tmp)
	if _, err = r.Read(h.Header); err != nil {
		return err
	}

	h.Header = h.Header[:h.HeaderLength]

	p := packet.NewPacket()
	d, err := p.Decoder(h.Header)
	if err != nil {
		println("ERROR::::", err.Error())
		return err
	}

	fmt.Printf("%#v\n", d)

	return nil
}

func decodeSampledExtSwitch(r io.Reader) {
	// TODO
}

func decodeSampledIPv4() {
	// TODO
}

func decodeSampledIPv6() {
	// TODO
}
<|endoftext|>


<commit_before>package jira

import (
	"fmt"
	"strings"

	"github.com/trivago/tgo/tcontainer"
)

// CreateMetaInfo contains information about fields and their attributed to create a ticket.
type CreateMetaInfo struct {
	Expand   string         `json:"expand,omitempty"`
	Projects []*MetaProject `json:"projects,omitempty"`
}

// MetaProject is the meta information about a project returned from createmeta api
type MetaProject struct {
	Expand string `json:"expand,omitempty"`
	Self   string `json:"self, omitempty"`
	Id     string `json:"id,omitempty"`
	Key    string `json:"key,omitempty"`
	Name   string `json:"name,omitempty"`
	// omitted avatarUrls
	IssueTypes []*MetaIssueType `json:"issuetypes,omitempty"`
}

// MetaIssueType represents the different issue types a project has.
//
// Note: Fields is interface because this is an object which can
// have arbitraty keys related to customfields. It is not possible to
// expect these for a general way. This will be returning a map.
// Further processing must be done depending on what is required.
type MetaIssueType struct {
	Self        string                `json:"expand,omitempty"`
	Id          string                `json:"id,omitempty"`
	Description string                `json:"description,omitempty"`
	IconUrl     string                `json:"iconurl,omitempty"`
	Name        string                `json:"name,omitempty"`
	Subtasks    bool                  `json:"subtask,omitempty"`
	Expand      string                `json:"expand,omitempty"`
	Fields      tcontainer.MarshalMap `json:"fields,omitempty"`
}

// GetCreateMeta makes the api call to get the meta information required to create a ticket
func (s *IssueService) GetCreateMeta(projectkey string) (*CreateMetaInfo, *Response, error) {

	apiEndpoint := fmt.Sprintf("/rest/api/2/issue/createmeta?projectKeys=%s&expand=projects.issuetypes.fields", projectkey)

	req, err := s.client.NewRequest("GET", apiEndpoint, nil)
	if err != nil {
		return nil, nil, err
	}

	meta := new(CreateMetaInfo)
	resp, err := s.client.Do(req, meta)

	if err != nil {
		return nil, resp, err
	}

	return meta, resp, nil
}

// GetProjectWithName returns a project with "name" from the meta information recieved. If not found, this returns nil.
// The comparision of the name is case insensitive.
func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject {
	for _, m := range m.Projects {
		if strings.ToLower(m.Name) == strings.ToLower(name) {
			return m
		}
	}
	return nil
}

// GetProjectWithKey returns a project with "name" from the meta information recieved. If not found, this returns nil.
// The comparision of the name is case insensitive.
func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject {
	for _, m := range m.Projects {
		if strings.ToLower(m.Key) == strings.ToLower(key) {
			return m
		}
	}
	return nil
}

// GetIssueTypeWithName returns an IssueType with name from a given MetaProject. If not found, this returns nil.
// The comparision of the name is case insensitive
func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType {
	for _, m := range p.IssueTypes {
		if strings.ToLower(m.Name) == strings.ToLower(name) {
			return m
		}
	}
	return nil
}

// GetMandatoryFields returns a map of all the required fields from the MetaIssueTypes.
// if a field returned by the api was:
// "customfield_10806": {
//					"required": true,
//					"schema": {
//						"type": "any",
//						"custom": "com.pyxis.greenhopper.jira:gh-epic-link",
//						"customId": 10806
//					},
//					"name": "Epic Link",
//					"hasDefaultValue": false,
//					"operations": [
//						"set"
//					]
//				}
// the returned map would have "Epic Link" as the key and "customfield_10806" as value.
// This choice has been made so that the it is easier to generate the create api request later.
func (t *MetaIssueType) GetMandatoryFields() (map[string]string, error) {
	ret := make(map[string]string)
	for key := range t.Fields {
		required, err := t.Fields.Bool(key + "/required")
		if err != nil {
			return nil, err
		}
		if required {
			name, err := t.Fields.String(key + "/name")
			if err != nil {
				return nil, err
			}
			ret[name] = key
		}
	}
	return ret, nil
}

// GetAllFields returns a map of all the fields for an IssueType. This includes all required and not required.
// The key of the returned map is what you see in the form and the value is how it is representated in the jira schema.
func (t *MetaIssueType) GetAllFields() (map[string]string, error) {
	ret := make(map[string]string)
	for key := range t.Fields {

		name, err := t.Fields.String(key + "/name")
		if err != nil {
			return nil, err
		}
		ret[name] = key
	}
	return ret, nil
}

// CheckCompleteAndAvailable checks if the given fields satisfies the mandatory field required to create a issue for the given type
// And also if the given fields are available.
func (t *MetaIssueType) CheckCompleteAndAvailable(config map[string]string) (bool, error) {
	mandatory, err := t.GetMandatoryFields()
	if err != nil {
		return false, err
	}
	all, err := t.GetAllFields()
	if err != nil {
		return false, err
	}

	// check templateconfig against mandatory fields
	for key := range mandatory {
		if _, okay := config[key]; !okay {
			var requiredFields []string
			for name := range mandatory {
				requiredFields = append(requiredFields, name)
			}
			return false, fmt.Errorf("Required field not found in provided jira.fields. Required are: %#v", requiredFields)
		}
	}

	// check templateConfig against all fields to verify they are available
	for key := range config {
		if _, okay := all[key]; !okay {
			var availableFields []string
			for name := range all {
				availableFields = append(availableFields, name)
			}
			return false, fmt.Errorf("Fields in jira.fields are not available in jira. Available are: %#v", availableFields)
		}
	}

	return true, nil
}
<commit_msg>Fix relative path IssueService.GetCreateMeta<commit_after>package jira

import (
	"fmt"
	"strings"

	"github.com/trivago/tgo/tcontainer"
)

// CreateMetaInfo contains information about fields and their attributed to create a ticket.
type CreateMetaInfo struct {
	Expand   string         `json:"expand,omitempty"`
	Projects []*MetaProject `json:"projects,omitempty"`
}

// MetaProject is the meta information about a project returned from createmeta api
type MetaProject struct {
	Expand string `json:"expand,omitempty"`
	Self   string `json:"self, omitempty"`
	Id     string `json:"id,omitempty"`
	Key    string `json:"key,omitempty"`
	Name   string `json:"name,omitempty"`
	// omitted avatarUrls
	IssueTypes []*MetaIssueType `json:"issuetypes,omitempty"`
}

// MetaIssueType represents the different issue types a project has.
//
// Note: Fields is interface because this is an object which can
// have arbitraty keys related to customfields. It is not possible to
// expect these for a general way. This will be returning a map.
// Further processing must be done depending on what is required.
type MetaIssueType struct {
	Self        string                `json:"expand,omitempty"`
	Id          string                `json:"id,omitempty"`
	Description string                `json:"description,omitempty"`
	IconUrl     string                `json:"iconurl,omitempty"`
	Name        string                `json:"name,omitempty"`
	Subtasks    bool                  `json:"subtask,omitempty"`
	Expand      string                `json:"expand,omitempty"`
	Fields      tcontainer.MarshalMap `json:"fields,omitempty"`
}

// GetCreateMeta makes the api call to get the meta information required to create a ticket
func (s *IssueService) GetCreateMeta(projectkey string) (*CreateMetaInfo, *Response, error) {

	apiEndpoint := fmt.Sprintf("rest/api/2/issue/createmeta?projectKeys=%s&expand=projects.issuetypes.fields", projectkey)

	req, err := s.client.NewRequest("GET", apiEndpoint, nil)
	if err != nil {
		return nil, nil, err
	}

	meta := new(CreateMetaInfo)
	resp, err := s.client.Do(req, meta)

	if err != nil {
		return nil, resp, err
	}

	return meta, resp, nil
}

// GetProjectWithName returns a project with "name" from the meta information recieved. If not found, this returns nil.
// The comparision of the name is case insensitive.
func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject {
	for _, m := range m.Projects {
		if strings.ToLower(m.Name) == strings.ToLower(name) {
			return m
		}
	}
	return nil
}

// GetProjectWithKey returns a project with "name" from the meta information recieved. If not found, this returns nil.
// The comparision of the name is case insensitive.
func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject {
	for _, m := range m.Projects {
		if strings.ToLower(m.Key) == strings.ToLower(key) {
			return m
		}
	}
	return nil
}

// GetIssueTypeWithName returns an IssueType with name from a given MetaProject. If not found, this returns nil.
// The comparision of the name is case insensitive
func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType {
	for _, m := range p.IssueTypes {
		if strings.ToLower(m.Name) == strings.ToLower(name) {
			return m
		}
	}
	return nil
}

// GetMandatoryFields returns a map of all the required fields from the MetaIssueTypes.
// if a field returned by the api was:
// "customfield_10806": {
//					"required": true,
//					"schema": {
//						"type": "any",
//						"custom": "com.pyxis.greenhopper.jira:gh-epic-link",
//						"customId": 10806
//					},
//					"name": "Epic Link",
//					"hasDefaultValue": false,
//					"operations": [
//						"set"
//					]
//				}
// the returned map would have "Epic Link" as the key and "customfield_10806" as value.
// This choice has been made so that the it is easier to generate the create api request later.
func (t *MetaIssueType) GetMandatoryFields() (map[string]string, error) {
	ret := make(map[string]string)
	for key := range t.Fields {
		required, err := t.Fields.Bool(key + "/required")
		if err != nil {
			return nil, err
		}
		if required {
			name, err := t.Fields.String(key + "/name")
			if err != nil {
				return nil, err
			}
			ret[name] = key
		}
	}
	return ret, nil
}

// GetAllFields returns a map of all the fields for an IssueType. This includes all required and not required.
// The key of the returned map is what you see in the form and the value is how it is representated in the jira schema.
func (t *MetaIssueType) GetAllFields() (map[string]string, error) {
	ret := make(map[string]string)
	for key := range t.Fields {

		name, err := t.Fields.String(key + "/name")
		if err != nil {
			return nil, err
		}
		ret[name] = key
	}
	return ret, nil
}

// CheckCompleteAndAvailable checks if the given fields satisfies the mandatory field required to create a issue for the given type
// And also if the given fields are available.
func (t *MetaIssueType) CheckCompleteAndAvailable(config map[string]string) (bool, error) {
	mandatory, err := t.GetMandatoryFields()
	if err != nil {
		return false, err
	}
	all, err := t.GetAllFields()
	if err != nil {
		return false, err
	}

	// check templateconfig against mandatory fields
	for key := range mandatory {
		if _, okay := config[key]; !okay {
			var requiredFields []string
			for name := range mandatory {
				requiredFields = append(requiredFields, name)
			}
			return false, fmt.Errorf("Required field not found in provided jira.fields. Required are: %#v", requiredFields)
		}
	}

	// check templateConfig against all fields to verify they are available
	for key := range config {
		if _, okay := all[key]; !okay {
			var availableFields []string
			for name := range all {
				availableFields = append(availableFields, name)
			}
			return false, fmt.Errorf("Fields in jira.fields are not available in jira. Available are: %#v", availableFields)
		}
	}

	return true, nil
}
<|endoftext|>