<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"
	"time"

	"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 i, count int
	var mixValue, remValue, changeValue dcrutil.Amount
	var feeRate = w.RelayFee()
SplitPoints:
	for i = 0; i < len(splitPoints); i++ {
		last := i == len(splitPoints)-1

		// 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 !last && mixValue >= sdiff {
			continue
		}

		mixValue = splitPoints[i]
		count = int(amount / mixValue)
		if count > 4 {
			count = 4
		}
		for ; count > 0; count-- {
			remValue = amount - dcrutil.Amount(count)*mixValue
			if remValue < 0 {
				continue
			}

			// Determine required fee and change value, if possible.
			// No change is ever included when mixing at the
			// smallest amount.
			const P2PKHv0Len = 25
			inScriptSizes := []int{txsizes.RedeemP2PKHSigScriptSize}
			outScriptSizes := make([]int, count)
			for i := range outScriptSizes {
				outScriptSizes[i] = P2PKHv0Len
			}
			size := txsizes.EstimateSerializeSizeFromScriptSizes(
				inScriptSizes, outScriptSizes, P2PKHv0Len)
			fee := txrules.FeeForSerializeSize(feeRate, size)
			changeValue = remValue - fee
			if last {
				changeValue = 0
			}
			if changeValue < 0 {
				// Determine required fee without a change
				// output.  A lower mix count or amount is
				// required if the fee is still not payable.
				size = txsizes.EstimateSerializeSizeFromScriptSizes(
					inScriptSizes, outScriptSizes, 0)
				fee = txrules.FeeForSerializeSize(feeRate, size)
				if remValue < fee {
					continue
				}
				changeValue = 0
			}
			if txrules.IsDustAmount(changeValue, P2PKHv0Len, feeRate) {
				changeValue = 0
			}

			break SplitPoints
		}
	}
	if i == len(splitPoints) {
		err := errors.Errorf("output %v (%v): %w", output, amount, errNoSplitDenomination)
		return errors.E(op, err)
	}
	select {
	case <-ctx.Done():
		return errors.E(op, ctx.Err())
	case splitSems[i] <- struct{}{}:
		defer func() { <-splitSems[i] }()
	default:
		return errThrottledMixRequest
	}

	var change *wire.TxOut
	var updates []func(walletdb.ReadWriteTx) error
	if changeValue > 0 {
		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)

	var watch []wire.OutPoint
	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
			}
		}
		rec, err := udb.NewTxRecordFromMsgTx(cj.tx, time.Now())
		if err != nil {
			return errors.E(op, err)
		}
		watch, err = w.processTransactionRecord(ctx, dbtx, rec, nil, nil)
		if err != nil {
			return err
		}
		return nil
	})
	w.lockedOutpointMu.Unlock()
	if err != nil {
		return errors.E(op, err)
	}
	n, _ := w.NetworkBackend()
	if n != nil {
		err = w.publishAndWatch(ctx, op, n, cj.tx, watch)
	}
	return err
}

// 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>Fix output mixing from using lowest denom with insane fees<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"
	"time"

	"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 i, count int
	var mixValue, remValue, changeValue dcrutil.Amount
	var feeRate = w.RelayFee()
SplitPoints:
	for i = 0; i < len(splitPoints); i++ {
		last := i == len(splitPoints)-1
		mixValue = splitPoints[i]

		// 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 !last && mixValue >= sdiff {
			continue
		}

		count = int(amount / mixValue)
		if count > 4 {
			count = 4
		}
		for ; count > 0; count-- {
			remValue = amount - dcrutil.Amount(count)*mixValue
			if remValue < 0 {
				continue
			}

			// Determine required fee and change value, if possible.
			// No change is ever included when mixing at the
			// smallest amount.
			const P2PKHv0Len = 25
			inScriptSizes := []int{txsizes.RedeemP2PKHSigScriptSize}
			outScriptSizes := make([]int, count)
			for i := range outScriptSizes {
				outScriptSizes[i] = P2PKHv0Len
			}
			size := txsizes.EstimateSerializeSizeFromScriptSizes(
				inScriptSizes, outScriptSizes, P2PKHv0Len)
			fee := txrules.FeeForSerializeSize(feeRate, size)
			changeValue = remValue - fee
			if last {
				changeValue = 0
			}
			if changeValue < 0 {
				// Determine required fee without a change
				// output.  A lower mix count or amount is
				// required if the fee is still not payable.
				size = txsizes.EstimateSerializeSizeFromScriptSizes(
					inScriptSizes, outScriptSizes, 0)
				fee = txrules.FeeForSerializeSize(feeRate, size)
				if remValue < fee {
					continue
				}
				changeValue = 0
			}
			if txrules.IsDustAmount(changeValue, P2PKHv0Len, feeRate) {
				changeValue = 0
			}

			break SplitPoints
		}
	}
	if i == len(splitPoints) {
		err := errors.Errorf("output %v (%v): %w", output, amount, errNoSplitDenomination)
		return errors.E(op, err)
	}
	select {
	case <-ctx.Done():
		return errors.E(op, ctx.Err())
	case splitSems[i] <- struct{}{}:
		defer func() { <-splitSems[i] }()
	default:
		return errThrottledMixRequest
	}

	var change *wire.TxOut
	var updates []func(walletdb.ReadWriteTx) error
	if changeValue > 0 {
		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)

	var watch []wire.OutPoint
	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
			}
		}
		rec, err := udb.NewTxRecordFromMsgTx(cj.tx, time.Now())
		if err != nil {
			return errors.E(op, err)
		}
		watch, err = w.processTransactionRecord(ctx, dbtx, rec, nil, nil)
		if err != nil {
			return err
		}
		return nil
	})
	w.lockedOutpointMu.Unlock()
	if err != nil {
		return errors.E(op, err)
	}
	n, _ := w.NetworkBackend()
	if n != nil {
		err = w.publishAndWatch(ctx, op, n, cj.tx, watch)
	}
	return err
}

// 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>// Package ipify provides a single function for retrieving your computer's
// public IP address from the ipify service: http://www.ipify.org
package ipify

import (
	"errors"
	"github.com/jpillora/backoff"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"
)

// GetIp queries the ipify service (http://www.ipify.org) to retrieve this
// machine's public IP address.  Returns your public IP address as a string, and
// any error encountered.  By default, this function will run using exponential
// backoff -- if this function fails for any reason, the request will be retried
// up to 3 times.
//
// Usage:
//
//		package main
//
//		import (
//			"fmt"
//			"github.com/rdegges/go-ipify"
//		)
//
//		func main() {
//			err, ip := ipify.GetIp()
//			if err != nil {
//				fmt.Println("Couldn't get my IP address:", err)
//			} else {
//				fmt.Println("My IP address is:", ip)
//			}
//		}
func GetIp() (string, error) {
	b := &backoff.Backoff{
		Jitter: true,
	}
	client := &http.Client{}

	req, err := http.NewRequest("GET", API_URI, nil)
	if err != nil {
		return "", errors.New("Received an invalid status code from ipify: 500. The service might be experiencing issues.")
	}

	req.Header.Add("User-Agent", USER_AGENT)

	for tries := 0; tries < MAX_TRIES; tries++ {
		resp, err := client.Do(req)
		if err != nil {
			d := b.Duration()
			time.Sleep(d)
			continue
		}

		defer resp.Body.Close()

		ip, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return "", errors.New("Received an invalid status code from ipify: 500. The service might be experiencing issues.")
		}

		if resp.StatusCode != 200 {
			return "", errors.New("Received an invalid status code from ipify: " + strconv.Itoa(resp.StatusCode) + ". The service might be experiencing issues.")
		}

		return string(ip), nil
	}

	return "", errors.New("The request failed because it wasn't able to reach the ipify service. This is most likely due to a networking error of some sort.")
}
<commit_msg>Fixing typo.<commit_after>// Package ipify provides a single function for retrieving your computer's
// public IP address from the ipify service: http://www.ipify.org
package ipify

import (
	"errors"
	"github.com/jpillora/backoff"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"
)

// GetIp queries the ipify service (http://www.ipify.org) to retrieve this
// machine's public IP address.  Returns your public IP address as a string, and
// any error encountered.  By default, this function will run using exponential
// backoff -- if this function fails for any reason, the request will be retried
// up to 3 times.
//
// Usage:
//
//		package main
//
//		import (
//			"fmt"
//			"github.com/rdegges/go-ipify"
//		)
//
//		func main() {
//			ip, err := ipify.GetIp()
//			if err != nil {
//				fmt.Println("Couldn't get my IP address:", err)
//			} else {
//				fmt.Println("My IP address is:", ip)
//			}
//		}
func GetIp() (string, error) {
	b := &backoff.Backoff{
		Jitter: true,
	}
	client := &http.Client{}

	req, err := http.NewRequest("GET", API_URI, nil)
	if err != nil {
		return "", errors.New("Received an invalid status code from ipify: 500. The service might be experiencing issues.")
	}

	req.Header.Add("User-Agent", USER_AGENT)

	for tries := 0; tries < MAX_TRIES; tries++ {
		resp, err := client.Do(req)
		if err != nil {
			d := b.Duration()
			time.Sleep(d)
			continue
		}

		defer resp.Body.Close()

		ip, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return "", errors.New("Received an invalid status code from ipify: 500. The service might be experiencing issues.")
		}

		if resp.StatusCode != 200 {
			return "", errors.New("Received an invalid status code from ipify: " + strconv.Itoa(resp.StatusCode) + ". The service might be experiencing issues.")
		}

		return string(ip), nil
	}

	return "", errors.New("The request failed because it wasn't able to reach the ipify service. This is most likely due to a networking error of some sort.")
}
<|endoftext|>


<commit_before>package pqstream

