<commit_before>package commands

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/github/hub/github"
	"github.com/github/hub/utils"
)

var cmdCompare = &Command{
	Run:   compare,
	Usage: "compare [-u] [USER] [<START>...]<END>",
	Short: "Open a compare page on GitHub",
	Long: `Open a GitHub compare view page in the system's default web browser.
<START> to <END> are branch names, tag names, or commit SHA1s specifying
the range of history to compare. If a range with two dots ("a..b") is given,
it will be transformed into one with three dots. If <START> is omitted,
GitHub will compare against the base branch (the default is "master").
If <END> is omitted, GitHub compare view is opened for the current branch.
With "-u", outputs the URL rather than opening the browser.
`,
}

var (
	flagCompareURLOnly bool
)

func init() {
	cmdCompare.Flag.BoolVarP(&flagCompareURLOnly, "url-only", "u", false, "URL only")

	CmdRunner.Use(cmdCompare)
}

/*
  $ gh compare refactor
  > open https://github.com/CURRENT_REPO/compare/refactor

  $ gh compare 1.0..1.1
  > open https://github.com/CURRENT_REPO/compare/1.0...1.1

  $ gh compare -u other-user patch
  > open https://github.com/other-user/REPO/compare/patch
*/
func compare(command *Command, args *Args) {
	localRepo, err := github.LocalRepo()
	utils.Check(err)

	var (
		branch  *github.Branch
		project *github.Project
		r       string
	)

	branch, project, err = localRepo.RemoteBranchAndProject("", false)
	utils.Check(err)

	if args.IsParamsEmpty() {
		if branch != nil && !branch.IsMaster() {
			r = branch.ShortName()
		} else {
			err = fmt.Errorf("Usage: hub compare [USER] [<START>...]<END>")
			utils.Check(err)
		}
	} else {
		r = parseCompareRange(args.RemoveParam(args.ParamsSize() - 1))
		if args.IsParamsEmpty() {
			project, err = localRepo.CurrentProject()
			utils.Check(err)
		} else {
			project = github.NewProject(args.RemoveParam(args.ParamsSize()-1), "", "")
		}
	}

	r = strings.Replace(r, "/", ";", -1)
	subpage := utils.ConcatPaths("compare", r)
	url := project.WebURL("", "", subpage)
	launcher, err := utils.BrowserLauncher()
	utils.Check(err)

	if flagCompareURLOnly {
		args.Replace("echo", url)
	} else {
		args.Replace(launcher[0], "", launcher[1:]...)
		args.AppendParams(url)
	}
}

func parseCompareRange(r string) string {
	shaOrTag := fmt.Sprintf("((?:%s:)?\\w[\\w.-]+\\w)", OwnerRe)
	shaOrTagRange := fmt.Sprintf("^%s\\.\\.%s$", shaOrTag, shaOrTag)
	shaOrTagRangeRegexp := regexp.MustCompile(shaOrTagRange)
	return shaOrTagRangeRegexp.ReplaceAllString(r, "$1...$2")
}
<commit_msg>Don't escape slashes in branch names with `;`<commit_after>package commands

import (
	"fmt"
	"regexp"

	"github.com/github/hub/github"
	"github.com/github/hub/utils"
)

var cmdCompare = &Command{
	Run:   compare,
	Usage: "compare [-u] [USER] [<START>...]<END>",
	Short: "Open a compare page on GitHub",
	Long: `Open a GitHub compare view page in the system's default web browser.
<START> to <END> are branch names, tag names, or commit SHA1s specifying
the range of history to compare. If a range with two dots ("a..b") is given,
it will be transformed into one with three dots. If <START> is omitted,
GitHub will compare against the base branch (the default is "master").
If <END> is omitted, GitHub compare view is opened for the current branch.
With "-u", outputs the URL rather than opening the browser.
`,
}

var (
	flagCompareURLOnly bool
)

func init() {
	cmdCompare.Flag.BoolVarP(&flagCompareURLOnly, "url-only", "u", false, "URL only")

	CmdRunner.Use(cmdCompare)
}

/*
  $ gh compare refactor
  > open https://github.com/CURRENT_REPO/compare/refactor

  $ gh compare 1.0..1.1
  > open https://github.com/CURRENT_REPO/compare/1.0...1.1

  $ gh compare -u other-user patch
  > open https://github.com/other-user/REPO/compare/patch
*/
func compare(command *Command, args *Args) {
	localRepo, err := github.LocalRepo()
	utils.Check(err)

	var (
		branch  *github.Branch
		project *github.Project
		r       string
	)

	branch, project, err = localRepo.RemoteBranchAndProject("", false)
	utils.Check(err)

	if args.IsParamsEmpty() {
		if branch != nil && !branch.IsMaster() {
			r = branch.ShortName()
		} else {
			err = fmt.Errorf("Usage: hub compare [USER] [<START>...]<END>")
			utils.Check(err)
		}
	} else {
		r = parseCompareRange(args.RemoveParam(args.ParamsSize() - 1))
		if args.IsParamsEmpty() {
			project, err = localRepo.CurrentProject()
			utils.Check(err)
		} else {
			project = github.NewProject(args.RemoveParam(args.ParamsSize()-1), "", "")
		}
	}

	subpage := utils.ConcatPaths("compare", r)
	url := project.WebURL("", "", subpage)
	launcher, err := utils.BrowserLauncher()
	utils.Check(err)

	if flagCompareURLOnly {
		args.Replace("echo", url)
	} else {
		args.Replace(launcher[0], "", launcher[1:]...)
		args.AppendParams(url)
	}
}

func parseCompareRange(r string) string {
	shaOrTag := fmt.Sprintf("((?:%s:)?\\w[\\w.-]+\\w)", OwnerRe)
	shaOrTagRange := fmt.Sprintf("^%s\\.\\.%s$", shaOrTag, shaOrTag)
	shaOrTagRangeRegexp := regexp.MustCompile(shaOrTagRange)
	return shaOrTagRangeRegexp.ReplaceAllString(r, "$1...$2")
}
<|endoftext|>


<commit_before>// Copyright (c) 2019-2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package wallet

import (
	"context"
	"crypto/rand"
	"crypto/tls"
	"net"

	"decred.org/cspp"
	"decred.org/cspp/coinjoin"
	"decred.org/dcrwallet/v2/errors"
	"decred.org/dcrwallet/v2/wallet/txauthor"
	"decred.org/dcrwallet/v2/wallet/txrules"
	"decred.org/dcrwallet/v2/wallet/txsizes"
	"decred.org/dcrwallet/v2/wallet/udb"
	"decred.org/dcrwallet/v2/wallet/walletdb"
	"github.com/decred/dcrd/dcrutil/v4"
	"github.com/decred/dcrd/wire"
	"github.com/decred/go-socks/socks"
	"golang.org/x/sync/errgroup"
)

// must be sorted large to small
var splitPoints = [...]dcrutil.Amount{
	1 << 36, // 687.19476736
	1 << 34, // 171.79869184
	1 << 32, // 042.94967296
	1 << 30, // 010.73741824
	1 << 28, // 002.68435456
	1 << 26, // 000.67108864
	1 << 24, // 000.16777216
	1 << 22, // 000.04194304
	1 << 20, // 000.01048576
	1 << 18, // 000.00262144
}

var splitSems = [len(splitPoints)]chan struct{}{}

func init() {
	for i := range splitSems {
		splitSems[i] = make(chan struct{}, 10)
	}
}

var (
	errNoSplitDenomination = errors.New("no suitable split denomination")
	errThrottledMixRequest = errors.New("throttled mix request for split denomination")
)

// DialFunc provides a method to dial a network connection.
// If the dialed network connection is secured by TLS, TLS
// configuration is provided by the method, not the caller.
type DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)

func (w *Wallet) MixOutput(ctx context.Context, dialTLS DialFunc, csppserver string, output *wire.OutPoint, changeAccount, mixAccount, mixBranch uint32) error {
	op := errors.Opf("wallet.MixOutput(%v)", output)

	sdiff, err := w.NextStakeDifficulty(ctx)
	if err != nil {
		return errors.E(op, err)
	}

	defer w.holdUnlock().release()

	w.lockedOutpointMu.Lock()
	if _, exists := w.lockedOutpoints[outpoint{output.Hash, output.Index}]; exists {
		w.lockedOutpointMu.Unlock()
		err = errors.Errorf("output %v already locked", output)
		return errors.E(op, err)
	}

	var prevScript []byte
	var amount dcrutil.Amount
	err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
		txDetails, err := w.txStore.TxDetails(txmgrNs, &output.Hash)
		if err != nil {
			return err
		}
		prevScript = txDetails.MsgTx.TxOut[output.Index].PkScript
		amount = dcrutil.Amount(txDetails.MsgTx.TxOut[output.Index].Value)
		return nil
	})
	if err != nil {
		w.lockedOutpointMu.Unlock()
		return errors.E(op, err)
	}
	w.lockedOutpoints[outpoint{output.Hash, output.Index}] = struct{}{}
	w.lockedOutpointMu.Unlock()

	defer func() {
		w.lockedOutpointMu.Lock()
		delete(w.lockedOutpoints, outpoint{output.Hash, output.Index})
		w.lockedOutpointMu.Unlock()
	}()

	var count int
	var mixValue, remValue dcrutil.Amount
	for i, v := range splitPoints {
		// When the sdiff is more than this mixed output amount, there
		// is a smaller common mixed amount with more pairing activity
		// (due to CoinShuffle++ participation from ticket buyers).
		// Skipping this amount and moving to the next smallest common
		// mixed amount will result in quicker pairings, or pairings
		// occurring at all.  The number of mixed outputs is capped to
		// prevent a single mix being overwhelmingly funded by a single
		// output, and to conserve memory resources.
		if i != len(splitPoints)-1 && v >= sdiff {
			continue
		}
		count = int(amount / v)
		if count > 4 {
			count = 4
		}
		if count > 0 {
			remValue = amount - dcrutil.Amount(count)*v
			mixValue = v
			select {
			case <-ctx.Done():
				return errors.E(op, ctx.Err())
			case splitSems[i] <- struct{}{}:
				defer func() { <-splitSems[i] }()
			default:
				return errThrottledMixRequest
			}
			break
		}
	}
	if mixValue == splitPoints[len(splitPoints)-1] {
		remValue = 0
	}
	if mixValue == 0 {
		err := errors.Errorf("output %v (%v): %w", output, amount, errNoSplitDenomination)
		return errors.E(op, err)
	}

	const (
		txVersion = 1
		locktime  = 0
		expiry    = 0
	)
	pairing := coinjoin.EncodeDesc(coinjoin.P2PKHv0, int64(mixValue), txVersion, locktime, expiry)
	ses, err := cspp.NewSession(rand.Reader, debugLog, pairing, count)
	if err != nil {
		return errors.E(op, err)
	}
	var conn net.Conn
	if dialTLS != nil {
		conn, err = dialTLS(ctx, "tcp", csppserver)
	} else {
		conn, err = tls.Dial("tcp", csppserver, nil)
	}
	if err != nil {
		return errors.E(op, err)
	}
	defer conn.Close()
	log.Infof("Dialed CSPPServer %v -> %v", conn.LocalAddr(), conn.RemoteAddr())

	// Create change output from remaining value and contributed fee
	const P2PKHv0Len = 25
	feeRate := w.RelayFee()
	inScriptSizes := []int{txsizes.RedeemP2PKHSigScriptSize}
	outScriptSizes := make([]int, count)
	for i := range outScriptSizes {
		outScriptSizes[i] = P2PKHv0Len
	}
	size := txsizes.EstimateSerializeSizeFromScriptSizes(inScriptSizes, outScriptSizes, P2PKHv0Len)
	changeValue := remValue - txrules.FeeForSerializeSize(feeRate, size)
	var change *wire.TxOut
	var updates []func(walletdb.ReadWriteTx) error
	if !txrules.IsDustAmount(changeValue, P2PKHv0Len, feeRate) {
		persist := w.deferPersistReturnedChild(ctx, &updates)
		const accountName = "" // not used, so can be faked.
		addr, err := w.nextAddress(ctx, op, persist,
			accountName, changeAccount, udb.InternalBranch, WithGapPolicyIgnore())
		if err != nil {
			return errors.E(op, err)
		}
		changeScript, version, err := addressScript(addr)
		if err != nil {
			return errors.E(op, err)
		}
		change = &wire.TxOut{
			Value:    int64(changeValue),
			PkScript: changeScript,
			Version:  version,
		}
	}

	log.Infof("Mixing output %v (%v)", output, amount)
	cj := w.newCsppJoin(ctx, change, mixValue, mixAccount, mixBranch, count)
	cj.addTxIn(prevScript, &wire.TxIn{
		PreviousOutPoint: *output,
		ValueIn:          int64(amount),
	})
	err = ses.DiceMix(ctx, conn, cj)
	if err != nil {
		return errors.E(op, err)
	}
	cjHash := cj.tx.TxHash()
	log.Infof("Completed CoinShuffle++ mix of output %v in transaction %v", output, &cjHash)

	w.lockedOutpointMu.Lock()
	err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
		for _, f := range updates {
			if err := f(dbtx); err != nil {
				return err
			}
		}
		return nil
	})
	w.lockedOutpointMu.Unlock()
	if err != nil {
		return errors.E(op, err)
	}

	return nil
}

