<commit_before>// Package tar provides functionality to write a unixfs merkledag
// as a tar archive.
package tar

import (
	"archive/tar"
	"context"
	"fmt"
	"io"
	"path"
	"time"

	mdag "github.com/ipfs/go-ipfs/merkledag"
	ft "github.com/ipfs/go-ipfs/unixfs"
	uio "github.com/ipfs/go-ipfs/unixfs/io"
	upb "github.com/ipfs/go-ipfs/unixfs/pb"

	proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
	ipld "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
)

// Writer is a utility structure that helps to write
// unixfs merkledag nodes as a tar archive format.
// It wraps any io.Writer.
type Writer struct {
	Dag  ipld.DAGService
	TarW *tar.Writer

	ctx context.Context
}

// NewWriter wraps given io.Writer.
func NewWriter(ctx context.Context, dag ipld.DAGService, archive bool, compression int, w io.Writer) (*Writer, error) {
	return &Writer{
		Dag:  dag,
		TarW: tar.NewWriter(w),
		ctx:  ctx,
	}, nil
}

func (w *Writer) writeDir(nd *mdag.ProtoNode, fpath string) error {
	dir, err := uio.NewDirectoryFromNode(w.Dag, nd)
	if err != nil {
		return err
	}
	if err := writeDirHeader(w.TarW, fpath); err != nil {
		return err
	}

	return dir.ForEachLink(w.ctx, func(l *ipld.Link) error {
		child, err := w.Dag.Get(w.ctx, l.Cid)
		if err != nil {
			return err
		}
		npath := path.Join(fpath, l.Name)
		if err := w.WriteNode(child, npath); err != nil {
			return err
		}
		return nil
	})
}

func (w *Writer) writeFile(nd *mdag.ProtoNode, pb *upb.Data, fpath string) error {
	if err := writeFileHeader(w.TarW, fpath, pb.GetFilesize()); err != nil {
		return err
	}

	dagr := uio.NewPBFileReader(w.ctx, nd, pb, w.Dag)
	if _, err := dagr.WriteTo(w.TarW); err != nil {
		return err
	}
	w.TarW.Flush()
	return nil
}

// WriteNode adds a node to the archive.
func (w *Writer) WriteNode(nd ipld.Node, fpath string) error {
	switch nd := nd.(type) {
	case *mdag.ProtoNode:
		pb := new(upb.Data)
		if err := proto.Unmarshal(nd.Data(), pb); err != nil {
			return err
		}

		switch pb.GetType() {
		case upb.Data_Metadata:
			fallthrough
		case upb.Data_Directory, upb.Data_HAMTShard:
			return w.writeDir(nd, fpath)
		case upb.Data_Raw:
			fallthrough
		case upb.Data_File:
			return w.writeFile(nd, pb, fpath)
		case upb.Data_Symlink:
			return writeSymlinkHeader(w.TarW, string(pb.GetData()), fpath)
		default:
			return ft.ErrUnrecognizedType
		}
	case *mdag.RawNode:
		if err := writeFileHeader(w.TarW, fpath, uint64(len(nd.RawData()))); err != nil {
			return err
		}

		if _, err := w.TarW.Write(nd.RawData()); err != nil {
			return err
		}
		w.TarW.Flush()
		return nil
	default:
		return fmt.Errorf("nodes of type %T are not supported in unixfs", nd)
	}
}

// Close closes the tar writer.
func (w *Writer) Close() error {
	return w.TarW.Close()
}

func writeDirHeader(w *tar.Writer, fpath string) error {
	return w.WriteHeader(&tar.Header{
		Name:     fpath,
		Typeflag: tar.TypeDir,
		Mode:     0777,
		ModTime:  time.Now(),
		// TODO: set mode, dates, etc. when added to unixFS
	})
}

func writeFileHeader(w *tar.Writer, fpath string, size uint64) error {
	return w.WriteHeader(&tar.Header{
		Name:     fpath,
		Size:     int64(size),
		Typeflag: tar.TypeReg,
		Mode:     0644,
		ModTime:  time.Now(),
		// TODO: set mode, dates, etc. when added to unixFS
	})
}

func writeSymlinkHeader(w *tar.Writer, target, fpath string) error {
	return w.WriteHeader(&tar.Header{
		Name:     fpath,
		Linkname: target,
		Mode:     0777,
		Typeflag: tar.TypeSymlink,
	})
}
<commit_msg>appease go lint<commit_after>// Package tar provides functionality to write a unixfs merkledag
// as a tar archive.
package tar

import (
	"archive/tar"
	"context"
	"fmt"
	"io"
	"path"
	"time"

	mdag "github.com/ipfs/go-ipfs/merkledag"
	ft "github.com/ipfs/go-ipfs/unixfs"
	uio "github.com/ipfs/go-ipfs/unixfs/io"
	upb "github.com/ipfs/go-ipfs/unixfs/pb"

	proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
	ipld "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
)

// Writer is a utility structure that helps to write
// unixfs merkledag nodes as a tar archive format.
// It wraps any io.Writer.
type Writer struct {
	Dag  ipld.DAGService
	TarW *tar.Writer

	ctx context.Context
}

// NewWriter wraps given io.Writer.
func NewWriter(ctx context.Context, dag ipld.DAGService, archive bool, compression int, w io.Writer) (*Writer, error) {
	return &Writer{
		Dag:  dag,
		TarW: tar.NewWriter(w),
		ctx:  ctx,
	}, nil
}

func (w *Writer) writeDir(nd *mdag.ProtoNode, fpath string) error {
	dir, err := uio.NewDirectoryFromNode(w.Dag, nd)
	if err != nil {
		return err
	}
	if err := writeDirHeader(w.TarW, fpath); err != nil {
		return err
	}

	return dir.ForEachLink(w.ctx, func(l *ipld.Link) error {
		child, err := w.Dag.Get(w.ctx, l.Cid)
		if err != nil {
			return err
		}
		npath := path.Join(fpath, l.Name)
		return w.WriteNode(child, npath)
	})
}

func (w *Writer) writeFile(nd *mdag.ProtoNode, pb *upb.Data, fpath string) error {
	if err := writeFileHeader(w.TarW, fpath, pb.GetFilesize()); err != nil {
		return err
	}

	dagr := uio.NewPBFileReader(w.ctx, nd, pb, w.Dag)
	if _, err := dagr.WriteTo(w.TarW); err != nil {
		return err
	}
	w.TarW.Flush()
	return nil
}

// WriteNode adds a node to the archive.
func (w *Writer) WriteNode(nd ipld.Node, fpath string) error {
	switch nd := nd.(type) {
	case *mdag.ProtoNode:
		pb := new(upb.Data)
		if err := proto.Unmarshal(nd.Data(), pb); err != nil {
			return err
		}

		switch pb.GetType() {
		case upb.Data_Metadata:
			fallthrough
		case upb.Data_Directory, upb.Data_HAMTShard:
			return w.writeDir(nd, fpath)
		case upb.Data_Raw:
			fallthrough
		case upb.Data_File:
			return w.writeFile(nd, pb, fpath)
		case upb.Data_Symlink:
			return writeSymlinkHeader(w.TarW, string(pb.GetData()), fpath)
		default:
			return ft.ErrUnrecognizedType
		}
	case *mdag.RawNode:
		if err := writeFileHeader(w.TarW, fpath, uint64(len(nd.RawData()))); err != nil {
			return err
		}

		if _, err := w.TarW.Write(nd.RawData()); err != nil {
			return err
		}
		w.TarW.Flush()
		return nil
	default:
		return fmt.Errorf("nodes of type %T are not supported in unixfs", nd)
	}
}

// Close closes the tar writer.
func (w *Writer) Close() error {
	return w.TarW.Close()
}

func writeDirHeader(w *tar.Writer, fpath string) error {
	return w.WriteHeader(&tar.Header{
		Name:     fpath,
		Typeflag: tar.TypeDir,
		Mode:     0777,
		ModTime:  time.Now(),
		// TODO: set mode, dates, etc. when added to unixFS
	})
}

func writeFileHeader(w *tar.Writer, fpath string, size uint64) error {
	return w.WriteHeader(&tar.Header{
		Name:     fpath,
		Size:     int64(size),
		Typeflag: tar.TypeReg,
		Mode:     0644,
		ModTime:  time.Now(),
		// TODO: set mode, dates, etc. when added to unixFS
	})
}

func writeSymlinkHeader(w *tar.Writer, target, fpath string) error {
	return w.WriteHeader(&tar.Header{
		Name:     fpath,
		Linkname: target,
		Mode:     0777,
		Typeflag: tar.TypeSymlink,
	})
}
<|endoftext|>


<commit_before>// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license

package irc

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/DanielOaks/girc-go/ircmsg"
	"github.com/tidwall/buntdb"
)

var (
	// EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
	// This can be moved to some other data structure/place if we need to load/unload mechs later.
	EnabledSaslMechanisms = map[string]func(*Server, *Client, string, []byte) bool{
		"PLAIN":    authPlainHandler,
		"EXTERNAL": authExternalHandler,
	}

	// NoAccount is a placeholder which means that the user is not logged into an account.
	NoAccount = ClientAccount{
		Name: "*", // * is used until actual account name is set
	}

	// generic sasl fail error
	errSaslFail = errors.New("SASL failed")
)

// ClientAccount represents a user account.
type ClientAccount struct {
	// Name of the account.
	Name string
	// RegisteredAt represents the time that the account was registered.
	RegisteredAt time.Time
	// Clients that are currently logged into this account (useful for notifications).
	Clients []*Client
}

// loadAccountCredentials loads an account's credentials from the store.
func loadAccountCredentials(tx *buntdb.Tx, accountKey string) (*AccountCredentials, error) {
	credText, err := tx.Get(fmt.Sprintf(keyAccountCredentials, accountKey))
	if err != nil {
		return nil, err
	}

	var creds AccountCredentials
	err = json.Unmarshal([]byte(credText), &creds)
	if err != nil {
		return nil, err
	}

	return &creds, nil
}

// loadAccount loads an account from the store, note that the account must actually exist.
func loadAccount(server *Server, tx *buntdb.Tx, accountKey string) *ClientAccount {
	name, _ := tx.Get(fmt.Sprintf(keyAccountName, accountKey))
	regTime, _ := tx.Get(fmt.Sprintf(keyAccountRegTime, accountKey))
	regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
	accountInfo := ClientAccount{
		Name:         name,
		RegisteredAt: time.Unix(regTimeInt, 0),
		Clients:      []*Client{},
	}
	server.accounts[accountKey] = &accountInfo

	return &accountInfo
}

// authenticateHandler parses the AUTHENTICATE command (for SASL authentication).
func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
	// sasl abort
	if !server.authenticationEnabled || len(msg.Params) == 1 && msg.Params[0] == "*" {
		if client.saslInProgress {
			client.Send(nil, server.name, ERR_SASLABORTED, client.nick, "SASL authentication aborted")
		} else {
			client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
		}
		client.saslInProgress = false
		client.saslMechanism = ""
		client.saslValue = ""
		return false
	}

	// start new sasl session
	if !client.saslInProgress {
		mechanism := strings.ToUpper(msg.Params[0])
		_, mechanismIsEnabled := EnabledSaslMechanisms[mechanism]

		if mechanismIsEnabled {
			client.saslInProgress = true
			client.saslMechanism = mechanism
			client.Send(nil, server.name, "AUTHENTICATE", "+")
		} else {
			client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
		}

		return false
	}

	// continue existing sasl session
	rawData := msg.Params[0]

	if len(rawData) > 400 {
		client.Send(nil, server.name, ERR_SASLTOOLONG, client.nick, "SASL message too long")
		client.saslInProgress = false
		client.saslMechanism = ""
		client.saslValue = ""
		return false
	} else if len(rawData) == 400 {
		client.saslValue += rawData
		// allow 4 'continuation' lines before rejecting for length
		if len(client.saslValue) > 400*4 {
			client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Passphrase too long")
			client.saslInProgress = false
			client.saslMechanism = ""
			client.saslValue = ""
			return false
		}
		return false
	}
	if rawData != "+" {
		client.saslValue += rawData
	}

	var data []byte
	var err error
	if client.saslValue != "+" {
		data, err = base64.StdEncoding.DecodeString(client.saslValue)
		if err != nil {
			client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Invalid b64 encoding")
			client.saslInProgress = false
			client.saslMechanism = ""
			client.saslValue = ""
			return false
		}
	}

	// call actual handler
	handler, handlerExists := EnabledSaslMechanisms[client.saslMechanism]

	// like 100% not required, but it's good to be safe I guess
	if !handlerExists {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
		client.saslInProgress = false
		client.saslMechanism = ""
		client.saslValue = ""
		return false
	}

	// let the SASL handler do its thing
	exiting := handler(server, client, client.saslMechanism, data)

	// wait 'til SASL is done before emptying the sasl vars
	client.saslInProgress = false
	client.saslMechanism = ""
	client.saslValue = ""

	return exiting
}