var (
	sqlQueryTables = `
SELECT table_name
  FROM information_schema.tables
 WHERE table_schema='public'
   AND table_type='BASE TABLE'
`
	sqlTriggerFunction = `
CREATE OR REPLACE FUNCTION pqstream_notify() RETURNS TRIGGER AS $$
    DECLARE
        payload json;
        previous json;
        notification json;
    BEGIN
        IF (TG_OP = 'DELETE') THEN
            payload = row_to_json(OLD);
        ELSE
            payload = row_to_json(NEW);
        END IF;
        IF (TG_OP = 'UPDATE') THEN
            previous = row_to_json(OLD);
        END IF;

        notification = json_build_object(
                          'schema', TG_TABLE_SCHEMA,
                          'table', TG_TABLE_NAME,
                          'op', TG_OP,
						  'id', json_extract_path(payload, 'id')::text,
                          'payload', payload,
						  'previous', previous);
        IF (length(notification::text) > 8000) THEN
          notification = json_build_object(
                          'schema', TG_TABLE_SCHEMA,
                          'table', TG_TABLE_NAME,
                          'op', TG_OP,
						  'id', json_extract_path(payload, 'id')::text,
						  'payload', payload);
        END IF;
        IF (length(notification::text) > 8000) THEN
          notification = json_build_object(
                            'schema', TG_TABLE_SCHEMA,
                            'table', TG_TABLE_NAME,
                            'op', TG_OP,
							'id', json_extract_path(payload, 'id')::text);
        END IF;

        PERFORM pg_notify('pqstream_notify', notification::text);
        RETURN NULL;
    END;
$$ LANGUAGE plpgsql;
`
	sqlRemoveTrigger = `
DROP TRIGGER IF EXISTS pqstream_notify ON %s
`
	sqlInstallTrigger = `
CREATE TRIGGER pqstream_notify
AFTER INSERT OR UPDATE OR DELETE ON %s
    FOR EACH ROW EXECUTE PROCEDURE pqstream_notify();
`
	sqlFetchRowById = `
	SELECT row_to_json(r)::text from (select * from %s where id = $1::%s) r;
`
)
<commit_msg>payload: bump to >= check<commit_after>package pqstream

var (
	sqlQueryTables = `
SELECT table_name
  FROM information_schema.tables
 WHERE table_schema='public'
   AND table_type='BASE TABLE'
`
	sqlTriggerFunction = `
CREATE OR REPLACE FUNCTION pqstream_notify() RETURNS TRIGGER AS $$
    DECLARE
        payload json;
        previous json;
        notification json;
    BEGIN
        IF (TG_OP = 'DELETE') THEN
            payload = row_to_json(OLD);
        ELSE
            payload = row_to_json(NEW);
        END IF;
        IF (TG_OP = 'UPDATE') THEN
            previous = row_to_json(OLD);
        END IF;

        notification = json_build_object(
                          'schema', TG_TABLE_SCHEMA,
                          'table', TG_TABLE_NAME,
                          'op', TG_OP,
						  'id', json_extract_path(payload, 'id')::text,
                          'payload', payload,
						  'previous', previous);
        IF (length(notification::text) >= 8000) THEN
          notification = json_build_object(
                          'schema', TG_TABLE_SCHEMA,
                          'table', TG_TABLE_NAME,
                          'op', TG_OP,
						  'id', json_extract_path(payload, 'id')::text,
						  'payload', payload);
        END IF;
        IF (length(notification::text) >= 8000) THEN
          notification = json_build_object(
                            'schema', TG_TABLE_SCHEMA,
                            'table', TG_TABLE_NAME,
                            'op', TG_OP,
							'id', json_extract_path(payload, 'id')::text);
        END IF;

        PERFORM pg_notify('pqstream_notify', notification::text);
        RETURN NULL;
    END;
$$ LANGUAGE plpgsql;
`
	sqlRemoveTrigger = `
DROP TRIGGER IF EXISTS pqstream_notify ON %s
`
	sqlInstallTrigger = `
CREATE TRIGGER pqstream_notify
AFTER INSERT OR UPDATE OR DELETE ON %s
    FOR EACH ROW EXECUTE PROCEDURE pqstream_notify();
`
	sqlFetchRowById = `
	SELECT row_to_json(r)::text from (select * from %s where id = $1::%s) r;
`
)
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package r2

import (
	"fmt"

	"github.com/golang/geo/r1"
)

// Point represents a point in ℝ².
type Point struct {
	X, Y float64
}

// Add returns the sum of p and op.
func (p Point) Add(op Point) Point { return Point{p.X + op.X, p.Y + op.Y} }

// Sub returns the difference of p and op.
func (p Point) Sub(op Point) Point { return Point{p.X - op.X, p.Y - op.Y} }

// Mul returns the scalar product of p and m.
func (p Point) Mul(m float64) Point { return Point{m * p.X, m * p.Y} }

// Rect represents a closed axis-aligned rectangle in the (x,y) plane.
type Rect struct {
	X, Y r1.Interval
}

// RectFromPoints constructs a rect that contains the given points.
func RectFromPoints(pts ...Point) Rect {
	// Because the default value on interval is 0,0, we need to manually
	// define the interval from the first point passed in as our starting
	// interval, otherwise we end up with the case of passing in
	// Point{0.2, 0.3} and getting the starting Rect of {0, 0.2}, {0, 0.3}
	// instead of the Rect {0.2, 0.2}, {0.3, 0.3} which is not correct.
	if len(pts) == 0 {
		return Rect{}
	}

	r := Rect{
		X: r1.Interval{Lo: pts[0].X, Hi: pts[0].X},
		Y: r1.Interval{Lo: pts[0].Y, Hi: pts[0].Y},
	}

	for _, p := range pts[1:] {
		r = r.AddPoint(p)
	}
	return r
}

// RectFromCenterSize constructs a rectangle with the given center and size.
// Both dimensions of size must be non-negative.
func RectFromCenterSize(center, size Point) Rect {
	return Rect{
		r1.Interval{Lo: center.X - size.X/2, Hi: center.X + size.X/2},
		r1.Interval{Lo: center.Y - size.Y/2, Hi: center.Y + size.Y/2},
	}
}

// EmptyRect constructs the canonical empty rectangle. Use IsEmpty() to test
// for empty rectangles, since they have more than one representation. A Rect{}
// is not the same as the EmptyRect.
func EmptyRect() Rect {
	return Rect{r1.EmptyInterval(), r1.EmptyInterval()}
}

// IsValid reports whether the rectangle is valid.
// This requires the width to be empty iff the height is empty.
func (r Rect) IsValid() bool {
	return r.X.IsEmpty() == r.Y.IsEmpty()
}

// IsEmpty reports whether the rectangle is empty.
func (r Rect) IsEmpty() bool {
	return r.X.IsEmpty()
}

// Vertices returns all four vertices of the rectangle. Vertices are returned in
// CCW direction starting with the lower left corner.
func (r Rect) Vertices() [4]Point {
	return [4]Point{
		{r.X.Lo, r.Y.Lo},
		{r.X.Hi, r.Y.Lo},
		{r.X.Hi, r.Y.Hi},
		{r.X.Lo, r.Y.Hi},
	}
}

// Center returns the center of the rectangle in (x,y)-space
func (r Rect) Center() Point {
	return Point{r.X.Center(), r.Y.Center()}
}

// Size returns the width and height of this rectangle in (x,y)-space. Empty
// rectangles have a negative width and height.
func (r Rect) Size() Point {
	return Point{r.X.Length(), r.Y.Length()}
}

// ContainsPoint reports whether the rectangle contains the given point.
// Rectangles are closed regions, i.e. they contain their boundary.
func (r Rect) ContainsPoint(p Point) bool {
	return r.X.Contains(p.X) && r.Y.Contains(p.Y)
}

// InteriorContainsPoint returns true iff the given point is contained in the interior
// of the region (i.e. the region excluding its boundary).
func (r Rect) InteriorContainsPoint(p Point) bool {
	return r.X.InteriorContains(p.X) && r.Y.InteriorContains(p.Y)
}

// Contains reports whether the rectangle contains the given rectangle.
func (r Rect) Contains(other Rect) bool {
	return r.X.ContainsInterval(other.X) && r.Y.ContainsInterval(other.Y)
}

// InteriorContains reports whether the interior of this rectangle contains all of the
// points of the given other rectangle (including its boundary).
func (r Rect) InteriorContains(other Rect) bool {
	return r.X.InteriorContainsInterval(other.X) && r.Y.InteriorContainsInterval(other.Y)
}

// Intersects reports whether this rectangle and the other rectangle have any points in common.
func (r Rect) Intersects(other Rect) bool {
	return r.X.Intersects(other.X) && r.Y.Intersects(other.Y)
}

// InteriorIntersects reports whether the interior of this rectangle intersects
// any point (including the boundary) of the given other rectangle.
func (r Rect) InteriorIntersects(other Rect) bool {
	return r.X.InteriorIntersects(other.X) && r.Y.InteriorIntersects(other.Y)
}

// AddPoint expands the rectangle to include the given point. The rectangle is
// expanded by the minimum amount possible.
func (r Rect) AddPoint(p Point) Rect {
	return Rect{r.X.AddPoint(p.X), r.Y.AddPoint(p.Y)}
}

// AddRect expands the rectangle to include the given rectangle. This is the
// same as replacing the rectangle by the union of the two rectangles, but
// is more efficient.
func (r Rect) AddRect(other Rect) Rect {
	return Rect{r.X.Union(other.X), r.Y.Union(other.Y)}
}

// ClampPoint returns the closest point in the rectangle to the given point.
// The rectangle must be non-empty.
func (r Rect) ClampPoint(p Point) Point {
	return Point{r.X.ClampPoint(p.X), r.Y.ClampPoint(p.Y)}
}

// Expanded returns a rectangle that has been expanded in the x-direction
// by margin.X, and in y-direction by margin.Y. If either margin is empty,
// then shrink the interval on the corresponding sides instead. The resulting
// rectangle may be empty. Any expansion of an empty rectangle remains empty.
func (r Rect) Expanded(margin Point) Rect {
	xx := r.X.Expanded(margin.X)
	yy := r.Y.Expanded(margin.Y)
	if xx.IsEmpty() || yy.IsEmpty() {
		return EmptyRect()
	}
	return Rect{xx, yy}
}