// MixAccount individually mixes outputs of an account into standard
// denominations, creating newly mixed outputs for a mixed account.
//
// Due to performance concerns of timing out in a CoinShuffle++ run, this
// function may throttle how many of the outputs are mixed each call.
func (w *Wallet) MixAccount(ctx context.Context, dialTLS DialFunc, csppserver string, changeAccount, mixAccount, mixBranch uint32) error {
	const op errors.Op = "wallet.MixAccount"

	defer w.holdUnlock().release()

	_, tipHeight := w.MainChainTip(ctx)
	w.lockedOutpointMu.Lock()
	var credits []Input
	err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
		var err error
		credits, err = w.findEligibleOutputs(dbtx, changeAccount, 1, tipHeight)
		return err
	})
	if err != nil {
		w.lockedOutpointMu.Unlock()
		return errors.E(op, err)
	}
	validCredits := credits[:0]
	for i := range credits {
		amount := dcrutil.Amount(credits[i].PrevOut.Value)
		if amount <= splitPoints[len(splitPoints)-1] {
			continue
		}
		validCredits = append(validCredits, credits[i])
	}
	credits = validCredits
	shuffle(len(credits), func(i, j int) {
		credits[i], credits[j] = credits[j], credits[i]
	})
	if len(credits) > 32 { // simple throttle
		credits = credits[:32]
	}
	w.lockedOutpointMu.Unlock()

	var g errgroup.Group
	for i := range credits {
		op := &credits[i].OutPoint
		g.Go(func() error {
			err := w.MixOutput(ctx, dialTLS, csppserver, op, changeAccount, mixAccount, mixBranch)
			if errors.Is(err, errThrottledMixRequest) {
				return nil
			}
			if errors.Is(err, errNoSplitDenomination) {
				return nil
			}
			if errors.Is(err, socks.ErrPoolMaxConnections) {
				return nil
			}
			return err
		})
	}
	err = g.Wait()
	if err != nil {
		return errors.E(op, err)
	}
	return nil
}

// randomInputSource wraps an InputSource to randomly pick UTXOs.
// This involves reading all UTXOs from the underlying source into memory.
func randomInputSource(source txauthor.InputSource) txauthor.InputSource {
	all, err := source(dcrutil.MaxAmount)
	if err == nil {
		shuffleUTXOs(all)
	}
	var n int
	var tot dcrutil.Amount
	return func(target dcrutil.Amount) (*txauthor.InputDetail, error) {
		if err != nil {
			return nil, err
		}
		if all.Amount <= target {
			return all, nil
		}
		for n < len(all.Inputs) {
			tot += dcrutil.Amount(all.Inputs[n].ValueIn)
			n++
			if tot >= target {
				break
			}
		}
		selected := &txauthor.InputDetail{
			Amount:            tot,
			Inputs:            all.Inputs[:n],
			Scripts:           all.Scripts[:n],
			RedeemScriptSizes: all.RedeemScriptSizes[:n],
		}
		return selected, nil
	}
}

// PossibleCoinJoin tests if a transaction may be a CSPP-mixed transaction.
// It can return false positives, as one can create a tx which looks like a
// coinjoin tx, although it isn't.
func PossibleCoinJoin(tx *wire.MsgTx) (isMix bool, mixDenom int64, mixCount uint32) {
	if len(tx.TxOut) < 3 || len(tx.TxIn) < 3 {
		return false, 0, 0
	}

	numberOfOutputs := len(tx.TxOut)
	numberOfInputs := len(tx.TxIn)

	mixedOuts := make(map[int64]uint32)
	scripts := make(map[string]int)
	for _, o := range tx.TxOut {
		scripts[string(o.PkScript)]++
		if scripts[string(o.PkScript)] > 1 {
			return false, 0, 0
		}
		val := o.Value
		// Multiple zero valued outputs do not count as a coinjoin mix.
		if val == 0 {
			continue
		}
		mixedOuts[val]++
	}

	for val, count := range mixedOuts {
		if count < 3 {
			continue
		}
		if val > mixDenom {
			mixDenom = val
			mixCount = count
		}

		outputsWithNotSameAmount := uint32(numberOfOutputs) - count
		if outputsWithNotSameAmount > uint32(numberOfInputs) {
			return false, 0, 0
		}
	}

	isMix = mixCount >= uint32(len(tx.TxOut)/2)
	return
}
<commit_msg>Move connection to CSPP server below fee estimations<commit_after>// Copyright (c) 2019-2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package wallet

import (
	"context"
	"crypto/rand"
	"crypto/tls"
	"net"

	"decred.org/cspp"
	"decred.org/cspp/coinjoin"
	"decred.org/dcrwallet/v2/errors"
	"decred.org/dcrwallet/v2/wallet/txauthor"
	"decred.org/dcrwallet/v2/wallet/txrules"
	"decred.org/dcrwallet/v2/wallet/txsizes"
	"decred.org/dcrwallet/v2/wallet/udb"
	"decred.org/dcrwallet/v2/wallet/walletdb"
	"github.com/decred/dcrd/dcrutil/v4"
	"github.com/decred/dcrd/wire"
	"github.com/decred/go-socks/socks"
	"golang.org/x/sync/errgroup"
)

// must be sorted large to small
var splitPoints = [...]dcrutil.Amount{
	1 << 36, // 687.19476736
	1 << 34, // 171.79869184
	1 << 32, // 042.94967296
	1 << 30, // 010.73741824
	1 << 28, // 002.68435456
	1 << 26, // 000.67108864
	1 << 24, // 000.16777216
	1 << 22, // 000.04194304
	1 << 20, // 000.01048576
	1 << 18, // 000.00262144
}

var splitSems = [len(splitPoints)]chan struct{}{}

func init() {
	for i := range splitSems {
		splitSems[i] = make(chan struct{}, 10)
	}
}

var (
	errNoSplitDenomination = errors.New("no suitable split denomination")
	errThrottledMixRequest = errors.New("throttled mix request for split denomination")
)

// DialFunc provides a method to dial a network connection.
// If the dialed network connection is secured by TLS, TLS
// configuration is provided by the method, not the caller.
type DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)

func (w *Wallet) MixOutput(ctx context.Context, dialTLS DialFunc, csppserver string, output *wire.OutPoint, changeAccount, mixAccount, mixBranch uint32) error {
	op := errors.Opf("wallet.MixOutput(%v)", output)

	sdiff, err := w.NextStakeDifficulty(ctx)
	if err != nil {
		return errors.E(op, err)
	}

	defer w.holdUnlock().release()

	w.lockedOutpointMu.Lock()
	if _, exists := w.lockedOutpoints[outpoint{output.Hash, output.Index}]; exists {
		w.lockedOutpointMu.Unlock()
		err = errors.Errorf("output %v already locked", output)
		return errors.E(op, err)
	}

	var prevScript []byte
	var amount dcrutil.Amount
	err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
		txDetails, err := w.txStore.TxDetails(txmgrNs, &output.Hash)
		if err != nil {
			return err
		}
		prevScript = txDetails.MsgTx.TxOut[output.Index].PkScript
		amount = dcrutil.Amount(txDetails.MsgTx.TxOut[output.Index].Value)
		return nil
	})
	if err != nil {
		w.lockedOutpointMu.Unlock()
		return errors.E(op, err)
	}
	w.lockedOutpoints[outpoint{output.Hash, output.Index}] = struct{}{}
	w.lockedOutpointMu.Unlock()

	defer func() {
		w.lockedOutpointMu.Lock()
		delete(w.lockedOutpoints, outpoint{output.Hash, output.Index})
		w.lockedOutpointMu.Unlock()
	}()

	var count int
	var mixValue, remValue dcrutil.Amount
	for i, v := range splitPoints {
		// When the sdiff is more than this mixed output amount, there
		// is a smaller common mixed amount with more pairing activity
		// (due to CoinShuffle++ participation from ticket buyers).
		// Skipping this amount and moving to the next smallest common
		// mixed amount will result in quicker pairings, or pairings
		// occurring at all.  The number of mixed outputs is capped to
		// prevent a single mix being overwhelmingly funded by a single
		// output, and to conserve memory resources.
		if i != len(splitPoints)-1 && v >= sdiff {
			continue
		}
		count = int(amount / v)
		if count > 4 {
			count = 4
		}
		if count > 0 {
			remValue = amount - dcrutil.Amount(count)*v
			mixValue = v
			select {
			case <-ctx.Done():
				return errors.E(op, ctx.Err())
			case splitSems[i] <- struct{}{}:
				defer func() { <-splitSems[i] }()
			default:
				return errThrottledMixRequest
			}
			break
		}
	}
	if mixValue == splitPoints[len(splitPoints)-1] {
		remValue = 0
	}
	if mixValue == 0 {
		err := errors.Errorf("output %v (%v): %w", output, amount, errNoSplitDenomination)
		return errors.E(op, err)
	}

	// Create change output from remaining value and contributed fee
	const P2PKHv0Len = 25
	feeRate := w.RelayFee()
	inScriptSizes := []int{txsizes.RedeemP2PKHSigScriptSize}
	outScriptSizes := make([]int, count)
	for i := range outScriptSizes {
		outScriptSizes[i] = P2PKHv0Len
	}
	size := txsizes.EstimateSerializeSizeFromScriptSizes(inScriptSizes, outScriptSizes, P2PKHv0Len)
	changeValue := remValue - txrules.FeeForSerializeSize(feeRate, size)
	var change *wire.TxOut
	var updates []func(walletdb.ReadWriteTx) error
	if !txrules.IsDustAmount(changeValue, P2PKHv0Len, feeRate) {
		persist := w.deferPersistReturnedChild(ctx, &updates)
		const accountName = "" // not used, so can be faked.
		addr, err := w.nextAddress(ctx, op, persist,
			accountName, changeAccount, udb.InternalBranch, WithGapPolicyIgnore())
		if err != nil {
			return errors.E(op, err)
		}
		changeScript, version, err := addressScript(addr)
		if err != nil {
			return errors.E(op, err)
		}
		change = &wire.TxOut{
			Value:    int64(changeValue),
			PkScript: changeScript,
			Version:  version,
		}
	}

	const (
		txVersion = 1
		locktime  = 0
		expiry    = 0
	)
	pairing := coinjoin.EncodeDesc(coinjoin.P2PKHv0, int64(mixValue), txVersion, locktime, expiry)
	ses, err := cspp.NewSession(rand.Reader, debugLog, pairing, count)
	if err != nil {
		return errors.E(op, err)
	}
	var conn net.Conn
	if dialTLS != nil {
		conn, err = dialTLS(ctx, "tcp", csppserver)
	} else {
		conn, err = tls.Dial("tcp", csppserver, nil)
	}
	if err != nil {
		return errors.E(op, err)
	}
	defer conn.Close()
	log.Infof("Dialed CSPPServer %v -> %v", conn.LocalAddr(), conn.RemoteAddr())

	log.Infof("Mixing output %v (%v)", output, amount)
	cj := w.newCsppJoin(ctx, change, mixValue, mixAccount, mixBranch, count)
	cj.addTxIn(prevScript, &wire.TxIn{
		PreviousOutPoint: *output,
		ValueIn:          int64(amount),
	})
	err = ses.DiceMix(ctx, conn, cj)
	if err != nil {
		return errors.E(op, err)
	}
	cjHash := cj.tx.TxHash()
	log.Infof("Completed CoinShuffle++ mix of output %v in transaction %v", output, &cjHash)

	w.lockedOutpointMu.Lock()
	err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
		for _, f := range updates {
			if err := f(dbtx); err != nil {
				return err
			}
		}
		return nil
	})
	w.lockedOutpointMu.Unlock()
	if err != nil {
		return errors.E(op, err)
	}

	return nil
}

// MixAccount individually mixes outputs of an account into standard
// denominations, creating newly mixed outputs for a mixed account.
//
// Due to performance concerns of timing out in a CoinShuffle++ run, this
// function may throttle how many of the outputs are mixed each call.
func (w *Wallet) MixAccount(ctx context.Context, dialTLS DialFunc, csppserver string, changeAccount, mixAccount, mixBranch uint32) error {
	const op errors.Op = "wallet.MixAccount"

	defer w.holdUnlock().release()

	_, tipHeight := w.MainChainTip(ctx)
	w.lockedOutpointMu.Lock()
	var credits []Input
	err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
		var err error
		credits, err = w.findEligibleOutputs(dbtx, changeAccount, 1, tipHeight)
		return err
	})
	if err != nil {
		w.lockedOutpointMu.Unlock()
		return errors.E(op, err)
	}
	validCredits := credits[:0]
	for i := range credits {
		amount := dcrutil.Amount(credits[i].PrevOut.Value)
		if amount <= splitPoints[len(splitPoints)-1] {
			continue
		}
		validCredits = append(validCredits, credits[i])
	}
	credits = validCredits
	shuffle(len(credits), func(i, j int) {
		credits[i], credits[j] = credits[j], credits[i]
	})
	if len(credits) > 32 { // simple throttle
		credits = credits[:32]
	}
	w.lockedOutpointMu.Unlock()

	var g errgroup.Group
	for i := range credits {
		op := &credits[i].OutPoint
		g.Go(func() error {
			err := w.MixOutput(ctx, dialTLS, csppserver, op, changeAccount, mixAccount, mixBranch)
			if errors.Is(err, errThrottledMixRequest) {
				return nil
			}
			if errors.Is(err, errNoSplitDenomination) {
				return nil
			}
			if errors.Is(err, socks.ErrPoolMaxConnections) {
				return nil
			}
			return err
		})
	}
	err = g.Wait()
	if err != nil {
		return errors.E(op, err)
	}
	return nil
}