// authPlainHandler parses the SASL PLAIN mechanism.
func authPlainHandler(server *Server, client *Client, mechanism string, value []byte) bool {
	splitValue := bytes.Split(value, []byte{'\000'})

	var accountKey, authzid string

	if len(splitValue) == 3 {
		accountKey = string(splitValue[0])
		authzid = string(splitValue[1])

		if accountKey == "" {
			accountKey = authzid
		} else if accountKey != authzid {
			client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: authcid and authzid should be the same")
			return false
		}
	} else {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Invalid auth blob")
		return false
	}

	// keep it the same as in the REG CREATE stage
	accountKey, err := CasefoldName(accountKey)
	if err != nil {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Bad account name")
		return false
	}

	// load and check acct data all in one update to prevent races.
	// as noted elsewhere, change to proper locking for Account type later probably
	err = server.store.Update(func(tx *buntdb.Tx) error {
		creds, err := loadAccountCredentials(tx, accountKey)
		if err != nil {
			return err
		}

		// ensure creds are valid
		password := string(splitValue[2])
		if len(creds.PassphraseHash) < 1 || len(creds.PassphraseSalt) < 1 || len(password) < 1 {
			return errSaslFail
		}
		err = server.passwords.CompareHashAndPassword(creds.PassphraseHash, creds.PassphraseSalt, password)

		// succeeded, load account info if necessary
		account, exists := server.accounts[accountKey]
		if !exists {
			account = loadAccount(server, tx, accountKey)
		}

		account.Clients = append(account.Clients, client)
		client.account = account

		return err
	})

	if err != nil {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
		return false
	}

	client.successfulSaslAuth()
	return false
}

// authExternalHandler parses the SASL EXTERNAL mechanism.
func authExternalHandler(server *Server, client *Client, mechanism string, value []byte) bool {
	if client.certfp == "" {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed, you are not connecting with a caertificate")
		return false
	}

	err := server.store.Update(func(tx *buntdb.Tx) error {
		// certfp lookup key
		accountKey, err := tx.Get(fmt.Sprintf(keyCertToAccount, client.certfp))
		if err != nil {
			return errSaslFail
		}

		// confirm account exists
		_, err = tx.Get(fmt.Sprintf(keyAccountExists, accountKey))
		if err != nil {
			return errSaslFail
		}

		// confirm the certfp in that account's credentials
		creds, err := loadAccountCredentials(tx, accountKey)
		if err != nil || creds.Certificate != client.certfp {
			return errSaslFail
		}

		// succeeded, load account info if necessary
		account, exists := server.accounts[accountKey]
		if !exists {
			account = loadAccount(server, tx, accountKey)
		}

		account.Clients = append(account.Clients, client)
		client.account = account

		return nil
	})

	if err != nil {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
		return false
	}

	client.successfulSaslAuth()
	return false
}

// successfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages.
func (c *Client) successfulSaslAuth() {
	c.Send(nil, c.server.name, RPL_LOGGEDIN, c.nick, c.nickMaskString, c.account.Name, fmt.Sprintf("You are now logged in as %s", c.account.Name))
	c.Send(nil, c.server.name, RPL_SASLSUCCESS, c.nick, "SASL authentication successful")

	// dispatch account-notify
	for friend := range c.Friends(AccountNotify) {
		friend.Send(nil, c.nickMaskString, "ACCOUNT", c.account.Name)
	}
}
<commit_msg>accounts: Only allow verified accounts to SASL auth<commit_after>// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license

package irc

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/DanielOaks/girc-go/ircmsg"
	"github.com/tidwall/buntdb"
)

var (
	// EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
	// This can be moved to some other data structure/place if we need to load/unload mechs later.
	EnabledSaslMechanisms = map[string]func(*Server, *Client, string, []byte) bool{
		"PLAIN":    authPlainHandler,
		"EXTERNAL": authExternalHandler,
	}

	// NoAccount is a placeholder which means that the user is not logged into an account.
	NoAccount = ClientAccount{
		Name: "*", // * is used until actual account name is set
	}

	// generic sasl fail error
	errSaslFail = errors.New("SASL failed")
)

// ClientAccount represents a user account.
type ClientAccount struct {
	// Name of the account.
	Name string
	// RegisteredAt represents the time that the account was registered.
	RegisteredAt time.Time
	// Clients that are currently logged into this account (useful for notifications).
	Clients []*Client
}

// loadAccountCredentials loads an account's credentials from the store.
func loadAccountCredentials(tx *buntdb.Tx, accountKey string) (*AccountCredentials, error) {
	credText, err := tx.Get(fmt.Sprintf(keyAccountCredentials, accountKey))
	if err != nil {
		return nil, err
	}

	var creds AccountCredentials
	err = json.Unmarshal([]byte(credText), &creds)
	if err != nil {
		return nil, err
	}

	return &creds, nil
}

// loadAccount loads an account from the store, note that the account must actually exist.
func loadAccount(server *Server, tx *buntdb.Tx, accountKey string) *ClientAccount {
	name, _ := tx.Get(fmt.Sprintf(keyAccountName, accountKey))
	regTime, _ := tx.Get(fmt.Sprintf(keyAccountRegTime, accountKey))
	regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
	accountInfo := ClientAccount{
		Name:         name,
		RegisteredAt: time.Unix(regTimeInt, 0),
		Clients:      []*Client{},
	}
	server.accounts[accountKey] = &accountInfo

	return &accountInfo
}

// authenticateHandler parses the AUTHENTICATE command (for SASL authentication).
func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
	// sasl abort
	if !server.authenticationEnabled || len(msg.Params) == 1 && msg.Params[0] == "*" {
		if client.saslInProgress {
			client.Send(nil, server.name, ERR_SASLABORTED, client.nick, "SASL authentication aborted")
		} else {
			client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
		}
		client.saslInProgress = false
		client.saslMechanism = ""
		client.saslValue = ""
		return false
	}

	// start new sasl session
	if !client.saslInProgress {
		mechanism := strings.ToUpper(msg.Params[0])
		_, mechanismIsEnabled := EnabledSaslMechanisms[mechanism]

		if mechanismIsEnabled {
			client.saslInProgress = true
			client.saslMechanism = mechanism
			client.Send(nil, server.name, "AUTHENTICATE", "+")
		} else {
			client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
		}

		return false
	}

	// continue existing sasl session
	rawData := msg.Params[0]

	if len(rawData) > 400 {
		client.Send(nil, server.name, ERR_SASLTOOLONG, client.nick, "SASL message too long")
		client.saslInProgress = false
		client.saslMechanism = ""
		client.saslValue = ""
		return false
	} else if len(rawData) == 400 {
		client.saslValue += rawData
		// allow 4 'continuation' lines before rejecting for length
		if len(client.saslValue) > 400*4 {
			client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Passphrase too long")
			client.saslInProgress = false
			client.saslMechanism = ""
			client.saslValue = ""
			return false
		}
		return false
	}
	if rawData != "+" {
		client.saslValue += rawData
	}

	var data []byte
	var err error
	if client.saslValue != "+" {
		data, err = base64.StdEncoding.DecodeString(client.saslValue)
		if err != nil {
			client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Invalid b64 encoding")
			client.saslInProgress = false
			client.saslMechanism = ""
			client.saslValue = ""
			return false
		}
	}

	// call actual handler
	handler, handlerExists := EnabledSaslMechanisms[client.saslMechanism]

	// like 100% not required, but it's good to be safe I guess
	if !handlerExists {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
		client.saslInProgress = false
		client.saslMechanism = ""
		client.saslValue = ""
		return false
	}

	// let the SASL handler do its thing
	exiting := handler(server, client, client.saslMechanism, data)

	// wait 'til SASL is done before emptying the sasl vars
	client.saslInProgress = false
	client.saslMechanism = ""
	client.saslValue = ""

	return exiting
}

// authPlainHandler parses the SASL PLAIN mechanism.
func authPlainHandler(server *Server, client *Client, mechanism string, value []byte) bool {
	splitValue := bytes.Split(value, []byte{'\000'})

	var accountKey, authzid string

	if len(splitValue) == 3 {
		accountKey = string(splitValue[0])
		authzid = string(splitValue[1])

		if accountKey == "" {
			accountKey = authzid
		} else if accountKey != authzid {
			client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: authcid and authzid should be the same")
			return false
		}
	} else {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Invalid auth blob")
		return false
	}

	// keep it the same as in the REG CREATE stage
	accountKey, err := CasefoldName(accountKey)
	if err != nil {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Bad account name")
		return false
	}

	// load and check acct data all in one update to prevent races.
	// as noted elsewhere, change to proper locking for Account type later probably
	err = server.store.Update(func(tx *buntdb.Tx) error {
		// confirm account is verified
		_, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey))
		if err != nil {
			return errSaslFail
		}

		creds, err := loadAccountCredentials(tx, accountKey)
		if err != nil {
			return err
		}

		// ensure creds are valid
		password := string(splitValue[2])
		if len(creds.PassphraseHash) < 1 || len(creds.PassphraseSalt) < 1 || len(password) < 1 {
			return errSaslFail
		}
		err = server.passwords.CompareHashAndPassword(creds.PassphraseHash, creds.PassphraseSalt, password)

		// succeeded, load account info if necessary
		account, exists := server.accounts[accountKey]
		if !exists {
			account = loadAccount(server, tx, accountKey)
		}

		account.Clients = append(account.Clients, client)
		client.account = account

		return err
	})

	if err != nil {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
		return false
	}

	client.successfulSaslAuth()
	return false
}

// authExternalHandler parses the SASL EXTERNAL mechanism.
func authExternalHandler(server *Server, client *Client, mechanism string, value []byte) bool {
	if client.certfp == "" {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed, you are not connecting with a caertificate")
		return false
	}

	err := server.store.Update(func(tx *buntdb.Tx) error {
		// certfp lookup key
		accountKey, err := tx.Get(fmt.Sprintf(keyCertToAccount, client.certfp))
		if err != nil {
			return errSaslFail
		}

		// confirm account exists
		_, err = tx.Get(fmt.Sprintf(keyAccountExists, accountKey))
		if err != nil {
			return errSaslFail
		}

		// confirm account is verified
		_, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey))
		if err != nil {
			return errSaslFail
		}

		// confirm the certfp in that account's credentials
		creds, err := loadAccountCredentials(tx, accountKey)
		if err != nil || creds.Certificate != client.certfp {
			return errSaslFail
		}

		// succeeded, load account info if necessary
		account, exists := server.accounts[accountKey]
		if !exists {
			account = loadAccount(server, tx, accountKey)
		}

		account.Clients = append(account.Clients, client)
		client.account = account

		return nil
	})

	if err != nil {
		client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed")
		return false
	}

	client.successfulSaslAuth()
	return false
}

// successfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages.
func (c *Client) successfulSaslAuth() {
	c.Send(nil, c.server.name, RPL_LOGGEDIN, c.nick, c.nickMaskString, c.account.Name, fmt.Sprintf("You are now logged in as %s", c.account.Name))
	c.Send(nil, c.server.name, RPL_SASLSUCCESS, c.nick, "SASL authentication successful")

	// dispatch account-notify
	for friend := range c.Friends(AccountNotify) {
		friend.Send(nil, c.nickMaskString, "ACCOUNT", c.account.Name)
	}
}
<|endoftext|>


<commit_before>package agent

import (
	"bytes"
	"fmt"
	"github.com/hashicorp/serf/serf"
	"io"
	"log"
	"os/exec"
	"runtime"
	"strings"
)

const (
	windows = "windows"
)

// invokeEventScript will execute the given event script with the given
// event. Depending on the event, the semantics of how data are passed
// are a bit different. For all events, the SERF_EVENT environmental
// variable is the type of the event. For user events, the SERF_USER_EVENT
// environmental variable is also set, containing the name of the user
// event that was fired.
//
// In all events, data is passed in via stdin to faciliate piping. See
// the various stdin functions below for more information.
func invokeEventScript(logger *log.Logger, script string, self serf.Member, event serf.Event) error {
	var output bytes.Buffer

	// Determine the shell invocation based on OS
	var shell, flag string
	if runtime.GOOS == windows {
		shell = "cmd"
		flag = "/C"
	} else {
		shell = "/bin/sh"
		flag = "-c"
	}

	cmd := exec.Command(shell, flag, script)
	cmd.Args[0] = "serf-event"
	cmd.Env = append(cmd.Env,
		"SERF_EVENT="+event.EventType().String(),
		"SERF_SELF_NAME="+self.Name,
		"SERF_SELF_ROLE="+self.Role,
	)
	cmd.Stderr = &output
	cmd.Stdout = &output

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}

	switch e := event.(type) {
	case serf.MemberEvent:
		go memberEventStdin(logger, stdin, &e)
	case serf.UserEvent:
		cmd.Env = append(cmd.Env, "SERF_USER_EVENT="+e.Name)
		cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_USER_LTIME=%d", e.LTime))
		go userEventStdin(logger, stdin, &e)
	default:
		return fmt.Errorf("Unknown event type: %s", event.EventType().String())
	}

	if err := cmd.Start(); err != nil {
		return err
	}

	err = cmd.Wait()
	logger.Printf("[DEBUG] Event '%s' script output: %s",
		event.EventType().String(), output.String())

	if err != nil {
		return err
	}

	return nil
}

// eventClean cleans a value to be a parameter in an event line.
func eventClean(v string) string {
	v = strings.Replace(v, "\t", "\\t", -1)
	v = strings.Replace(v, "\n", "\\n", -1)
	return v
}