// ExpandedByMargin returns a Rect that has been expanded by the amount on all sides.
func (r Rect) ExpandedByMargin(margin float64) Rect {
	return r.Expanded(Point{margin, margin})
}

// Union returns the smallest rectangle containing the union of this rectangle and
// the given rectangle.
func (r Rect) Union(other Rect) Rect {
	return Rect{r.X.Union(other.X), r.Y.Union(other.Y)}
}

// Intersection returns the smallest rectangle containing the intersection of this
// rectangle and the given rectangle.
func (r Rect) Intersection(other Rect) Rect {
	xx := r.X.Intersection(other.X)
	yy := r.Y.Intersection(other.Y)
	if xx.IsEmpty() || yy.IsEmpty() {
		return EmptyRect()
	}

	return Rect{xx, yy}
}

// ApproxEquals returns true if the x- and y-intervals of the two rectangles are
// the same up to the given tolerance.
func (r Rect) ApproxEquals(r2 Rect) bool {
	return r.X.ApproxEqual(r2.X) && r.Y.ApproxEqual(r2.Y)
}

func (r Rect) String() string { return fmt.Sprintf("X %s, Y %s", r.X, r.Y) }
<commit_msg>r2: Add Rect.Lo and Rect.Hi. Tweak Rect.String format to match C++.<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package r2

import (
	"fmt"

	"github.com/golang/geo/r1"
)

// Point represents a point in ℝ².
type Point struct {
	X, Y float64
}

// Add returns the sum of p and op.
func (p Point) Add(op Point) Point { return Point{p.X + op.X, p.Y + op.Y} }

// Sub returns the difference of p and op.
func (p Point) Sub(op Point) Point { return Point{p.X - op.X, p.Y - op.Y} }

// Mul returns the scalar product of p and m.
func (p Point) Mul(m float64) Point { return Point{m * p.X, m * p.Y} }

// Rect represents a closed axis-aligned rectangle in the (x,y) plane.
type Rect struct {
	X, Y r1.Interval
}

// RectFromPoints constructs a rect that contains the given points.
func RectFromPoints(pts ...Point) Rect {
	// Because the default value on interval is 0,0, we need to manually
	// define the interval from the first point passed in as our starting
	// interval, otherwise we end up with the case of passing in
	// Point{0.2, 0.3} and getting the starting Rect of {0, 0.2}, {0, 0.3}
	// instead of the Rect {0.2, 0.2}, {0.3, 0.3} which is not correct.
	if len(pts) == 0 {
		return Rect{}
	}

	r := Rect{
		X: r1.Interval{Lo: pts[0].X, Hi: pts[0].X},
		Y: r1.Interval{Lo: pts[0].Y, Hi: pts[0].Y},
	}

	for _, p := range pts[1:] {
		r = r.AddPoint(p)
	}
	return r
}

// RectFromCenterSize constructs a rectangle with the given center and size.
// Both dimensions of size must be non-negative.
func RectFromCenterSize(center, size Point) Rect {
	return Rect{
		r1.Interval{Lo: center.X - size.X/2, Hi: center.X + size.X/2},
		r1.Interval{Lo: center.Y - size.Y/2, Hi: center.Y + size.Y/2},
	}
}

// EmptyRect constructs the canonical empty rectangle. Use IsEmpty() to test
// for empty rectangles, since they have more than one representation. A Rect{}
// is not the same as the EmptyRect.
func EmptyRect() Rect {
	return Rect{r1.EmptyInterval(), r1.EmptyInterval()}
}

// IsValid reports whether the rectangle is valid.
// This requires the width to be empty iff the height is empty.
func (r Rect) IsValid() bool {
	return r.X.IsEmpty() == r.Y.IsEmpty()
}

// IsEmpty reports whether the rectangle is empty.
func (r Rect) IsEmpty() bool {
	return r.X.IsEmpty()
}

// Vertices returns all four vertices of the rectangle. Vertices are returned in
// CCW direction starting with the lower left corner.
func (r Rect) Vertices() [4]Point {
	return [4]Point{
		{r.X.Lo, r.Y.Lo},
		{r.X.Hi, r.Y.Lo},
		{r.X.Hi, r.Y.Hi},
		{r.X.Lo, r.Y.Hi},
	}
}

// Lo returns the low corner of the rect.
func (r Rect) Lo() Point {
	return Point{r.X.Lo, r.Y.Lo}
}

// Hi returns the high corner of the rect.
func (r Rect) Hi() Point {
	return Point{r.X.Hi, r.Y.Hi}
}

// Center returns the center of the rectangle in (x,y)-space
func (r Rect) Center() Point {
	return Point{r.X.Center(), r.Y.Center()}
}

// Size returns the width and height of this rectangle in (x,y)-space. Empty
// rectangles have a negative width and height.
func (r Rect) Size() Point {
	return Point{r.X.Length(), r.Y.Length()}
}

// ContainsPoint reports whether the rectangle contains the given point.
// Rectangles are closed regions, i.e. they contain their boundary.
func (r Rect) ContainsPoint(p Point) bool {
	return r.X.Contains(p.X) && r.Y.Contains(p.Y)
}

// InteriorContainsPoint returns true iff the given point is contained in the interior
// of the region (i.e. the region excluding its boundary).
func (r Rect) InteriorContainsPoint(p Point) bool {
	return r.X.InteriorContains(p.X) && r.Y.InteriorContains(p.Y)
}

// Contains reports whether the rectangle contains the given rectangle.
func (r Rect) Contains(other Rect) bool {
	return r.X.ContainsInterval(other.X) && r.Y.ContainsInterval(other.Y)
}

// InteriorContains reports whether the interior of this rectangle contains all of the
// points of the given other rectangle (including its boundary).
func (r Rect) InteriorContains(other Rect) bool {
	return r.X.InteriorContainsInterval(other.X) && r.Y.InteriorContainsInterval(other.Y)
}

// Intersects reports whether this rectangle and the other rectangle have any points in common.
func (r Rect) Intersects(other Rect) bool {
	return r.X.Intersects(other.X) && r.Y.Intersects(other.Y)
}

// InteriorIntersects reports whether the interior of this rectangle intersects
// any point (including the boundary) of the given other rectangle.
func (r Rect) InteriorIntersects(other Rect) bool {
	return r.X.InteriorIntersects(other.X) && r.Y.InteriorIntersects(other.Y)
}

// AddPoint expands the rectangle to include the given point. The rectangle is
// expanded by the minimum amount possible.
func (r Rect) AddPoint(p Point) Rect {
	return Rect{r.X.AddPoint(p.X), r.Y.AddPoint(p.Y)}
}

// AddRect expands the rectangle to include the given rectangle. This is the
// same as replacing the rectangle by the union of the two rectangles, but
// is more efficient.
func (r Rect) AddRect(other Rect) Rect {
	return Rect{r.X.Union(other.X), r.Y.Union(other.Y)}
}

// ClampPoint returns the closest point in the rectangle to the given point.
// The rectangle must be non-empty.
func (r Rect) ClampPoint(p Point) Point {
	return Point{r.X.ClampPoint(p.X), r.Y.ClampPoint(p.Y)}
}

// Expanded returns a rectangle that has been expanded in the x-direction
// by margin.X, and in y-direction by margin.Y. If either margin is empty,
// then shrink the interval on the corresponding sides instead. The resulting
// rectangle may be empty. Any expansion of an empty rectangle remains empty.
func (r Rect) Expanded(margin Point) Rect {
	xx := r.X.Expanded(margin.X)
	yy := r.Y.Expanded(margin.Y)
	if xx.IsEmpty() || yy.IsEmpty() {
		return EmptyRect()
	}
	return Rect{xx, yy}
}

// ExpandedByMargin returns a Rect that has been expanded by the amount on all sides.
func (r Rect) ExpandedByMargin(margin float64) Rect {
	return r.Expanded(Point{margin, margin})
}

// Union returns the smallest rectangle containing the union of this rectangle and
// the given rectangle.
func (r Rect) Union(other Rect) Rect {
	return Rect{r.X.Union(other.X), r.Y.Union(other.Y)}
}

// Intersection returns the smallest rectangle containing the intersection of this
// rectangle and the given rectangle.
func (r Rect) Intersection(other Rect) Rect {
	xx := r.X.Intersection(other.X)
	yy := r.Y.Intersection(other.Y)
	if xx.IsEmpty() || yy.IsEmpty() {
		return EmptyRect()
	}

	return Rect{xx, yy}
}

// ApproxEquals returns true if the x- and y-intervals of the two rectangles are
// the same up to the given tolerance.
func (r Rect) ApproxEquals(r2 Rect) bool {
	return r.X.ApproxEqual(r2.X) && r.Y.ApproxEqual(r2.Y)
}

func (r Rect) String() string { return fmt.Sprintf("[Lo%s, Hi%s]", r.Lo(), r.Hi()) }
<|endoftext|>


<commit_before>package jamon

import (
	"bufio"
	"os"
	"strings"
)

// A configuration type may hold multiple categories of settings
type Config map[string]Category

// A category holds key-value pairs of settings
type Category map[string]string

// Internal name for category that holds settings without one
const defaultCategory = "JAMON.NO_CATEGORY"

// Loads a configuration file
func Load(filename string) (Config, error) {
	file, err := os.Open(filename)
	if err != nil {
		return Config{}, err
	}

	defer file.Close()

	reader := bufio.NewReader(file)
	config := Config{}
	currentCategory := defaultCategory

	for {
		line, _, err := reader.ReadLine()
		if err != nil {
			break
		}

		isCategory, value, key, skip := parseLine(string(line))

		switch {
		case skip:
			continue

		case isCategory:
			currentCategory = value
			continue

		case config[currentCategory] == nil:
			config[currentCategory] = make(Category)
			fallthrough
		default:
			config[currentCategory][key] = value
		}
	}

	return config, nil
}