// randomInputSource wraps an InputSource to randomly pick UTXOs.
// This involves reading all UTXOs from the underlying source into memory.
func randomInputSource(source txauthor.InputSource) txauthor.InputSource {
	all, err := source(dcrutil.MaxAmount)
	if err == nil {
		shuffleUTXOs(all)
	}
	var n int
	var tot dcrutil.Amount
	return func(target dcrutil.Amount) (*txauthor.InputDetail, error) {
		if err != nil {
			return nil, err
		}
		if all.Amount <= target {
			return all, nil
		}
		for n < len(all.Inputs) {
			tot += dcrutil.Amount(all.Inputs[n].ValueIn)
			n++
			if tot >= target {
				break
			}
		}
		selected := &txauthor.InputDetail{
			Amount:            tot,
			Inputs:            all.Inputs[:n],
			Scripts:           all.Scripts[:n],
			RedeemScriptSizes: all.RedeemScriptSizes[:n],
		}
		return selected, nil
	}
}

// PossibleCoinJoin tests if a transaction may be a CSPP-mixed transaction.
// It can return false positives, as one can create a tx which looks like a
// coinjoin tx, although it isn't.
func PossibleCoinJoin(tx *wire.MsgTx) (isMix bool, mixDenom int64, mixCount uint32) {
	if len(tx.TxOut) < 3 || len(tx.TxIn) < 3 {
		return false, 0, 0
	}

	numberOfOutputs := len(tx.TxOut)
	numberOfInputs := len(tx.TxIn)

	mixedOuts := make(map[int64]uint32)
	scripts := make(map[string]int)
	for _, o := range tx.TxOut {
		scripts[string(o.PkScript)]++
		if scripts[string(o.PkScript)] > 1 {
			return false, 0, 0
		}
		val := o.Value
		// Multiple zero valued outputs do not count as a coinjoin mix.
		if val == 0 {
			continue
		}
		mixedOuts[val]++
	}

	for val, count := range mixedOuts {
		if count < 3 {
			continue
		}
		if val > mixDenom {
			mixDenom = val
			mixCount = count
		}

		outputsWithNotSameAmount := uint32(numberOfOutputs) - count
		if outputsWithNotSameAmount > uint32(numberOfInputs) {
			return false, 0, 0
		}
	}

	isMix = mixCount >= uint32(len(tx.TxOut)/2)
	return
}
<|endoftext|>


<commit_before>// Copyright 2016-2019 Authors of Cilium
//
// 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 client

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strings"
	"text/tabwriter"
	"time"

	clientapi "github.com/cilium/cilium/api/v1/client"
	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/defaults"

	runtime_client "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"
	"golang.org/x/net/context"
)

type Client struct {
	clientapi.Cilium
}

// DefaultSockPath returns default UNIX domain socket path or
// path set using CILIUM_SOCK env variable
func DefaultSockPath() string {
	// Check if environment variable points to socket
	e := os.Getenv(defaults.SockPathEnv)
	if e == "" {
		// If unset, fall back to default value
		e = defaults.SockPath
	}
	return "unix://" + e

}

func configureTransport(tr *http.Transport, proto, addr string) *http.Transport {
	if tr == nil {
		tr = &http.Transport{}
	}

	if proto == "unix" {
		// No need for compression in local communications.
		tr.DisableCompression = true
		tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
			return net.Dial(proto, addr)
		}
	} else {
		tr.Proxy = http.ProxyFromEnvironment
		tr.DialContext = (&net.Dialer{}).DialContext
	}

	return tr
}

// NewDefaultClient creates a client with default parameters connecting to UNIX domain socket.
func NewDefaultClient() (*Client, error) {
	return NewClient("")
}

// NewDefaultClientWithTimeout creates a client with default parameters connecting to UNIX
// domain socket and waits for cilium-agent availability.
func NewDefaultClientWithTimeout(timeout time.Duration) (*Client, error) {
	timeoutAfter := time.After(timeout)
	var c *Client
	var err error
	for {
		select {
		case <-timeoutAfter:
			return nil, fmt.Errorf("failed to create cilium agent client after %f seconds timeout: %s", timeout.Seconds(), err)
		default:
		}

		c, err = NewDefaultClient()
		if err != nil {
			time.Sleep(500 * time.Millisecond)
			continue
		}

		for {
			select {
			case <-timeoutAfter:
				return nil, fmt.Errorf("failed to create cilium agent client after %f seconds timeout: %s", timeout.Seconds(), err)
			default:
			}
			// This is an API call that we do to the cilium-agent to check
			// if it is up and running.
			_, err = c.Daemon.GetConfig(nil)
			if err != nil {
				time.Sleep(500 * time.Millisecond)
				continue
			}
			return c, nil
		}
	}
}

// NewClient creates a client for the given `host`.
// If host is nil then use SockPath provided by CILIUM_SOCK
// or the cilium default SockPath
func NewClient(host string) (*Client, error) {
	if host == "" {
		host = DefaultSockPath()
	}
	tmp := strings.SplitN(host, "://", 2)
	if len(tmp) != 2 {
		return nil, fmt.Errorf("invalid host format '%s'", host)
	}

	switch tmp[0] {
	case "tcp":
		if _, err := url.Parse("tcp://" + tmp[1]); err != nil {
			return nil, err
		}
		host = "http://" + tmp[1]
	case "unix":
		host = tmp[1]
	}

	transport := configureTransport(nil, tmp[0], host)
	httpClient := &http.Client{Transport: transport}
	clientTrans := runtime_client.NewWithClient(tmp[1], clientapi.DefaultBasePath,
		clientapi.DefaultSchemes, httpClient)
	return &Client{*clientapi.New(clientTrans, strfmt.Default)}, nil
}

// ClientError is the error returned by all client functions which use Hint()
type ClientError struct {
	msg         string
	recoverable bool
}

// Recoverable returns true if the error is likely to be recoverable
func (c ClientError) Recoverable() bool {
	return c.recoverable
}

// Error returns the error message representing the error
func (c ClientError) Error() string {
	return c.msg
}

func newRecoverableError(msg string, args ...interface{}) ClientError {
	return ClientError{recoverable: true, msg: fmt.Sprintf(msg, args...)}
}

func newUnrecoverableError(msg string, args ...interface{}) ClientError {
	return ClientError{recoverable: false, msg: fmt.Sprintf(msg, args...)}
}

// Hint tries to improve the error message displayed to the user.
func Hint(err error) error {
	if err == nil {
		return err
	}

	if err == context.DeadlineExceeded {
		return newRecoverableError("Cilium API client timeout exceeded")
	}

	e, _ := url.PathUnescape(err.Error())
	if strings.Contains(err.Error(), defaults.SockPath) {
		return newRecoverableError("%s\nIs the agent running?", e)
	}
	return newUnrecoverableError("%s", e)
}

func timeSince(since time.Time) string {
	out := "never"
	if !since.IsZero() {
		// Poor man's implementtion of time.Truncate(). Can be refined
		// when we rebase to go 1.9
		t := time.Since(since)
		t -= t % time.Second
		out = t.String() + " ago"
	}

	return out
}

func stateUnhealthy(state string) bool {
	return state == models.StatusStateWarning ||
		state == models.StatusStateFailure
}

func statusUnhealthy(s *models.Status) bool {
	if s != nil {
		return stateUnhealthy(s.State)
	}
	return false
}

// FormatStatusResponseBrief writes a one-line status to the writer. If
// everything ok, this is "ok", otherwise a message of the form "error in ..."
func FormatStatusResponseBrief(w io.Writer, sr *models.StatusResponse) {
	msg := ""

	switch {
	case statusUnhealthy(sr.Kvstore):
		msg = fmt.Sprintf("kvstore: %s", sr.Kvstore.Msg)
	case statusUnhealthy(sr.ContainerRuntime):
		msg = fmt.Sprintf("container runtime: %s", sr.ContainerRuntime.Msg)
	case sr.Kubernetes != nil && stateUnhealthy(sr.Kubernetes.State):
		msg = fmt.Sprintf("kubernetes: %s", sr.Kubernetes.Msg)
	case statusUnhealthy(sr.Cilium):
		msg = fmt.Sprintf("cilium: %s", sr.Cilium.Msg)
	case sr.Cluster != nil && statusUnhealthy(sr.Cluster.CiliumHealth):
		msg = fmt.Sprintf("cilium-health: %s", sr.Cluster.CiliumHealth.Msg)
	}

	// Only bother looking at controller failures if everything else is ok
	if msg == "" {
		for _, ctrl := range sr.Controllers {
			if ctrl.Status == nil {
				continue
			}
			if ctrl.Status.LastFailureMsg != "" {
				msg = fmt.Sprintf("controller %s: %s",
					ctrl.Name, ctrl.Status.LastFailureMsg)
				break
			}
		}
	}

	if msg == "" {
		fmt.Fprintf(w, "OK\n")
	} else {
		fmt.Fprintf(w, "error in %s\n", msg)
	}
}

// FormatStatusResponse writes a StatusResponse as a string to the writer.
//
// The parameters 'allAddresses', 'allControllers', 'allNodes', respectively,
// cause all details about that aspect of the status to be printed to the
// terminal. For each of these, if they are false then only a summary will be
// printed, with perhaps some detail if there are errors.
func FormatStatusResponse(w io.Writer, sr *models.StatusResponse, allAddresses, allControllers, allNodes, allRedirects bool) {
	if sr.Kvstore != nil {
		fmt.Fprintf(w, "KVStore:\t%s\t%s\n", sr.Kvstore.State, sr.Kvstore.Msg)
	}
	if sr.ContainerRuntime != nil {
		fmt.Fprintf(w, "ContainerRuntime:\t%s\t%s\n",
			sr.ContainerRuntime.State, sr.ContainerRuntime.Msg)
	}
	if sr.Kubernetes != nil {
		fmt.Fprintf(w, "Kubernetes:\t%s\t%s\n", sr.Kubernetes.State, sr.Kubernetes.Msg)
		if sr.Kubernetes.State != models.K8sStatusStateDisabled {
			sort.Strings(sr.Kubernetes.K8sAPIVersions)
			fmt.Fprintf(w, "Kubernetes APIs:\t[\"%s\"]\n", strings.Join(sr.Kubernetes.K8sAPIVersions, "\", \""))
		}
	}
	if sr.Cilium != nil {
		fmt.Fprintf(w, "Cilium:\t%s\t%s\n", sr.Cilium.State, sr.Cilium.Msg)
	}

	if sr.Stale != nil {
		sortedProbes := make([]string, 0, len(sr.Stale))
		for probe := range sr.Stale {
			sortedProbes = append(sortedProbes, probe)
		}
		sort.Strings(sortedProbes)

		stalesStr := make([]string, 0, len(sr.Stale))
		for _, probe := range sortedProbes {
			stalesStr = append(stalesStr, fmt.Sprintf("%q since %s", probe, sr.Stale[probe]))
		}

		fmt.Fprintf(w, "Stale status:\t%s\n", strings.Join(stalesStr, ", "))
	}

	if nm := sr.NodeMonitor; nm != nil {
		fmt.Fprintf(w, "NodeMonitor:\tListening for events on %d CPUs with %dx%d of shared memory\n",
			nm.Cpus, nm.Npages, nm.Pagesize)
		if nm.Lost != 0 || nm.Unknown != 0 {
			fmt.Fprintf(w, "\t%d events lost, %d unknown notifications\n", nm.Lost, nm.Unknown)
		}
	} else {
		fmt.Fprintf(w, "NodeMonitor:\tDisabled\n")
	}

	if sr.Cluster != nil {
		if sr.Cluster.CiliumHealth != nil {
			ch := sr.Cluster.CiliumHealth
			fmt.Fprintf(w, "Cilium health daemon:\t%s\t%s\n", ch.State, ch.Msg)
		}
	}

	if sr.IPAM != nil {
		fmt.Fprintf(w, "IPAM:\t%s\n", sr.IPAM.Status)
		if allAddresses {
			fmt.Fprintf(w, "Allocated addresses:\n")
			out := []string{}
			for ip, owner := range sr.IPAM.Allocations {
				out = append(out, fmt.Sprintf("  %s (%s)", ip, owner))
			}
			sort.Strings(out)
			for _, line := range out {
				fmt.Fprintln(w, line)
			}
		}
	}

	if sr.Controllers != nil {
		nFailing, out := 0, []string{"  Name\tLast success\tLast error\tCount\tMessage\n"}
		for _, ctrl := range sr.Controllers {
			status := ctrl.Status
			if status == nil {
				continue
			}

			if status.ConsecutiveFailureCount > 0 {
				nFailing++
			} else if !allControllers {
				continue
			}

			failSince := timeSince(time.Time(status.LastFailureTimestamp))
			successSince := timeSince(time.Time(status.LastSuccessTimestamp))

			err := "no error"
			if status.LastFailureMsg != "" {
				err = status.LastFailureMsg
			}

			out = append(out, fmt.Sprintf("  %s\t%s\t%s\t%d\t%s\t\n",
				ctrl.Name, successSince, failSince, status.ConsecutiveFailureCount, err))
		}

		nOK := len(sr.Controllers) - nFailing
		fmt.Fprintf(w, "Controller Status:\t%d/%d healthy\n", nOK, len(sr.Controllers))
		if len(out) > 1 {
			tab := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
			sort.Strings(out)
			for _, s := range out {
				fmt.Fprint(tab, s)
			}
			tab.Flush()
		}

	}

	if sr.Proxy != nil {
		fmt.Fprintf(w, "Proxy Status:\tOK, ip %s, port-range %s\n",
			sr.Proxy.IP, sr.Proxy.PortRange)
	} else {
		fmt.Fprintf(w, "Proxy Status:\tNo managed proxy redirect\n")
	}
}
<commit_msg>client: Remove ClientError<commit_after>// Copyright 2016-2019 Authors of Cilium
//
// 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 client

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strings"
	"text/tabwriter"
	"time"

	clientapi "github.com/cilium/cilium/api/v1/client"
	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/defaults"

	runtime_client "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"
	"golang.org/x/net/context"
)