// Sends data on stdin for a member event.
//
// The format for the data is unix tool friendly, separated by whitespace
// and newlines. The structure of each line for any member event is:
// "NAME    ADDRESS    ROLE" where the whitespace is actually tabs.
// The name and role are cleaned so that newlines and tabs are replaced
// with "\n" and "\t" respectively.
func memberEventStdin(logger *log.Logger, stdin io.WriteCloser, e *serf.MemberEvent) {
	defer stdin.Close()
	for _, member := range e.Members {
		_, err := stdin.Write([]byte(fmt.Sprintf(
			"%s\t%s\t%s\n",
			eventClean(member.Name),
			member.Addr.String(),
			eventClean(member.Role))))
		if err != nil {
			return
		}
	}
}

// Sends data on stdin for a user event. The stdin simply contains the
// payload (if any) of the event.
func userEventStdin(logger *log.Logger, stdin io.WriteCloser, e *serf.UserEvent) {
	defer stdin.Close()
	if _, err := stdin.Write(e.Payload); err != nil {
		logger.Printf("[ERR] Error writing user event payload: %s", err)
		return
	}
}
<commit_msg>Ensure user-events have a newline<commit_after>package agent

import (
	"bytes"
	"fmt"
	"github.com/hashicorp/serf/serf"
	"io"
	"log"
	"os/exec"
	"runtime"
	"strings"
)

const (
	windows = "windows"
)

// invokeEventScript will execute the given event script with the given
// event. Depending on the event, the semantics of how data are passed
// are a bit different. For all events, the SERF_EVENT environmental
// variable is the type of the event. For user events, the SERF_USER_EVENT
// environmental variable is also set, containing the name of the user
// event that was fired.
//
// In all events, data is passed in via stdin to faciliate piping. See
// the various stdin functions below for more information.
func invokeEventScript(logger *log.Logger, script string, self serf.Member, event serf.Event) error {
	var output bytes.Buffer

	// Determine the shell invocation based on OS
	var shell, flag string
	if runtime.GOOS == windows {
		shell = "cmd"
		flag = "/C"
	} else {
		shell = "/bin/sh"
		flag = "-c"
	}

	cmd := exec.Command(shell, flag, script)
	cmd.Args[0] = "serf-event"
	cmd.Env = append(cmd.Env,
		"SERF_EVENT="+event.EventType().String(),
		"SERF_SELF_NAME="+self.Name,
		"SERF_SELF_ROLE="+self.Role,
	)
	cmd.Stderr = &output
	cmd.Stdout = &output

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}

	switch e := event.(type) {
	case serf.MemberEvent:
		go memberEventStdin(logger, stdin, &e)
	case serf.UserEvent:
		cmd.Env = append(cmd.Env, "SERF_USER_EVENT="+e.Name)
		cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_USER_LTIME=%d", e.LTime))
		go userEventStdin(logger, stdin, &e)
	default:
		return fmt.Errorf("Unknown event type: %s", event.EventType().String())
	}

	if err := cmd.Start(); err != nil {
		return err
	}

	err = cmd.Wait()
	logger.Printf("[DEBUG] Event '%s' script output: %s",
		event.EventType().String(), output.String())

	if err != nil {
		return err
	}

	return nil
}

// eventClean cleans a value to be a parameter in an event line.
func eventClean(v string) string {
	v = strings.Replace(v, "\t", "\\t", -1)
	v = strings.Replace(v, "\n", "\\n", -1)
	return v
}

// Sends data on stdin for a member event.
//
// The format for the data is unix tool friendly, separated by whitespace
// and newlines. The structure of each line for any member event is:
// "NAME    ADDRESS    ROLE" where the whitespace is actually tabs.
// The name and role are cleaned so that newlines and tabs are replaced
// with "\n" and "\t" respectively.
func memberEventStdin(logger *log.Logger, stdin io.WriteCloser, e *serf.MemberEvent) {
	defer stdin.Close()
	for _, member := range e.Members {
		_, err := stdin.Write([]byte(fmt.Sprintf(
			"%s\t%s\t%s\n",
			eventClean(member.Name),
			member.Addr.String(),
			eventClean(member.Role))))
		if err != nil {
			return
		}
	}
}

// Sends data on stdin for a user event. The stdin simply contains the
// payload (if any) of the event.
// Most shells read implementations need a newline, force it to be there
func userEventStdin(logger *log.Logger, stdin io.WriteCloser, e *serf.UserEvent) {
    defer stdin.Close()
    _, err := stdin.Write([]byte(fmt.Sprintf(
        "%s\n",
        bytes.TrimSuffix(e.Payload, []byte("\n")))))
    if err != nil {
        logger.Printf("[ERR] Error writing user event payload: %s", err)
        return
    }
}
<|endoftext|>


<commit_before>package command

import (
	"regexp"
	"testing"
)

func Test_NoRewrites(t *testing.T) {
	for _, str := range []string{
		`INSERT INTO "names" VALUES (1, 'bob', '123-45-678')`,
		`INSERT INTO "names" VALUES (RANDOM(), 'bob', '123-45-678')`,
		`SELECT title FROM albums ORDER BY RANDOM()`,
		`INSERT INTO foo(name, age) VALUES(?, ?)`,
	} {

		stmts := []*Statement{
			{
				Sql: str,
			},
		}
		if err := Rewrite(stmts, false); err != nil {
			t.Fatalf("failed to not rewrite: %s", err)
		}
		if stmts[0].Sql != str {
			t.Fatalf("SQL is modified: %s", stmts[0].Sql)
		}
	}
}

func Test_NoRewritesMulti(t *testing.T) {
	stmts := []*Statement{
		{
			Sql: `INSERT INTO "names" VALUES (1, 'bob', '123-45-678')`,
		},
		{
			Sql: `INSERT INTO "names" VALUES (RANDOM(), 'bob', '123-45-678')`,
		},
		{
			Sql: `SELECT title FROM albums ORDER BY RANDOM()`,
		},
	}
	if err := Rewrite(stmts, false); err != nil {
		t.Fatalf("failed to not rewrite: %s", err)
	}
	if len(stmts) != 3 {
		t.Fatalf("returned stmts is wrong length: %d", len(stmts))
	}
	if stmts[0].Sql != `INSERT INTO "names" VALUES (1, 'bob', '123-45-678')` {
		t.Fatalf("SQL is modified: %s", stmts[0].Sql)
	}
	if stmts[1].Sql != `INSERT INTO "names" VALUES (RANDOM(), 'bob', '123-45-678')` {
		t.Fatalf("SQL is modified: %s", stmts[0].Sql)
	}
	if stmts[2].Sql != `SELECT title FROM albums ORDER BY RANDOM()` {
		t.Fatalf("SQL is modified: %s", stmts[0].Sql)
	}
}

func Test_Rewrites(t *testing.T) {
	testSQLs := []string{
		`INSERT INTO "names" VALUES (1, 'bob', '123-45-678')`, `INSERT INTO "names" VALUES \(1, 'bob', '123-45-678'\)`,
		`INSERT INTO "names" VALUES (RANDOM(), 'bob', '123-45-678')`, `INSERT INTO "names" VALUES \(-?[0-9]+, 'bob', '123-45-678'\)`,
		`SELECT title FROM albums ORDER BY RANDOM()`, `SELECT title FROM albums ORDER BY RANDOM\(\)`,
		`SELECT RANDOM()`, `SELECT -?[0-9]+`,
	}
	for i := 0; i < len(testSQLs)-1; i += 2 {
		stmts := []*Statement{
			{
				Sql: testSQLs[i],
			},
		}
		if err := Rewrite(stmts, true); err != nil {
			t.Fatalf("failed to not rewrite: %s", err)
		}

		match := regexp.MustCompile(testSQLs[i+1])
		if !match.MatchString(stmts[0].Sql) {
			t.Fatalf("test %d failed, %s (rewritten as %s) does not regex-match with %s", i, testSQLs[i], stmts[0].Sql, testSQLs[i+1])
		}
	}
}
<commit_msg>Add unit test which should not (now) panic<commit_after>package command

import (
	"regexp"
	"testing"
)

func Test_NoRewrites(t *testing.T) {
	for _, str := range []string{
		`INSERT INTO "names" VALUES (1, 'bob', '123-45-678')`,
		`INSERT INTO "names" VALUES (RANDOM(), 'bob', '123-45-678')`,
		`SELECT title FROM albums ORDER BY RANDOM()`,
		`INSERT INTO foo(name, age) VALUES(?, ?)`,
	} {

		stmts := []*Statement{
			{
				Sql: str,
			},
		}
		if err := Rewrite(stmts, false); err != nil {
			t.Fatalf("failed to not rewrite: %s", err)
		}
		if stmts[0].Sql != str {
			t.Fatalf("SQL is modified: %s", stmts[0].Sql)
		}
	}
}

func Test_NoRewritesMulti(t *testing.T) {
	stmts := []*Statement{
		{
			Sql: `INSERT INTO "names" VALUES (1, 'bob', '123-45-678')`,
		},
		{
			Sql: `INSERT INTO "names" VALUES (RANDOM(), 'bob', '123-45-678')`,
		},
		{
			Sql: `SELECT title FROM albums ORDER BY RANDOM()`,
		},
	}
	if err := Rewrite(stmts, false); err != nil {
		t.Fatalf("failed to not rewrite: %s", err)
	}
	if len(stmts) != 3 {
		t.Fatalf("returned stmts is wrong length: %d", len(stmts))
	}
	if stmts[0].Sql != `INSERT INTO "names" VALUES (1, 'bob', '123-45-678')` {
		t.Fatalf("SQL is modified: %s", stmts[0].Sql)
	}
	if stmts[1].Sql != `INSERT INTO "names" VALUES (RANDOM(), 'bob', '123-45-678')` {
		t.Fatalf("SQL is modified: %s", stmts[0].Sql)
	}
	if stmts[2].Sql != `SELECT title FROM albums ORDER BY RANDOM()` {
		t.Fatalf("SQL is modified: %s", stmts[0].Sql)
	}
}