// Attempts to parse an entry in the config file. The first return value specifies
// whether 'value' is the name of a category or the value of a key.
func parseLine(line string) (isCategory bool, value, key string, skip bool) {
	line = strings.SplitN(line, "#", 2)[0]
	line = strings.Trim(line, " \t\r")

	// Is comment?
	if strings.HasPrefix(line, "#") || len(line) == 0 {
		skip = true
		return
	}

	// Is category?
	if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
		isCategory = true
		value = strings.Trim(line, "[]")
		return
	}

	// Attempt to parse key/value pair
	parts := strings.SplitN(line, "=", 2)
	if len(parts) < 2 {
		skip = true
		return
	}

	// Trim end-of-line comments
	key = parts[0]
	value = strings.TrimRight(parts[1], " ")

	return
}

// Returns the value of a key that is not in any category. These keys should
// be placed at the top of the file with no title if desired.
func (c Config) Get(key string) string {
	category, ok := c["JAMON.NO_CATEGORY"]
	if !ok {
		return ""
	}

	return category.Get(key)
}

// Returns a category by name. If the category does not exist, an empty category
// is returned. Errors are not returned here in order to allow chaining.
func (c Config) Category(name string) Category { return c[name] }

// Returns a key from a category
func (c Category) Get(key string) string { return c[key] }
<commit_msg>Use const<commit_after>package jamon

import (
	"bufio"
	"os"
	"strings"
)

// A configuration type may hold multiple categories of settings
type Config map[string]Category

// A category holds key-value pairs of settings
type Category map[string]string

// Internal name for category that holds settings without one
const defaultCategory = "JAMON.NO_CATEGORY"

// Loads a configuration file
func Load(filename string) (Config, error) {
	file, err := os.Open(filename)
	if err != nil {
		return Config{}, err
	}

	defer file.Close()

	reader := bufio.NewReader(file)
	config := Config{}
	currentCategory := defaultCategory

	for {
		line, _, err := reader.ReadLine()
		if err != nil {
			break
		}

		isCategory, value, key, skip := parseLine(string(line))

		switch {
		case skip:
			continue

		case isCategory:
			currentCategory = value
			continue

		case config[currentCategory] == nil:
			config[currentCategory] = make(Category)
			fallthrough
		default:
			config[currentCategory][key] = value
		}
	}

	return config, nil
}

// Attempts to parse an entry in the config file. The first return value specifies
// whether 'value' is the name of a category or the value of a key.
func parseLine(line string) (isCategory bool, value, key string, skip bool) {
	line = strings.SplitN(line, "#", 2)[0]
	line = strings.Trim(line, " \t\r")

	// Is comment?
	if strings.HasPrefix(line, "#") || len(line) == 0 {
		skip = true
		return
	}

	// Is category?
	if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
		isCategory = true
		value = strings.Trim(line, "[]")
		return
	}

	// Attempt to parse key/value pair
	parts := strings.SplitN(line, "=", 2)
	if len(parts) < 2 {
		skip = true
		return
	}

	// Trim end-of-line comments
	key = parts[0]
	value = strings.TrimRight(parts[1], " ")

	return
}

// Returns the value of a key that is not in any category. These keys should
// be placed at the top of the file with no title if desired.
func (c Config) Get(key string) string {
	category, ok := c[defaultCategory]
	if !ok {
		return ""
	}

	return category.Get(key)
}

// Returns a category by name. If the category does not exist, an empty category
// is returned. Errors are not returned here in order to allow chaining.
func (c Config) Category(name string) Category { return c[name] }

// Returns a key from a category
func (c Category) Get(key string) string { return c[key] }
<|endoftext|>


<commit_before>/*
Package Jamon is an INI-like configuration file parser. An example configuration
file may look like this:
	address=127.0.0.1:1234 # root-level values

	[defaults]
	key=value
	name=Gabriel

	[Group]
	key=value
Trailing comments are also allowed, and root-level keys are only accepted at the
top of the file.
*/
package jamon

import (
	"bufio"
	"os"
	"regexp"
	"strings"
)

// Internal name for root-level group.
const rootGroup = "JAMON.ROOT_GROUP"

// A configuration holds keys and/or groups of keys.
type Config map[string]Group

// A group holds key-value pairs.
type Group map[string]string

// Returns the value of a root-level key.
func (c Config) Get(key string) string { return c[rootGroup].Get(key) }

// Verifies the existence of a root-level key.
func (c Config) Has(key string) bool {
	_, ok := c[rootGroup][key]
	return ok
}

// Returns a group in the configuration file or an empty one if it doesn't exist.
func (c Config) Group(name string) Group { return c[name] }

// Verifies if a group exists.
func (c Config) HasGroup(category string) bool {
	_, ok := c[category]
	return ok
}

// Returns a key from the group.
func (c Group) Get(key string) string { return c[key] }

// Verifies if the group contains the key.
func (c Group) Has(key string) bool {
	_, ok := c[key]
	return ok
}

// Regexp for substitions
var regexSubst = regexp.MustCompile(`\$\{([^\}\{]*)\}`)

// Loads a configuration file.
func Load(filename string) (Config, error) {
	file, err := os.Open(filename)
	if err != nil {
		return Config{}, err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	grp := rootGroup
	cfg := Config{}

	for scanner.Scan() {
		isGroup, val, key, skip := parseLine(scanner.Text())

		switch {
		case skip:
			continue

		case isGroup:
			grp = val
			continue

		case cfg[grp] == nil:
			cfg[grp] = make(Group)
			fallthrough

		default:
			replace := func(r string) string {
				k := r[2 : len(r)-1]

				// Is replacement in own group?
				if _, ok := cfg[grp][k]; ok {
					return cfg[grp][k]
				}
				// Is replacement in root group?
				if _, ok := cfg[rootGroup][k]; ok {
					return cfg[rootGroup][k]
				}
				// If it's not found, no change happens
				return r
			}

			cfg[grp][key] = regexSubst.ReplaceAllStringFunc(val, replace)
		}
	}

	return cfg, nil
}

// Attempts to parse an entry in the config file. The first return value specifies
// whether 'value' is the name of a category or the value of a key. Skip indicates
// whether the line was a comment or could not be parsed.
func parseLine(line string) (isGroup bool, val, key string, skip bool) {
	line = strings.SplitN(line, "#", 2)[0]
	line = strings.Trim(line, " \t\r")

	// Is comment or empty line?
	if len(line) == 0 {
		skip = true
		return
	}

	// Is category?
	if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
		isGroup = true
		val = strings.Trim(line, "[]")
		return
	}

	// Is key/value pair?
	parts := strings.SplitN(line, "=", 2)
	if len(parts) < 2 {
		skip = true
		return
	}

	key = parts[0]
	val = strings.TrimRight(parts[1], " ")

	return
}
<commit_msg>Testing<commit_after>/*
Package Jamon is an INI-like configuration file parser. An example configuration
file may look like this:
	address=127.0.0.1:1234 # root-level values

	[defaults]
	key=value
	name=Gabriel

	[Group]
	key=value
Trailing comments are also allowed, and root-level keys are only accepted at the
top of the file.
*/
package jamon

import (
	"bufio"
	"os"
	"regexp"
	"strings"
)

// Internal name for root-level group.
const rootGroup = "JAMON.ROOT_GROUP"

// A configuration holds keys and/or groups of keys.
type Config map[string]Group

// A group holds key-value pairs.
type Group map[string]string

// Returns the value of a root-level key.
func (c Config) Get(key string) string { return c[rootGroup].Get(key) }

// Verifies the existence of a root-level key.
func (c Config) Has(key string) bool {
	_, ok := c[rootGroup][key]
	return ok
}

// Returns a group in the configuration file or an empty one if it doesn't exist.
func (c Config) Group(name string) Group { return c[name] }

// Verifies if a group exists.
func (c Config) HasGroup(category string) bool {
	_, ok := c[category]
	return ok
}

// Returns a key from the group.
func (c Group) Get(key string) string { return c[key] }

// Verifies if the group contains the key.
func (c Group) Has(key string) bool {
	_, ok := c[key]
	return ok
}

// Regexp for substitions
var regexSubst = regexp.MustCompile(`\$\{([^\}\{]*)\}`)

// Loads a configuration file.
func Load(filename string) (Config, error) {
	file, err := os.Open(filename)
	if err != nil {
		return Config{}, err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	grp := rootGroup
	cfg := Config{}

	for scanner.Scan() {
		isGroup, val, key, skip := parseLine(scanner.Text())

		switch {
		case skip:
			continue

		case isGroup:
			grp = val
			continue

		case cfg[grp] == nil:
			cfg[grp] = make(Group)
			fallthrough

		default:
			replace := func(r string) string {
				k := r[2 : len(r)-1]

				// Is replacement in own group?
				if _, ok := cfg[grp][k]; ok {
					return cfg[grp][k]
				}
				// Is replacement in root group?
				if _, ok := cfg[rootGroup][k]; ok {
					return cfg[rootGroup][k]
				}
				// If it's not found, no change happens
				return r
			}

			cfg[grp][key] = regexSubst.ReplaceAllStringFunc(val, replace)
		}
	}

	return cfg, nil
}

// Attempts to parse an entry in the config file. The first return value specifies
// whether 'value' is the name of a category or the value of a key. Skip indicates
// whether the line was a comment or could not be parsed.
func parseLine(line string) (isGroup bool, val, key string, skip bool) {
	line = strings.SplitN(line, "#", 2)[0]
	line = strings.Trim(line, " \t\r")

	// Is comment or empty line?
	if len(line) == 0 {
		skip = true
		return
	}
	// Is category?
	if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
		isGroup = true
		val = strings.Trim(line, "[]")
		return
	}
	// Is key/value pair?
	parts := strings.SplitN(line, "=", 2)
	if len(parts) < 2 {
		skip = true
		return
	}

	key = parts[0]
	val = strings.TrimRight(parts[1], " ")

	return
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2019 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy ofthe 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 specificlanguage governing permissions and
 * limitations under the License.
 *
 */

package client

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	cache "github.com/pmylund/go-cache"

	api "github.com/skydive-project/skydive/api/server"
	"github.com/skydive-project/skydive/api/types"
	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/etcd"
	"github.com/skydive-project/skydive/filters"
	"github.com/skydive-project/skydive/graffiti/graph"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/ondemand"
	ws "github.com/skydive-project/skydive/websocket"
)