type Client struct {
	clientapi.Cilium
}

// DefaultSockPath returns default UNIX domain socket path or
// path set using CILIUM_SOCK env variable
func DefaultSockPath() string {
	// Check if environment variable points to socket
	e := os.Getenv(defaults.SockPathEnv)
	if e == "" {
		// If unset, fall back to default value
		e = defaults.SockPath
	}
	return "unix://" + e

}

func configureTransport(tr *http.Transport, proto, addr string) *http.Transport {
	if tr == nil {
		tr = &http.Transport{}
	}

	if proto == "unix" {
		// No need for compression in local communications.
		tr.DisableCompression = true
		tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
			return net.Dial(proto, addr)
		}
	} else {
		tr.Proxy = http.ProxyFromEnvironment
		tr.DialContext = (&net.Dialer{}).DialContext
	}

	return tr
}

// NewDefaultClient creates a client with default parameters connecting to UNIX domain socket.
func NewDefaultClient() (*Client, error) {
	return NewClient("")
}

// NewDefaultClientWithTimeout creates a client with default parameters connecting to UNIX
// domain socket and waits for cilium-agent availability.
func NewDefaultClientWithTimeout(timeout time.Duration) (*Client, error) {
	timeoutAfter := time.After(timeout)
	var c *Client
	var err error
	for {
		select {
		case <-timeoutAfter:
			return nil, fmt.Errorf("failed to create cilium agent client after %f seconds timeout: %s", timeout.Seconds(), err)
		default:
		}

		c, err = NewDefaultClient()
		if err != nil {
			time.Sleep(500 * time.Millisecond)
			continue
		}

		for {
			select {
			case <-timeoutAfter:
				return nil, fmt.Errorf("failed to create cilium agent client after %f seconds timeout: %s", timeout.Seconds(), err)
			default:
			}
			// This is an API call that we do to the cilium-agent to check
			// if it is up and running.
			_, err = c.Daemon.GetConfig(nil)
			if err != nil {
				time.Sleep(500 * time.Millisecond)
				continue
			}
			return c, nil
		}
	}
}

// NewClient creates a client for the given `host`.
// If host is nil then use SockPath provided by CILIUM_SOCK
// or the cilium default SockPath
func NewClient(host string) (*Client, error) {
	if host == "" {
		host = DefaultSockPath()
	}
	tmp := strings.SplitN(host, "://", 2)
	if len(tmp) != 2 {
		return nil, fmt.Errorf("invalid host format '%s'", host)
	}

	switch tmp[0] {
	case "tcp":
		if _, err := url.Parse("tcp://" + tmp[1]); err != nil {
			return nil, err
		}
		host = "http://" + tmp[1]
	case "unix":
		host = tmp[1]
	}

	transport := configureTransport(nil, tmp[0], host)
	httpClient := &http.Client{Transport: transport}
	clientTrans := runtime_client.NewWithClient(tmp[1], clientapi.DefaultBasePath,
		clientapi.DefaultSchemes, httpClient)
	return &Client{*clientapi.New(clientTrans, strfmt.Default)}, nil
}

// Hint tries to improve the error message displayed to the user.
func Hint(err error) error {
	if err == nil {
		return err
	}

	if err == context.DeadlineExceeded {
		return fmt.Errorf("Cilium API client timeout exceeded")
	}

	e, _ := url.PathUnescape(err.Error())
	if strings.Contains(err.Error(), defaults.SockPath) {
		return fmt.Errorf("%s\nIs the agent running?", e)
	}
	return fmt.Errorf("%s", e)
}

func timeSince(since time.Time) string {
	out := "never"
	if !since.IsZero() {
		// Poor man's implementtion of time.Truncate(). Can be refined
		// when we rebase to go 1.9
		t := time.Since(since)
		t -= t % time.Second
		out = t.String() + " ago"
	}

	return out
}

func stateUnhealthy(state string) bool {
	return state == models.StatusStateWarning ||
		state == models.StatusStateFailure
}

func statusUnhealthy(s *models.Status) bool {
	if s != nil {
		return stateUnhealthy(s.State)
	}
	return false
}

// FormatStatusResponseBrief writes a one-line status to the writer. If
// everything ok, this is "ok", otherwise a message of the form "error in ..."
func FormatStatusResponseBrief(w io.Writer, sr *models.StatusResponse) {
	msg := ""

	switch {
	case statusUnhealthy(sr.Kvstore):
		msg = fmt.Sprintf("kvstore: %s", sr.Kvstore.Msg)
	case statusUnhealthy(sr.ContainerRuntime):
		msg = fmt.Sprintf("container runtime: %s", sr.ContainerRuntime.Msg)
	case sr.Kubernetes != nil && stateUnhealthy(sr.Kubernetes.State):
		msg = fmt.Sprintf("kubernetes: %s", sr.Kubernetes.Msg)
	case statusUnhealthy(sr.Cilium):
		msg = fmt.Sprintf("cilium: %s", sr.Cilium.Msg)
	case sr.Cluster != nil && statusUnhealthy(sr.Cluster.CiliumHealth):
		msg = fmt.Sprintf("cilium-health: %s", sr.Cluster.CiliumHealth.Msg)
	}

	// Only bother looking at controller failures if everything else is ok
	if msg == "" {
		for _, ctrl := range sr.Controllers {
			if ctrl.Status == nil {
				continue
			}
			if ctrl.Status.LastFailureMsg != "" {
				msg = fmt.Sprintf("controller %s: %s",
					ctrl.Name, ctrl.Status.LastFailureMsg)
				break
			}
		}
	}

	if msg == "" {
		fmt.Fprintf(w, "OK\n")
	} else {
		fmt.Fprintf(w, "error in %s\n", msg)
	}
}

// FormatStatusResponse writes a StatusResponse as a string to the writer.
//
// The parameters 'allAddresses', 'allControllers', 'allNodes', respectively,
// cause all details about that aspect of the status to be printed to the
// terminal. For each of these, if they are false then only a summary will be
// printed, with perhaps some detail if there are errors.
func FormatStatusResponse(w io.Writer, sr *models.StatusResponse, allAddresses, allControllers, allNodes, allRedirects bool) {
	if sr.Kvstore != nil {
		fmt.Fprintf(w, "KVStore:\t%s\t%s\n", sr.Kvstore.State, sr.Kvstore.Msg)
	}
	if sr.ContainerRuntime != nil {
		fmt.Fprintf(w, "ContainerRuntime:\t%s\t%s\n",
			sr.ContainerRuntime.State, sr.ContainerRuntime.Msg)
	}
	if sr.Kubernetes != nil {
		fmt.Fprintf(w, "Kubernetes:\t%s\t%s\n", sr.Kubernetes.State, sr.Kubernetes.Msg)
		if sr.Kubernetes.State != models.K8sStatusStateDisabled {
			sort.Strings(sr.Kubernetes.K8sAPIVersions)
			fmt.Fprintf(w, "Kubernetes APIs:\t[\"%s\"]\n", strings.Join(sr.Kubernetes.K8sAPIVersions, "\", \""))
		}
	}
	if sr.Cilium != nil {
		fmt.Fprintf(w, "Cilium:\t%s\t%s\n", sr.Cilium.State, sr.Cilium.Msg)
	}

	if sr.Stale != nil {
		sortedProbes := make([]string, 0, len(sr.Stale))
		for probe := range sr.Stale {
			sortedProbes = append(sortedProbes, probe)
		}
		sort.Strings(sortedProbes)

		stalesStr := make([]string, 0, len(sr.Stale))
		for _, probe := range sortedProbes {
			stalesStr = append(stalesStr, fmt.Sprintf("%q since %s", probe, sr.Stale[probe]))
		}

		fmt.Fprintf(w, "Stale status:\t%s\n", strings.Join(stalesStr, ", "))
	}

	if nm := sr.NodeMonitor; nm != nil {
		fmt.Fprintf(w, "NodeMonitor:\tListening for events on %d CPUs with %dx%d of shared memory\n",
			nm.Cpus, nm.Npages, nm.Pagesize)
		if nm.Lost != 0 || nm.Unknown != 0 {
			fmt.Fprintf(w, "\t%d events lost, %d unknown notifications\n", nm.Lost, nm.Unknown)
		}
	} else {
		fmt.Fprintf(w, "NodeMonitor:\tDisabled\n")
	}

	if sr.Cluster != nil {
		if sr.Cluster.CiliumHealth != nil {
			ch := sr.Cluster.CiliumHealth
			fmt.Fprintf(w, "Cilium health daemon:\t%s\t%s\n", ch.State, ch.Msg)
		}
	}

	if sr.IPAM != nil {
		fmt.Fprintf(w, "IPAM:\t%s\n", sr.IPAM.Status)
		if allAddresses {
			fmt.Fprintf(w, "Allocated addresses:\n")
			out := []string{}
			for ip, owner := range sr.IPAM.Allocations {
				out = append(out, fmt.Sprintf("  %s (%s)", ip, owner))
			}
			sort.Strings(out)
			for _, line := range out {
				fmt.Fprintln(w, line)
			}
		}
	}

	if sr.Controllers != nil {
		nFailing, out := 0, []string{"  Name\tLast success\tLast error\tCount\tMessage\n"}
		for _, ctrl := range sr.Controllers {
			status := ctrl.Status
			if status == nil {
				continue
			}

			if status.ConsecutiveFailureCount > 0 {
				nFailing++
			} else if !allControllers {
				continue
			}

			failSince := timeSince(time.Time(status.LastFailureTimestamp))
			successSince := timeSince(time.Time(status.LastSuccessTimestamp))

			err := "no error"
			if status.LastFailureMsg != "" {
				err = status.LastFailureMsg
			}

			out = append(out, fmt.Sprintf("  %s\t%s\t%s\t%d\t%s\t\n",
				ctrl.Name, successSince, failSince, status.ConsecutiveFailureCount, err))
		}

		nOK := len(sr.Controllers) - nFailing
		fmt.Fprintf(w, "Controller Status:\t%d/%d healthy\n", nOK, len(sr.Controllers))
		if len(out) > 1 {
			tab := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
			sort.Strings(out)
			for _, s := range out {
				fmt.Fprint(tab, s)
			}
			tab.Flush()
		}

	}

	if sr.Proxy != nil {
		fmt.Fprintf(w, "Proxy Status:\tOK, ip %s, port-range %s\n",
			sr.Proxy.IP, sr.Proxy.PortRange)
	} else {
		fmt.Fprintf(w, "Proxy Status:\tNo managed proxy redirect\n")
	}
}
<|endoftext|>


<commit_before>package config

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"

	"github.com/moul/advanced-ssh-config/pkg/flexyaml"
	. "github.com/moul/advanced-ssh-config/pkg/logger"
)

const defaultSshConfigPath string = "~/.ssh/config"

// Config contains a list of Hosts sections and a Defaults section representing a configuration file
type Config struct {
	Hosts     map[string]Host `yaml:"hosts,omitempty,flow" json:"hosts"`
	Templates map[string]Host `yaml:"templates,omitempty,flow" json:"templates"`
	Defaults  Host            `yaml:"defaults,omitempty,flow" json:"defaults",omitempty`
	Includes  []string        `yaml:"includes,omitempty,flow" json:"includes",omitempty`

	includedFiles map[string]bool
	sshConfigPath string
}

// IncludedFiles returns the list of the included files
func (c *Config) IncludedFiles() []string {
	includedFiles := []string{}
	for file, _ := range c.includedFiles {
		includedFiles = append(includedFiles, file)
	}
	return includedFiles
}

// JsonString returns a string representing the JSON of a Config object
func (c *Config) JsonString() ([]byte, error) {
	output, err := json.MarshalIndent(c, "", "  ")
	return output, err
}

// computeHost returns a copy of the host with applied defaults, resolved inheritances and configured internal fields
func computeHost(host *Host, config *Config, name string, fullCompute bool) (*Host, error) {
	computedHost := NewHost(name)
	if host != nil {
		*computedHost = *host
	}

	// name internal field
	computedHost.name = name
	computedHost.inherited = make(map[string]bool, 0)
	// self is already inherited
	computedHost.inherited[name] = true

	// Inheritance
	// FIXME: allow deeper inheritance:
	//     currently not resolving inherited hosts
	//     we should resolve all inherited hosts and pass the
	//     currently resolved hosts to avoid computing an host twice
	for _, name := range host.Inherits {
		_, found := computedHost.inherited[name]
		if found {
			Logger.Debugf("Detected circular loop inheritance, skiping...")
			continue
		}
		computedHost.inherited[name] = true

		target, err := config.getHostByPath(name, false, false, true)
		if err != nil {
			Logger.Warnf("Cannot inherits from %q: %v", name, err)
			continue
		}
		computedHost.ApplyDefaults(target)
	}

	// fullCompute applies config.Defaults
	// config.Defaults should be applied when proxying
	// but should not when exporting .ssh/config file
	if fullCompute {
		// apply defaults based on "Host *"
		computedHost.ApplyDefaults(&config.Defaults)

		if computedHost.HostName == "" {
			computedHost.HostName = name
		}
		// expands variables in host
		// i.e: %h.some.zone -> {name}.some.zone
		hostname := strings.Replace(computedHost.HostName, "%h", "%n", -1)
		computedHost.HostName = computedHost.ExpandString(hostname)
	}

	return computedHost, nil
}