func Test_Rewrites(t *testing.T) {
	testSQLs := []string{
		`INSERT INTO "names" VALUES (1, 'bob', '123-45-678')`, `INSERT INTO "names" VALUES \(1, 'bob', '123-45-678'\)`,
		`INSERT INTO "names" VALUES (RANDOM(), 'bob', '123-45-678')`, `INSERT INTO "names" VALUES \(-?[0-9]+, 'bob', '123-45-678'\)`,
		`SELECT title FROM albums ORDER BY RANDOM()`, `SELECT title FROM albums ORDER BY RANDOM\(\)`,
		`SELECT RANDOM()`, `SELECT -?[0-9]+`,
		`CREATE TABLE tbl (col1 TEXT, ts DATETIME DEFAULT CURRENT_TIMESTAMP)`, `CREATE TABLE tbl \(col1 TEXT, ts DATETIME DEFAULT CURRENT_TIMESTAMP\)`,
	}
	for i := 0; i < len(testSQLs)-1; i += 2 {
		stmts := []*Statement{
			{
				Sql: testSQLs[i],
			},
		}
		if err := Rewrite(stmts, true); err != nil {
			t.Fatalf("failed to not rewrite: %s", err)
		}

		match := regexp.MustCompile(testSQLs[i+1])
		if !match.MatchString(stmts[0].Sql) {
			t.Fatalf("test %d failed, %s (rewritten as %s) does not regex-match with %s", i, testSQLs[i], stmts[0].Sql, testSQLs[i+1])
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2019, OpenTelemetry Authors
//
// 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 registry

import (
	"go.opentelemetry.io/api/unit"
)

type Sequence uint64

type Option func(Variable) Variable

type Variable struct {
	Name        string
	Description string
	Unit        unit.Unit
	Type        Type
}

type Type interface {
	String() string
}

func Register(name string, vtype Type, opts ...Option) Variable {
	return newVar(name, vtype, opts...)
}

func newVar(name string, vtype Type, opts ...Option) Variable {
	v := Variable{
		Name: name,
	}
	for _, o := range opts {
		v = o(v)
	}
	return v
}

func (v *Variable) Defined() bool {
	return len(v.Name) != 0
}

// WithDescription applies the provided description.
func WithDescription(desc string) Option {
	return func(v Variable) Variable {
		v.Description = desc
		return v
	}
}

// WithUnit applies the provided unit.
func WithUnit(unit unit.Unit) Option {
	return func(v Variable) Variable {
		v.Unit = unit
		return v
	}
}
<commit_msg>set vtype for newVar (#56)<commit_after>// Copyright 2019, OpenTelemetry Authors
//
// 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 registry

import (
	"go.opentelemetry.io/api/unit"
)

type Sequence uint64

type Option func(Variable) Variable

type Variable struct {
	Name        string
	Description string
	Unit        unit.Unit
	Type        Type
}

type Type interface {
	String() string
}

func Register(name string, vtype Type, opts ...Option) Variable {
	return newVar(name, vtype, opts...)
}

func newVar(name string, vtype Type, opts ...Option) Variable {
	v := Variable{
		Name: name,
		Type: vtype,
	}
	for _, o := range opts {
		v = o(v)
	}
	return v
}

func (v *Variable) Defined() bool {
	return len(v.Name) != 0
}

// WithDescription applies the provided description.
func WithDescription(desc string) Option {
	return func(v Variable) Variable {
		v.Description = desc
		return v
	}
}

// WithUnit applies the provided unit.
func WithUnit(unit unit.Unit) Option {
	return func(v Variable) Variable {
		v.Unit = unit
		return v
	}
}
<|endoftext|>


<commit_before>package api

import (
	"net/http"
	"net/http/httptest"
	"time"

	. "gopkg.in/check.v1"

	"github.com/hoffie/larasync/repository"
)

type RepoListTests struct {
	server *Server
	req    *http.Request
	repos  string
}

var _ = Suite(&RepoListTests{})

func (t *RepoListTests) SetUpTest(c *C) {
	req, err := http.NewRequest("GET", "http://example.org/repositories", nil)
	c.Assert(err, IsNil)
	t.req = req
}

func (t *RepoListTests) SetUpSuite(c *C) {
	t.repos = c.MkDir()
	rm, err := repository.NewManager(t.repos)
	c.Assert(err, IsNil)
	t.server = New(adminSecret, time.Minute, rm)
}

func (t *RepoListTests) getResponse(req *http.Request) *httptest.ResponseRecorder {
	rw := httptest.NewRecorder()
	t.server.router.ServeHTTP(rw, req)
	return rw
}

func (t *RepoListTests) TestRepoListUnauthorized(c *C) {
	resp := t.getResponse(t.req)
	c.Assert(resp.Code, Equals, 401)
	c.Assert(resp.Body.String(), Equals, "Unauthorized\n")
}

func (t *RepoListTests) TestRepoListAdmin(c *C) {
	SignAsAdmin(t.req, adminSecret)
	resp := t.getResponse(t.req)
	c.Assert(resp.Code, Equals, 200)
}

func (t *RepoListTests) TestRepoListOutput(c *C) {
	SignAsAdmin(t.req, adminSecret)
	resp := t.getResponse(t.req)
	//FIXME test repo list output
	c.Assert(resp.Code, Equals, 200)
	c.Assert(resp.Body.String(), Equals, "[]")
	c.Assert(resp.Body.Len(), Not(Equals), 0)
}
<commit_msg>api tests: improve coverage<commit_after>package api

import (
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"time"

	. "gopkg.in/check.v1"

	"github.com/hoffie/larasync/repository"
)

type RepoListTests struct {
	server *Server
	req    *http.Request
	repos  string
}

var _ = Suite(&RepoListTests{})

func (t *RepoListTests) SetUpTest(c *C) {
	req, err := http.NewRequest("GET", "http://example.org/repositories", nil)
	c.Assert(err, IsNil)
	t.req = req
}

func (t *RepoListTests) SetUpSuite(c *C) {
	t.repos = c.MkDir()
	rm, err := repository.NewManager(t.repos)
	c.Assert(err, IsNil)
	t.server = New(adminSecret, time.Minute, rm)
}

func (t *RepoListTests) getResponse(req *http.Request) *httptest.ResponseRecorder {
	rw := httptest.NewRecorder()
	t.server.router.ServeHTTP(rw, req)
	return rw
}

func (t *RepoListTests) TestRepoListUnauthorized(c *C) {
	resp := t.getResponse(t.req)
	c.Assert(resp.Code, Equals, 401)
	c.Assert(resp.Body.String(), Equals, "Unauthorized\n")
}

func (t *RepoListTests) TestRepoListAdmin(c *C) {
	SignAsAdmin(t.req, adminSecret)
	resp := t.getResponse(t.req)
	c.Assert(resp.Code, Equals, 200)
}

func (t *RepoListTests) TestRepoListOutput(c *C) {
	SignAsAdmin(t.req, adminSecret)
	resp := t.getResponse(t.req)
	//FIXME test repo list output
	c.Assert(resp.Code, Equals, 200)
	c.Assert(resp.Body.String(), Equals, "[]")
	c.Assert(resp.Body.Len(), Not(Equals), 0)
}

func (t *RepoListTests) TestRepoListOutputExcludeFiles(c *C) {
	f, err := os.Create(filepath.Join(t.repos, "somefile"))
	c.Assert(err, IsNil)
	f.Close()
	SignAsAdmin(t.req, adminSecret)
	resp := t.getResponse(t.req)
	c.Assert(resp.Code, Equals, 200)
	c.Assert(resp.Body.String(), Equals, "[]")
	c.Assert(resp.Body.Len(), Not(Equals), 0)
}
<|endoftext|>


<commit_before>// Copyright 2012-2013 Christian Neumann
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package service

import (
	"reflect"
	"testing"
)

func TestGetConfig(t *testing.T) {
	tests := []struct {
		Body string
		Out  interface{}
		Ret  interface{}
	}{
		{`{"fookey":"foovalue"}`, "", "foovalue"},
		{`{"fookey": null}`, "", ""},
	}
	for _, test := range tests {
		err := getConfig([]byte(test.Body), &test.Out)
		if err != nil {
			t.Error("getConfig returned error: %v", err)
		}
		if !reflect.DeepEqual(test.Out, test.Ret) {
			t.Error("getConfig(%q, out); out is %q, should be %q",
				test.Body, test.Out, test.Ret)
		}
	}
}

func TestNodeToData(t *testing.T) {
	tests := []struct {
		Node   Node
		Indent bool
		Data   string
	}{
		{Node{Path: "/foo", Type: "Bar"}, false,
			`{"Type":"Bar","Order":0,"Hide":false,"Fields":null}`},
	}
	for i, test := range tests {
		oldPath := test.Node.Path
		ret, err := nodeToData(&test.Node, test.Indent)
		if oldPath != test.Node.Path {
			t.Errorf("nodeToData altered node")
		}
		if err != nil {
			t.Errorf("Test %d failed, got error: %v", i, err)
		}
		if string(ret) != test.Data {
			t.Errorf("nodeToData(%v,%v) = %v, should be %v", test.Node, test.Indent,
				string(ret), test.Data)
		}
	}
}
<commit_msg>Disable nodeToData test for now.<commit_after>// Copyright 2012-2013 Christian Neumann
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package service

import (
	"reflect"
	"testing"
)

func TestGetConfig(t *testing.T) {
	tests := []struct {
		Body string
		Out  interface{}
		Ret  interface{}
	}{
		{`{"fookey":"foovalue"}`, "", "foovalue"},
		{`{"fookey": null}`, "", ""},
	}
	for _, test := range tests {
		err := getConfig([]byte(test.Body), &test.Out)
		if err != nil {
			t.Error("getConfig returned error: %v", err)
		}
		if !reflect.DeepEqual(test.Out, test.Ret) {
			t.Error("getConfig(%q, out); out is %q, should be %q",
				test.Body, test.Out, test.Ret)
		}
	}
}

/*
func TestNodeToData(t *testing.T) {
	tests := []struct {
		Node   Node
		Indent bool
		Data   string
	}{
		{Node{Path: "/foo", Type: "Bar"}, false,
			`{"Type":"Bar","Order":0,"Hide":false,"Fields":null}`},
	}
	for i, test := range tests {
		oldPath := test.Node.Path
		ret, err := nodeToData(&test.Node, test.Indent)
		if oldPath != test.Node.Path {
			t.Errorf("nodeToData altered node")
		}
		if err != nil {
			t.Errorf("Test %d failed, got error: %v", i, err)
		}
		if string(ret) != test.Data {
			t.Errorf("nodeToData(%v,%v) = %v, should be %v", test.Node, test.Indent,
				string(ret), test.Data)
		}
	}
}
*/
<|endoftext|>


<commit_before>package main

import (
	"encoding/csv"
	"fmt"
	"os"
)

func outputCSV(entries []entry) {
	csvFile, err := os.Create("cs-reboot-info-output.csv")
	if err != nil {
		fmt.Println("Error creating csv file:", err)
		return
	}
	defer csvFile.Close()
	writer := csv.NewWriter(csvFile)
	writer.Write([]string{"generation", "region", "server_uuid", "server_name", "reboot_window_start_UTC", "reboot_window_end_UTC", "reboot_window_start_local", "reboot_window_end_local"})
	for _, e := range entries {
		record := []string{e.GenType, e.Region, e.Server.ID, e.Server.Name, e.WindowStart.String(), e.WindowEnd.String(), e.WindowStart.Local().String(), e.WindowEnd.Local().String()}
		err := writer.Write(record)
		if err != nil {
			fmt.Println("Error writing row to CSV: ", err)
			return
		}
	}
}
<commit_msg>tell user csv file name<commit_after>package main

import (
	"encoding/csv"
	"fmt"
	"os"
)

func outputCSV(entries []entry) {
	outputFile := "cs-reboot-info-output.csv"
	csvFile, err := os.Create(outputFile)
	if err != nil {
		fmt.Println("Error creating csv file:", err)
		return
	}
	fmt.Printf("Writing output to %s\n", outputFile)
	defer csvFile.Close()
	writer := csv.NewWriter(csvFile)
	writer.Write([]string{"generation", "region", "server_uuid", "server_name", "reboot_window_start_UTC", "reboot_window_end_UTC", "reboot_window_start_local", "reboot_window_end_local"})
	for _, e := range entries {
		record := []string{e.GenType, e.Region, e.Server.ID, e.Server.Name, e.WindowStart.String(), e.WindowEnd.String(), e.WindowStart.Local().String(), e.WindowEnd.Local().String()}
		err := writer.Write(record)
		if err != nil {
			fmt.Println("Error writing row to CSV: ", err)
			return
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/abates/AdventOfCode/2017/graph"
	"github.com/abates/AdventOfCode/2017/hash"
)

type Row struct {
	buf []byte
}

func (r *Row) Len() int {
	return 8 * len(r.buf)
}

func (r *Row) Bit(n int) int {
	bit := r.Len() - n - 1
	index := len(r.buf) - 1 - bit/8
	offset := bit % 8
	return int(0x01 & (r.buf[index] >> uint(offset)))
}

type Disk struct {
	rows  []*Row
	graph *graph.Graph
}

func NewDisk() *Disk {
	return &Disk{
		graph: graph.New(),
	}
}

func (d *Disk) Groups() int {
	count := 0
	visited := make(map[string]bool)
	for id, vertex := range d.graph.Vertices {
		if found := visited[id]; !found {
			count++
			nodes := vertex.Connected()
			for id, _ := range nodes {
				visited[id] = true
			}
		}
	}
	return count
}

func (d *Disk) Count() int {
	count := 0
	for _, row := range d.rows {
		for i := 0; i < row.Len(); i++ {
			count += int(row.Bit(i))
		}
	}
	return count
}

func (d *Disk) Append(buf []byte) {
	row := &Row{buf}
	d.rows = append(d.rows, row)
	y := len(d.rows) - 1
	for x := 0; x < row.Len(); x++ {
		id := y*row.Len() + x
		if row.Bit(x) > 0 {
			vertex := d.graph.FindOrCreateVertex(fmt.Sprintf("%d", id))
			if y > 0 {
				prevId := (y-1)*row.Len() + x
				prevRow := d.rows[y-1]
				if prevRow.Bit(x) > 0 {
					vertex.Connect(d.graph.FindOrCreateVertex(fmt.Sprintf("%d", prevId)))
				}
			}
			if x > 0 {
				if row.Bit(x-1) > 0 {
					vertex.Connect(d.graph.FindOrCreateVertex(fmt.Sprintf("%d", id-1)))
				}
			}
		}
	}
}

func main() {
	//input := "flqrgnkx"
	//input := "a0c2017..."
	input := "jxqlasbh"
	disk := NewDisk()
	for i := 0; i < 128; i++ {
		disk.Append([]byte(hash.ComputeString(fmt.Sprintf("%s-%d", input, i)).Hash()))
	}
	fmt.Printf("%d\n", disk.Count())

	fmt.Printf("Groups: %d\n", disk.Groups())
}
<commit_msg>Removed debugging<commit_after>package main

import (
	"fmt"

	"github.com/abates/AdventOfCode/2017/graph"
	"github.com/abates/AdventOfCode/2017/hash"
)

type Row struct {
	buf []byte
}

func (r *Row) Len() int {
	return 8 * len(r.buf)
}

func (r *Row) Bit(n int) int {
	bit := r.Len() - n - 1
	index := len(r.buf) - 1 - bit/8
	offset := bit % 8
	return int(0x01 & (r.buf[index] >> uint(offset)))
}

type Disk struct {
	rows  []*Row
	graph *graph.Graph
}

func NewDisk() *Disk {
	return &Disk{
		graph: graph.New(),
	}
}

func (d *Disk) Groups() int {
	count := 0
	visited := make(map[string]bool)
	for id, vertex := range d.graph.Vertices {
		if found := visited[id]; !found {
			count++
			nodes := vertex.Connected()
			for id, _ := range nodes {
				visited[id] = true
			}
		}
	}
	return count
}

func (d *Disk) Count() int {
	count := 0
	for _, row := range d.rows {
		for i := 0; i < row.Len(); i++ {
			count += int(row.Bit(i))
		}
	}
	return count
}

func (d *Disk) Append(buf []byte) {
	row := &Row{buf}
	d.rows = append(d.rows, row)
	y := len(d.rows) - 1
	for x := 0; x < row.Len(); x++ {
		id := y*row.Len() + x
		if row.Bit(x) > 0 {
			vertex := d.graph.FindOrCreateVertex(fmt.Sprintf("%d", id))
			if y > 0 {
				prevId := (y-1)*row.Len() + x
				prevRow := d.rows[y-1]
				if prevRow.Bit(x) > 0 {
					vertex.Connect(d.graph.FindOrCreateVertex(fmt.Sprintf("%d", prevId)))
				}
			}
			if x > 0 {
				if row.Bit(x-1) > 0 {
					vertex.Connect(d.graph.FindOrCreateVertex(fmt.Sprintf("%d", id-1)))
				}
			}
		}
	}
}

func main() {
	input := "jxqlasbh"
	disk := NewDisk()
	for i := 0; i < 128; i++ {
		disk.Append([]byte(hash.ComputeString(fmt.Sprintf("%s-%d", input, i)).Hash()))
	}
	fmt.Printf("Part 1: %d\n", disk.Count())

	fmt.Printf("Part 2: %d\n", disk.Groups())
}
<|endoftext|>


<commit_before>package redix

import (
	"github.com/davyxu/cellnet"
	"github.com/davyxu/cellnet/peer"
	"github.com/mediocregopher/radix.v2/pool"
	"github.com/mediocregopher/radix.v2/redis"
	"sync"
	"time"
)

type redisConnector struct {
	peer.CorePeerProperty
	peer.CoreContextSet
	peer.CoreRedisParameter

	pool      *pool.Pool
	poolGuard sync.RWMutex
}

func (self *redisConnector) IsReady() bool {
	return self.Pool() != nil
}

func (self *redisConnector) TypeName() string {
	return "redix.Connector"
}

func (self *redisConnector) Pool() *pool.Pool {
	self.poolGuard.RLock()
	defer self.poolGuard.RUnlock()

	return self.pool
}

func (self *redisConnector) Raw() interface{} {

	return self.Pool()
}

func (self *redisConnector) Operate(callback func(client interface{}) interface{}) interface{} {

	pool := self.Pool()
	c, err := pool.Get()
	if err != nil {
		log.Errorf("get client failed, %s", err)
		return err
	}

	defer func() {
		pool.Put(c)
	}()

	return callback(c)
}

func (self *redisConnector) tryConnect() {

	for {
		pool, err := pool.NewCustom("tcp", self.Address(), self.PoolConnCount, func(network, addr string) (*redis.Client, error) {

			client, err := redis.DialTimeout(network, addr, time.Second*5)
			if err != nil {
				log.Errorf("redis.Dial %s", err.Error())
				return nil, err
			}

			if len(self.Password) > 0 {
				if err = client.Cmd("AUTH", self.Password).Err; err != nil {
					log.Errorf("redis.Auth %s %s", self.Password, err.Error())
					client.Close()
					return nil, err
				}
			}
			if self.DBIndex > 0 {
				if err = client.Cmd("SELECT", self.DBIndex).Err; err != nil {
					log.Errorf("redis.SELECT %d %s", self.DBIndex, err.Error())
					client.Close()
					return nil, err
				}
			}

			log.Infof("Create redis pool connection: %s name: %s index: %d", addr, self.Name(), self.DBIndex)

			return client, nil
		})

		if err != nil {
			log.Errorln("Redis connect failed:", err)
			time.Sleep(3 * time.Second)
			continue
		}

		self.poolGuard.Lock()
		self.pool = pool
		self.poolGuard.Unlock()

		break
	}

}

func (self *redisConnector) Start() cellnet.Peer {

	go self.tryConnect()

	return self
}

func (self *redisConnector) Stop() {

}

func init() {

	peer.RegisterPeerCreator(func() cellnet.Peer {

		self := &redisConnector{}
		self.CoreRedisParameter.Init()

		return self
	})
}
<commit_msg>redis连接断开重试时按指数扩展时间<commit_after>package redix

import (
	"github.com/davyxu/cellnet"
	"github.com/davyxu/cellnet/peer"
	"github.com/mediocregopher/radix.v2/pool"
	"github.com/mediocregopher/radix.v2/redis"
	"sync"
	"time"
)

type redisConnector struct {
	peer.CorePeerProperty
	peer.CoreContextSet
	peer.CoreRedisParameter

	pool      *pool.Pool
	poolGuard sync.RWMutex
}

func (self *redisConnector) IsReady() bool {
	return self.Pool() != nil
}

func (self *redisConnector) TypeName() string {
	return "redix.Connector"
}

func (self *redisConnector) Pool() *pool.Pool {
	self.poolGuard.RLock()
	defer self.poolGuard.RUnlock()

	return self.pool
}

func (self *redisConnector) Raw() interface{} {

	return self.Pool()
}

func (self *redisConnector) Operate(callback func(client interface{}) interface{}) interface{} {

	pool := self.Pool()
	c, err := pool.Get()
	if err != nil {
		log.Errorf("get client failed, %s", err)
		return err
	}

	defer func() {
		pool.Put(c)
	}()

	return callback(c)
}

func (self *redisConnector) tryConnect() {

	var faildWaitSec = 2

	for {
		pool, err := pool.NewCustom("tcp", self.Address(), self.PoolConnCount, func(network, addr string) (*redis.Client, error) {

			client, err := redis.DialTimeout(network, addr, time.Second*5)
			if err != nil {
				log.Errorf("redis.Dial %s", err.Error())
				return nil, err
			}

			if len(self.Password) > 0 {
				if err = client.Cmd("AUTH", self.Password).Err; err != nil {
					log.Errorf("redis.Auth %s %s", self.Password, err.Error())
					client.Close()
					return nil, err
				}
			}
			if self.DBIndex > 0 {
				if err = client.Cmd("SELECT", self.DBIndex).Err; err != nil {
					log.Errorf("redis.SELECT %d %s", self.DBIndex, err.Error())
					client.Close()
					return nil, err
				}
			}

			log.Infof("Create redis pool connection: %s name: %s index: %d", addr, self.Name(), self.DBIndex)

			return client, nil
		})

		if err != nil {
			log.Errorf("Redis connect failed: %s, wait %d secods retry...", err, faildWaitSec)

			time.Sleep(time.Duration(faildWaitSec) * time.Second)

			if faildWaitSec < 10 {
				faildWaitSec += 2
			}

			continue
		}

		self.poolGuard.Lock()
		self.pool = pool
		self.poolGuard.Unlock()

		break
	}

}

func (self *redisConnector) Start() cellnet.Peer {

	go self.tryConnect()

	return self
}

func (self *redisConnector) Stop() {

}

func init() {

	peer.RegisterPeerCreator(func() cellnet.Peer {

		self := &redisConnector{}
		self.CoreRedisParameter.Init()

		return self
	})
}
<|endoftext|>


<commit_before>package mci

type MattermostConfig struct {
	ServiceSettings   struct {
				  ListenAddress                     string `json:"ListenAddress"`
				  MaximumLoginAttempts              int `json:"MaximumLoginAttempts"`
				  SegmentDeveloperKey               string `json:"SegmentDeveloperKey"`
				  GoogleDeveloperKey                string `json:"GoogleDeveloperKey"`
				  EnableOAuthServiceProvider        bool `json:"EnableOAuthServiceProvider"`
				  EnableIncomingWebhooks            bool `json:"EnableIncomingWebhooks"`
				  EnableOutgoingWebhooks            bool `json:"EnableOutgoingWebhooks"`
				  EnableCommands                    bool `json:"EnableCommands"`
				  EnableOnlyAdminIntegrations       bool `json:"EnableOnlyAdminIntegrations"`
				  EnablePostUsernameOverride        bool `json:"EnablePostUsernameOverride"`
				  EnablePostIconOverride            bool `json:"EnablePostIconOverride"`
				  EnableTesting                     bool `json:"EnableTesting"`
				  EnableDeveloper                   bool `json:"EnableDeveloper"`
				  EnableSecurityFixAlert            bool `json:"EnableSecurityFixAlert"`
				  EnableInsecureOutgoingConnections bool `json:"EnableInsecureOutgoingConnections"`
				  AllowCorsFrom                     string `json:"AllowCorsFrom"`
				  SessionLengthWebInDays            int `json:"SessionLengthWebInDays"`
				  SessionLengthMobileInDays         int `json:"SessionLengthMobileInDays"`
				  SessionLengthSSOInDays            int `json:"SessionLengthSSOInDays"`
				  SessionCacheInMinutes             int `json:"SessionCacheInMinutes"`
			  } `json:"ServiceSettings"`
	TeamSettings      struct {
				  SiteName                  string `json:"SiteName"`
				  MaxUsersPerTeam           int `json:"MaxUsersPerTeam"`
				  EnableTeamCreation        bool `json:"EnableTeamCreation"`
				  EnableUserCreation        bool `json:"EnableUserCreation"`
				  RestrictCreationToDomains string `json:"RestrictCreationToDomains"`
				  RestrictTeamNames         bool `json:"RestrictTeamNames"`
				  EnableTeamListing         bool `json:"EnableTeamListing"`
			  } `json:"TeamSettings"`
	SqlSettings       struct {
				  DriverName         string `json:"DriverName"`
				  DataSource         string `json:"DataSource"`
				  DataSourceReplicas []interface{} `json:"DataSourceReplicas"`
				  MaxIdleConns       int `json:"MaxIdleConns"`
				  MaxOpenConns       int `json:"MaxOpenConns"`
				  Trace              bool `json:"Trace"`
				  AtRestEncryptKey   string `json:"AtRestEncryptKey"`
			  } `json:"SqlSettings"`
	LogSettings       struct {
				  EnableConsole bool `json:"EnableConsole"`
				  ConsoleLevel  string `json:"ConsoleLevel"`
				  EnableFile    bool `json:"EnableFile"`
				  FileLevel     string `json:"FileLevel"`
				  FileFormat    string `json:"FileFormat"`
				  FileLocation  string `json:"FileLocation"`
			  } `json:"LogSettings"`
	FileSettings      struct {
				  DriverName                 string `json:"DriverName"`
				  Directory                  string `json:"Directory"`
				  EnablePublicLink           bool `json:"EnablePublicLink"`
				  PublicLinkSalt             string `json:"PublicLinkSalt"`
				  ThumbnailWidth             int `json:"ThumbnailWidth"`
				  ThumbnailHeight            int `json:"ThumbnailHeight"`
				  PreviewWidth               int `json:"PreviewWidth"`
				  PreviewHeight              int `json:"PreviewHeight"`
				  ProfileWidth               int `json:"ProfileWidth"`
				  ProfileHeight              int `json:"ProfileHeight"`
				  InitialFont                string `json:"InitialFont"`
				  AmazonS3AccessKeyID        string `json:"AmazonS3AccessKeyId"`
				  AmazonS3SecretAccessKey    string `json:"AmazonS3SecretAccessKey"`
				  AmazonS3Bucket             string `json:"AmazonS3Bucket"`
				  AmazonS3Region             string `json:"AmazonS3Region"`
				  AmazonS3Endpoint           string `json:"AmazonS3Endpoint"`
				  AmazonS3BucketEndpoint     string `json:"AmazonS3BucketEndpoint"`
				  AmazonS3LocationConstraint bool `json:"AmazonS3LocationConstraint"`
				  AmazonS3LowercaseBucket    bool `json:"AmazonS3LowercaseBucket"`
			  } `json:"FileSettings"`
	EmailSettings     struct {
				  EnableSignUpWithEmail    bool `json:"EnableSignUpWithEmail"`
				  EnableSignInWithEmail    bool `json:"EnableSignInWithEmail"`
				  EnableSignInWithUsername bool `json:"EnableSignInWithUsername"`
				  SendEmailNotifications   bool `json:"SendEmailNotifications"`
				  RequireEmailVerification bool `json:"RequireEmailVerification"`
				  FeedbackName             string `json:"FeedbackName"`
				  FeedbackEmail            string `json:"FeedbackEmail"`
				  SMTPUsername             string `json:"SMTPUsername"`
				  SMTPPassword             string `json:"SMTPPassword"`
				  SMTPServer               string `json:"SMTPServer"`
				  SMTPPort                 string `json:"SMTPPort"`
				  ConnectionSecurity       string `json:"ConnectionSecurity"`
				  InviteSalt               string `json:"InviteSalt"`
				  PasswordResetSalt        string `json:"PasswordResetSalt"`
				  SendPushNotifications    bool `json:"SendPushNotifications"`
				  PushNotificationServer   string `json:"PushNotificationServer"`
			  } `json:"EmailSettings"`
	RateLimitSettings struct {
				  EnableRateLimiter bool `json:"EnableRateLimiter"`
				  PerSec            int `json:"PerSec"`
				  MemoryStoreSize   int `json:"MemoryStoreSize"`
				  VaryByRemoteAddr  bool `json:"VaryByRemoteAddr"`
				  VaryByHeader      string `json:"VaryByHeader"`
			  } `json:"RateLimitSettings"`
	PrivacySettings   struct {
				  ShowEmailAddress bool `json:"ShowEmailAddress"`
				  ShowFullName     bool `json:"ShowFullName"`
			  } `json:"PrivacySettings"`
	SupportSettings   struct {
				  TermsOfServiceLink string `json:"TermsOfServiceLink"`
				  PrivacyPolicyLink  string `json:"PrivacyPolicyLink"`
				  AboutLink          string `json:"AboutLink"`
				  HelpLink           string `json:"HelpLink"`
				  ReportAProblemLink string `json:"ReportAProblemLink"`
				  SupportEmail       string `json:"SupportEmail"`
			  } `json:"SupportSettings"`
	GitLabSettings    struct {
				  Enable          bool `json:"Enable"`
				  Secret          string `json:"Secret"`
				  ID              string `json:"Id"`
				  Scope           string `json:"Scope"`
				  AuthEndpoint    string `json:"AuthEndpoint"`
				  TokenEndpoint   string `json:"TokenEndpoint"`
				  UserAPIEndpoint string `json:"UserApiEndpoint"`
			  } `json:"GitLabSettings"`
}<commit_msg>Avoid clobbering any explicit value set for Websocket[Secure]Port<commit_after>package mci

type MattermostConfig struct {
	ServiceSettings   struct {
				  ListenAddress                     string `json:"ListenAddress"`
				  MaximumLoginAttempts              int `json:"MaximumLoginAttempts"`
				  SegmentDeveloperKey               string `json:"SegmentDeveloperKey"`
				  GoogleDeveloperKey                string `json:"GoogleDeveloperKey"`
				  EnableOAuthServiceProvider        bool `json:"EnableOAuthServiceProvider"`
				  EnableIncomingWebhooks            bool `json:"EnableIncomingWebhooks"`
				  EnableOutgoingWebhooks            bool `json:"EnableOutgoingWebhooks"`
				  EnableCommands                    bool `json:"EnableCommands"`
				  EnableOnlyAdminIntegrations       bool `json:"EnableOnlyAdminIntegrations"`
				  EnablePostUsernameOverride        bool `json:"EnablePostUsernameOverride"`
				  EnablePostIconOverride            bool `json:"EnablePostIconOverride"`
				  EnableTesting                     bool `json:"EnableTesting"`
				  EnableDeveloper                   bool `json:"EnableDeveloper"`
				  EnableSecurityFixAlert            bool `json:"EnableSecurityFixAlert"`
				  EnableInsecureOutgoingConnections bool `json:"EnableInsecureOutgoingConnections"`
				  AllowCorsFrom                     string `json:"AllowCorsFrom"`
				  SessionLengthWebInDays            int `json:"SessionLengthWebInDays"`
				  SessionLengthMobileInDays         int `json:"SessionLengthMobileInDays"`
				  SessionLengthSSOInDays            int `json:"SessionLengthSSOInDays"`
				  SessionCacheInMinutes             int `json:"SessionCacheInMinutes"`
				  WebsocketSecurePort				int `json:"WebsocketSecurePort"`
				  WebsocketPort						int `json:"WebsocketPort"`
			  } `json:"ServiceSettings"`
	TeamSettings      struct {
				  SiteName                  string `json:"SiteName"`
				  MaxUsersPerTeam           int `json:"MaxUsersPerTeam"`
				  EnableTeamCreation        bool `json:"EnableTeamCreation"`
				  EnableUserCreation        bool `json:"EnableUserCreation"`
				  RestrictCreationToDomains string `json:"RestrictCreationToDomains"`
				  RestrictTeamNames         bool `json:"RestrictTeamNames"`
				  EnableTeamListing         bool `json:"EnableTeamListing"`
			  } `json:"TeamSettings"`
	SqlSettings       struct {
				  DriverName         string `json:"DriverName"`
				  DataSource         string `json:"DataSource"`
				  DataSourceReplicas []interface{} `json:"DataSourceReplicas"`
				  MaxIdleConns       int `json:"MaxIdleConns"`
				  MaxOpenConns       int `json:"MaxOpenConns"`
				  Trace              bool `json:"Trace"`
				  AtRestEncryptKey   string `json:"AtRestEncryptKey"`
			  } `json:"SqlSettings"`
	LogSettings       struct {
				  EnableConsole bool `json:"EnableConsole"`
				  ConsoleLevel  string `json:"ConsoleLevel"`
				  EnableFile    bool `json:"EnableFile"`
				  FileLevel     string `json:"FileLevel"`
				  FileFormat    string `json:"FileFormat"`
				  FileLocation  string `json:"FileLocation"`
			  } `json:"LogSettings"`
	FileSettings      struct {
				  DriverName                 string `json:"DriverName"`
				  Directory                  string `json:"Directory"`
				  EnablePublicLink           bool `json:"EnablePublicLink"`
				  PublicLinkSalt             string `json:"PublicLinkSalt"`
				  ThumbnailWidth             int `json:"ThumbnailWidth"`
				  ThumbnailHeight            int `json:"ThumbnailHeight"`
				  PreviewWidth               int `json:"PreviewWidth"`
				  PreviewHeight              int `json:"PreviewHeight"`
				  ProfileWidth               int `json:"ProfileWidth"`
				  ProfileHeight              int `json:"ProfileHeight"`
				  InitialFont                string `json:"InitialFont"`
				  AmazonS3AccessKeyID        string `json:"AmazonS3AccessKeyId"`
				  AmazonS3SecretAccessKey    string `json:"AmazonS3SecretAccessKey"`
				  AmazonS3Bucket             string `json:"AmazonS3Bucket"`
				  AmazonS3Region             string `json:"AmazonS3Region"`
				  AmazonS3Endpoint           string `json:"AmazonS3Endpoint"`
				  AmazonS3BucketEndpoint     string `json:"AmazonS3BucketEndpoint"`
				  AmazonS3LocationConstraint bool `json:"AmazonS3LocationConstraint"`
				  AmazonS3LowercaseBucket    bool `json:"AmazonS3LowercaseBucket"`
			  } `json:"FileSettings"`
	EmailSettings     struct {
				  EnableSignUpWithEmail    bool `json:"EnableSignUpWithEmail"`
				  EnableSignInWithEmail    bool `json:"EnableSignInWithEmail"`
				  EnableSignInWithUsername bool `json:"EnableSignInWithUsername"`
				  SendEmailNotifications   bool `json:"SendEmailNotifications"`
				  RequireEmailVerification bool `json:"RequireEmailVerification"`
				  FeedbackName             string `json:"FeedbackName"`
				  FeedbackEmail            string `json:"FeedbackEmail"`
				  SMTPUsername             string `json:"SMTPUsername"`
				  SMTPPassword             string `json:"SMTPPassword"`
				  SMTPServer               string `json:"SMTPServer"`
				  SMTPPort                 string `json:"SMTPPort"`
				  ConnectionSecurity       string `json:"ConnectionSecurity"`
				  InviteSalt               string `json:"InviteSalt"`
				  PasswordResetSalt        string `json:"PasswordResetSalt"`
				  SendPushNotifications    bool `json:"SendPushNotifications"`
				  PushNotificationServer   string `json:"PushNotificationServer"`
			  } `json:"EmailSettings"`
	RateLimitSettings struct {
				  EnableRateLimiter bool `json:"EnableRateLimiter"`
				  PerSec            int `json:"PerSec"`
				  MemoryStoreSize   int `json:"MemoryStoreSize"`
				  VaryByRemoteAddr  bool `json:"VaryByRemoteAddr"`
				  VaryByHeader      string `json:"VaryByHeader"`
			  } `json:"RateLimitSettings"`
	PrivacySettings   struct {
				  ShowEmailAddress bool `json:"ShowEmailAddress"`
				  ShowFullName     bool `json:"ShowFullName"`
			  } `json:"PrivacySettings"`
	SupportSettings   struct {
				  TermsOfServiceLink string `json:"TermsOfServiceLink"`
				  PrivacyPolicyLink  string `json:"PrivacyPolicyLink"`
				  AboutLink          string `json:"AboutLink"`
				  HelpLink           string `json:"HelpLink"`
				  ReportAProblemLink string `json:"ReportAProblemLink"`
				  SupportEmail       string `json:"SupportEmail"`
			  } `json:"SupportSettings"`
	GitLabSettings    struct {
				  Enable          bool `json:"Enable"`
				  Secret          string `json:"Secret"`
				  ID              string `json:"Id"`
				  Scope           string `json:"Scope"`
				  AuthEndpoint    string `json:"AuthEndpoint"`
				  TokenEndpoint   string `json:"TokenEndpoint"`
				  UserAPIEndpoint string `json:"UserApiEndpoint"`
			  } `json:"GitLabSettings"`
}<|endoftext|>


<commit_before>// +build linux

package test

import (
	"testing"
)

func TestIntegration__date(t *testing.T) {
	cmd := Command("date", "--date='@0'").Trim()
	cmd.EqualT(t, "Thu Jan  1 00:00:00 UTC 1970")
}
<commit_msg>test: try and fix linux date test<commit_after>// +build linux

package test

import (
	"testing"
)

func TestIntegration__date(t *testing.T) {
	cmd := Command("date", "--date", "'@0'").Trim()
	cmd.EqualT(t, "Thu Jan  1 00:00:00 UTC 1970")
}
<|endoftext|>


<commit_before>// a hashmap used to store information and history for each boat
package storage

import (
	"encoding/json"
	"errors"
	"reflect"
	"strings"
	"sync"
	"time"

	"github.com/tormol/AIS/geo"
)

const HISTORY_MAX = 100 // The maximum number of points allowed to be stored in the history
const HISTORY_MIN = 60  // The minimum number of points stored in the history

// "A point in history"
type checkpoint struct {
	lat  float64
	long float64
	t    time.Time
}

//TODO decide what information to store for each boat
type info struct {
	Name        string       `json:"name, omitempty"` //Omitted by json if empty
	Destination string       `json:"destination, omitempty"`
	Heading     uint16       `json:"heading, omitempty"` // between 0 and 359 degrees	- can be used to rotate the leaflet marker
	Callsign    string       `json:"callsign, omitempty"`
	Length      uint16       `json:"length, omitempty"`
	history     []checkpoint // first checkpoint is the oldest				(unexported fields is ignored by json)
	hLength     uint16       // current number of checkpoints in the history(ignored by json)
	mu          *sync.Mutex  // Mutex lock for currency						(ignored by json)
}

/* properties returns the ships geojson "properties" with the specified fields.
Example: properties("name", "heading", "length") 	returns: {"name": "SHIPNAME", "heading":123, "length":321}
Empty fields are omitted
*/
func (i *info) properties(fields ...string) []byte {
	tmpSet := make(map[string]bool, len(fields)) //A map of the specified fields
	for _, p := range fields {
		tmpSet[p] = true
	}
	i.mu.Lock()
	defer i.mu.Unlock()
	t := reflect.TypeOf(*i)
	v := reflect.ValueOf(*i)
	prop := make(map[string]interface{}, t.NumField()) //The set containing the specified fields and its values
	for k := 0; k < t.NumField(); k++ {
		field := t.Field(k) //Gets the k'th field of t
		jsonKey := field.Tag.Get("json")
		//remove the omitempty part of the json tag
		//	- otherwise the geojson objects would be named like: "name, omitempty":"THESHIPNAME", "length, omitempty": 123, ...
		if len(jsonKey) > 11 && jsonKey[len(jsonKey)-9:] == "omitempty" {
			jsonKey = jsonKey[:len(jsonKey)-10] // if "name,omitempty"
			if jsonKey[len(jsonKey)-1] == ',' {
				jsonKey = jsonKey[:len(jsonKey)-1] // if "name, omitempty"
			}
		}
		if tmpSet[jsonKey] {
			value := v.Field(k).Interface()
			//manual omitempty
			if value == "" || value == nil || value == 0 {
				continue
			}
			prop[jsonKey] = value
		}
	}
	//Make the geojson object
	b, err := json.MarshalIndent(prop, "", "\t")
	if err != nil {
		panic(err.Error()) //FIXME
	}
	return b //Return the geoJSON string
}

// Contains an info-object for each ship
type ShipInfo struct {
	allInfo map[uint32]*info
	rw      *sync.RWMutex
}

// NewShipInfo creates and returns a pointer to a new ShipInfo object.
func NewShipInfo() *ShipInfo {
	return &ShipInfo{make(map[uint32]*info), &sync.RWMutex{}}
}

// IsKnown returns true if the given mmsi is stored in the structure.
func (si *ShipInfo) IsKnown(mmsi uint32) bool {
	si.rw.RLock()
	_, ok := si.allInfo[mmsi]
	si.rw.RUnlock()
	return ok
}

// Adds a new checkpoint to the ship (this is called for every(?) AIS message)
func (si *ShipInfo) AddCheckpoint(mmsi uint32, nlat, nlong float64, nt time.Time, heading uint16) error {
	if !geo.LegalCoord(nlat, nlong) || nt.IsZero() {
		return errors.New("Illegal checkpoint")
	}
	si.rw.RLock()
	i, ok := si.allInfo[mmsi] //Get the info-object for this ship
	si.rw.RUnlock()
	if !ok { // A new ship
		i = &info{history: make([]checkpoint, HISTORY_MAX), hLength: 0, mu: &sync.Mutex{}}
		si.rw.Lock()
		si.allInfo[mmsi] = i
		si.rw.Unlock()
	}
	// Update the ship's info-object
	i.mu.Lock()
	i.history[i.hLength] = checkpoint{lat: nlat, long: nlong, t: nt}
	i.hLength++
	if i.hLength >= HISTORY_MAX { //purge the slice
		newH := make([]checkpoint, HISTORY_MAX)
		copy(newH, i.history[:HISTORY_MIN])
		i.history = newH
		i.hLength = HISTORY_MIN
	}
	if heading >= 0 && heading <= 359 {
		i.Heading = heading
	}
	i.Heading = heading
	i.mu.Unlock()
	return nil
}

// UpdateSVD updates the Static Voyage Data for the ship.
func (si *ShipInfo) UpdateSVD(mmsi uint32, callsign, destination, name string, toBow, toStern uint16) error {
	if mmsi <= 0 {
		return errors.New("Illegal MMSI")
	}
	si.rw.RLock()
	i, ok := si.allInfo[mmsi]
	si.rw.RUnlock()
	if !ok { // A new ship
		return nil // Only care about ships that have a position... for now...
	}
	i.mu.Lock()
	if callsign != "" {
		i.Callsign = callsign
	}
	if destination != "" {
		i.Destination = destination
	}
	if name != "" {
		i.Name = name
	}
	if toBow >= 0 {
		i.Length = toBow + toStern
	}
	i.mu.Unlock()
	return nil
}

// GetDuration returns the duration since last message recieved from the ship.
func (si *ShipInfo) GetDuration(mmsi uint32) (time.Duration, error) {
	si.rw.RLock()
	i, ok := si.allInfo[mmsi]
	si.rw.RUnlock()
	if ok {
		i.mu.Lock()
		defer i.mu.Unlock()
		if i.hLength > 0 {
			return time.Since(i.history[i.hLength-1].t), nil
		}
	}
	return 0, errors.New("Can't find log of that ship")
}

// GetCoords returns the last known position of ship.
func (si *ShipInfo) GetCoords(mmsi uint32) (lat, long float64) {
	si.rw.RLock()
	i, ok := si.allInfo[mmsi]
	si.rw.RUnlock()
	if ok {
		i.mu.Lock()
		defer i.mu.Unlock()
		if i.hLength > 0 {
			lat = i.history[i.hLength-1].lat
			long = i.history[i.hLength-1].long
		}
	}
	return
}

/*GeoJSON structures*/
type feature struct {
	Type       string           `json:"type"`
	ID         uint32           `json:"id"`
	Geometry   geometry         `json:"geometry"`
	Properties *json.RawMessage `json:"properties"`
}

type geometry struct {
	Type        string      `json:"type"`
	Coordinates interface{} `json:"coordinates"`
}

var emptyJsonObject = json.RawMessage(`{}`) //empty struct

// GetAllInfo returns the info about the ship and its tracklog, in a geojson FeatureCollection.
func (si *ShipInfo) GetAllInfo(mmsi uint32) string {
	si.rw.RLock()
	i, ok := si.allInfo[mmsi]
	si.rw.RUnlock()
	if ok {
		properties := json.RawMessage(i.properties("name", "destination", "heading", "callsign", "length"))
		var features string
		i.mu.Lock()
		defer i.mu.Unlock()
		if i.hLength >= 1 { //The geojson point of the current location and all the properties
			point := geometry{
				Type:        "Point",
				Coordinates: []float64{i.history[i.hLength-1].long, i.history[i.hLength-1].lat},
			}
			feature1 := feature{
				Type:       "Feature",
				ID:         mmsi,
				Geometry:   point,
				Properties: &properties,
			}
			b1, err := json.MarshalIndent(feature1, "", "\t")
			if err != nil {
				panic(err.Error()) //FIXME
			}
			features = string(b1)

			//Making the LineString object of the ships tracklog (must contain at least 2 points).
			if i.hLength >= 2 {
				c := make([][]float64, 0, i.hLength)
				k := uint16(0)
				for k < i.hLength {
					c = append(c, []float64{i.history[k].long, i.history[k].lat})
					k++
				}
				ls := geometry{
					Type:        "LineString",
					Coordinates: c,
				}
				feature2 := feature{
					Type:       "Feature",
					ID:         mmsi,
					Geometry:   ls,
					Properties: &emptyJsonObject,
				}
				b2, err := json.MarshalIndent(feature2, "", "\t")
				if err != nil {
					panic(err.Error()) //FIXME
				}
				features = features + ", " + string(b2)
			}
		}
		return `{
			"type": "FeatureCollection",
			"features": [` + features + `]}`
	}
	return ""
}

// Matches produces the geojson FeatureCollection containing all the matching ships and some of their properties.
func Matches(matches *[]Match, si *ShipInfo) string { //TODO move this to archive.go instead?
	features := []string{}
	for _, s := range *matches {
		si.rw.RLock()
		i, ok := si.allInfo[s.MMSI]
		si.rw.RUnlock()
		if ok {
			c := []float64{s.Long, s.Lat}
			point := geometry{
				Type:        "Point",
				Coordinates: c,
			}
			prop := json.RawMessage(i.properties("name", "length", "heading"))
			f := feature{
				Type:       "Feature",
				ID:         s.MMSI,
				Geometry:   point,
				Properties: &prop,
			}
			b, err := json.MarshalIndent(f, "", "\t")
			if err != nil {
				panic(err.Error()) //FIXME
			}
			features = append(features, string(b))
		}
	}
	return `{
		"type": "FeatureCollection",
		"features": [` + strings.Join(features, ", ") + `]}`
}

/*
References:
	https://en.wikipedia.org/wiki/Automatic_identification_system#Broadcast_information
	https://golang.org/pkg/encoding/json/
	http://stackoverflow.com/questions/17306358/golang-removing-fields-from-struct-or-hiding-them-in-json-response#17306470
	http://geojsonlint.com/
	http://stackoverflow.com/questions/7933460/how-do-you-write-multiline-strings-in-go#7933487
*/
<commit_msg>fixed json error<commit_after>// a hashmap used to store information and history for each boat
package storage

import (
	"encoding/json"
	"errors"
	"reflect"
	"strings"
	"sync"
	"time"

	"github.com/tormol/AIS/geo"
)

const HISTORY_MAX = 100 // The maximum number of points allowed to be stored in the history
const HISTORY_MIN = 60  // The minimum number of points stored in the history

// "A point in history"
type checkpoint struct {
	lat  float64
	long float64
	t    time.Time
}

//TODO decide what information to store for each boat
type info struct {
	Name        string       `json:"name,omitempty"` //Omitted by json if empty
	Destination string       `json:"destination,omitempty"`
	Heading     uint16       `json:"heading,omitempty"` // between 0 and 359 degrees	- can be used to rotate the leaflet marker
	Callsign    string       `json:"callsign,omitempty"`
	Length      uint16       `json:"length,omitempty"`
	history     []checkpoint // first checkpoint is the oldest				(unexported fields is ignored by json)
	hLength     uint16       // current number of checkpoints in the history(ignored by json)
	mu          *sync.Mutex  // Mutex lock for currency						(ignored by json)
}

/* properties returns the ships geojson "properties" with the specified fields.
Example: properties("name", "heading", "length") 	returns: {"name": "SHIPNAME", "heading":123, "length":321}
Empty fields are omitted
*/
func (i *info) properties(fields ...string) []byte {
	tmpSet := make(map[string]bool, len(fields)) //A map of the specified fields
	for _, p := range fields {
		tmpSet[p] = true
	}
	i.mu.Lock()
	defer i.mu.Unlock()
	t := reflect.TypeOf(*i)
	v := reflect.ValueOf(*i)
	prop := make(map[string]interface{}, t.NumField()) //The set containing the specified fields and its values
	for k := 0; k < t.NumField(); k++ {
		field := t.Field(k) //Gets the k'th field of t
		jsonKey := field.Tag.Get("json")
		//remove the omitempty part of the json tag
		//	- otherwise the geojson objects would be named like: "name,omitempty":"THESHIPNAME", "length,omitempty": 123, ...
		if len(jsonKey) > 11 && jsonKey[len(jsonKey)-9:] == "omitempty" {
			jsonKey = jsonKey[:len(jsonKey)-10] // if "name,omitempty"
		}
		if tmpSet[jsonKey] {
			value := v.Field(k).Interface()
			//manual omitempty
			if value == "" || value == nil || value == 0 {
				continue
			}
			prop[jsonKey] = value
		}
	}
	//Make the geojson object
	b, err := json.MarshalIndent(prop, "", "\t")
	if err != nil {
		panic(err.Error()) //FIXME
	}
	return b //Return the geoJSON string
}

// Contains an info-object for each ship
type ShipInfo struct {
	allInfo map[uint32]*info
	rw      *sync.RWMutex
}

// NewShipInfo creates and returns a pointer to a new ShipInfo object.
func NewShipInfo() *ShipInfo {
	return &ShipInfo{make(map[uint32]*info), &sync.RWMutex{}}
}

// IsKnown returns true if the given mmsi is stored in the structure.
func (si *ShipInfo) IsKnown(mmsi uint32) bool {
	si.rw.RLock()
	_, ok := si.allInfo[mmsi]
	si.rw.RUnlock()
	return ok
}

// Adds a new checkpoint to the ship (this is called for every(?) AIS message)
func (si *ShipInfo) AddCheckpoint(mmsi uint32, nlat, nlong float64, nt time.Time, heading uint16) error {
	if !geo.LegalCoord(nlat, nlong) || nt.IsZero() {
		return errors.New("Illegal checkpoint")
	}
	si.rw.RLock()
	i, ok := si.allInfo[mmsi] //Get the info-object for this ship
	si.rw.RUnlock()
	if !ok { // A new ship
		i = &info{history: make([]checkpoint, HISTORY_MAX), hLength: 0, mu: &sync.Mutex{}}
		si.rw.Lock()
		si.allInfo[mmsi] = i
		si.rw.Unlock()
	}
	// Update the ship's info-object
	i.mu.Lock()
	i.history[i.hLength] = checkpoint{lat: nlat, long: nlong, t: nt}
	i.hLength++
	if i.hLength >= HISTORY_MAX { //purge the slice
		newH := make([]checkpoint, HISTORY_MAX)
		copy(newH, i.history[:HISTORY_MIN])
		i.history = newH
		i.hLength = HISTORY_MIN
	}
	if heading >= 0 && heading <= 359 {
		i.Heading = heading
	}
	i.Heading = heading
	i.mu.Unlock()
	return nil
}

// UpdateSVD updates the Static Voyage Data for the ship.
func (si *ShipInfo) UpdateSVD(mmsi uint32, callsign, destination, name string, toBow, toStern uint16) error {
	if mmsi <= 0 {
		return errors.New("Illegal MMSI")
	}
	si.rw.RLock()
	i, ok := si.allInfo[mmsi]
	si.rw.RUnlock()
	if !ok { // A new ship
		return nil // Only care about ships that have a position... for now...
	}
	i.mu.Lock()
	if callsign != "" {
		i.Callsign = callsign
	}
	if destination != "" {
		i.Destination = destination
	}
	if name != "" {
		i.Name = name
	}
	if toBow >= 0 {
		i.Length = toBow + toStern
	}
	i.mu.Unlock()
	return nil
}

// GetDuration returns the duration since last message recieved from the ship.
func (si *ShipInfo) GetDuration(mmsi uint32) (time.Duration, error) {
	si.rw.RLock()
	i, ok := si.allInfo[mmsi]
	si.rw.RUnlock()
	if ok {
		i.mu.Lock()
		defer i.mu.Unlock()
		if i.hLength > 0 {
			return time.Since(i.history[i.hLength-1].t), nil
		}
	}
	return 0, errors.New("Can't find log of that ship")
}

// GetCoords returns the last known position of ship.
func (si *ShipInfo) GetCoords(mmsi uint32) (lat, long float64) {
	si.rw.RLock()
	i, ok := si.allInfo[mmsi]
	si.rw.RUnlock()
	if ok {
		i.mu.Lock()
		defer i.mu.Unlock()
		if i.hLength > 0 {
			lat = i.history[i.hLength-1].lat
			long = i.history[i.hLength-1].long
		}
	}
	return
}

/*GeoJSON structures*/
type feature struct {
	Type       string           `json:"type"`
	ID         uint32           `json:"id"`
	Geometry   geometry         `json:"geometry"`
	Properties *json.RawMessage `json:"properties"`
}

type geometry struct {
	Type        string      `json:"type"`
	Coordinates interface{} `json:"coordinates"`
}

var emptyJsonObject = json.RawMessage(`{}`) //empty struct

// GetAllInfo returns the info about the ship and its tracklog, in a geojson FeatureCollection.
func (si *ShipInfo) GetAllInfo(mmsi uint32) string {
	si.rw.RLock()
	i, ok := si.allInfo[mmsi]
	si.rw.RUnlock()
	if ok {
		properties := json.RawMessage(i.properties("name", "destination", "heading", "callsign", "length"))
		var features string
		i.mu.Lock()
		defer i.mu.Unlock()
		if i.hLength >= 1 { //The geojson point of the current location and all the properties
			point := geometry{
				Type:        "Point",
				Coordinates: []float64{i.history[i.hLength-1].long, i.history[i.hLength-1].lat},
			}
			feature1 := feature{
				Type:       "Feature",
				ID:         mmsi,
				Geometry:   point,
				Properties: &properties,
			}
			b1, err := json.MarshalIndent(feature1, "", "\t")
			if err != nil {
				panic(err.Error()) //FIXME
			}
			features = string(b1)

			//Making the LineString object of the ships tracklog (must contain at least 2 points).
			if i.hLength >= 2 {
				c := make([][]float64, 0, i.hLength)
				k := uint16(0)
				for k < i.hLength {
					c = append(c, []float64{i.history[k].long, i.history[k].lat})
					k++
				}
				ls := geometry{
					Type:        "LineString",
					Coordinates: c,
				}
				feature2 := feature{
					Type:       "Feature",
					ID:         mmsi,
					Geometry:   ls,
					Properties: &emptyJsonObject,
				}
				b2, err := json.MarshalIndent(feature2, "", "\t")
				if err != nil {
					panic(err.Error()) //FIXME
				}
				features = features + ", " + string(b2)
			}
		}
		return `{
			"type": "FeatureCollection",
			"features": [` + features + `]}`
	}
	return ""
}

// Matches produces the geojson FeatureCollection containing all the matching ships and some of their properties.
func Matches(matches *[]Match, si *ShipInfo) string { //TODO move this to archive.go instead?
	features := []string{}
	for _, s := range *matches {
		si.rw.RLock()
		i, ok := si.allInfo[s.MMSI]
		si.rw.RUnlock()
		if ok {
			c := []float64{s.Long, s.Lat}
			point := geometry{
				Type:        "Point",
				Coordinates: c,
			}
			prop := json.RawMessage(i.properties("name", "length", "heading"))
			f := feature{
				Type:       "Feature",
				ID:         s.MMSI,
				Geometry:   point,
				Properties: &prop,
			}
			b, err := json.MarshalIndent(f, "", "\t")
			if err != nil {
				panic(err.Error()) //FIXME
			}
			features = append(features, string(b))
		}
	}
	return `{
		"type": "FeatureCollection",
		"features": [` + strings.Join(features, ", ") + `]}`
}

/*
References:
	https://en.wikipedia.org/wiki/Automatic_identification_system#Broadcast_information
	https://golang.org/pkg/encoding/json/
	http://stackoverflow.com/questions/17306358/golang-removing-fields-from-struct-or-hiding-them-in-json-response#17306470
	http://geojsonlint.com/
	http://stackoverflow.com/questions/7933460/how-do-you-write-multiline-strings-in-go#7933487
*/
<|endoftext|>


<commit_before>package store_test

import (
	"github.com/alexandre-normand/slackscot/v2/store"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"os"
	"testing"
)

func TestNewStoreWithInvalidPath(t *testing.T) {
	tmpfile, err := ioutil.TempFile("", "example")
	assert.Nil(t, err)

	defer os.Remove(tmpfile.Name()) // clean up

	_, err = store.New("test", tmpfile.Name())
	if assert.Error(t, err) {
		assert.Contains(t, err.Error(), "failed to open")
	}
}

func TestNewStore(t *testing.T) {
	dir, err := ioutil.TempDir("", "tmpTest")
	assert.Nil(t, err)

	defer os.RemoveAll(dir)

	ts, err := store.New("test", dir)
	assert.Nil(t, err)

	assert.Equal(t, "test", ts.Name)
}

func TestPutGetScan(t *testing.T) {
	dir, err := ioutil.TempDir("", "tmpTest")
	assert.Nil(t, err)
	defer os.RemoveAll(dir)

	ts, err := store.New("test", dir)
	assert.Nil(t, err)
	assert.Equal(t, "test", ts.Name)

	err = ts.Put("testKey", "value1")
	assert.Nil(t, err)

	v, err := ts.Get("testKey")
	assert.Nil(t, err)

	assert.Equal(t, "value1", v)

	m, err := ts.Scan()
	assert.Nil(t, err)

	assert.Equal(t, map[string]string{"testKey": "value1"}, m)
}
<commit_msg>Add Tests of store package with Close Functionality<commit_after>package store_test

import (
	"github.com/alexandre-normand/slackscot/v2/store"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"os"
	"testing"
)

func TestNewStoreWithInvalidPath(t *testing.T) {
	tmpfile, err := ioutil.TempFile("", "example")
	assert.Nil(t, err)

	defer os.Remove(tmpfile.Name()) // clean up

	_, err = store.New("test", tmpfile.Name())
	if assert.Error(t, err) {
		assert.Contains(t, err.Error(), "failed to open")
	}
}

func TestNewStore(t *testing.T) {
	dir, err := ioutil.TempDir("", "tmpTest")
	assert.Nil(t, err)

	defer os.RemoveAll(dir)

	ts, err := store.New("test", dir)
	assert.Nil(t, err)
	defer ts.Close()

	assert.Equal(t, "test", ts.Name)
}

func TestGetAfterCloseShouldResultInError(t *testing.T) {
	dir, err := ioutil.TempDir("", "tmpTest")
	assert.Nil(t, err)

	defer os.RemoveAll(dir)

	ts, err := store.New("test", dir)
	assert.Nil(t, err)

	ts.Close()
	_, err = ts.Get("testKey")

	assert.NotNil(t, err)
}

func TestPutGetScan(t *testing.T) {
	dir, err := ioutil.TempDir("", "tmpTest")
	assert.Nil(t, err)
	defer os.RemoveAll(dir)

	ts, err := store.New("test", dir)
	assert.Nil(t, err)
	defer ts.Close()

	assert.Equal(t, "test", ts.Name)

	err = ts.Put("testKey", "value1")
	assert.Nil(t, err)

	v, err := ts.Get("testKey")
	assert.Nil(t, err)

	assert.Equal(t, "value1", v)

	m, err := ts.Scan()
	assert.Nil(t, err)

	assert.Equal(t, map[string]string{"testKey": "value1"}, m)
}
<|endoftext|>


<commit_before>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ping

import (
	"encoding/binary"
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/google/cloudprober/probes/probeutils"
	"golang.org/x/net/ipv4"
	"golang.org/x/net/ipv6"
)

const timeBytesSize = 8

func bytesToTime(b []byte) int64 {
	var unixNano int64
	for i := uint8(0); i < timeBytesSize; i++ {
		unixNano += int64(b[i]) << ((timeBytesSize - i - 1) * timeBytesSize)
	}
	return unixNano
}

func ipToKey(ip net.IP) (key [16]byte) {
	copy(key[:], ip.To16())
	return
}

func validEchoReply(ipVer int, typeByte byte) bool {
	switch ipVer {
	case 6:
		return ipv6.ICMPType(typeByte) == ipv6.ICMPTypeEchoReply
	default:
		return ipv4.ICMPType(typeByte) == ipv4.ICMPTypeEchoReply
	}
}

func prepareRequestPayload(payload []byte, unixNano int64) {
	var timeBytes [timeBytesSize]byte
	for i := uint8(0); i < timeBytesSize; i++ {
		// To get timeBytes:
		// 0th byte - shift bits by 56 (7*8) bits, AND with 0xff to get the last 8 bits
		// 1st byte - shift bits by 48 (6*8) bits, AND with 0xff to get the last 8 bits
		// ... ...
		// 7th byte - shift bits by 0 (0*8) bits, AND with 0xff to get the last 8 bits
		timeBytes[i] = byte((unixNano >> ((timeBytesSize - i - 1) * timeBytesSize)) & 0xff)
	}
	probeutils.PatternPayload(payload, timeBytes[:])
}

// This function is a direct copy of checksum from the following package:
// https://godoc.org/golang.org/x/net/icmp
// TODO(manugarg): Follow up to find out if checksum from icmp package can be
// made public.
func checksum(b []byte) uint16 {
	csumcv := len(b) - 1 // checksum coverage
	s := uint32(0)
	for i := 0; i < csumcv; i += 2 {
		s += uint32(b[i+1])<<8 | uint32(b[i])
	}
	if csumcv&1 == 0 {
		s += uint32(b[csumcv])
	}
	s = s>>16 + s&0xffff
	s = s + s>>16
	return ^uint16(s)
}

// prepareRequestPacket prepares the Echo request packet in the given pktbuf
// bytes buffer.
func (p *Probe) prepareRequestPacket(pktbuf []byte, runID, seq uint16, unixNano int64) {
	if p.ipVer == 6 {
		pktbuf[0] = byte(ipv6.ICMPTypeEchoRequest)
	} else {
		pktbuf[0] = byte(ipv4.ICMPTypeEcho)
	}
	pktbuf[1] = byte(0)

	// We fill these 2 bytes later with the checksum.
	pktbuf[2] = 0
	pktbuf[3] = 0

	binary.BigEndian.PutUint16(pktbuf[4:6], uint16(runID))
	binary.BigEndian.PutUint16(pktbuf[6:8], uint16(seq))

	// Fill payload with the bytes corresponding to current time.
	prepareRequestPayload(pktbuf[8:], unixNano)

	// For IPv6 and datagram sockets ,checksum is computed by the kernel.
	if p.ipVer != 6 && !p.useDatagramSocket {
		csum := checksum(pktbuf)
		pktbuf[2] ^= byte(csum)
		pktbuf[3] ^= byte(csum >> 8)
	}
}

func (pkt *rcvdPkt) String(rtt time.Duration) string {
	var b strings.Builder
	b.WriteString("peer=")
	b.WriteString(pkt.target)
	b.WriteString(" id=")
	b.WriteString(strconv.FormatInt(int64(pkt.id), 10))
	b.WriteString(" seq=")
	b.WriteString(strconv.FormatInt(int64(pkt.seq), 10))
	b.WriteString(" rtt=")
	b.WriteString(rtt.String())
	return b.String()
}
<commit_msg>For OS X, compute checksum for IPv4 SOCK_DGRAM sockets as well. (#539)<commit_after>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ping

import (
	"encoding/binary"
	"net"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/google/cloudprober/probes/probeutils"
	"golang.org/x/net/ipv4"
	"golang.org/x/net/ipv6"
)

const timeBytesSize = 8

func bytesToTime(b []byte) int64 {
	var unixNano int64
	for i := uint8(0); i < timeBytesSize; i++ {
		unixNano += int64(b[i]) << ((timeBytesSize - i - 1) * timeBytesSize)
	}
	return unixNano
}

func ipToKey(ip net.IP) (key [16]byte) {
	copy(key[:], ip.To16())
	return
}

func validEchoReply(ipVer int, typeByte byte) bool {
	switch ipVer {
	case 6:
		return ipv6.ICMPType(typeByte) == ipv6.ICMPTypeEchoReply
	default:
		return ipv4.ICMPType(typeByte) == ipv4.ICMPTypeEchoReply
	}
}

func prepareRequestPayload(payload []byte, unixNano int64) {
	var timeBytes [timeBytesSize]byte
	for i := uint8(0); i < timeBytesSize; i++ {
		// To get timeBytes:
		// 0th byte - shift bits by 56 (7*8) bits, AND with 0xff to get the last 8 bits
		// 1st byte - shift bits by 48 (6*8) bits, AND with 0xff to get the last 8 bits
		// ... ...
		// 7th byte - shift bits by 0 (0*8) bits, AND with 0xff to get the last 8 bits
		timeBytes[i] = byte((unixNano >> ((timeBytesSize - i - 1) * timeBytesSize)) & 0xff)
	}
	probeutils.PatternPayload(payload, timeBytes[:])
}

// This function is a direct copy of checksum from the following package:
// https://godoc.org/golang.org/x/net/icmp
// TODO(manugarg): Follow up to find out if checksum from icmp package can be
// made public.
func checksum(b []byte) uint16 {
	csumcv := len(b) - 1 // checksum coverage
	s := uint32(0)
	for i := 0; i < csumcv; i += 2 {
		s += uint32(b[i+1])<<8 | uint32(b[i])
	}
	if csumcv&1 == 0 {
		s += uint32(b[csumcv])
	}
	s = s>>16 + s&0xffff
	s = s + s>>16
	return ^uint16(s)
}

// prepareRequestPacket prepares the Echo request packet in the given pktbuf
// bytes buffer.
func (p *Probe) prepareRequestPacket(pktbuf []byte, runID, seq uint16, unixNano int64) {
	if p.ipVer == 6 {
		pktbuf[0] = byte(ipv6.ICMPTypeEchoRequest)
	} else {
		pktbuf[0] = byte(ipv4.ICMPTypeEcho)
	}
	pktbuf[1] = byte(0)

	// We fill these 2 bytes later with the checksum.
	pktbuf[2] = 0
	pktbuf[3] = 0

	binary.BigEndian.PutUint16(pktbuf[4:6], uint16(runID))
	binary.BigEndian.PutUint16(pktbuf[6:8], uint16(seq))

	// Fill payload with the bytes corresponding to current time.
	prepareRequestPayload(pktbuf[8:], unixNano)

	// For IPv6 checksum is always computed by the kernel.
	// For IPv4, we compute checksum only if using RAW socket or OS is darwin.
	if p.ipVer == 4 {
		if !p.useDatagramSocket || runtime.GOOS == "darwin" {
			csum := checksum(pktbuf)
			pktbuf[2] ^= byte(csum)
			pktbuf[3] ^= byte(csum >> 8)
		}
	}
}

func (pkt *rcvdPkt) String(rtt time.Duration) string {
	var b strings.Builder
	b.WriteString("peer=")
	b.WriteString(pkt.target)
	b.WriteString(" id=")
	b.WriteString(strconv.FormatInt(int64(pkt.id), 10))
	b.WriteString(" seq=")
	b.WriteString(strconv.FormatInt(int64(pkt.seq), 10))
	b.WriteString(" rtt=")
	b.WriteString(rtt.String())
	return b.String()
}
<|endoftext|>