// OnDemandClientHandler is the interface to be implemented by ondemand clients
type OnDemandClientHandler interface {
	ResourceName() string
	GetNodes(resource types.Resource) []interface{}
	CheckState(n *graph.Node, resource types.Resource) bool
	DecodeMessage(msg json.RawMessage) (types.Resource, error)
	EncodeMessage(nodeID graph.Identifier, resource types.Resource) (json.RawMessage, error)
}

// OnDemandClient describes an ondemand task client based on a websocket
type OnDemandClient struct {
	common.RWMutex
	common.MasterElection
	graph.DefaultGraphListener
	graph                   *graph.Graph
	apiHandler              api.Handler
	agentPool               ws.StructSpeakerPool
	subscriberPool          ws.StructSpeakerPool
	wsNamespace             string
	wsNotificationNamespace string
	resources               map[string]types.Resource
	watcher                 api.StoppableWatcher
	registeredNodes         map[graph.Identifier]map[string]bool
	deletedNodeCache        *cache.Cache
	checkForRegistration    *common.Debouncer
	resourceName            string
	handler                 OnDemandClientHandler
}

type handlerNodeState struct {
	uuid    string
	started bool
}

type nodeTask struct {
	id       graph.Identifier
	host     string
	resource types.Resource
}

func (o *OnDemandClient) removeRegisteredNode(nodeID graph.Identifier, resourceID string) {
	o.Lock()
	tasks, found := o.registeredNodes[nodeID]
	if !found {
		o.Unlock()
		return
	}
	delete(tasks, resourceID)
	if len(o.registeredNodes) == 0 {
		delete(o.registeredNodes, nodeID)
	}
	o.Unlock()
}

// OnStructMessage event, valid message type: StartReply or StopReply message
func (o *OnDemandClient) OnStructMessage(c ws.Speaker, m *ws.StructMessage) {
	var rawQuery ondemand.RawQuery
	if err := json.Unmarshal(m.Obj, &rawQuery); err != nil {
		logging.GetLogger().Errorf("unable to decode %s message: %s", o.resourceName, err)
		return
	}

	resource, err := o.handler.DecodeMessage(rawQuery.Resource)
	if err != nil {
		logging.GetLogger().Errorf("unable to decode %s: %s", o.resourceName, err)
		return
	}

	switch m.Type {
	case "StartReply":
		// not registered thus remove from registered cache
		if m.Status != http.StatusOK {
			logging.GetLogger().Debugf("%s start request failed %v", o.resourceName, m.Debug())
			o.removeRegisteredNode(rawQuery.NodeID, resource.ID())
		} else {
			logging.GetLogger().Debugf("%s start request succeeded %v", o.resourceName, m.Debug())
		}
		o.subscriberPool.BroadcastMessage(ws.NewStructMessage(o.wsNotificationNamespace, "NodeUpdated", resource))
	case "StopReply":
		if m.Status == http.StatusOK {
			o.removeRegisteredNode(rawQuery.NodeID, resource.ID())
		} else {
			logging.GetLogger().Debugf("%s stop request failed %v", o.resourceName, m.Debug())
		}
	}
}

func (o *OnDemandClient) registerTasks(nodes []interface{}, resource types.Resource) {
	toRegister := func(node *graph.Node) (nodeID graph.Identifier, host string, register bool) {
		o.graph.RLock()
		defer o.graph.RUnlock()

		// check not already registered
		o.RLock()
		tasks, ok := o.registeredNodes[node.ID]
		if ok {
			_, ok = tasks[resource.ID()]
		}
		o.RUnlock()

		if ok {
			logging.GetLogger().Debugf("%s already registered on %s", resource.ID(), node.ID)
			return
		}

		return node.ID, node.Host, true
	}

	nps := map[graph.Identifier]nodeTask{}
	for _, i := range nodes {
		switch i.(type) {
		case *graph.Node:
			node := i.(*graph.Node)
			if nodeID, host, ok := toRegister(node); ok {
				nps[nodeID] = nodeTask{nodeID, host, resource}
			}
		case []*graph.Node:
			// case of shortestpath that returns a list of nodes
			for _, node := range i.([]*graph.Node) {
				if nodeID, host, ok := toRegister(node); ok {
					nps[nodeID] = nodeTask{nodeID, host, resource}
				}
			}
		}
	}

	if len(nps) > 0 {
		go func() {
			for _, np := range nps {
				o.registerTask(np)
			}
		}()
	}
}

func (o *OnDemandClient) registerTask(np nodeTask) bool {
	body, err := o.handler.EncodeMessage(np.id, np.resource)
	if err != nil {
		logging.GetLogger().Errorf("Unable to encode message for agent %s: %s", np.host, err)
		return false
	}

	msg := ws.NewStructMessage(o.wsNamespace, "Start", ondemand.RawQuery{
		NodeID:   np.id,
		Resource: body,
	})

	if err := o.agentPool.SendMessageTo(msg, np.host); err != nil {
		logging.GetLogger().Errorf("Unable to send message to agent %s: %s", np.host, err)
		return false
	}

	o.Lock()
	if _, found := o.registeredNodes[np.id]; !found {
		o.registeredNodes[np.id] = make(map[string]bool)
	}
	o.registeredNodes[np.id][np.resource.ID()] = false
	o.Unlock()

	logging.GetLogger().Debugf("Registered task on %s with resource %s", np.id, np.resource.ID())

	return true
}

func (o *OnDemandClient) unregisterTask(node *graph.Node, resource types.Resource) bool {
	body, err := o.handler.EncodeMessage(node.ID, resource)
	if err != nil {
		logging.GetLogger().Errorf("Unable to encode message for agent %s: %s", node.Host, err)
		return false
	}

	msg := ws.NewStructMessage(o.wsNamespace, "Stop", ondemand.RawQuery{
		NodeID:   node.ID,
		Resource: body,
	})

	if err := o.agentPool.SendMessageTo(msg, node.Host); err != nil {
		logging.GetLogger().Errorf("Unable to send message to agent %s: %s", node.Host, err)
		return false
	}

	o.removeRegisteredNode(node.ID, resource.ID())

	return true
}

// checkForRegistration check the resource gremlin expression in order to
// register new task.
func (o *OnDemandClient) checkForRegistrationCallback() {
	if !o.IsMaster() {
		return
	}

	o.graph.RLock()
	defer o.graph.RUnlock()

	o.RLock()
	defer o.RUnlock()

	for _, resource := range o.resources {
		nodes := o.handler.GetNodes(resource)
		if len(nodes) > 0 {
			go o.registerTasks(nodes, resource)
		}
	}
}

// OnNodeAdded graph event
func (o *OnDemandClient) OnNodeAdded(n *graph.Node) {
	if !o.IsMaster() {
		return
	}

	// a node comes up with already a resource, this could be due to a re-connect of
	// an agent. Check if the handler is still active.
	if field, err := n.GetField(o.resourceName); err == nil {
		if resources, ok := field.(map[string]interface{}); ok {
			for id := range resources {
				o.RLock()
				_, found := o.resources[id]
				o.RUnlock()

				if found {
					continue
				}

				// not present unregister it
				logging.GetLogger().Debugf("Unregister remaining %s for node %s: %s", o.resourceName, n.ID, id)
				go o.unregisterTask(n, &types.BasicResource{UUID: id})
			}
		}
	} else {
		o.checkForRegistration.Call()
	}
}

// OnNodeUpdated graph event
func (o *OnDemandClient) OnNodeUpdated(n *graph.Node) {
	o.RLock()
	if tasks, ok := o.registeredNodes[n.ID]; ok {
		for resourceID, started := range tasks {
			if resource := o.resources[resourceID]; !started && resource != nil {
				if o.handler.CheckState(n, resource) {
					o.registeredNodes[n.ID][resourceID] = true
					o.subscriberPool.BroadcastMessage(ws.NewStructMessage(o.wsNotificationNamespace, "NodeUpdated", resource))
				}
			}
		}
	}
	o.RUnlock()

	o.checkForRegistration.Call()
}

// OnNodeDeleted graph event
func (o *OnDemandClient) OnNodeDeleted(n *graph.Node) {
	o.RLock()
	if tasks, ok := o.registeredNodes[n.ID]; ok {
		for resourceID := range tasks {
			o.subscriberPool.BroadcastMessage(ws.NewStructMessage(o.wsNotificationNamespace, "NodeUpdated", o.resources[resourceID]))
		}
	}
	o.RUnlock()
}

// OnEdgeAdded graph event
func (o *OnDemandClient) OnEdgeAdded(e *graph.Edge) {
	o.checkForRegistration.Call()
}

func (o *OnDemandClient) registerResource(resource types.Resource) {
	o.graph.RLock()
	defer o.graph.RUnlock()

	o.Lock()
	o.resources[resource.ID()] = resource
	o.Unlock()

	nodes := o.handler.GetNodes(resource)
	if len(nodes) > 0 {
		go o.registerTasks(nodes, resource)
	}
}

func (o *OnDemandClient) onResourceAdded(resource types.Resource) {
	if !o.IsMaster() {
		return
	}

	o.registerResource(resource)
}

func (o *OnDemandClient) unregisterResource(resource types.Resource) {
	o.graph.RLock()
	defer o.graph.RUnlock()

	o.deletedNodeCache.Delete(resource.ID())

	o.Lock()
	delete(o.resources, resource.ID())
	o.Unlock()

	filter := filters.NewTermStringFilter(fmt.Sprintf("%ss.ID", o.resourceName), resource.ID())
	nodes := o.graph.GetNodes(graph.NewElementFilter(filter))
	for _, node := range nodes {
		o.unregisterTask(node, resource)
	}
}

func (o *OnDemandClient) onResourceDeleted(resource types.Resource) {
	if !o.IsMaster() {
		// fill the cache with recent delete in order to be able to delete then
		// in case we lose the master and nobody is master yet. This cache will
		// be used when becoming master.
		o.deletedNodeCache.Set(resource.ID(), resource, cache.DefaultExpiration)
		return
	}

	o.unregisterResource(resource)
}