func (c *Config) getHostByName(name string, safe bool, compute bool, allowTemplate bool) (*Host, error) {
	if host, ok := c.Hosts[name]; ok {
		return computeHost(&host, c, name, compute)
	}

	for pattern, host := range c.Hosts {
		matched, err := path.Match(pattern, name)
		if err != nil {
			return nil, err
		}
		if matched {
			return computeHost(&host, c, name, compute)
		}
	}

	if allowTemplate {
		for pattern, template := range c.Templates {
			matched, err := path.Match(pattern, name)
			if err != nil {
				return nil, err
			}
			if matched {
				return computeHost(&template, c, name, compute)
			}
		}
	}

	if safe {
		host := NewHost(name)
		host.HostName = name
		return computeHost(host, c, name, compute)
	}

	return nil, fmt.Errorf("no such host: %s", name)
}

func (c *Config) getHostByPath(path string, safe bool, compute bool, allowTemplate bool) (*Host, error) {
	parts := strings.SplitN(path, "/", 2)

	host, err := c.getHostByName(parts[0], safe, compute, allowTemplate)
	if err != nil {
		return nil, err
	}

	if len(parts) > 1 {
		host.Gateways = []string{parts[1]}
	}

	return host, nil
}

// GetGatewaySafe returns gateway Host configuration, a gateway is like a Host, except, the host path is not resolved
func (c *Config) GetGatewaySafe(name string) *Host {
	host, err := c.getHostByName(name, true, true, false) // FIXME: fullCompute for gateway ?
	if err != nil {
		panic(err)
	}
	return host
}

// GetHost returns a matching host form Config hosts list
func (c *Config) GetHost(name string) (*Host, error) {
	return c.getHostByPath(name, false, true, false)
}

// GetHostSafe won't fail, in case the host is not found, it will returns a virtual host matching the pattern
func (c *Config) GetHostSafe(name string) *Host {
	host, err := c.getHostByPath(name, true, true, false)
	if err != nil {
		panic(err)
	}
	return host
}

// LoadConfig loads the content of an io.Reader source
func (c *Config) LoadConfig(source io.Reader) error {
	buf, err := ioutil.ReadAll(source)
	if err != nil {
		return err
	}
	err = flexyaml.Unmarshal(buf, &c)
	if err != nil {
		return err
	}
	c.applyMissingNames()
	return nil
}

func (c *Config) applyMissingNames() {
	for key, _ := range c.Hosts {
		host := c.Hosts[key]
		host.name = key
		c.Hosts[key] = host
	}
	for key, _ := range c.Templates {
		template := c.Templates[key]
		template.name = key
		template.isTemplate = true
		c.Templates[key] = template
	}
	c.Defaults.isDefault = true
}

// SaveSshConfig saves the configuration to ~/.ssh/config
func (c *Config) SaveSshConfig() error {
	if c.sshConfigPath == "" {
		return fmt.Errorf("no Config.sshConfigPath configured")
	}
	filepath, err := expandUser(c.sshConfigPath)
	if err != nil {
		return err
	}
	file, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer file.Close()
	Logger.Debugf("Writing SSH config file to %q", filepath)
	return c.WriteSshConfigTo(file)
}

// LoadFile loads the content of a configuration file in the Config object
func (c *Config) LoadFile(filename string) error {
	// Resolve '~' and '$HOME'
	filepath, err := expandUser(filename)
	if err != nil {
		return err
	}

	// Anti-loop protection
	if _, ok := c.includedFiles[filepath]; ok {
		return nil
	}
	c.includedFiles[filepath] = false

	Logger.Debugf("Loading config file '%s'", filepath)

	// Read file
	source, err := os.Open(filepath)
	if err != nil {
		return err
	}

	// Load config stream
	err = c.LoadConfig(source)
	if err != nil {
		return err
	}

	// Successful loading
	c.includedFiles[filepath] = true

	// Handling includes
	for _, include := range c.Includes {
		err = c.LoadFiles(include)
		if err != nil {
			return err
		}
	}

	return nil
}

// Loadfiles will try to glob the pattern and load each maching entries
func (c *Config) LoadFiles(pattern string) error {
	// Resolve '~' and '$HOME'
	expandedPattern, err := expandUser(pattern)
	if err != nil {
		return err
	}

	// Globbing
	filepaths, err := filepath.Glob(expandedPattern)
	if err != nil {
		return err
	}

	// Load files iteratively
	for _, filepath := range filepaths {
		err := c.LoadFile(filepath)
		if err != nil {
			return err
		}
	}

	return nil
}

// sortedNames returns the host names sorted alphabetically
func (c *Config) sortedNames() []string {
	names := sort.StringSlice{}
	for key, _ := range c.Hosts {
		names = append(names, key)
	}
	sort.Sort(names)
	return names
}

// ExportSshConfig returns a .ssh/config valid file containing assh configuration
func (c *Config) WriteSshConfigTo(w io.Writer) error {
	fmt.Fprintln(w, "# ssh config generated by advanced-ssh-config")
	// FIXME: add version
	fmt.Fprintln(w)

	fmt.Fprintln(w, "# host-based configuration")
	for _, name := range c.sortedNames() {
		host := c.Hosts[name]
		computedHost, err := computeHost(&host, c, name, false)
		if err != nil {
			return err
		}
		computedHost.WriteSshConfigTo(w)
		fmt.Fprintln(w)
	}

	fmt.Fprintln(w, "# global configuration")
	c.Defaults.name = "*"
	c.Defaults.WriteSshConfigTo(w)

	return nil
}

// New returns an instantiated Config object
func New() *Config {
	var config Config
	config.Hosts = make(map[string]Host)
	config.Templates = make(map[string]Host)
	config.includedFiles = make(map[string]bool)
	config.sshConfigPath = defaultSshConfigPath
	return &config
}

// Open returns a Config object loaded with standard configuration file paths
func Open() (*Config, error) {
	config := New()
	err := config.LoadFile("~/.ssh/assh.yml")
	if err != nil {
		return nil, err
	}
	return config, nil
}
<commit_msg>Added more debug<commit_after>package config

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"

	"github.com/moul/advanced-ssh-config/pkg/flexyaml"
	. "github.com/moul/advanced-ssh-config/pkg/logger"
)

const defaultSshConfigPath string = "~/.ssh/config"

// Config contains a list of Hosts sections and a Defaults section representing a configuration file
type Config struct {
	Hosts     map[string]Host `yaml:"hosts,omitempty,flow" json:"hosts"`
	Templates map[string]Host `yaml:"templates,omitempty,flow" json:"templates"`
	Defaults  Host            `yaml:"defaults,omitempty,flow" json:"defaults",omitempty`
	Includes  []string        `yaml:"includes,omitempty,flow" json:"includes",omitempty`

	includedFiles map[string]bool
	sshConfigPath string
}

// IncludedFiles returns the list of the included files
func (c *Config) IncludedFiles() []string {
	includedFiles := []string{}
	for file, _ := range c.includedFiles {
		includedFiles = append(includedFiles, file)
	}
	return includedFiles
}

// JsonString returns a string representing the JSON of a Config object
func (c *Config) JsonString() ([]byte, error) {
	output, err := json.MarshalIndent(c, "", "  ")
	return output, err
}

// computeHost returns a copy of the host with applied defaults, resolved inheritances and configured internal fields
func computeHost(host *Host, config *Config, name string, fullCompute bool) (*Host, error) {
	computedHost := NewHost(name)
	if host != nil {
		*computedHost = *host
	}

	// name internal field
	computedHost.name = name
	computedHost.inherited = make(map[string]bool, 0)
	// self is already inherited
	computedHost.inherited[name] = true

	// Inheritance
	// FIXME: allow deeper inheritance:
	//     currently not resolving inherited hosts
	//     we should resolve all inherited hosts and pass the
	//     currently resolved hosts to avoid computing an host twice
	for _, name := range host.Inherits {
		_, found := computedHost.inherited[name]
		if found {
			Logger.Debugf("Detected circular loop inheritance, skiping...")
			continue
		}
		computedHost.inherited[name] = true

		target, err := config.getHostByPath(name, false, false, true)
		if err != nil {
			Logger.Warnf("Cannot inherits from %q: %v", name, err)
			continue
		}
		computedHost.ApplyDefaults(target)
	}

	// fullCompute applies config.Defaults
	// config.Defaults should be applied when proxying
	// but should not when exporting .ssh/config file
	if fullCompute {
		// apply defaults based on "Host *"
		computedHost.ApplyDefaults(&config.Defaults)

		if computedHost.HostName == "" {
			computedHost.HostName = name
		}
		// expands variables in host
		// i.e: %h.some.zone -> {name}.some.zone
		hostname := strings.Replace(computedHost.HostName, "%h", "%n", -1)
		computedHost.HostName = computedHost.ExpandString(hostname)
	}

	return computedHost, nil
}

func (c *Config) getHostByName(name string, safe bool, compute bool, allowTemplate bool) (*Host, error) {
	if host, ok := c.Hosts[name]; ok {
		Logger.Debugf("getHostByName direct matching: %q", name)
		return computeHost(&host, c, name, compute)
	}

	for pattern, host := range c.Hosts {
		matched, err := path.Match(pattern, name)
		if err != nil {
			return nil, err
		}
		if matched {
			Logger.Debugf("getHostByName pattern matching: %q => %q", pattern, name)
			return computeHost(&host, c, name, compute)
		}
	}

	if allowTemplate {
		for pattern, template := range c.Templates {
			matched, err := path.Match(pattern, name)
			if err != nil {
				return nil, err
			}
			if matched {
				return computeHost(&template, c, name, compute)
			}
		}
	}

	if safe {
		host := NewHost(name)
		host.HostName = name
		return computeHost(host, c, name, compute)
	}

	return nil, fmt.Errorf("no such host: %s", name)
}

func (c *Config) getHostByPath(path string, safe bool, compute bool, allowTemplate bool) (*Host, error) {
	parts := strings.SplitN(path, "/", 2)

	host, err := c.getHostByName(parts[0], safe, compute, allowTemplate)
	if err != nil {
		return nil, err
	}

	if len(parts) > 1 {
		host.Gateways = []string{parts[1]}
	}

	return host, nil
}

// GetGatewaySafe returns gateway Host configuration, a gateway is like a Host, except, the host path is not resolved
func (c *Config) GetGatewaySafe(name string) *Host {
	host, err := c.getHostByName(name, true, true, false) // FIXME: fullCompute for gateway ?
	if err != nil {
		panic(err)
	}
	return host
}

// GetHost returns a matching host form Config hosts list
func (c *Config) GetHost(name string) (*Host, error) {
	return c.getHostByPath(name, false, true, false)
}

// GetHostSafe won't fail, in case the host is not found, it will returns a virtual host matching the pattern
func (c *Config) GetHostSafe(name string) *Host {
	host, err := c.getHostByPath(name, true, true, false)
	if err != nil {
		panic(err)
	}
	return host
}

// LoadConfig loads the content of an io.Reader source
func (c *Config) LoadConfig(source io.Reader) error {
	buf, err := ioutil.ReadAll(source)
	if err != nil {
		return err
	}
	err = flexyaml.Unmarshal(buf, &c)
	if err != nil {
		return err
	}
	c.applyMissingNames()
	return nil
}

func (c *Config) applyMissingNames() {
	for key, _ := range c.Hosts {
		host := c.Hosts[key]
		host.name = key
		c.Hosts[key] = host
	}
	for key, _ := range c.Templates {
		template := c.Templates[key]
		template.name = key
		template.isTemplate = true
		c.Templates[key] = template
	}
	c.Defaults.isDefault = true
}

// SaveSshConfig saves the configuration to ~/.ssh/config
func (c *Config) SaveSshConfig() error {
	if c.sshConfigPath == "" {
		return fmt.Errorf("no Config.sshConfigPath configured")
	}
	filepath, err := expandUser(c.sshConfigPath)
	if err != nil {
		return err
	}
	file, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer file.Close()
	Logger.Debugf("Writing SSH config file to %q", filepath)
	return c.WriteSshConfigTo(file)
}

// LoadFile loads the content of a configuration file in the Config object
func (c *Config) LoadFile(filename string) error {
	// Resolve '~' and '$HOME'
	filepath, err := expandUser(filename)
	if err != nil {
		return err
	}

	// Anti-loop protection
	if _, ok := c.includedFiles[filepath]; ok {
		return nil
	}
	c.includedFiles[filepath] = false

	Logger.Debugf("Loading config file '%s'", filepath)

	// Read file
	source, err := os.Open(filepath)
	if err != nil {
		return err
	}

	// Load config stream
	err = c.LoadConfig(source)
	if err != nil {
		return err
	}

	// Successful loading
	c.includedFiles[filepath] = true

	// Handling includes
	for _, include := range c.Includes {
		err = c.LoadFiles(include)
		if err != nil {
			return err
		}
	}

	Logger.Debugf("Loaded config file '%s'", filepath)

	return nil
}

// Loadfiles will try to glob the pattern and load each maching entries
func (c *Config) LoadFiles(pattern string) error {
	// Resolve '~' and '$HOME'
	expandedPattern, err := expandUser(pattern)
	if err != nil {
		return err
	}

	// Globbing
	filepaths, err := filepath.Glob(expandedPattern)
	if err != nil {
		return err
	}

	// Load files iteratively
	for _, filepath := range filepaths {
		err := c.LoadFile(filepath)
		if err != nil {
			return err
		}
	}

	return nil
}

// sortedNames returns the host names sorted alphabetically
func (c *Config) sortedNames() []string {
	names := sort.StringSlice{}
	for key, _ := range c.Hosts {
		names = append(names, key)
	}
	sort.Sort(names)
	return names
}