// OnStartAsMaster event
func (o *OnDemandClient) OnStartAsMaster() {
}

// OnStartAsSlave event
func (o *OnDemandClient) OnStartAsSlave() {
}

// OnSwitchToMaster event
func (o *OnDemandClient) OnSwitchToMaster() {
	// try to delete recently added resource to handle case where the api got a delete but wasn't yet master
	for _, item := range o.deletedNodeCache.Items() {
		resource := item.Object.(types.Resource)
		o.unregisterResource(resource)
	}

	for _, resource := range o.apiHandler.Index() {
		resource := resource.(types.Resource)
		o.onResourceAdded(resource)
	}
}

// OnSwitchToSlave event
func (o *OnDemandClient) OnSwitchToSlave() {
}

func (o *OnDemandClient) onAPIWatcherEvent(action string, id string, resource types.Resource) {
	logging.GetLogger().Debugf("New watcher event %s for %s", action, id)
	switch action {
	case "init", "create", "set", "update":
		o.subscriberPool.BroadcastMessage(ws.NewStructMessage(o.wsNotificationNamespace, "Added", resource))
		o.onResourceAdded(resource)
	case "expire", "delete":
		o.subscriberPool.BroadcastMessage(ws.NewStructMessage(o.wsNotificationNamespace, "Deleted", resource))
		o.onResourceDeleted(resource)
	}
}

// Start the task
func (o *OnDemandClient) Start() {
	o.MasterElection.AddEventListener(o)
	o.agentPool.AddStructMessageHandler(o, []string{o.wsNamespace})

	o.MasterElection.StartAndWait()

	o.checkForRegistration.Start()

	o.watcher = o.apiHandler.AsyncWatch(o.onAPIWatcherEvent)
	o.graph.AddEventListener(o)
}

// Stop the task
func (o *OnDemandClient) Stop() {
	o.watcher.Stop()
	o.MasterElection.Stop()
	o.checkForRegistration.Stop()
}

// NewOnDemandClient creates a new ondemand task client based on API, graph and websocket
func NewOnDemandClient(g *graph.Graph, ch api.Handler, agentPool ws.StructSpeakerPool, subscriberPool ws.StructSpeakerPool, etcdClient *etcd.Client, handler OnDemandClientHandler) *OnDemandClient {
	election := etcdClient.NewElection("ondemand-client-" + handler.ResourceName())
	o := &OnDemandClient{
		MasterElection:          election,
		graph:                   g,
		handler:                 handler,
		apiHandler:              ch,
		agentPool:               agentPool,
		subscriberPool:          subscriberPool,
		wsNamespace:             ondemand.Namespace + handler.ResourceName(),
		wsNotificationNamespace: ondemand.Namespace + handler.ResourceName() + "Notification",
		resourceName:            handler.ResourceName(),
		resources:               ch.Index(),
		registeredNodes:         make(map[graph.Identifier]map[string]bool),
		deletedNodeCache:        cache.New(election.TTL()*2, election.TTL()*2),
	}
	o.checkForRegistration = common.NewDebouncer(time.Second, o.checkForRegistrationCallback)

	return o
}
<commit_msg>ondemand: remove pending requests for a deleted node<commit_after>/*
 * Copyright (C) 2019 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy ofthe 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 specificlanguage governing permissions and
 * limitations under the License.
 *
 */

package client

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	cache "github.com/pmylund/go-cache"

	api "github.com/skydive-project/skydive/api/server"
	"github.com/skydive-project/skydive/api/types"
	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/etcd"
	"github.com/skydive-project/skydive/filters"
	"github.com/skydive-project/skydive/graffiti/graph"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/ondemand"
	ws "github.com/skydive-project/skydive/websocket"
)

// OnDemandClientHandler is the interface to be implemented by ondemand clients
type OnDemandClientHandler interface {
	ResourceName() string
	GetNodes(resource types.Resource) []interface{}
	CheckState(n *graph.Node, resource types.Resource) bool
	DecodeMessage(msg json.RawMessage) (types.Resource, error)
	EncodeMessage(nodeID graph.Identifier, resource types.Resource) (json.RawMessage, error)
}

// OnDemandClient describes an ondemand task client based on a websocket
type OnDemandClient struct {
	common.RWMutex
	common.MasterElection
	graph.DefaultGraphListener
	graph                   *graph.Graph
	apiHandler              api.Handler
	agentPool               ws.StructSpeakerPool
	subscriberPool          ws.StructSpeakerPool
	wsNamespace             string
	wsNotificationNamespace string
	resources               map[string]types.Resource
	watcher                 api.StoppableWatcher
	registeredNodes         map[graph.Identifier]map[string]bool
	deletedNodeCache        *cache.Cache
	checkForRegistration    *common.Debouncer
	resourceName            string
	handler                 OnDemandClientHandler
}

type handlerNodeState struct {
	uuid    string
	started bool
}

type nodeTask struct {
	id       graph.Identifier
	host     string
	resource types.Resource
}

func (o *OnDemandClient) removeRegisteredNode(nodeID graph.Identifier, resourceID string) {
	o.Lock()
	tasks, found := o.registeredNodes[nodeID]
	if !found {
		o.Unlock()
		return
	}
	delete(tasks, resourceID)
	if len(o.registeredNodes) == 0 {
		delete(o.registeredNodes, nodeID)
	}
	o.Unlock()
}

// OnStructMessage event, valid message type: StartReply or StopReply message
func (o *OnDemandClient) OnStructMessage(c ws.Speaker, m *ws.StructMessage) {
	var rawQuery ondemand.RawQuery
	if err := json.Unmarshal(m.Obj, &rawQuery); err != nil {
		logging.GetLogger().Errorf("unable to decode %s message: %s", o.resourceName, err)
		return
	}

	resource, err := o.handler.DecodeMessage(rawQuery.Resource)
	if err != nil {
		logging.GetLogger().Errorf("unable to decode %s: %s", o.resourceName, err)
		return
	}

	switch m.Type {
	case "StartReply":
		// not registered thus remove from registered cache
		if m.Status != http.StatusOK {
			logging.GetLogger().Debugf("%s start request failed %v", o.resourceName, m.Debug())
			o.removeRegisteredNode(rawQuery.NodeID, resource.ID())
		} else {
			logging.GetLogger().Debugf("%s start request succeeded %v", o.resourceName, m.Debug())
		}
		o.subscriberPool.BroadcastMessage(ws.NewStructMessage(o.wsNotificationNamespace, "NodeUpdated", resource))
	case "StopReply":
		if m.Status == http.StatusOK {
			o.removeRegisteredNode(rawQuery.NodeID, resource.ID())
		} else {
			logging.GetLogger().Debugf("%s stop request failed %v", o.resourceName, m.Debug())
		}
	}
}

func (o *OnDemandClient) registerTasks(nodes []interface{}, resource types.Resource) {
	toRegister := func(node *graph.Node) (nodeID graph.Identifier, host string, register bool) {
		o.graph.RLock()
		defer o.graph.RUnlock()

		// check not already registered
		o.RLock()
		tasks, ok := o.registeredNodes[node.ID]
		if ok {
			_, ok = tasks[resource.ID()]
		}
		o.RUnlock()

		if ok {
			logging.GetLogger().Debugf("%s already registered on %s", resource.ID(), node.ID)
			return
		}

		return node.ID, node.Host, true
	}

	nps := map[graph.Identifier]nodeTask{}
	for _, i := range nodes {
		switch i.(type) {
		case *graph.Node:
			node := i.(*graph.Node)
			if nodeID, host, ok := toRegister(node); ok {
				nps[nodeID] = nodeTask{nodeID, host, resource}
			}
		case []*graph.Node:
			// case of shortestpath that returns a list of nodes
			for _, node := range i.([]*graph.Node) {
				if nodeID, host, ok := toRegister(node); ok {
					nps[nodeID] = nodeTask{nodeID, host, resource}
				}
			}
		}
	}

	if len(nps) > 0 {
		go func() {
			for _, np := range nps {
				o.registerTask(np)
			}
		}()
	}
}

func (o *OnDemandClient) registerTask(np nodeTask) bool {
	body, err := o.handler.EncodeMessage(np.id, np.resource)
	if err != nil {
		logging.GetLogger().Errorf("Unable to encode message for agent %s: %s", np.host, err)
		return false
	}

	msg := ws.NewStructMessage(o.wsNamespace, "Start", ondemand.RawQuery{
		NodeID:   np.id,
		Resource: body,
	})

	if err := o.agentPool.SendMessageTo(msg, np.host); err != nil {
		logging.GetLogger().Errorf("Unable to send message to agent %s: %s", np.host, err)
		return false
	}

	o.Lock()
	if _, found := o.registeredNodes[np.id]; !found {
		o.registeredNodes[np.id] = make(map[string]bool)
	}
	o.registeredNodes[np.id][np.resource.ID()] = false
	o.Unlock()

	logging.GetLogger().Debugf("Registered task on %s with resource %s", np.id, np.resource.ID())

	return true
}

func (o *OnDemandClient) unregisterTask(node *graph.Node, resource types.Resource) bool {
	body, err := o.handler.EncodeMessage(node.ID, resource)
	if err != nil {
		logging.GetLogger().Errorf("Unable to encode message for agent %s: %s", node.Host, err)
		return false
	}

	msg := ws.NewStructMessage(o.wsNamespace, "Stop", ondemand.RawQuery{
		NodeID:   node.ID,
		Resource: body,
	})

	if err := o.agentPool.SendMessageTo(msg, node.Host); err != nil {
		logging.GetLogger().Errorf("Unable to send message to agent %s: %s", node.Host, err)
		return false
	}

	o.removeRegisteredNode(node.ID, resource.ID())

	return true
}

// checkForRegistration check the resource gremlin expression in order to
// register new task.
func (o *OnDemandClient) checkForRegistrationCallback() {
	if !o.IsMaster() {
		return
	}

	o.graph.RLock()
	defer o.graph.RUnlock()

	o.RLock()
	defer o.RUnlock()

	for _, resource := range o.resources {
		nodes := o.handler.GetNodes(resource)
		if len(nodes) > 0 {
			go o.registerTasks(nodes, resource)
		}
	}
}