// ExportSshConfig returns a .ssh/config valid file containing assh configuration
func (c *Config) WriteSshConfigTo(w io.Writer) error {
	fmt.Fprintln(w, "# ssh config generated by advanced-ssh-config")
	// FIXME: add version
	fmt.Fprintln(w)

	fmt.Fprintln(w, "# host-based configuration")
	for _, name := range c.sortedNames() {
		host := c.Hosts[name]
		computedHost, err := computeHost(&host, c, name, false)
		if err != nil {
			return err
		}
		computedHost.WriteSshConfigTo(w)
		fmt.Fprintln(w)
	}

	fmt.Fprintln(w, "# global configuration")
	c.Defaults.name = "*"
	c.Defaults.WriteSshConfigTo(w)

	return nil
}

// New returns an instantiated Config object
func New() *Config {
	var config Config
	config.Hosts = make(map[string]Host)
	config.Templates = make(map[string]Host)
	config.includedFiles = make(map[string]bool)
	config.sshConfigPath = defaultSshConfigPath
	return &config
}

// Open returns a Config object loaded with standard configuration file paths
func Open() (*Config, error) {
	config := New()
	err := config.LoadFile("~/.ssh/assh.yml")
	if err != nil {
		return nil, err
	}
	return config, nil
}
<|endoftext|>


<commit_before>package serve

import (
	"github.com/gomqtt/packet"
	"github.com/trafero/tstack/auth"
	"io"
	"log"
	"net"
	"sync"
	"time"
	"unsafe"
)

type client struct {
	broker                *Broker
	conn                  net.Conn
	auth                  auth.Auth
	cleanSession          bool
	processedConnect      bool
	clientid              string
	username              string
	rights                string
	will                  *packet.Message
	keepalive             uint16
	encoder               *packet.Encoder
	decoder               *packet.Decoder
	subscriptions         map[string]packet.Subscription // Mapped by topic
	mutex                 *sync.Mutex
	connectionMutex       *sync.Mutex
	packetIDCounter       uint16
	inboundInTransit      map[uint16]packet.Message // QOS 2 messages to be received (and passeed to broker)
	outboundInTransit     map[uint16]packet.Message // QOS 2 messages to be sent
	internalClientCounter int                       // For internal client ids (MQTT-3.1.3-6)

}

func NewClient(a auth.Auth, b *Broker, c net.Conn) *client {
	return &client{
		auth:              a,
		broker:            b,
		conn:              c,
		processedConnect:  false,
		mutex:             &sync.Mutex{},
		connectionMutex:   &sync.Mutex{},
		inboundInTransit:  make(map[uint16]packet.Message),
		outboundInTransit: make(map[uint16]packet.Message),
		subscriptions:     make(map[string]packet.Subscription),
		packetIDCounter:   0,
		keepalive:         0, // in seconds
	}
}

func (c *client) HandleConnection() {
	var err error
	var pkt packet.Packet
	c.decoder = packet.NewDecoder(c.conn)
	c.encoder = packet.NewEncoder(c.conn)
	for {
		pkt, err = c.decoder.Read()
		if err != nil {
			if err == io.EOF {
				log.Println("Connection disconnected")
			}
			c.conn.Close()
			break
		}

		// Connection timeout
		c.setReadDeadline()

		switch pkt := pkt.(type) {
		default:
			log.Println("Unknown MQTT packet received")
			c.conn.Close()
		case *packet.ConnectPacket:
			c.processConnect(pkt)
		case *packet.PublishPacket:
			go c.processPublish(pkt)
		case *packet.SubscribePacket:
			go c.processSubscribe(pkt)
		case *packet.UnsubscribePacket:
			go c.processUnsubscribe(pkt)
		case *packet.PubackPacket:
			go c.processPuback(pkt)
		case *packet.PubcompPacket:
			go c.processComp(pkt)
		case *packet.PubrecPacket:
			go c.processPubrec(pkt)
		case *packet.PubrelPacket:
			go c.processPubrel(pkt)
		case *packet.PingreqPacket:
			c.processPing(pkt)
		case *packet.DisconnectPacket:
			c.processDisconnect(pkt)
		}
	}
	// Send out with last will. Last will set to nill if never set or
	// client send disconnect
	if c.will != nil {
		c.broker.deliver(c.will)
	}

	// Remove the client from the list
	if c.keepalive != 1 {
		c.broker.RemoveClient(c)
	}
}

/*
 * CONNECT – Client requests a connection to a Server (3.1)
 */
func (c *client) processConnect(pkt *packet.ConnectPacket) {
	if c.processedConnect {
		log.Println("Connect packet received for a second time on same connection")
		// No acknowledgement, just disconnect
		c.conn.Close()
	}
	c.processedConnect = true
	if pkt.Version != 4 {
		c.writeConnack(packet.ErrInvalidProtocolVersion)
		log.Println("Unsupported MQTT version")
		c.conn.Close()
		return
	}
	if c.auth.Authenticate(pkt.Username, pkt.Password) == false {
		c.writeConnack(packet.ErrNotAuthorized)
		log.Printf("User %s could not be authenticated", pkt.Username)
		c.conn.Close()
		return
	}
	// MQTT-3.1.3-8
	if pkt.ClientID == "" && pkt.CleanSession == false {
		c.writeConnack(packet.ErrIdentifierRejected)
		c.conn.Close()
		return
	}
	// MQTT-3.1.3-6
	if pkt.ClientID == "" {
		// Must be a clean session, which we know already from above
		pkt.ClientID = c.newInternalClientID()
	}

	// TODO check Clinet ID is not already in use
	c.clientid = pkt.ClientID

	c.cleanSession = pkt.CleanSession
	c.username = pkt.Username
	c.rights = c.auth.Rights(c.username)

	if pkt.Will != nil && !matches(c.rights, pkt.Will.Topic) {
		log.Println("Client not authorized to write this will")
	} else {
		c.will = pkt.Will // May be nil but that is ok
	}
	c.keepalive = pkt.KeepAlive
	c.setReadDeadline()
	c.broker.AddClient(c)
	c.writeConnack(packet.ConnectionAccepted)
}

/*
* CONNACK – Acknowledge connection request (3.2)
 */
func (c *client) writeConnack(code packet.ConnackCode) {
	connack := packet.NewConnackPacket()
	connack.SessionPresent = false
	connack.ReturnCode = code
	c.sendPacket(connack)

	// Now we are connected, check if there's any unfinished business
	// Unfinished packets
	for packetID, msg := range c.outboundInTransit {
		c.resend(packetID, &msg)
	}
}

/*
 * PUBLISH – Publish message (3.3)
 */
func (c *client) processPublish(pkt *packet.PublishPacket) {
	if !matches(c.rights, pkt.Message.Topic) {
		// TODO send code back?
		log.Printf("Not authorized to publish to topic %s", pkt.Message.Topic)
		// Give them a hint
		c.conn.Close()
	} else {
		// QOS 1

		switch pkt.Message.QOS {

		case packet.QOSAtMostOnce:
			// QOS 0
			c.broker.deliver(&pkt.Message)

		case packet.QOSAtLeastOnce:
			// QOS 1
			c.broker.deliver(&pkt.Message)
			p := packet.NewPubackPacket()
			p.PacketID = pkt.PacketID
			c.sendPacket(p)

		case packet.QOSExactlyOnce:
			// QOS 2
			c.mutex.Lock()
			c.inboundInTransit[pkt.PacketID] = pkt.Message
			c.mutex.Unlock()
			p := packet.NewPubrecPacket()
			p.PacketID = pkt.PacketID
			c.sendPacket(p)
			// Send it back to the main switch for a Pubrel

		default:
			// Unknown QOS
			log.Printf("Unknown QOS level")
			c.conn.Close()
		}
	}
}

/*
 *  PUBACK – Publish acknowledgement (3.4)
 */
func (c *client) processPuback(pkt *packet.PubackPacket) {
	delete(c.outboundInTransit, pkt.PacketID)
}

/*
 * PUBREC – Publish received (QoS 2 publish received, part 1) (3.5)
 */
func (c *client) processPubrec(pkt *packet.PubrecPacket) {
	// Only send resonse if we have the message
	if _, ok := c.outboundInTransit[pkt.PacketID]; !ok {
		log.Println("Pubrec for a message that I do not have")
		c.conn.Close()
		return
	}
	p := packet.NewPubrelPacket()
	p.PacketID = pkt.PacketID
	c.sendPacket(p)
}

/*
 * PUBREL – Publish release (QoS 2 publish received, part 2) (3.6)
 */
func (c *client) processPubrel(pkt *packet.PubrelPacket) {
	msg := c.inboundInTransit[pkt.PacketID]

	if unsafe.Sizeof(msg) != 0 {
		// msg is not an empty stuct
		c.broker.deliver(&msg)
		delete(c.inboundInTransit, pkt.PacketID)
		p := packet.NewPubcompPacket()
		p.PacketID = pkt.PacketID
		c.sendPacket(p)
	}

}

/*
 * PUBCOMP – Publish complete (QoS 2 publish received, part 3) (3.7)
 */
func (c *client) processComp(pkt *packet.PubcompPacket) {
	delete(c.outboundInTransit, pkt.PacketID)
}

/*
 * SUBSCRIBE - Subscribe to topics (3.8)
 */
func (c *client) processSubscribe(pkt *packet.SubscribePacket) {
	suback := packet.NewSubackPacket()
	suback.PacketID = pkt.PacketID

	for _, s := range pkt.Subscriptions {
		if !matches(c.rights, s.Topic) {
			log.Printf("Not authorized to subscribe to topic %s", s.Topic)
			suback.ReturnCodes = append(suback.ReturnCodes, 0x80) // sec 3.9.3 of spec
		} else {
			c.mutex.Lock()
			c.subscriptions[s.Topic] = s
			c.mutex.Unlock()
			suback.ReturnCodes = append(suback.ReturnCodes, s.QOS)
			// Send any retained messages for this subscription
			c.sendRetained(s.Topic, s.QOS)
		}
	}
	c.sendPacket(suback) // SUBACK 3.9
}

/*
 * UNSUBSCRIBE – Unsubscribe from topics (3.10)
 */
func (c *client) processUnsubscribe(pkt *packet.UnsubscribePacket) {
	c.mutex.Lock()
	for _, t := range pkt.Topics {
		delete(c.subscriptions, t)
	}
	c.mutex.Unlock()
	p := packet.NewUnsubackPacket()
	p.PacketID = pkt.PacketID
	c.sendPacket(p) // UNSUBACK 3.11
}

/*
 * PINGREQ – PING request (3.12)
 */
func (c *client) processPing(pkt *packet.PingreqPacket) {
	p := packet.NewPingrespPacket()
	c.sendPacket(p) // PINGRESP 3.13
}

/*
 * DISCONNECT – Disconnect notification(3.14)
 */
func (c *client) processDisconnect(pkt *packet.DisconnectPacket) {
	//discard Will
	c.will = nil
	// Close connection if the client has not already done so
	c.conn.Close()
}

func (c *client) send(msg *packet.Message, qos byte, retain bool) {
	p := packet.NewPublishPacket()

	// Re-pack the messgae to use the reciever's QoS
	// and set retain to false
	m := &packet.Message{
		Topic:   msg.Topic,
		Payload: msg.Payload,
		QOS:     qos,
		Retain:  retain,
	}
	p.Message = *m
	// TODO set to true if this is a retry
	p.Dup = false
	// Sec. 2.3.1
	if qos > 0 {
		p.PacketID = c.newPacketID()
		c.outboundInTransit[p.PacketID] = p.Message
	}
	c.sendPacket(p)
}

func (c *client) resend(packetID uint16, msg *packet.Message) {
	log.Printf("Re-sending message %d", packetID)
	p := packet.NewPublishPacket()
	p.Message = *msg
	p.Dup = true
	p.PacketID = packetID
	c.sendPacket(p)
}

func (c *client) sendRetained(topic string, qos uint8) {

	// Retained messages [MQTT-3.3.1-6]
	for t, msg := range c.broker.retained {
		if matches(topic, t) {
			// Retain flag set to 1 [MQTT-3.3.1-8]
			c.send(msg, qos, true)
		}
	}
}

func (c *client) setReadDeadline() {
	if c.keepalive > 0 {
		if err := c.conn.SetReadDeadline(time.Now().Add(time.Duration(c.keepalive) * time.Second)); err != nil {
			log.Printf("Error setting read deadline on network connection", err)
		}
	}
}

func (c *client) sendPacket(p packet.Packet) {
	c.connectionMutex.Lock()
	c.encoder.Write(p)
	c.encoder.Flush()
	c.connectionMutex.Unlock()
}

func (c *client) newInternalClientID() string {
	c.internalClientCounter++
	return "internalClient" + string(c.internalClientCounter)

}

func (c *client) newPacketID() uint16 {
	c.packetIDCounter++
	return c.packetIDCounter
}
<commit_msg>Removed decoder from client object<commit_after>package serve

import (
	"github.com/gomqtt/packet"
	"github.com/trafero/tstack/auth"
	"io"
	"log"
	"net"
	"sync"
	"time"
	"unsafe"
)

type client struct {
	broker           *Broker
	conn             net.Conn
	auth             auth.Auth
	cleanSession     bool
	processedConnect bool
	clientid         string
	username         string
	rights           string
	will             *packet.Message
	keepalive        uint16
	encoder          *packet.Encoder
	// decoder               *packet.Decoder
	subscriptions         map[string]packet.Subscription // Mapped by topic
	mutex                 *sync.Mutex
	connectionMutex       *sync.Mutex
	packetIDCounter       uint16
	inboundInTransit      map[uint16]packet.Message // QOS 2 messages to be received (and passeed to broker)
	outboundInTransit     map[uint16]packet.Message // QOS 2 messages to be sent
	internalClientCounter int                       // For internal client ids (MQTT-3.1.3-6)

}

func NewClient(a auth.Auth, b *Broker, c net.Conn) *client {
	return &client{
		auth:              a,
		broker:            b,
		conn:              c,
		processedConnect:  false,
		mutex:             &sync.Mutex{},
		connectionMutex:   &sync.Mutex{},
		inboundInTransit:  make(map[uint16]packet.Message),
		outboundInTransit: make(map[uint16]packet.Message),
		subscriptions:     make(map[string]packet.Subscription),
		packetIDCounter:   0,
		keepalive:         0, // in seconds
	}
}

func (c *client) HandleConnection() {
	var err error
	var pkt packet.Packet

	defer c.conn.Close()

	c.encoder = packet.NewEncoder(c.conn)

	decoder := packet.NewDecoder(c.conn) // Only required in this loop
	for {
		pkt, err = decoder.Read()
		if err != nil {
			if err == io.EOF {
				log.Println("Connection disconnected")
			}
			c.conn.Close()
			break
		}

		// Connection timeout
		c.setReadDeadline()

		switch pkt := pkt.(type) {
		default:
			log.Println("Unknown MQTT packet received")
			c.conn.Close()
		case *packet.ConnectPacket:
			c.processConnect(pkt)
		case *packet.PublishPacket:
			go c.processPublish(pkt)
		case *packet.SubscribePacket:
			go c.processSubscribe(pkt)
		case *packet.UnsubscribePacket:
			go c.processUnsubscribe(pkt)
		case *packet.PubackPacket:
			go c.processPuback(pkt)
		case *packet.PubcompPacket:
			go c.processComp(pkt)
		case *packet.PubrecPacket:
			go c.processPubrec(pkt)
		case *packet.PubrelPacket:
			go c.processPubrel(pkt)
		case *packet.PingreqPacket:
			c.processPing(pkt)
		case *packet.DisconnectPacket:
			c.processDisconnect(pkt)
		}
	}
	// Send out with last will. Last will set to nill if never set or
	// client send disconnect
	if c.will != nil {
		c.broker.deliver(c.will)
	}

	// Remove the client from the list
	if c.keepalive != 1 {
		c.broker.RemoveClient(c)
	}
}

/*
 * CONNECT – Client requests a connection to a Server (3.1)
 */
func (c *client) processConnect(pkt *packet.ConnectPacket) {
	if c.processedConnect {
		log.Println("Connect packet received for a second time on same connection")
		// No acknowledgement, just disconnect
		c.conn.Close()
	}
	c.processedConnect = true
	if pkt.Version != 4 {
		c.writeConnack(packet.ErrInvalidProtocolVersion)
		log.Println("Unsupported MQTT version")
		c.conn.Close()
		return
	}

	if c.auth.Authenticate(pkt.Username, pkt.Password) == false {
		c.writeConnack(packet.ErrNotAuthorized)
		log.Printf("User %s could not be authenticated", pkt.Username)
		c.conn.Close()
		return
	}

	// MQTT-3.1.3-8
	if pkt.ClientID == "" && pkt.CleanSession == false {
		c.writeConnack(packet.ErrIdentifierRejected)
		c.conn.Close()
		return
	}
	// MQTT-3.1.3-6
	if pkt.ClientID == "" {
		// Must be a clean session, which we know already from above
		pkt.ClientID = c.newInternalClientID()
	}

	// TODO check Clinet ID is not already in use
	c.clientid = pkt.ClientID

	c.cleanSession = pkt.CleanSession
	c.username = pkt.Username
	c.rights = c.auth.Rights(c.username)

	if pkt.Will != nil && !matches(c.rights, pkt.Will.Topic) {
		log.Println("Client not authorized to write this will")
	} else {
		c.will = pkt.Will // May be nil but that is ok
	}
	c.keepalive = pkt.KeepAlive
	c.setReadDeadline()
	c.broker.AddClient(c)
	c.writeConnack(packet.ConnectionAccepted)
}

/*
* CONNACK – Acknowledge connection request (3.2)
 */
func (c *client) writeConnack(code packet.ConnackCode) {
	connack := packet.NewConnackPacket()
	connack.SessionPresent = false
	connack.ReturnCode = code
	c.sendPacket(connack)

	// Now we are connected, check if there's any unfinished business
	// Unfinished packets
	for packetID, msg := range c.outboundInTransit {
		c.resend(packetID, &msg)
	}
}

/*
 * PUBLISH – Publish message (3.3)
 */
func (c *client) processPublish(pkt *packet.PublishPacket) {
	if !matches(c.rights, pkt.Message.Topic) {
		// TODO send code back?
		log.Printf("Not authorized to publish to topic %s", pkt.Message.Topic)
		// Give them a hint
		c.conn.Close()
	} else {
		// QOS 1

		switch pkt.Message.QOS {

		case packet.QOSAtMostOnce:
			// QOS 0
			c.broker.deliver(&pkt.Message)

		case packet.QOSAtLeastOnce:
			// QOS 1
			c.broker.deliver(&pkt.Message)
			p := packet.NewPubackPacket()
			p.PacketID = pkt.PacketID
			c.sendPacket(p)

		case packet.QOSExactlyOnce:
			// QOS 2
			c.mutex.Lock()
			c.inboundInTransit[pkt.PacketID] = pkt.Message
			c.mutex.Unlock()
			p := packet.NewPubrecPacket()
			p.PacketID = pkt.PacketID
			c.sendPacket(p)
			// Send it back to the main switch for a Pubrel

		default:
			// Unknown QOS
			log.Printf("Unknown QOS level")
			c.conn.Close()
		}
	}
}

/*
 *  PUBACK – Publish acknowledgement (3.4)
 */
func (c *client) processPuback(pkt *packet.PubackPacket) {
	delete(c.outboundInTransit, pkt.PacketID)
}

/*
 * PUBREC – Publish received (QoS 2 publish received, part 1) (3.5)
 */
func (c *client) processPubrec(pkt *packet.PubrecPacket) {
	// Only send resonse if we have the message
	if _, ok := c.outboundInTransit[pkt.PacketID]; !ok {
		log.Println("Pubrec for a message that I do not have")
		c.conn.Close()
		return
	}
	p := packet.NewPubrelPacket()
	p.PacketID = pkt.PacketID
	c.sendPacket(p)
}

/*
 * PUBREL – Publish release (QoS 2 publish received, part 2) (3.6)
 */
func (c *client) processPubrel(pkt *packet.PubrelPacket) {
	msg := c.inboundInTransit[pkt.PacketID]

	if unsafe.Sizeof(msg) != 0 {
		// msg is not an empty stuct
		c.broker.deliver(&msg)
		delete(c.inboundInTransit, pkt.PacketID)
		p := packet.NewPubcompPacket()
		p.PacketID = pkt.PacketID
		c.sendPacket(p)
	}

}

/*
 * PUBCOMP – Publish complete (QoS 2 publish received, part 3) (3.7)
 */
func (c *client) processComp(pkt *packet.PubcompPacket) {
	delete(c.outboundInTransit, pkt.PacketID)
}

/*
 * SUBSCRIBE - Subscribe to topics (3.8)
 */
func (c *client) processSubscribe(pkt *packet.SubscribePacket) {
	suback := packet.NewSubackPacket()
	suback.PacketID = pkt.PacketID

	for _, s := range pkt.Subscriptions {
		if !matches(c.rights, s.Topic) {
			log.Printf("Not authorized to subscribe to topic %s", s.Topic)
			suback.ReturnCodes = append(suback.ReturnCodes, 0x80) // sec 3.9.3 of spec
		} else {
			c.mutex.Lock()
			c.subscriptions[s.Topic] = s
			c.mutex.Unlock()
			suback.ReturnCodes = append(suback.ReturnCodes, s.QOS)
			// Send any retained messages for this subscription
			c.sendRetained(s.Topic, s.QOS)
		}
	}
	c.sendPacket(suback) // SUBACK 3.9
}

/*
 * UNSUBSCRIBE – Unsubscribe from topics (3.10)
 */
func (c *client) processUnsubscribe(pkt *packet.UnsubscribePacket) {
	c.mutex.Lock()
	for _, t := range pkt.Topics {
		delete(c.subscriptions, t)
	}
	c.mutex.Unlock()
	p := packet.NewUnsubackPacket()
	p.PacketID = pkt.PacketID
	c.sendPacket(p) // UNSUBACK 3.11
}

/*
 * PINGREQ – PING request (3.12)
 */
func (c *client) processPing(pkt *packet.PingreqPacket) {
	p := packet.NewPingrespPacket()
	c.sendPacket(p) // PINGRESP 3.13
}

/*
 * DISCONNECT – Disconnect notification(3.14)
 */
func (c *client) processDisconnect(pkt *packet.DisconnectPacket) {
	//discard Will
	c.will = nil
	// Close connection if the client has not already done so
	c.conn.Close()
}

func (c *client) send(msg *packet.Message, qos byte, retain bool) {
	p := packet.NewPublishPacket()

	// Re-pack the messgae to use the reciever's QoS
	// and set retain to false
	m := &packet.Message{
		Topic:   msg.Topic,
		Payload: msg.Payload,
		QOS:     qos,
		Retain:  retain,
	}
	p.Message = *m
	p.Dup = false
	// Sec. 2.3.1
	if qos > 0 {
		p.PacketID = c.newPacketID()
		c.outboundInTransit[p.PacketID] = p.Message
	}
	c.sendPacket(p)
}

func (c *client) resend(packetID uint16, msg *packet.Message) {
	log.Printf("Re-sending message %d", packetID)
	p := packet.NewPublishPacket()
	p.Message = *msg
	p.Dup = true
	p.PacketID = packetID
	c.sendPacket(p)
}

func (c *client) sendRetained(topic string, qos uint8) {

	// Retained messages [MQTT-3.3.1-6]
	for t, msg := range c.broker.retained {
		if matches(topic, t) {
			// Retain flag set to 1 [MQTT-3.3.1-8]
			c.send(msg, qos, true)
		}
	}
}

func (c *client) setReadDeadline() {
	if c.keepalive > 0 {
		if err := c.conn.SetReadDeadline(time.Now().Add(time.Duration(c.keepalive) * time.Second)); err != nil {
			log.Printf("Error setting read deadline on network connection", err)
		}
	}
}

func (c *client) sendPacket(p packet.Packet) {
	c.connectionMutex.Lock()
	c.encoder.Write(p)
	c.encoder.Flush()
	c.connectionMutex.Unlock()
}

func (c *client) newInternalClientID() string {
	c.internalClientCounter++
	return "internalClient" + string(c.internalClientCounter)

}

func (c *client) newPacketID() uint16 {
	c.packetIDCounter++
	return c.packetIDCounter
}
<|endoftext|>


<commit_before>package crank

import (
	"fmt"
	"log"
	"os"
	"syscall"
	"time"
)

// Manager manages multiple process groups
type Manager struct {
	build           string
	configPath      string
	config          *ProcessConfig
	socket          *os.File
	processCount    int
	events          chan Event
	actions         chan Action
	childs          processSet
	shuttingDown    bool
	startingTracker *TimeoutTracker
	stoppingTracker *TimeoutTracker
	startingReply   *StartReply
	startingDone    chan<- error
}

func NewManager(build string, configPath string, socket *os.File) *Manager {
	config, err := loadProcessConfig(configPath)
	if err != nil {
		log.Println("Could not load config file: ", err)
	}

	manager := &Manager{
		build:           build,
		configPath:      configPath,
		config:          config,
		socket:          socket,
		events:          make(chan Event),
		actions:         make(chan Action),
		childs:          make(processSet),
		startingTracker: NewTimeoutTracker(),
		stoppingTracker: NewTimeoutTracker(),
	}
	return manager
}