// OnNodeAdded graph event
func (o *OnDemandClient) OnNodeAdded(n *graph.Node) {
	if !o.IsMaster() {
		return
	}

	// a node comes up with already a resource, this could be due to a re-connect of
	// an agent. Check if the handler is still active.
	if field, err := n.GetField(o.resourceName); err == nil {
		if resources, ok := field.(map[string]interface{}); ok {
			for id := range resources {
				o.RLock()
				_, found := o.resources[id]
				o.RUnlock()

				if found {
					continue
				}

				// not present unregister it
				logging.GetLogger().Debugf("Unregister remaining %s for node %s: %s", o.resourceName, n.ID, id)
				go o.unregisterTask(n, &types.BasicResource{UUID: id})
			}
		}
	} else {
		o.checkForRegistration.Call()
	}
}

// OnNodeUpdated graph event
func (o *OnDemandClient) OnNodeUpdated(n *graph.Node) {
	o.RLock()
	if tasks, ok := o.registeredNodes[n.ID]; ok {
		for resourceID, started := range tasks {
			if resource := o.resources[resourceID]; !started && resource != nil {
				if o.handler.CheckState(n, resource) {
					o.registeredNodes[n.ID][resourceID] = true
					o.subscriberPool.BroadcastMessage(ws.NewStructMessage(o.wsNotificationNamespace, "NodeUpdated", resource))
				}
			}
		}
	}
	o.RUnlock()

	o.checkForRegistration.Call()
}

// OnNodeDeleted graph event
func (o *OnDemandClient) OnNodeDeleted(n *graph.Node) {
	o.RLock()
	if tasks, ok := o.registeredNodes[n.ID]; ok {
		for resourceID := range tasks {
			o.subscriberPool.BroadcastMessage(ws.NewStructMessage(o.wsNotificationNamespace, "NodeUpdated", o.resources[resourceID]))
		}
	}
	delete(o.registeredNodes, n.ID)
	o.RUnlock()
}

// OnEdgeAdded graph event
func (o *OnDemandClient) OnEdgeAdded(e *graph.Edge) {
	o.checkForRegistration.Call()
}

func (o *OnDemandClient) registerResource(resource types.Resource) {
	o.graph.RLock()
	defer o.graph.RUnlock()

	o.Lock()
	o.resources[resource.ID()] = resource
	o.Unlock()

	nodes := o.handler.GetNodes(resource)
	if len(nodes) > 0 {
		go o.registerTasks(nodes, resource)
	}
}

func (o *OnDemandClient) onResourceAdded(resource types.Resource) {
	if !o.IsMaster() {
		return
	}

	o.registerResource(resource)
}

func (o *OnDemandClient) unregisterResource(resource types.Resource) {
	o.graph.RLock()
	defer o.graph.RUnlock()

	o.deletedNodeCache.Delete(resource.ID())

	o.Lock()
	delete(o.resources, resource.ID())
	o.Unlock()

	filter := filters.NewTermStringFilter(fmt.Sprintf("%ss.ID", o.resourceName), resource.ID())
	nodes := o.graph.GetNodes(graph.NewElementFilter(filter))
	for _, node := range nodes {
		o.unregisterTask(node, resource)
	}
}

func (o *OnDemandClient) onResourceDeleted(resource types.Resource) {
	if !o.IsMaster() {
		// fill the cache with recent delete in order to be able to delete then
		// in case we lose the master and nobody is master yet. This cache will
		// be used when becoming master.
		o.deletedNodeCache.Set(resource.ID(), resource, cache.DefaultExpiration)
		return
	}

	o.unregisterResource(resource)
}

// OnStartAsMaster event
func (o *OnDemandClient) OnStartAsMaster() {
}

// OnStartAsSlave event
func (o *OnDemandClient) OnStartAsSlave() {
}

// OnSwitchToMaster event
func (o *OnDemandClient) OnSwitchToMaster() {
	// try to delete recently added resource to handle case where the api got a delete but wasn't yet master
	for _, item := range o.deletedNodeCache.Items() {
		resource := item.Object.(types.Resource)
		o.unregisterResource(resource)
	}

	for _, resource := range o.apiHandler.Index() {
		resource := resource.(types.Resource)
		o.onResourceAdded(resource)
	}
}

// OnSwitchToSlave event
func (o *OnDemandClient) OnSwitchToSlave() {
}

func (o *OnDemandClient) onAPIWatcherEvent(action string, id string, resource types.Resource) {
	logging.GetLogger().Debugf("New watcher event %s for %s", action, id)
	switch action {
	case "init", "create", "set", "update":
		o.subscriberPool.BroadcastMessage(ws.NewStructMessage(o.wsNotificationNamespace, "Added", resource))
		o.onResourceAdded(resource)
	case "expire", "delete":
		o.subscriberPool.BroadcastMessage(ws.NewStructMessage(o.wsNotificationNamespace, "Deleted", resource))
		o.onResourceDeleted(resource)
	}
}

// Start the task
func (o *OnDemandClient) Start() {
	o.MasterElection.AddEventListener(o)
	o.agentPool.AddStructMessageHandler(o, []string{o.wsNamespace})

	o.MasterElection.StartAndWait()

	o.checkForRegistration.Start()

	o.watcher = o.apiHandler.AsyncWatch(o.onAPIWatcherEvent)
	o.graph.AddEventListener(o)
}

// Stop the task
func (o *OnDemandClient) Stop() {
	o.watcher.Stop()
	o.MasterElection.Stop()
	o.checkForRegistration.Stop()
}

// NewOnDemandClient creates a new ondemand task client based on API, graph and websocket
func NewOnDemandClient(g *graph.Graph, ch api.Handler, agentPool ws.StructSpeakerPool, subscriberPool ws.StructSpeakerPool, etcdClient *etcd.Client, handler OnDemandClientHandler) *OnDemandClient {
	election := etcdClient.NewElection("ondemand-client-" + handler.ResourceName())
	o := &OnDemandClient{
		MasterElection:          election,
		graph:                   g,
		handler:                 handler,
		apiHandler:              ch,
		agentPool:               agentPool,
		subscriberPool:          subscriberPool,
		wsNamespace:             ondemand.Namespace + handler.ResourceName(),
		wsNotificationNamespace: ondemand.Namespace + handler.ResourceName() + "Notification",
		resourceName:            handler.ResourceName(),
		resources:               ch.Index(),
		registeredNodes:         make(map[graph.Identifier]map[string]bool),
		deletedNodeCache:        cache.New(election.TTL()*2, election.TTL()*2),
	}
	o.checkForRegistration = common.NewDebouncer(time.Second, o.checkForRegistrationCallback)

	return o
}
<|endoftext|>


<commit_before>package main

import "os"
import "github.com/docker/engine-api/client"
import "math/rand"
import "os/exec"
import "strconv"
import "github.com/seehuhn/mt19937"
import "time"
import "os/user"
import "fmt"

var cli *client.Client

type ExecRequest struct {
	Image string
	Cmd []string
	SourceFileName string
}

type Judge struct {
	Code string
	Compile *ExecRequest
	Exec ExecRequest
	Time int64
	Mem  int64
	TCCount int // The number of test cases
}

type JudgeResult int

const (
	Accepted            JudgeResult = 0
	WrongAnswer         JudgeResult = 1
	CompileError        JudgeResult = 2
	TimeLimitExceeded   JudgeResult = 3
	MemoryLimitExceeded JudgeResult = 4
	RuntimeError        JudgeResult = 5
	InternalError       JudgeResult = 6
	Judging             JudgeResult = 7
	CompileTimeLimitExceeded JudgeResult = 8
	CompileMemoryLimitExceeded JudgeResult = 9
)

type JudgeStatus struct {
	Case     *string
	JR       JudgeResult
	Mem      int64
	Time     int64
	Msg      *string
}

func CreateInternalError(msg string) JudgeStatus {
	return JudgeStatus{nil, InternalError, 0, 0, &msg}
}

const BASE_RAND_STRING = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

func RandomName() string {
	rng := rand.New(mt19937.New())
	rng.Seed(time.Now().UnixNano())

	res := make([]byte, 0, 32)
	for i := 0; i < 32; i++ {
		res = append(res, BASE_RAND_STRING[rng.Intn(len(BASE_RAND_STRING))])
	}

	return string(res)
}