// Run starts the event loop for the manager process
func (self *Manager) Run() {
	if len(self.config.Command) == 0 {
		self.log("Ignoring process start, command is missing")
	} else {
		err := self.startProcess(self.config)
		if err != nil {
			self.log("Failed to start the process: %s", err)
			return
		}
	}

	go self.startingTracker.Run()
	go self.stoppingTracker.Run()

	for {
		select {
		// actions
		case a := <-self.actions:
			switch action := a.(type) {
			case ShutdownAction:
				if self.shuttingDown {
					self.log("Already shutting down")
					continue
				}
				self.log("Shutting down")
				self.shuttingDown = true
				self.childs.each(func(p *Process) {
					self.stopProcess(p)
				})
				if self.childs.len() == 0 {
					goto exit
				}
			case *StartAction:
				query := action.query
				//reply := action.reply -- not used

				if self.shuttingDown {
					err := fmt.Errorf("Ignore start, manager is shutting down")
					self.log(err.Error())
					action.done <- err
					continue
				}
				if self.childs.starting() != nil {
					err := fmt.Errorf("Ignore start, new process is already being started")
					self.log(err.Error())
					action.done <- err
					continue
				}

				config := self.config.clone()

				if len(query.Command) > 0 {
					config.Command = query.Command
				}

				if config.Cwd != "" {
					config.Cwd = query.Cwd
				}

				if query.StartTimeout > 0 {
					config.StartTimeout = time.Duration(query.StartTimeout) * time.Second
				}

				if query.StopTimeout > 0 {
					config.StopTimeout = time.Duration(query.StopTimeout) * time.Second
				}

				err := self.startProcess(config)
				if err != nil {
					self.log("Failed to start the process: %s", err)
					action.done <- err
					continue
				}

				if query.Wait {
					self.log("RPC waiting for the process to start")
					self.startingReply = action.reply
					self.startingDone = action.done
				} else {
					action.done <- nil
				}
			case *InfoAction:
				//query := action.query -- not used
				reply := action.reply

				reply.Info = GetInfo(self.build)

				action.done <- nil
			case *PsAction:
				query := action.query
				reply := action.reply
				ps := self.childs

				if query.Pid > 0 {
					ps = ps.choose(func(p *Process, _ ProcessState) bool {
						return p.Pid() == query.Pid
					})
				}

				if query.Starting || query.Ready || query.Stopping {
					ps = ps.choose(func(p *Process, state ProcessState) bool {
						if query.Starting && (state == PROCESS_STARTING) {
							return true
						}
						if query.Ready && (state == PROCESS_READY) {
							return true
						}
						if query.Stopping && (state == PROCESS_STOPPING) {
							return true
						}
						return false
					})
				}

				reply.PS = make([]*ProcessInfo, 0, ps.len())
				for p, state := range ps {
					reply.PS = append(reply.PS, &ProcessInfo{p.Pid(), p.id, state.String(), p.config.Cwd, p.config.Command})
				}

				action.done <- nil
			case *KillAction:
				query := action.query
				//reply := action.reply -- not used

				var sig syscall.Signal
				if query.Signal == "" {
					sig = syscall.SIGTERM
				} else {
					var err error
					if sig, err = str2signal(query.Signal); err != nil {
						action.done <- err
						continue
					}
				}

				var ps processSet
				if query.Starting || query.Ready || query.Stopping || query.Pid > 0 {
					ps = self.childs
				} else {
					// Empty set
					ps = EmptyProcessSet
				}

				if query.Starting || query.Ready || query.Stopping {
					ps = ps.choose(func(p *Process, state ProcessState) bool {
						if query.Starting && (state == PROCESS_STARTING) {
							return true
						}
						if query.Ready && (state == PROCESS_READY) {
							return true
						}
						if query.Stopping && (state == PROCESS_STOPPING) {
							return true
						}
						return false
					})
				}

				if query.Pid > 0 {
					ps = ps.choose(func(p *Process, _ ProcessState) bool {
						return p.Pid() == query.Pid
					})
				}

				ps.each(func(p *Process) {
					p.Signal(sig)
				})

				action.done <- nil
			default:
				fail("Unknown action: ", a)
			}
		// timeouts
		case process := <-self.startingTracker.timeoutNotification:
			self.plog(process, "Killing, did not start in time.")
			process.Kill()
		case process := <-self.stoppingTracker.timeoutNotification:
			self.plog(process, "Killing, did not stop in time.")
			process.Kill()
		// process state transitions
		case e := <-self.events:
			switch event := e.(type) {
			case *ProcessReadyEvent:
				process := event.process
				self.startingTracker.Remove(process)

				if process != self.childs.starting() {
					self.plog(process, "Oops, some other process is ready")
					continue
				}
				self.plog(process, "Process is ready")

				current := self.childs.ready()
				if current != nil {
					self.plog(current, "Shutting down old current")
					self.stopProcess(current)
				}

				self.config = process.config
				err := self.config.save(self.configPath)
				if err != nil {
					self.log("Failed saving the config: %s", err)
				}

				if process == self.childs.starting() && self.startingReply != nil {
					self.startingDone <- nil
					self.startingReply = nil
					self.startingDone = nil
				}

				self.childs.updateState(process, PROCESS_READY)
			case *ProcessExitEvent:
				process := event.process

				self.startingTracker.Remove(process)
				self.stoppingTracker.Remove(process)

				if process == self.childs.starting() && self.startingReply != nil {
					self.startingReply.Code = event.code
					self.startingDone <- event.err
					self.startingReply = nil
					self.startingDone = nil
				}

				self.childs.rem(process)

				self.plog(process, "Process exited. code=%d err=%v", event.code, event.err)

				if self.childs.len() == 0 {
					goto exit
				}
			default:
				fail("Unknown event: ", e)
			}
		}
	}

exit:

	// Cleanup
	self.childs.each(func(p *Process) {
		p.Kill()
	})
}

func (self *Manager) SendAction(action Action) {
	self.actions <- action
}

// Restart queues and starts excecuting a restart job to replace the old process group with a new one.
func (self *Manager) Reload() {
	done := make(chan error)
	self.SendAction(&StartAction{&StartQuery{}, nil, done})
	<-done
}

func (self *Manager) Shutdown() {
	self.SendAction(ShutdownAction(true))
}

// Private methods

func (_ *Manager) log(format string, v ...interface{}) {
	log.Printf("[manager] "+format, v...)
}

func (m *Manager) plog(p *Process, format string, v ...interface{}) {
	args := make([]interface{}, 1, 1+len(v))
	args[0] = p
	args = append(args, v...)
	log.Printf("%s "+format, args...)
}

func (self *Manager) startProcess(config *ProcessConfig) error {
	self.log("Starting a new process: %s", config)
	if len(config.Command) == 0 {
		return fmt.Errorf("Command is missing")
	}

	self.processCount += 1
	process, err := startProcess(self.processCount, config, self.socket, self.events)
	if err != nil {
		return err
	}

	self.childs.add(process, PROCESS_STARTING)
	self.startingTracker.Add(process, process.config.StartTimeout)
	return nil
}

func (self *Manager) stopProcess(process *Process) {
	if self.childs[process] == PROCESS_STOPPING {
		return
	}
	process.Shutdown()
	self.stoppingTracker.Add(process, process.config.StopTimeout)
	self.childs.updateState(process, PROCESS_STOPPING)
}
<commit_msg>Close the bound socket early in the crank shutdown process. Fixes #23<commit_after>package crank

import (
	"fmt"
	"log"
	"os"
	"syscall"
	"time"
)

// Manager manages multiple process groups
type Manager struct {
	build           string
	configPath      string
	config          *ProcessConfig
	socket          *os.File
	processCount    int
	events          chan Event
	actions         chan Action
	childs          processSet
	shuttingDown    bool
	startingTracker *TimeoutTracker
	stoppingTracker *TimeoutTracker
	startingReply   *StartReply
	startingDone    chan<- error
}

func NewManager(build string, configPath string, socket *os.File) *Manager {
	config, err := loadProcessConfig(configPath)
	if err != nil {
		log.Println("Could not load config file: ", err)
	}

	manager := &Manager{
		build:           build,
		configPath:      configPath,
		config:          config,
		socket:          socket,
		events:          make(chan Event),
		actions:         make(chan Action),
		childs:          make(processSet),
		startingTracker: NewTimeoutTracker(),
		stoppingTracker: NewTimeoutTracker(),
	}
	return manager
}

// Run starts the event loop for the manager process
func (self *Manager) Run() {
	if len(self.config.Command) == 0 {
		self.log("Ignoring process start, command is missing")
	} else {
		err := self.startProcess(self.config)
		if err != nil {
			self.log("Failed to start the process: %s", err)
			return
		}
	}

	go self.startingTracker.Run()
	go self.stoppingTracker.Run()

	for {
		select {
		// actions
		case a := <-self.actions:
			switch action := a.(type) {
			case ShutdownAction:
				if self.shuttingDown {
					self.log("Already shutting down")
					continue
				}
				self.log("Shutting down")
				self.shuttingDown = true

				// Makes the socket unavailable as soon as possible
				self.socket.Close()

				self.childs.each(func(p *Process) {
					self.stopProcess(p)
				})
				if self.childs.len() == 0 {
					goto exit
				}
			case *StartAction:
				query := action.query
				//reply := action.reply -- not used

				if self.shuttingDown {
					err := fmt.Errorf("Ignore start, manager is shutting down")
					self.log(err.Error())
					action.done <- err
					continue
				}
				if self.childs.starting() != nil {
					err := fmt.Errorf("Ignore start, new process is already being started")
					self.log(err.Error())
					action.done <- err
					continue
				}

				config := self.config.clone()

				if len(query.Command) > 0 {
					config.Command = query.Command
				}

				if config.Cwd != "" {
					config.Cwd = query.Cwd
				}

				if query.StartTimeout > 0 {
					config.StartTimeout = time.Duration(query.StartTimeout) * time.Second
				}

				if query.StopTimeout > 0 {
					config.StopTimeout = time.Duration(query.StopTimeout) * time.Second
				}

				err := self.startProcess(config)
				if err != nil {
					self.log("Failed to start the process: %s", err)
					action.done <- err
					continue
				}

				if query.Wait {
					self.log("RPC waiting for the process to start")
					self.startingReply = action.reply
					self.startingDone = action.done
				} else {
					action.done <- nil
				}
			case *InfoAction:
				//query := action.query -- not used
				reply := action.reply

				reply.Info = GetInfo(self.build)

				action.done <- nil
			case *PsAction:
				query := action.query
				reply := action.reply
				ps := self.childs

				if query.Pid > 0 {
					ps = ps.choose(func(p *Process, _ ProcessState) bool {
						return p.Pid() == query.Pid
					})
				}

				if query.Starting || query.Ready || query.Stopping {
					ps = ps.choose(func(p *Process, state ProcessState) bool {
						if query.Starting && (state == PROCESS_STARTING) {
							return true
						}
						if query.Ready && (state == PROCESS_READY) {
							return true
						}
						if query.Stopping && (state == PROCESS_STOPPING) {
							return true
						}
						return false
					})
				}

				reply.PS = make([]*ProcessInfo, 0, ps.len())
				for p, state := range ps {
					reply.PS = append(reply.PS, &ProcessInfo{p.Pid(), p.id, state.String(), p.config.Cwd, p.config.Command})
				}

				action.done <- nil
			case *KillAction:
				query := action.query
				//reply := action.reply -- not used

				var sig syscall.Signal
				if query.Signal == "" {
					sig = syscall.SIGTERM
				} else {
					var err error
					if sig, err = str2signal(query.Signal); err != nil {
						action.done <- err
						continue
					}
				}

				var ps processSet
				if query.Starting || query.Ready || query.Stopping || query.Pid > 0 {
					ps = self.childs
				} else {
					// Empty set
					ps = EmptyProcessSet
				}

				if query.Starting || query.Ready || query.Stopping {
					ps = ps.choose(func(p *Process, state ProcessState) bool {
						if query.Starting && (state == PROCESS_STARTING) {
							return true
						}
						if query.Ready && (state == PROCESS_READY) {
							return true
						}
						if query.Stopping && (state == PROCESS_STOPPING) {
							return true
						}
						return false
					})
				}

				if query.Pid > 0 {
					ps = ps.choose(func(p *Process, _ ProcessState) bool {
						return p.Pid() == query.Pid
					})
				}

				ps.each(func(p *Process) {
					p.Signal(sig)
				})

				action.done <- nil
			default:
				fail("Unknown action: ", a)
			}
		// timeouts
		case process := <-self.startingTracker.timeoutNotification:
			self.plog(process, "Killing, did not start in time.")
			process.Kill()
		case process := <-self.stoppingTracker.timeoutNotification:
			self.plog(process, "Killing, did not stop in time.")
			process.Kill()
		// process state transitions
		case e := <-self.events:
			switch event := e.(type) {
			case *ProcessReadyEvent:
				process := event.process
				self.startingTracker.Remove(process)

				if process != self.childs.starting() {
					self.plog(process, "Oops, some other process is ready")
					continue
				}
				self.plog(process, "Process is ready")

				current := self.childs.ready()
				if current != nil {
					self.plog(current, "Shutting down old current")
					self.stopProcess(current)
				}

				self.config = process.config
				err := self.config.save(self.configPath)
				if err != nil {
					self.log("Failed saving the config: %s", err)
				}

				if process == self.childs.starting() && self.startingReply != nil {
					self.startingDone <- nil
					self.startingReply = nil
					self.startingDone = nil
				}

				self.childs.updateState(process, PROCESS_READY)
			case *ProcessExitEvent:
				process := event.process

				self.startingTracker.Remove(process)
				self.stoppingTracker.Remove(process)

				if process == self.childs.starting() && self.startingReply != nil {
					self.startingReply.Code = event.code
					self.startingDone <- event.err
					self.startingReply = nil
					self.startingDone = nil
				}

				self.childs.rem(process)

				self.plog(process, "Process exited. code=%d err=%v", event.code, event.err)

				if self.childs.len() == 0 {
					goto exit
				}
			default:
				fail("Unknown event: ", e)
			}
		}
	}

exit:

	// Cleanup
	self.childs.each(func(p *Process) {
		p.Kill()
	})
}

func (self *Manager) SendAction(action Action) {
	self.actions <- action
}

// Restart queues and starts excecuting a restart job to replace the old process group with a new one.
func (self *Manager) Reload() {
	done := make(chan error)
	self.SendAction(&StartAction{&StartQuery{}, nil, done})
	<-done
}

func (self *Manager) Shutdown() {
	self.SendAction(ShutdownAction(true))
}

// Private methods

func (_ *Manager) log(format string, v ...interface{}) {
	log.Printf("[manager] "+format, v...)
}

func (m *Manager) plog(p *Process, format string, v ...interface{}) {
	args := make([]interface{}, 1, 1+len(v))
	args[0] = p
	args = append(args, v...)
	log.Printf("%s "+format, args...)
}

func (self *Manager) startProcess(config *ProcessConfig) error {
	self.log("Starting a new process: %s", config)
	if len(config.Command) == 0 {
		return fmt.Errorf("Command is missing")
	}

	self.processCount += 1
	process, err := startProcess(self.processCount, config, self.socket, self.events)
	if err != nil {
		return err
	}

	self.childs.add(process, PROCESS_STARTING)
	self.startingTracker.Add(process, process.config.StartTimeout)
	return nil
}

func (self *Manager) stopProcess(process *Process) {
	if self.childs[process] == PROCESS_STOPPING {
		return
	}
	process.Shutdown()
	self.stoppingTracker.Add(process, process.config.StopTimeout)
	self.childs.updateState(process, PROCESS_STOPPING)
}
<|endoftext|>