func (j *Judge) Run(ch chan<- JudgeStatus, tests <-chan struct {
	Name string
	In  string
	Out string
}) {
	// Close a channel to send results of judging
	defer close(ch)

	// Identity
	id := RandomName()

	// User
	_, err := exec.Command("useradd", "--no-create-home", id).Output()

	if err != nil {
		ch <- CreateInternalError("Failed to create a directory to build your code. " + err.Error())

		return
	}

	uid, err := user.Lookup(id)

	if err != nil {
		ch <- CreateInternalError("Failed to look up a user. " + err.Error())

		return
	}

	uidInt, err := strconv.ParseInt(uid.Uid, 10, 64)
	if err != nil {
		ch <- CreateInternalError("Failed to parseInt uid. " + err.Error())

		return
	}

	gidInt, err := strconv.ParseInt(uid.Gid, 10, 64)
	if err != nil {
		ch <- CreateInternalError("Failed to parseInt gid. " + err.Error())

		return
	}

	defer exec.Command("userdel", id)

	// Working Directory
	path := workingDirectory + "/" + id

	err = os.Mkdir(path, 0777)

	if err != nil {
		ch <- CreateInternalError("Failed to create a directory. " + err.Error())

		return
	}

	defer os.RemoveAll(path)

	uidInt = uidInt * gidInt / gidInt
	err = os.Chown(path, int(uidInt), int(gidInt))

	if err != nil {
		ch <- CreateInternalError("Failed to chown the directory. " + err.Error())

		return
	}
	err = os.Chmod(path, 0777)

	if err != nil {
		ch <- CreateInternalError("Failed to chmod the directory. " + err.Error())

		return
	}

	o, err := exec.Command("ls", "-la", "/tmp/pj").Output()
	fmt.Println(string(o))

	// Source File
	fp, err := os.Create(path + "/" + j.Compile.SourceFileName)

	if err != nil {
		ch <- CreateInternalError("Failed to create source file." + err.Error())

		return
	}

	l, err := fp.Write([]byte(j.Code))

	if err != nil {
		ch <- CreateInternalError("Failed to write your code on your file. " + err.Error())

		return
	}

	if l != len(j.Code) {
		ch <- CreateInternalError("Failed to write your code on your file.")

		return
	}

	fp.Close()

	err = os.Chmod(path + "/" + j.Compile.SourceFileName, 0644)

	if err != nil {
		ch <- CreateInternalError("Failed to chmod the source file. " + err.Error())

		return
	}

	// Compile
	if j.Compile != nil {
		exe, err := NewExecutor(id, 512 * 1024 * 1024, []string{"ls", "-la"}, j.Compile.Image, []string{path + ":" + "/work"}, uid.Uid)

		if err != nil {
			ch <- CreateInternalError("Failed to create a Docker container to compile your code." + err.Error())

			return
		}

		res := exe.Run(10000, "")

		exe.Delete()
		if res.Status != ExecFinished {
			switch res.Status {
			case ExecError:
				ch <- CreateInternalError("Failed to execute a compiler." + res.Stderr)

				return
			case ExecMemoryLimitExceeded:
				ch <- JudgeStatus{JR: CompileMemoryLimitExceeded}

				return
			case ExecTimeLimitExceeded:
				ch <- JudgeStatus{JR: CompileTimeLimitExceeded}

				return
			}
		}

		if res.ExitCode != 0 || true { // Debug
			msg := res.Stdout + res.Stderr
			ch <- JudgeStatus{JR: CompileError, Msg: &msg}

			return
		}
	}
	return // Debug

	exe, err := NewExecutor(id, j.Mem, j.Exec.Cmd, j.Exec.Image, []string{path + ":" + "/work:ro"}, uid.Uid)

	if err != nil {
		ch <- CreateInternalError("Failed to create a Docker container to judge." + err.Error())

		return
	}

	defer exe.Delete()

	tcCounter := 0
	for tc, res := <-tests; res; tc, res = <-tests {
		res := exe.Run(j.Time, tc.In)

		if res.Status != ExecFinished {
			switch res.Status {
			case ExecError:
				msg := "Failed to execute your code." + res.Stderr
				ch <- JudgeStatus{Case: &tc.Name, JR: InternalError, Msg: &msg}
			case ExecMemoryLimitExceeded:
				ch <- JudgeStatus{Case: &tc.Name, JR: MemoryLimitExceeded}
			case ExecTimeLimitExceeded:
				ch <- JudgeStatus{Case: &tc.Name, JR: TimeLimitExceeded}
			}
		}else {
			if res.ExitCode != 0 {
				ch <- JudgeStatus{Case: &tc.Name, JR: RuntimeError}
			}else {
				if res.Stdout == tc.Out {
					ch <- JudgeStatus{Case: &tc.Name, JR: Accepted}
				}else {
					ch <- JudgeStatus{Case: &tc.Name, JR: WrongAnswer}
				}
			}
		}

		tcCounter++

		msg := strconv.FormatInt(int64(tcCounter), 10) + "/" + strconv.FormatInt(int64(j.TCCount), 10)
		ch <- JudgeStatus{JR: Judging, Msg: &msg}
	}

}
<commit_msg>Sun May 22 19:48:34 JST 2016<commit_after>package main

import "os"
import "github.com/docker/engine-api/client"
import "math/rand"
import "os/exec"
import "strconv"
import "github.com/seehuhn/mt19937"
import "time"
import "os/user"
import "fmt"

var cli *client.Client

type ExecRequest struct {
	Image string
	Cmd []string
	SourceFileName string
}

type Judge struct {
	Code string
	Compile *ExecRequest
	Exec ExecRequest
	Time int64
	Mem  int64
	TCCount int // The number of test cases
}

type JudgeResult int

const (
	Accepted            JudgeResult = 0
	WrongAnswer         JudgeResult = 1
	CompileError        JudgeResult = 2
	TimeLimitExceeded   JudgeResult = 3
	MemoryLimitExceeded JudgeResult = 4
	RuntimeError        JudgeResult = 5
	InternalError       JudgeResult = 6
	Judging             JudgeResult = 7
	CompileTimeLimitExceeded JudgeResult = 8
	CompileMemoryLimitExceeded JudgeResult = 9
)

type JudgeStatus struct {
	Case     *string
	JR       JudgeResult
	Mem      int64
	Time     int64
	Msg      *string
}

func CreateInternalError(msg string) JudgeStatus {
	return JudgeStatus{nil, InternalError, 0, 0, &msg}
}

const BASE_RAND_STRING = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

func RandomName() string {
	rng := rand.New(mt19937.New())
	rng.Seed(time.Now().UnixNano())

	res := make([]byte, 0, 32)
	for i := 0; i < 32; i++ {
		res = append(res, BASE_RAND_STRING[rng.Intn(len(BASE_RAND_STRING))])
	}

	return string(res)
}

func (j *Judge) Run(ch chan<- JudgeStatus, tests <-chan struct {
	Name string
	In  string
	Out string
}) {
	// Close a channel to send results of judging
	defer close(ch)

	// Identity
	id := RandomName()

	// User
	_, err := exec.Command("useradd", "--no-create-home", id).Output()

	if err != nil {
		ch <- CreateInternalError("Failed to create a directory to build your code. " + err.Error())

		return
	}

	uid, err := user.Lookup(id)

	if err != nil {
		ch <- CreateInternalError("Failed to look up a user. " + err.Error())

		return
	}

	uidInt, err := strconv.ParseInt(uid.Uid, 10, 64)
	if err != nil {
		ch <- CreateInternalError("Failed to parseInt uid. " + err.Error())

		return
	}

	gidInt, err := strconv.ParseInt(uid.Gid, 10, 64)
	if err != nil {
		ch <- CreateInternalError("Failed to parseInt gid. " + err.Error())

		return
	}

	defer exec.Command("userdel", id)

	// Working Directory
	path := workingDirectory + "/" + id

	err = os.Mkdir(path, 0777)

	if err != nil {
		ch <- CreateInternalError("Failed to create a directory. " + err.Error())

		return
	}

	defer os.RemoveAll(path)

	uidInt = uidInt * gidInt / gidInt
	err = os.Chown(path, int(uidInt), int(gidInt))

	if err != nil {
		ch <- CreateInternalError("Failed to chown the directory. " + err.Error())

		return
	}

	err = os.Chmod(path, 0777)

	if err != nil {
		ch <- CreateInternalError("Failed to chmod the directory. " + err.Error())

		return
	}

	// Source File
	fp, err := os.Create(path + "/" + j.Compile.SourceFileName)

	if err != nil {
		ch <- CreateInternalError("Failed to create source file." + err.Error())

		return
	}

	l, err := fp.Write([]byte(j.Code))

	if err != nil {
		ch <- CreateInternalError("Failed to write your code on your file. " + err.Error())

		return
	}

	if l != len(j.Code) {
		ch <- CreateInternalError("Failed to write your code on your file.")

		return
	}

	fp.Close()

	err = os.Chmod(path + "/" + j.Compile.SourceFileName, 0644)

	if err != nil {
		ch <- CreateInternalError("Failed to chmod the source file. " + err.Error())

		return
	}

	// Compile
	if j.Compile != nil {
		exe, err := NewExecutor(id, 512 * 1024 * 1024, j.Compile.Cmd, j.Compile.Image, []string{path + ":" + "/work"}, uid.Uid)

		if err != nil {
			ch <- CreateInternalError("Failed to create a Docker container to compile your code." + err.Error())

			return
		}

		res := exe.Run(10000, "")

		exe.Delete()
		if res.Status != ExecFinished {
			switch res.Status {
			case ExecError:
				ch <- CreateInternalError("Failed to execute a compiler." + res.Stderr)

				return
			case ExecMemoryLimitExceeded:
				ch <- JudgeStatus{JR: CompileMemoryLimitExceeded}

				return
			case ExecTimeLimitExceeded:
				ch <- JudgeStatus{JR: CompileTimeLimitExceeded}

				return
			}
		}

		if res.ExitCode != 0 { // Debug
			msg := res.Stdout + res.Stderr
			ch <- JudgeStatus{JR: CompileError, Msg: &msg}

			return
		}
	}
	return // Debug

	exe, err := NewExecutor(id, j.Mem, j.Exec.Cmd, j.Exec.Image, []string{path + ":" + "/work:ro"}, uid.Uid)

	if err != nil {
		ch <- CreateInternalError("Failed to create a Docker container to judge." + err.Error())

		return
	}

	defer exe.Delete()

	tcCounter := 0
	for tc, res := <-tests; res; tc, res = <-tests {
		res := exe.Run(j.Time, tc.In)

		if res.Status != ExecFinished {
			switch res.Status {
			case ExecError:
				msg := "Failed to execute your code." + res.Stderr
				ch <- JudgeStatus{Case: &tc.Name, JR: InternalError, Msg: &msg}
			case ExecMemoryLimitExceeded:
				ch <- JudgeStatus{Case: &tc.Name, JR: MemoryLimitExceeded}
			case ExecTimeLimitExceeded:
				ch <- JudgeStatus{Case: &tc.Name, JR: TimeLimitExceeded}
			}
		}else {
			if res.ExitCode != 0 {
				ch <- JudgeStatus{Case: &tc.Name, JR: RuntimeError}
			}else {
				if res.Stdout == tc.Out {
					ch <- JudgeStatus{Case: &tc.Name, JR: Accepted}
				}else {
					ch <- JudgeStatus{Case: &tc.Name, JR: WrongAnswer}
				}
			}
		}

		tcCounter++

		msg := strconv.FormatInt(int64(tcCounter), 10) + "/" + strconv.FormatInt(int64(j.TCCount), 10)
		ch <- JudgeStatus{JR: Judging, Msg: &msg}
	}

}
<|endoftext|>