<commit_before>package client

import (
	"fmt"
	"io"
	"os"
	"time"

	"strings"

	"github.com/ncodes/cocoon/core/orderer"
	proto "github.com/ncodes/cocoon/core/runtime/golang/proto"
	"github.com/ncodes/cocoon/core/types"
	logging "github.com/op/go-logging"
	cmap "github.com/orcaman/concurrent-map"
	context "golang.org/x/net/context"
	"google.golang.org/grpc"
)

var log = logging.MustGetLogger("connector.client")

// txChannels holds the channels to send transaction responses to
var txRespChannels = cmap.New()

// Client represents a cocoon code GRPC client
// that interacts with a cocoon code.
type Client struct {
	ccodeAddr        string
	stub             proto.StubClient
	conCtx           context.Context
	conCancel        context.CancelFunc
	orderDiscoTicker *time.Ticker
	ordererAddrs     []string
	stream           proto.Stub_TransactClient
	cocoonID         string
}

// NewClient creates a new cocoon code client
func NewClient() *Client {
	return &Client{}
}

// SetCocoonID sets the cocoon id
func (c *Client) SetCocoonID(id string) {
	c.cocoonID = id
}

// SetCocoonCodeAddr sets the cocoon code bind address
func (c *Client) SetCocoonCodeAddr(ccAddr string) {
	c.ccodeAddr = ccAddr
}

// getCCAddr returns the cocoon code bind address.
// For development, if DEV_COCOON_ADDR is set, it connects to it.
func (c *Client) getCCAddr() string {
	if devCCodeAddr := os.Getenv("DEV_COCOON_ADDR"); len(devCCodeAddr) > 0 {
		return devCCodeAddr
	}
	return c.ccodeAddr
}

// Close the stream and cancel connections
func (c *Client) Close() {
	if c.stream != nil {
		c.stream.CloseSend()
	}
	if c.conCancel != nil {
		c.conCancel()
	}
}

// GetStream returns the grpc stream connected to the grpc cocoon code server
func (c *Client) GetStream() proto.Stub_TransactClient {
	return c.stream
}

// Connect connects to a cocoon code server
// running on a known port
func (c *Client) Connect() error {

	log.Info("Starting cocoon code client")

	// start a ticker to continously discover orderer addreses
	go func() {
		c.orderDiscoTicker = time.NewTicker(60 * time.Second)
		for _ = range c.orderDiscoTicker.C {
			var ordererAddrs []string
			ordererAddrs, err := orderer.DiscoverOrderers()
			if err != nil {
				log.Error(err.Error())
				continue
			}
			c.ordererAddrs = ordererAddrs
		}
	}()

	var ordererAddrs []string
	ordererAddrs, err := orderer.DiscoverOrderers()
	if err != nil {
		return err
	}
	c.ordererAddrs = ordererAddrs

	if len(c.ordererAddrs) > 0 {
		log.Infof("Orderer address list updated. Contains %d orderer address(es)", len(c.ordererAddrs))
	} else {
		log.Warning("No orderer address was found. We won't be able to reach the orderer. ")
	}

	log.Info(">>>> ", c.getCCAddr())

	conn, err := grpc.Dial(c.getCCAddr(), grpc.WithInsecure())
	if err != nil {
		return fmt.Errorf("failed to connect to cocoon code server. %s", err)
	}
	defer conn.Close()

	log.Debugf("Now connected to cocoon code at port=%s", strings.Split(c.getCCAddr(), ":")[1])

	c.stub = proto.NewStubClient(conn)

	time.Sleep(5 * time.Second)
	if err = c.Do(conn); err != nil {
		return err
	}

	return nil
}

// Do starts a request loop that continously asks the
// server for transactions. When it receives a transaction,
// it processes it and returns the result.
func (c *Client) Do(conn *grpc.ClientConn) error {

	var err error

	// create a context so we have complete controll of the connection
	c.conCtx, c.conCancel = context.WithCancel(context.Background())

	// connect to the cocoon code
	c.stream, err = c.stub.Transact(c.conCtx)
	if err != nil {
		return fmt.Errorf("failed to start transaction stream with cocoon code. %s", err)
	}

	for {

		in, err := c.stream.Recv()
		if err == io.EOF {
			return fmt.Errorf("connection with cocoon code has ended")
		}
		if err != nil {
			if strings.Contains(err.Error(), "context canceled") {
				log.Info("Connection to cocoon code closed")
				return nil
			}
			return fmt.Errorf("failed to read message from cocoon code. %s", err)
		}

		switch in.Invoke {
		case true:
			go func() {
				log.Debugf("New invoke transaction (%s) from cocoon code", in.GetId())
				if err = c.handleInvokeTransaction(in); err != nil {
					log.Error(err.Error())
					c.stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		case false:
			log.Debugf("New response transaction (%s) from cocoon code", in.GetId())
			go func() {
				if err = c.handleRespTransaction(in); err != nil {
					log.Error(err.Error())
					c.stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		}
	}
}

// handleInvokeTransaction processes invoke transaction requests
func (c *Client) handleInvokeTransaction(tx *proto.Tx) error {
	switch tx.GetName() {
	case types.TxCreateLedger:
		return c.createLedger(tx)
	case types.TxPut:
		return c.put(tx)
	case types.TxGetLedger:
		return c.getLedger(tx)
	case types.TxGet:
		return c.get(tx, false)
	case types.TxGetByID:
		return c.get(tx, true)
	case types.TxGetBlockByID:
		return c.getBlock(tx)
	case types.TxRangeGet:
		return c.getRange(tx)
	default:
		return c.stream.Send(&proto.Tx{
			Id:       tx.GetId(),
			Response: true,
			Status:   500,
			Body:     []byte(fmt.Sprintf("unsupported transaction name (%s)", tx.GetName())),
		})
	}
}

// handleRespTransaction passes the transaction to a response
// channel with a matching transaction id and deletes the channel afterwards.
func (c *Client) handleRespTransaction(tx *proto.Tx) error {
	if !txRespChannels.Has(tx.GetId()) {
		return fmt.Errorf("response transaction (%s) does not have a corresponding response channel", tx.GetId())
	}

	txRespCh, _ := txRespChannels.Get(tx.GetId())
	txRespCh.(chan *proto.Tx) <- tx
	txRespChannels.Remove(tx.GetId())
	return nil
}

// SendTx sends a transaction to the cocoon code
// and saves the response channel. The response channel will
// be passed a response when it is available in the Transact loop.
func (c *Client) SendTx(tx *proto.Tx, respCh chan *proto.Tx) error {
	txRespChannels.Set(tx.GetId(), respCh)
	if err := c.stream.Send(tx); err != nil {
		txRespChannels.Remove(tx.GetId())
		return err
	}
	log.Debugf("Successfully sent transaction (%s) to cocoon code", tx.GetId())
	return nil
}
<commit_msg>debug<commit_after>package client

import (
	"fmt"
	"io"
	"os"
	"time"

	"strings"

	"github.com/ncodes/cocoon/core/orderer"
	proto "github.com/ncodes/cocoon/core/runtime/golang/proto"
	"github.com/ncodes/cocoon/core/types"
	logging "github.com/op/go-logging"
	cmap "github.com/orcaman/concurrent-map"
	context "golang.org/x/net/context"
	"google.golang.org/grpc"
)

var log = logging.MustGetLogger("connector.client")

// txChannels holds the channels to send transaction responses to
var txRespChannels = cmap.New()

// Client represents a cocoon code GRPC client
// that interacts with a cocoon code.
type Client struct {
	ccodeAddr        string
	stub             proto.StubClient
	conCtx           context.Context
	conCancel        context.CancelFunc
	orderDiscoTicker *time.Ticker
	ordererAddrs     []string
	stream           proto.Stub_TransactClient
	cocoonID         string
}

// NewClient creates a new cocoon code client
func NewClient() *Client {
	return &Client{}
}

// SetCocoonID sets the cocoon id
func (c *Client) SetCocoonID(id string) {
	c.cocoonID = id
}

// SetCocoonCodeAddr sets the cocoon code bind address
func (c *Client) SetCocoonCodeAddr(ccAddr string) {
	c.ccodeAddr = ccAddr
}

// getCCAddr returns the cocoon code bind address.
// For development, if DEV_COCOON_ADDR is set, it connects to it.
func (c *Client) getCCAddr() string {
	if devCCodeAddr := os.Getenv("DEV_COCOON_ADDR"); len(devCCodeAddr) > 0 {
		return devCCodeAddr
	}
	return c.ccodeAddr
}

// Close the stream and cancel connections
func (c *Client) Close() {
	if c.stream != nil {
		c.stream.CloseSend()
	}
	if c.conCancel != nil {
		c.conCancel()
	}
}

// GetStream returns the grpc stream connected to the grpc cocoon code server
func (c *Client) GetStream() proto.Stub_TransactClient {
	return c.stream
}

// Connect connects to a cocoon code server
// running on a known port
func (c *Client) Connect() error {

	log.Info("Starting cocoon code client")

	// start a ticker to continously discover orderer addreses
	go func() {
		c.orderDiscoTicker = time.NewTicker(60 * time.Second)
		for _ = range c.orderDiscoTicker.C {
			var ordererAddrs []string
			ordererAddrs, err := orderer.DiscoverOrderers()
			if err != nil {
				log.Error(err.Error())
				continue
			}
			c.ordererAddrs = ordererAddrs
		}
	}()

	var ordererAddrs []string
	ordererAddrs, err := orderer.DiscoverOrderers()
	if err != nil {
		return err
	}
	c.ordererAddrs = ordererAddrs

	if len(c.ordererAddrs) > 0 {
		log.Infof("Orderer address list updated. Contains %d orderer address(es)", len(c.ordererAddrs))
	} else {
		log.Warning("No orderer address was found. We won't be able to reach the orderer. ")
	}

	conn, err := grpc.Dial(c.getCCAddr(), grpc.WithInsecure())
	if err != nil {
		return fmt.Errorf("failed to connect to cocoon code server. %s", err)
	}
	defer conn.Close()

	log.Debugf("Now connected to cocoon code at port=%s", strings.Split(c.getCCAddr(), ":")[1])

	c.stub = proto.NewStubClient(conn)

	time.Sleep(20 * time.Second)
	if err = c.Do(conn); err != nil {
		return err
	}

	return nil
}

// Do starts a request loop that continously asks the
// server for transactions. When it receives a transaction,
// it processes it and returns the result.
func (c *Client) Do(conn *grpc.ClientConn) error {

	var err error

	// create a context so we have complete controll of the connection
	c.conCtx, c.conCancel = context.WithCancel(context.Background())

	// connect to the cocoon code
	c.stream, err = c.stub.Transact(c.conCtx)
	if err != nil {
		return fmt.Errorf("failed to start transaction stream with cocoon code. %s", err)
	}

	for {

		in, err := c.stream.Recv()
		if err == io.EOF {
			return fmt.Errorf("connection with cocoon code has ended")
		}
		if err != nil {
			if strings.Contains(err.Error(), "context canceled") {
				log.Info("Connection to cocoon code closed")
				return nil
			}
			return fmt.Errorf("failed to read message from cocoon code. %s", err)
		}

		switch in.Invoke {
		case true:
			go func() {
				log.Debugf("New invoke transaction (%s) from cocoon code", in.GetId())
				if err = c.handleInvokeTransaction(in); err != nil {
					log.Error(err.Error())
					c.stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		case false:
			log.Debugf("New response transaction (%s) from cocoon code", in.GetId())
			go func() {
				if err = c.handleRespTransaction(in); err != nil {
					log.Error(err.Error())
					c.stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		}
	}
}

// handleInvokeTransaction processes invoke transaction requests
func (c *Client) handleInvokeTransaction(tx *proto.Tx) error {
	switch tx.GetName() {
	case types.TxCreateLedger:
		return c.createLedger(tx)
	case types.TxPut:
		return c.put(tx)
	case types.TxGetLedger:
		return c.getLedger(tx)
	case types.TxGet:
		return c.get(tx, false)
	case types.TxGetByID:
		return c.get(tx, true)
	case types.TxGetBlockByID:
		return c.getBlock(tx)
	case types.TxRangeGet:
		return c.getRange(tx)
	default:
		return c.stream.Send(&proto.Tx{
			Id:       tx.GetId(),
			Response: true,
			Status:   500,
			Body:     []byte(fmt.Sprintf("unsupported transaction name (%s)", tx.GetName())),
		})
	}
}

// handleRespTransaction passes the transaction to a response
// channel with a matching transaction id and deletes the channel afterwards.
func (c *Client) handleRespTransaction(tx *proto.Tx) error {
	if !txRespChannels.Has(tx.GetId()) {
		return fmt.Errorf("response transaction (%s) does not have a corresponding response channel", tx.GetId())
	}

	txRespCh, _ := txRespChannels.Get(tx.GetId())
	txRespCh.(chan *proto.Tx) <- tx
	txRespChannels.Remove(tx.GetId())
	return nil
}

// SendTx sends a transaction to the cocoon code
// and saves the response channel. The response channel will
// be passed a response when it is available in the Transact loop.
func (c *Client) SendTx(tx *proto.Tx, respCh chan *proto.Tx) error {
	txRespChannels.Set(tx.GetId(), respCh)
	if err := c.stream.Send(tx); err != nil {
		txRespChannels.Remove(tx.GetId())
		return err
	}
	log.Debugf("Successfully sent transaction (%s) to cocoon code", tx.GetId())
	return nil
}
<|endoftext|>


<commit_before>package daemon

import (
	"os"
	"runtime"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/autogen/dockerversion"
	"github.com/docker/docker/pkg/fileutils"
	"github.com/docker/docker/pkg/parsers/kernel"
	"github.com/docker/docker/pkg/parsers/operatingsystem"
	"github.com/docker/docker/pkg/system"
	"github.com/docker/docker/registry"
	"github.com/docker/docker/utils"
)

func (daemon *Daemon) SystemInfo() (*types.Info, error) {
	images, _ := daemon.Graph().Map()
	var imgcount int
	if images == nil {
		imgcount = 0
	} else {
		imgcount = len(images)
	}
	kernelVersion := "<unknown>"
	if kv, err := kernel.GetKernelVersion(); err == nil {
		kernelVersion = kv.String()
	}

	operatingSystem := "<unknown>"
	if s, err := operatingsystem.GetOperatingSystem(); err == nil {
		operatingSystem = s
	}
	if inContainer, err := operatingsystem.IsContainerized(); err != nil {
		logrus.Errorf("Could not determine if daemon is containerized: %v", err)
		operatingSystem += " (error determining if containerized)"
	} else if inContainer {
		operatingSystem += " (containerized)"
	}

	meminfo, err := system.ReadMemInfo()
	if err != nil {
		logrus.Errorf("Could not read system memory info: %v", err)
	}

	// if we still have the original dockerinit binary from before we copied it locally, let's return the path to that, since that's more intuitive (the copied path is trivial to derive by hand given VERSION)
	initPath := utils.DockerInitPath("")
	if initPath == "" {
		// if that fails, we'll just return the path from the daemon
		initPath = daemon.SystemInitPath()
	}

	v := &types.Info{
		ID:                 daemon.ID,
		Containers:         len(daemon.List()),
		Images:             imgcount,
		Driver:             daemon.GraphDriver().String(),
		DriverStatus:       daemon.GraphDriver().Status(),
		MemoryLimit:        daemon.SystemConfig().MemoryLimit,
		SwapLimit:          daemon.SystemConfig().SwapLimit,
		CpuCfsQuota:        daemon.SystemConfig().CpuCfsQuota,
		IPv4Forwarding:     !daemon.SystemConfig().IPv4ForwardingDisabled,
		Debug:              os.Getenv("DEBUG") != "",
		NFd:                fileutils.GetTotalUsedFds(),
		NGoroutines:        runtime.NumGoroutine(),
		SystemTime:         time.Now().Format(time.RFC3339Nano),
		ExecutionDriver:    daemon.ExecutionDriver().Name(),
		LoggingDriver:      daemon.defaultLogConfig.Type,
		NEventsListener:    daemon.EventsService.SubscribersCount(),
		KernelVersion:      kernelVersion,
		OperatingSystem:    operatingSystem,
		IndexServerAddress: registry.IndexServerAddress(),
		RegistryConfig:     daemon.RegistryService.Config,
		InitSha1:           dockerversion.INITSHA1,
		InitPath:           initPath,
		NCPU:               runtime.NumCPU(),
		MemTotal:           meminfo.MemTotal,
		DockerRootDir:      daemon.Config().Root,
		Labels:             daemon.Config().Labels,
	}

	if httpProxy := os.Getenv("http_proxy"); httpProxy != "" {
		v.HttpProxy = httpProxy
	}
	if httpsProxy := os.Getenv("https_proxy"); httpsProxy != "" {
		v.HttpsProxy = httpsProxy
	}
	if noProxy := os.Getenv("no_proxy"); noProxy != "" {
		v.NoProxy = noProxy
	}
	if hostname, err := os.Hostname(); err == nil {
		v.Name = hostname
	}

	return v, nil
}
<commit_msg>Windows: Info no containerized check<commit_after>package daemon

import (
	"os"
	"runtime"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/autogen/dockerversion"
	"github.com/docker/docker/pkg/fileutils"
	"github.com/docker/docker/pkg/parsers/kernel"
	"github.com/docker/docker/pkg/parsers/operatingsystem"
	"github.com/docker/docker/pkg/system"
	"github.com/docker/docker/registry"
	"github.com/docker/docker/utils"
)

func (daemon *Daemon) SystemInfo() (*types.Info, error) {
	images, _ := daemon.Graph().Map()
	var imgcount int
	if images == nil {
		imgcount = 0
	} else {
		imgcount = len(images)
	}
	kernelVersion := "<unknown>"
	if kv, err := kernel.GetKernelVersion(); err == nil {
		kernelVersion = kv.String()
	}

	operatingSystem := "<unknown>"
	if s, err := operatingsystem.GetOperatingSystem(); err == nil {
		operatingSystem = s
	}

	// Don't do containerized check on Windows
	if runtime.GOOS != "windows" {
		if inContainer, err := operatingsystem.IsContainerized(); err != nil {
			logrus.Errorf("Could not determine if daemon is containerized: %v", err)
			operatingSystem += " (error determining if containerized)"
		} else if inContainer {
			operatingSystem += " (containerized)"
		}
	}

	meminfo, err := system.ReadMemInfo()
	if err != nil {
		logrus.Errorf("Could not read system memory info: %v", err)
	}

	// if we still have the original dockerinit binary from before we copied it locally, let's return the path to that, since that's more intuitive (the copied path is trivial to derive by hand given VERSION)
	initPath := utils.DockerInitPath("")
	if initPath == "" {
		// if that fails, we'll just return the path from the daemon
		initPath = daemon.SystemInitPath()
	}

	v := &types.Info{
		ID:                 daemon.ID,
		Containers:         len(daemon.List()),
		Images:             imgcount,
		Driver:             daemon.GraphDriver().String(),
		DriverStatus:       daemon.GraphDriver().Status(),
		MemoryLimit:        daemon.SystemConfig().MemoryLimit,
		SwapLimit:          daemon.SystemConfig().SwapLimit,
		CpuCfsQuota:        daemon.SystemConfig().CpuCfsQuota,
		IPv4Forwarding:     !daemon.SystemConfig().IPv4ForwardingDisabled,
		Debug:              os.Getenv("DEBUG") != "",
		NFd:                fileutils.GetTotalUsedFds(),
		NGoroutines:        runtime.NumGoroutine(),
		SystemTime:         time.Now().Format(time.RFC3339Nano),
		ExecutionDriver:    daemon.ExecutionDriver().Name(),
		LoggingDriver:      daemon.defaultLogConfig.Type,
		NEventsListener:    daemon.EventsService.SubscribersCount(),
		KernelVersion:      kernelVersion,
		OperatingSystem:    operatingSystem,
		IndexServerAddress: registry.IndexServerAddress(),
		RegistryConfig:     daemon.RegistryService.Config,
		InitSha1:           dockerversion.INITSHA1,
		InitPath:           initPath,
		NCPU:               runtime.NumCPU(),
		MemTotal:           meminfo.MemTotal,
		DockerRootDir:      daemon.Config().Root,
		Labels:             daemon.Config().Labels,
	}

	if httpProxy := os.Getenv("http_proxy"); httpProxy != "" {
		v.HttpProxy = httpProxy
	}
	if httpsProxy := os.Getenv("https_proxy"); httpsProxy != "" {
		v.HttpsProxy = httpsProxy
	}
	if noProxy := os.Getenv("no_proxy"); noProxy != "" {
		v.NoProxy = noProxy
	}
	if hostname, err := os.Hostname(); err == nil {
		v.Name = hostname
	}

	return v, nil
}
<|endoftext|>


<commit_before>package daemon

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/spolu/warp"
	"github.com/spolu/warp/lib/logging"
	"github.com/spolu/warp/lib/plex"
)

// Warp represents a pty served from a remote host attached to a token.
type Warp struct {
	token string

	windowSize warp.Size

	host    *HostState
	clients map[string]*UserState

	data chan []byte

	mutex *sync.Mutex
}

// UserState represents the state of a user along with a list of all his
// sessions.
type UserState struct {
	token    string
	username string
	mode     warp.Mode
	sessions map[string]*Session
}

// User returns a warp.User from the current UserState.
func (u *UserState) User(
	ctx context.Context,
) warp.User {
	return warp.User{
		Token:    u.token,
		Username: u.username,
		Mode:     u.mode,
		Hosting:  false,
	}
}

// HostState represents the state of the host, in particular the host session,
// along with its UserState.
type HostState struct {
	UserState
	session *Session
}

// User returns a warp.User from the current HostState.
func (h *HostState) User(
	ctx context.Context,
) warp.User {
	return warp.User{
		Token:    h.UserState.token,
		Username: h.UserState.username,
		Mode:     h.UserState.mode,
		Hosting:  true,
	}
}

// State computes a warp.State from the current warp. It acquires the warp
// lock.
func (w *Warp) State(
	ctx context.Context,
) warp.State {
	w.mutex.Lock()
	defer w.mutex.Unlock()
	state := warp.State{
		Warp:       w.token,
		WindowSize: w.windowSize,
		Users:      map[string]warp.User{},
	}

	state.Users[w.host.session.session.User] = w.host.User(ctx)

	for token, user := range w.clients {
		state.Users[token] = user.User(ctx)
	}

	return state
}

// CientSessions return all connected sessions that are not the host session.
func (w *Warp) CientSessions(
	ctx context.Context,
) []*Session {
	sessions := []*Session{}
	w.mutex.Lock()
	for _, user := range w.clients {
		for _, c := range user.sessions {
			sessions = append(sessions, c)
		}
	}
	// The host user's shell client sessions, if any.
	for _, c := range w.host.UserState.sessions {
		sessions = append(sessions, c)
	}
	w.mutex.Unlock()
	return sessions
}

// updateClientSessions updates all shell clients with the current warp state.
func (w *Warp) updateClientSessions(
	ctx context.Context,
) {
	st := w.State(ctx)
	sessions := w.CientSessions(ctx)
	for _, ss := range sessions {
		logging.Logf(ctx,
			"Sending (client) state: session=%s cols=%d rows=%d",
			ss.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
		)

		ss.stateW.Encode(st)
	}
}

// updateHost updates the host with the current warp state.
func (w *Warp) updateHost(
	ctx context.Context,
) {
	if !w.host.session.tornDown {
		st := w.State(ctx)

		logging.Logf(ctx,
			"Sending (host) state: session=%s cols=%d rows=%d",
			w.host.session.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
		)

		w.host.session.stateW.Encode(st)
	}
}

// rcvShellClientData handles incoming client data and commits it to the data
// channel if the client is authorized to do so.
func (w *Warp) rcvShellClientData(
	ctx context.Context,
	ss *Session,
	data []byte,
) {
	var mode warp.Mode
	w.mutex.Lock()
	mode = w.clients[ss.session.User].mode
	w.mutex.Unlock()

	if mode&warp.ModeShellWrite != 0 {
		w.data <- data
	}
}

func (w *Warp) rcvHostData(
	ctx context.Context,
	ss *Session,
	data []byte,
) {
	sessions := w.CientSessions(ctx)
	for _, s := range sessions {
		logging.Logf(ctx,
			"Sending data to session: session=%s size=%d",
			s.ToString(), len(data),
		)
		_, err := s.dataC.Write(data)
		if err != nil {
			// If we fail to write to a session, send an internal error there
			// and tear down the session. This will not impact the warp.
			s.SendInternalError(ctx)
			s.TearDown()
		}
	}
}

// handleHost is responsible for handling the host session. It is in charge of:
// - receiving and validating host update.
// - multiplexing host data to shell clients.
// - sending received (and authorized) data to the host session.
func (w *Warp) handleHost(
	ctx context.Context,
	ss *Session,
) {
	// Add the host.
	w.mutex.Lock()
	w.host = &HostState{
		UserState: UserState{
			token:    ss.session.User,
			username: ss.username,
			mode:     warp.DefaultHostMode,
			// Initialize host sessions as empty as the current client is
			// the host session and does not act as "client". Subsequent
			// client session coming from the host would be added to this
			// list.
			sessions: map[string]*Session{},
		},
		session: ss,
	}
	w.mutex.Unlock()

	// run state updates
	go func() {
	STATELOOP:
		for {
			var st warp.HostUpdate
			if err := w.host.session.updateR.Decode(&st); err != nil {
				logging.Logf(ctx,
					"Error receiving host update: session=%s error=%v",
					ss.ToString(), err,
				)
				break STATELOOP
			}

			// Check that the warp token is the same.
			if st.Warp != w.token {
				logging.Logf(ctx,
					"Host update warp mismatch: session=%s "+
						"expected=% received=%s",
					ss.ToString, w.token, st.Warp,
				)
				break STATELOOP
			}

			// Check that the session is the same in particular the secret to
			// protect against spoofing attempts.
			if st.From.Token != ss.session.Token ||
				st.From.User != ss.session.User ||
				st.From.Secret != ss.session.Secret {
				logging.Logf(ctx,
					"Host credentials mismatch: session=%s",
					ss.ToString,
				)
				break STATELOOP
			}

			w.mutex.Lock()
			w.windowSize = st.WindowSize
			for user, mode := range st.Modes {
				if _, ok := w.clients[user]; ok {
					w.clients[user].mode = mode
				} else {
					logging.Logf(ctx,
						"Unknown user from host update: session=%s user=%s",
						ss.ToString(), user,
					)
					break STATELOOP
				}
			}
			w.mutex.Unlock()

			logging.Logf(ctx,
				"Received host update: session=%s cols=%d rows=%d",
				ss.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
			)

			w.updateClientSessions(ctx)
		}
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Receive host data.
	go func() {
		plex.Run(ctx, func(data []byte) {
			logging.Logf(ctx,
				"Received data from host: session=%s size=%d",
				ss.ToString(), len(data),
			)
			w.rcvHostData(ctx, ss, data)
		}, ss.dataC)
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Send data to host.
	go func() {
	DATALOOP:
		for {
			select {
			case buf, ok := <-w.data:
				logging.Logf(ctx,
					"Sending data to host: session=%s size=%d",
					ss.ToString(), len(buf),
				)
				_, err := ss.dataC.Write(buf)
				if err != nil {
					break DATALOOP
				}
				if !ok {
					fmt.Printf("asdasd\n")
					break DATALOOP
				}
			case <-ss.ctx.Done():
				break DATALOOP
			default:
				time.Sleep(1 * time.Millisecond)
			}
		}
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Update host and clients (should be no client).
	w.updateHost(ctx)
	w.updateClientSessions(ctx)

	logging.Logf(ctx,
		"Host session running: session=%s",
		ss.ToString(),
	)

	<-ss.ctx.Done()

	close(w.data)

	// Cancel all clients.
	logging.Logf(ctx,
		"Cancelling all clients: session=%s",
		ss.ToString(),
	)
	sessions := w.CientSessions(ctx)
	for _, s := range sessions {
		s.SendError(ctx,
			"host_disconnected",
			"The warp host disconnected.",
		)
		s.TearDown()
	}
}

// handleShellClient is responsible for handling the SsTpShellClient sessions.
// It is in charge of:
// - receiving shell client data and passing it to the host if authorized.
func (w *Warp) handleShellClient(
	ctx context.Context,
	ss *Session,
) {
	// Add the client.
	w.mutex.Lock()
	isHostSession := false
	if ss.session.User == w.host.UserState.token {
		isHostSession = true
		// If we have a session conflict, let's kill the old one.
		if s, ok := w.host.UserState.sessions[ss.session.Token]; ok {
			s.TearDown()
		}
		w.host.UserState.sessions[ss.session.Token] = ss
	} else {
		if _, ok := w.clients[ss.session.User]; !ok {
			w.clients[ss.session.User] = &UserState{
				token:    ss.session.User,
				username: ss.username,
				mode:     warp.DefaultUserMode,
				sessions: map[string]*Session{},
			}
		}
		// If we have a session conflict, let's kill the old one.
		if s, ok := w.clients[ss.session.User].sessions[ss.session.Token]; ok {
			s.TearDown()
		}
		w.clients[ss.session.User].sessions[ss.session.Token] = ss
	}
	w.mutex.Unlock()

	// Receive shell client data.
	go func() {
		plex.Run(ctx, func(data []byte) {
			logging.Logf(ctx,
				"Received data from client: session=%s size=%d",
				ss.ToString(), len(data),
			)
			w.rcvShellClientData(ctx, ss, data)
		}, ss.dataC)
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Update host and clients (including the new session).
	w.updateHost(ctx)
	w.updateClientSessions(ctx)

	logging.Logf(ctx,
		"Client session running: session=%s",
		ss.ToString(),
	)

	<-ss.ctx.Done()

	// Clean-up client.
	logging.Logf(ctx,
		"Cleaning-up client: session=%s",
		ss.ToString(),
	)

	w.mutex.Lock()
	if isHostSession {
		delete(w.host.sessions, ss.session.Token)
	} else {
		delete(w.clients[ss.session.User].sessions, ss.session.Token)
		if len(w.clients[ss.session.User].sessions) == 0 {
			delete(w.clients, ss.session.User)
		}
	}
	w.mutex.Unlock()

	// Update host and remaining clients
	w.updateHost(ctx)
	w.updateClientSessions(ctx)
}
<commit_msg>Fix data loop<commit_after>package daemon

import (
	"context"
	"sync"

	"github.com/spolu/warp"
	"github.com/spolu/warp/lib/logging"
	"github.com/spolu/warp/lib/plex"
)

// Warp represents a pty served from a remote host attached to a token.
type Warp struct {
	token string

	windowSize warp.Size

	host    *HostState
	clients map[string]*UserState

	data chan []byte

	mutex *sync.Mutex
}

// UserState represents the state of a user along with a list of all his
// sessions.
type UserState struct {
	token    string
	username string
	mode     warp.Mode
	sessions map[string]*Session
}

// User returns a warp.User from the current UserState.
func (u *UserState) User(
	ctx context.Context,
) warp.User {
	return warp.User{
		Token:    u.token,
		Username: u.username,
		Mode:     u.mode,
		Hosting:  false,
	}
}

// HostState represents the state of the host, in particular the host session,
// along with its UserState.
type HostState struct {
	UserState
	session *Session
}

// User returns a warp.User from the current HostState.
func (h *HostState) User(
	ctx context.Context,
) warp.User {
	return warp.User{
		Token:    h.UserState.token,
		Username: h.UserState.username,
		Mode:     h.UserState.mode,
		Hosting:  true,
	}
}

// State computes a warp.State from the current warp. It acquires the warp
// lock.
func (w *Warp) State(
	ctx context.Context,
) warp.State {
	w.mutex.Lock()
	defer w.mutex.Unlock()
	state := warp.State{
		Warp:       w.token,
		WindowSize: w.windowSize,
		Users:      map[string]warp.User{},
	}

	state.Users[w.host.session.session.User] = w.host.User(ctx)

	for token, user := range w.clients {
		state.Users[token] = user.User(ctx)
	}

	return state
}

// CientSessions return all connected sessions that are not the host session.
func (w *Warp) CientSessions(
	ctx context.Context,
) []*Session {
	sessions := []*Session{}
	w.mutex.Lock()
	for _, user := range w.clients {
		for _, c := range user.sessions {
			sessions = append(sessions, c)
		}
	}
	// The host user's shell client sessions, if any.
	for _, c := range w.host.UserState.sessions {
		sessions = append(sessions, c)
	}
	w.mutex.Unlock()
	return sessions
}

// updateClientSessions updates all shell clients with the current warp state.
func (w *Warp) updateClientSessions(
	ctx context.Context,
) {
	st := w.State(ctx)
	sessions := w.CientSessions(ctx)
	for _, ss := range sessions {
		logging.Logf(ctx,
			"Sending (client) state: session=%s cols=%d rows=%d",
			ss.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
		)

		ss.stateW.Encode(st)
	}
}

// updateHost updates the host with the current warp state.
func (w *Warp) updateHost(
	ctx context.Context,
) {
	if !w.host.session.tornDown {
		st := w.State(ctx)

		logging.Logf(ctx,
			"Sending (host) state: session=%s cols=%d rows=%d",
			w.host.session.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
		)

		w.host.session.stateW.Encode(st)
	}
}

// rcvShellClientData handles incoming client data and commits it to the data
// channel if the client is authorized to do so.
func (w *Warp) rcvShellClientData(
	ctx context.Context,
	ss *Session,
	data []byte,
) {
	var mode warp.Mode
	w.mutex.Lock()
	mode = w.clients[ss.session.User].mode
	w.mutex.Unlock()

	if mode&warp.ModeShellWrite != 0 {
		w.data <- data
	}
}

func (w *Warp) rcvHostData(
	ctx context.Context,
	ss *Session,
	data []byte,
) {
	sessions := w.CientSessions(ctx)
	for _, s := range sessions {
		logging.Logf(ctx,
			"Sending data to session: session=%s size=%d",
			s.ToString(), len(data),
		)
		_, err := s.dataC.Write(data)
		if err != nil {
			// If we fail to write to a session, send an internal error there
			// and tear down the session. This will not impact the warp.
			s.SendInternalError(ctx)
			s.TearDown()
		}
	}
}

// handleHost is responsible for handling the host session. It is in charge of:
// - receiving and validating host update.
// - multiplexing host data to shell clients.
// - sending received (and authorized) data to the host session.
func (w *Warp) handleHost(
	ctx context.Context,
	ss *Session,
) {
	// Add the host.
	w.mutex.Lock()
	w.host = &HostState{
		UserState: UserState{
			token:    ss.session.User,
			username: ss.username,
			mode:     warp.DefaultHostMode,
			// Initialize host sessions as empty as the current client is
			// the host session and does not act as "client". Subsequent
			// client session coming from the host would be added to this
			// list.
			sessions: map[string]*Session{},
		},
		session: ss,
	}
	w.mutex.Unlock()

	// run state updates
	go func() {
	STATELOOP:
		for {
			var st warp.HostUpdate
			if err := w.host.session.updateR.Decode(&st); err != nil {
				logging.Logf(ctx,
					"Error receiving host update: session=%s error=%v",
					ss.ToString(), err,
				)
				break STATELOOP
			}

			// Check that the warp token is the same.
			if st.Warp != w.token {
				logging.Logf(ctx,
					"Host update warp mismatch: session=%s "+
						"expected=% received=%s",
					ss.ToString, w.token, st.Warp,
				)
				break STATELOOP
			}

			// Check that the session is the same in particular the secret to
			// protect against spoofing attempts.
			if st.From.Token != ss.session.Token ||
				st.From.User != ss.session.User ||
				st.From.Secret != ss.session.Secret {
				logging.Logf(ctx,
					"Host credentials mismatch: session=%s",
					ss.ToString,
				)
				break STATELOOP
			}

			w.mutex.Lock()
			w.windowSize = st.WindowSize
			for user, mode := range st.Modes {
				if _, ok := w.clients[user]; ok {
					w.clients[user].mode = mode
				} else {
					logging.Logf(ctx,
						"Unknown user from host update: session=%s user=%s",
						ss.ToString(), user,
					)
					break STATELOOP
				}
			}
			w.mutex.Unlock()

			logging.Logf(ctx,
				"Received host update: session=%s cols=%d rows=%d",
				ss.ToString(), st.WindowSize.Rows, st.WindowSize.Cols,
			)

			w.updateClientSessions(ctx)
		}
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Receive host data.
	go func() {
		plex.Run(ctx, func(data []byte) {
			logging.Logf(ctx,
				"Received data from host: session=%s size=%d",
				ss.ToString(), len(data),
			)
			w.rcvHostData(ctx, ss, data)
		}, ss.dataC)
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Send data to host.
	go func() {
	DATALOOP:
		for {
			buf, ok := <-w.data
			logging.Logf(ctx,
				"Sending data to host: session=%s size=%d",
				ss.ToString(), len(buf),
			)
			_, err := ss.dataC.Write(buf)
			if err != nil {
				break DATALOOP
			}
			if !ok {
				break DATALOOP
			}
		}
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Update host and clients (should be no client).
	w.updateHost(ctx)
	w.updateClientSessions(ctx)

	logging.Logf(ctx,
		"Host session running: session=%s",
		ss.ToString(),
	)

	<-ss.ctx.Done()

	close(w.data)

	// Cancel all clients.
	logging.Logf(ctx,
		"Cancelling all clients: session=%s",
		ss.ToString(),
	)
	sessions := w.CientSessions(ctx)
	for _, s := range sessions {
		s.SendError(ctx,
			"host_disconnected",
			"The warp host disconnected.",
		)
		s.TearDown()
	}
}

// handleShellClient is responsible for handling the SsTpShellClient sessions.
// It is in charge of:
// - receiving shell client data and passing it to the host if authorized.
func (w *Warp) handleShellClient(
	ctx context.Context,
	ss *Session,
) {
	// Add the client.
	w.mutex.Lock()
	isHostSession := false
	if ss.session.User == w.host.UserState.token {
		isHostSession = true
		// If we have a session conflict, let's kill the old one.
		if s, ok := w.host.UserState.sessions[ss.session.Token]; ok {
			s.TearDown()
		}
		w.host.UserState.sessions[ss.session.Token] = ss
	} else {
		if _, ok := w.clients[ss.session.User]; !ok {
			w.clients[ss.session.User] = &UserState{
				token:    ss.session.User,
				username: ss.username,
				mode:     warp.DefaultUserMode,
				sessions: map[string]*Session{},
			}
		}
		// If we have a session conflict, let's kill the old one.
		if s, ok := w.clients[ss.session.User].sessions[ss.session.Token]; ok {
			s.TearDown()
		}
		w.clients[ss.session.User].sessions[ss.session.Token] = ss
	}
	w.mutex.Unlock()

	// Receive shell client data.
	go func() {
		plex.Run(ctx, func(data []byte) {
			logging.Logf(ctx,
				"Received data from client: session=%s size=%d",
				ss.ToString(), len(data),
			)
			w.rcvShellClientData(ctx, ss, data)
		}, ss.dataC)
		ss.SendInternalError(ctx)
		ss.TearDown()
	}()

	// Update host and clients (including the new session).
	w.updateHost(ctx)
	w.updateClientSessions(ctx)

	logging.Logf(ctx,
		"Client session running: session=%s",
		ss.ToString(),
	)

	<-ss.ctx.Done()

	// Clean-up client.
	logging.Logf(ctx,
		"Cleaning-up client: session=%s",
		ss.ToString(),
	)

	w.mutex.Lock()
	if isHostSession {
		delete(w.host.sessions, ss.session.Token)
	} else {
		delete(w.clients[ss.session.User].sessions, ss.session.Token)
		if len(w.clients[ss.session.User].sessions) == 0 {
			delete(w.clients, ss.session.User)
		}
	}
	w.mutex.Unlock()

	// Update host and remaining clients
	w.updateHost(ctx)
	w.updateClientSessions(ctx)
}
<|endoftext|>


<commit_before>// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package gateway

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	pb_lorawan "github.com/TheThingsNetwork/api/protocol/lorawan"
	router_pb "github.com/TheThingsNetwork/api/router"
	ttnlog "github.com/TheThingsNetwork/go-utils/log"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/TheThingsNetwork/ttn/utils/random"
	"github.com/TheThingsNetwork/ttn/utils/toa"
)

// Schedule is used to schedule downlink transmissions
type Schedule interface {
	fmt.GoStringer
	// Synchronize the schedule with the gateway timestamp (in microseconds)
	Sync(timestamp uint32)
	// Get an "option" on a transmission slot at timestamp for the maximum duration of length (both in microseconds)
	GetOption(timestamp uint32, length uint32) (id string, score uint)
	// Schedule a transmission on a slot
	Schedule(id string, downlink *router_pb.DownlinkMessage) error
	// Subscribe to downlink messages
	Subscribe(subscriptionID string) <-chan *router_pb.DownlinkMessage
	// Whether the gateway has active downlink
	IsActive() bool
	// Stop the subscription
	Stop(subscriptionID string)
}

// NewSchedule creates a new Schedule
func NewSchedule(ctx ttnlog.Interface) Schedule {
	s := &schedule{
		ctx:                   ctx,
		items:                 make(map[string]*scheduledItem),
		downlinkSubscriptions: make(map[string]chan *router_pb.DownlinkMessage),
	}
	go func() {
		for {
			<-time.After(10 * time.Second)
			s.RLock()
			numItems := len(s.items)
			s.RUnlock()
			if numItems > 0 {
				s.Lock()
				for id, item := range s.items {
					// Delete the item if we are more than 2 seconds after the deadline
					if time.Now().After(item.deadlineAt.Add(2 * time.Second)) {
						delete(s.items, id)
					}
				}
				s.Unlock()
			}
		}
	}()
	return s
}

type scheduledItem struct {
	id         string
	deadlineAt time.Time
	timestamp  uint32
	length     uint32
	score      uint
	payload    *router_pb.DownlinkMessage
}

type schedule struct {
	offset int64 // should be on top to ensure memory alignment needed for sync/atomic

	sync.RWMutex
	ctx                       ttnlog.Interface
	items                     map[string]*scheduledItem
	downlink                  chan *router_pb.DownlinkMessage
	downlinkSubscriptionsLock sync.RWMutex
	downlinkSubscriptions     map[string]chan *router_pb.DownlinkMessage
	gateway                   *Gateway
}

func (s *schedule) GoString() (str string) {
	s.RLock()
	defer s.RUnlock()
	for _, item := range s.items {
		str += fmt.Sprintf("%s at %s\n", item.id, item.deadlineAt)
	}
	return
}

// Deadline for sending a downlink back to the gateway
// TODO: Make configurable
var Deadline = 1000 * time.Millisecond

const uintmax = 1 << 32

// getConflicts walks over the schedule and returns the number of conflicts.
// Both timestamp and length are in microseconds
func (s *schedule) getConflicts(timestamp uint32, length uint32) (conflicts uint) {
	s.RLock()
	defer s.RUnlock()
	for _, item := range s.items {
		scheduledFrom := uint64(item.timestamp) % uintmax
		scheduledTo := scheduledFrom + uint64(item.length)
		from := uint64(timestamp)
		to := from + uint64(length)

		if scheduledTo > uintmax || to > uintmax {
			if scheduledTo-uintmax <= from || scheduledFrom >= to-uintmax {
				continue
			}
		} else if scheduledTo <= from || scheduledFrom >= to {
			continue
		}

		if item.payload == nil {
			conflicts++
		} else {
			conflicts += 100
		}
	}
	return
}

// realtime gets the synchronized time for a timestamp (in microseconds). Time
// should first be syncronized using func Sync()
func (s *schedule) realtime(timestamp uint32) (t time.Time) {
	offset := atomic.LoadInt64(&s.offset)
	t = time.Unix(0, 0)
	t = t.Add(time.Duration(int64(timestamp)*1000 + offset))
	if t.Before(time.Now()) {
		t = t.Add(time.Duration(int64(1<<32) * 1000))
	}
	return
}

// see interface
func (s *schedule) Sync(timestamp uint32) {
	atomic.StoreInt64(&s.offset, time.Now().UnixNano()-int64(timestamp)*1000)
}

// see interface
func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score uint) {
	id = random.String(32)
	score = s.getConflicts(timestamp, length)
	item := &scheduledItem{
		id:         id,
		deadlineAt: s.realtime(timestamp).Add(-1 * Deadline),
		timestamp:  timestamp,
		length:     length,
		score:      score,
	}
	s.Lock()
	defer s.Unlock()
	s.items[id] = item
	return id, score
}

// see interface
func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) error {
	ctx := s.ctx.WithFields(ttnlog.Fields{
		"Identifier": id,
		"DataRate":   downlink.ProtocolConfiguration.GetLoRaWAN().GetDataRate(),
		"Frequency":  downlink.GatewayConfiguration.Frequency,
		"TxPower":    downlink.GatewayConfiguration.Power,
	})

	s.Lock()
	defer s.Unlock()
	if item, ok := s.items[id]; ok {
		item.payload = downlink

		conf := downlink.GetProtocolConfiguration()
		if lorawan := conf.GetLoRaWAN(); lorawan != nil {
			var time time.Duration
			if lorawan.Modulation == pb_lorawan.Modulation_LORA {
				// Calculate max ToA
				time, _ = toa.ComputeLoRa(
					uint(len(downlink.Payload)),
					lorawan.DataRate,
					lorawan.CodingRate,
				)
			}
			if lorawan.Modulation == pb_lorawan.Modulation_FSK {
				// Calculate max ToA
				time, _ = toa.ComputeFSK(
					uint(len(downlink.Payload)),
					int(lorawan.BitRate),
				)
			}
			item.length = uint32(time / 1000)
		}

		if time.Now().Before(item.deadlineAt) {
			// Schedule transmission before the Deadline
			go func() {
				waitTime := item.deadlineAt.Sub(time.Now())
				ctx.WithField("Remaining", waitTime).Debug("Scheduled downlink")
				downlink.Trace = downlink.Trace.WithEvent("schedule", "duration", waitTime)
				<-time.After(waitTime)
				s.RLock()
				defer s.RUnlock()
				if s.downlink != nil {
					ctx.Info("Send Downlink")
					s.downlink <- item.payload
				}
			}()
		} else {
			go func() {
				s.RLock()
				defer s.RUnlock()
				if s.downlink != nil {
					overdue := time.Now().Sub(item.deadlineAt)
					if overdue < Deadline {
						ctx.Info("Send Downlink")
						s.downlink <- item.payload
					} else {
						ctx.WithField("Overdue", overdue).Warn("Discard Late Downlink")
					}
				} else {
					ctx.Warn("Unable to send Downlink")
				}
			}()
		}

		return nil
	}
	return errors.NewErrNotFound(id)
}

func (s *schedule) Stop(subscriptionID string) {
	s.downlinkSubscriptionsLock.Lock()
	defer s.downlinkSubscriptionsLock.Unlock()
	if sub, ok := s.downlinkSubscriptions[subscriptionID]; ok {
		close(sub)
		delete(s.downlinkSubscriptions, subscriptionID)
	}
	if len(s.downlinkSubscriptions) == 0 {
		s.Lock()
		defer s.Unlock()
		close(s.downlink)
		s.downlink = nil
	}
}

func (s *schedule) Subscribe(subscriptionID string) <-chan *router_pb.DownlinkMessage {
	s.Lock()
	if s.downlink == nil {
		s.downlink = make(chan *router_pb.DownlinkMessage)
		go func() {
			for downlink := range s.downlink {
				if s.gateway != nil && s.gateway.Utilization != nil {
					s.gateway.Utilization.AddTx(downlink) // FIXME: Issue #420
				}
				s.downlinkSubscriptionsLock.RLock()
				for _, ch := range s.downlinkSubscriptions {
					select {
					case ch <- downlink:
					default:
						s.ctx.WithField("SubscriptionID", subscriptionID).Warn("Could not send downlink message")
					}
				}
				s.downlinkSubscriptionsLock.RUnlock()
			}
		}()
	}
	s.Unlock()

	s.downlinkSubscriptionsLock.Lock()
	if _, ok := s.downlinkSubscriptions[subscriptionID]; ok {
		return nil
	}
	sub := make(chan *router_pb.DownlinkMessage)
	s.downlinkSubscriptions[subscriptionID] = sub
	s.downlinkSubscriptionsLock.Unlock()

	return sub
}

func (s *schedule) IsActive() bool {
	s.RLock()
	defer s.RUnlock()
	return s.downlink != nil
}
<commit_msg>Add logfields to Router Downlink<commit_after>// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package gateway

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"github.com/TheThingsNetwork/api/logfields"
	pb_lorawan "github.com/TheThingsNetwork/api/protocol/lorawan"
	router_pb "github.com/TheThingsNetwork/api/router"
	ttnlog "github.com/TheThingsNetwork/go-utils/log"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/TheThingsNetwork/ttn/utils/random"
	"github.com/TheThingsNetwork/ttn/utils/toa"
)

// Schedule is used to schedule downlink transmissions
type Schedule interface {
	fmt.GoStringer
	// Synchronize the schedule with the gateway timestamp (in microseconds)
	Sync(timestamp uint32)
	// Get an "option" on a transmission slot at timestamp for the maximum duration of length (both in microseconds)
	GetOption(timestamp uint32, length uint32) (id string, score uint)
	// Schedule a transmission on a slot
	Schedule(id string, downlink *router_pb.DownlinkMessage) error
	// Subscribe to downlink messages
	Subscribe(subscriptionID string) <-chan *router_pb.DownlinkMessage
	// Whether the gateway has active downlink
	IsActive() bool
	// Stop the subscription
	Stop(subscriptionID string)
}

// NewSchedule creates a new Schedule
func NewSchedule(ctx ttnlog.Interface) Schedule {
	s := &schedule{
		ctx:                   ctx,
		items:                 make(map[string]*scheduledItem),
		downlinkSubscriptions: make(map[string]chan *router_pb.DownlinkMessage),
	}
	go func() {
		for {
			<-time.After(10 * time.Second)
			s.RLock()
			numItems := len(s.items)
			s.RUnlock()
			if numItems > 0 {
				s.Lock()
				for id, item := range s.items {
					// Delete the item if we are more than 2 seconds after the deadline
					if time.Now().After(item.deadlineAt.Add(2 * time.Second)) {
						delete(s.items, id)
					}
				}
				s.Unlock()
			}
		}
	}()
	return s
}

type scheduledItem struct {
	id         string
	deadlineAt time.Time
	timestamp  uint32
	length     uint32
	score      uint
	payload    *router_pb.DownlinkMessage
}

type schedule struct {
	offset int64 // should be on top to ensure memory alignment needed for sync/atomic

	sync.RWMutex
	ctx                       ttnlog.Interface
	items                     map[string]*scheduledItem
	downlink                  chan *router_pb.DownlinkMessage
	downlinkSubscriptionsLock sync.RWMutex
	downlinkSubscriptions     map[string]chan *router_pb.DownlinkMessage
	gateway                   *Gateway
}

func (s *schedule) GoString() (str string) {
	s.RLock()
	defer s.RUnlock()
	for _, item := range s.items {
		str += fmt.Sprintf("%s at %s\n", item.id, item.deadlineAt)
	}
	return
}

// Deadline for sending a downlink back to the gateway
// TODO: Make configurable
var Deadline = 1000 * time.Millisecond

const uintmax = 1 << 32

// getConflicts walks over the schedule and returns the number of conflicts.
// Both timestamp and length are in microseconds
func (s *schedule) getConflicts(timestamp uint32, length uint32) (conflicts uint) {
	s.RLock()
	defer s.RUnlock()
	for _, item := range s.items {
		scheduledFrom := uint64(item.timestamp) % uintmax
		scheduledTo := scheduledFrom + uint64(item.length)
		from := uint64(timestamp)
		to := from + uint64(length)

		if scheduledTo > uintmax || to > uintmax {
			if scheduledTo-uintmax <= from || scheduledFrom >= to-uintmax {
				continue
			}
		} else if scheduledTo <= from || scheduledFrom >= to {
			continue
		}

		if item.payload == nil {
			conflicts++
		} else {
			conflicts += 100
		}
	}
	return
}

// realtime gets the synchronized time for a timestamp (in microseconds). Time
// should first be syncronized using func Sync()
func (s *schedule) realtime(timestamp uint32) (t time.Time) {
	offset := atomic.LoadInt64(&s.offset)
	t = time.Unix(0, 0)
	t = t.Add(time.Duration(int64(timestamp)*1000 + offset))
	if t.Before(time.Now()) {
		t = t.Add(time.Duration(int64(1<<32) * 1000))
	}
	return
}

// see interface
func (s *schedule) Sync(timestamp uint32) {
	atomic.StoreInt64(&s.offset, time.Now().UnixNano()-int64(timestamp)*1000)
}

// see interface
func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score uint) {
	id = random.String(32)
	score = s.getConflicts(timestamp, length)
	item := &scheduledItem{
		id:         id,
		deadlineAt: s.realtime(timestamp).Add(-1 * Deadline),
		timestamp:  timestamp,
		length:     length,
		score:      score,
	}
	s.Lock()
	defer s.Unlock()
	s.items[id] = item
	return id, score
}

// see interface
func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) error {
	ctx := s.ctx.WithFields(logfields.ForMessage(downlink)).WithFields(ttnlog.Fields{
		"Identifier": id,
		"TxPower":    downlink.GatewayConfiguration.Power,
	})

	s.Lock()
	defer s.Unlock()
	if item, ok := s.items[id]; ok {
		item.payload = downlink

		conf := downlink.GetProtocolConfiguration()
		if lorawan := conf.GetLoRaWAN(); lorawan != nil {
			var time time.Duration
			if lorawan.Modulation == pb_lorawan.Modulation_LORA {
				// Calculate max ToA
				time, _ = toa.ComputeLoRa(
					uint(len(downlink.Payload)),
					lorawan.DataRate,
					lorawan.CodingRate,
				)
			}
			if lorawan.Modulation == pb_lorawan.Modulation_FSK {
				// Calculate max ToA
				time, _ = toa.ComputeFSK(
					uint(len(downlink.Payload)),
					int(lorawan.BitRate),
				)
			}
			item.length = uint32(time / 1000)
		}

		if time.Now().Before(item.deadlineAt) {
			// Schedule transmission before the Deadline
			go func() {
				waitTime := item.deadlineAt.Sub(time.Now())
				ctx.WithField("Remaining", waitTime).Debug("Scheduled downlink")
				downlink.Trace = downlink.Trace.WithEvent("schedule", "duration", waitTime)
				<-time.After(waitTime)
				s.RLock()
				defer s.RUnlock()
				if s.downlink != nil {
					ctx.Info("Send Downlink")
					s.downlink <- item.payload
				}
			}()
		} else {
			go func() {
				s.RLock()
				defer s.RUnlock()
				if s.downlink != nil {
					overdue := time.Now().Sub(item.deadlineAt)
					if overdue < Deadline {
						ctx.Info("Send Downlink")
						s.downlink <- item.payload
					} else {
						ctx.WithField("Overdue", overdue).Warn("Discard Late Downlink")
					}
				} else {
					ctx.Warn("Unable to send Downlink")
				}
			}()
		}

		return nil
	}
	return errors.NewErrNotFound(id)
}

func (s *schedule) Stop(subscriptionID string) {
	s.downlinkSubscriptionsLock.Lock()
	defer s.downlinkSubscriptionsLock.Unlock()
	if sub, ok := s.downlinkSubscriptions[subscriptionID]; ok {
		close(sub)
		delete(s.downlinkSubscriptions, subscriptionID)
	}
	if len(s.downlinkSubscriptions) == 0 {
		s.Lock()
		defer s.Unlock()
		close(s.downlink)
		s.downlink = nil
	}
}

func (s *schedule) Subscribe(subscriptionID string) <-chan *router_pb.DownlinkMessage {
	s.Lock()
	if s.downlink == nil {
		s.downlink = make(chan *router_pb.DownlinkMessage)
		go func() {
			for downlink := range s.downlink {
				if s.gateway != nil && s.gateway.Utilization != nil {
					s.gateway.Utilization.AddTx(downlink) // FIXME: Issue #420
				}
				s.downlinkSubscriptionsLock.RLock()
				for _, ch := range s.downlinkSubscriptions {
					select {
					case ch <- downlink:
					default:
						s.ctx.WithField("SubscriptionID", subscriptionID).Warn("Could not send downlink message")
					}
				}
				s.downlinkSubscriptionsLock.RUnlock()
			}
		}()
	}
	s.Unlock()

	s.downlinkSubscriptionsLock.Lock()
	if _, ok := s.downlinkSubscriptions[subscriptionID]; ok {
		return nil
	}
	sub := make(chan *router_pb.DownlinkMessage)
	s.downlinkSubscriptions[subscriptionID] = sub
	s.downlinkSubscriptionsLock.Unlock()

	return sub
}

func (s *schedule) IsActive() bool {
	s.RLock()
	defer s.RUnlock()
	return s.downlink != nil
}
<|endoftext|>


<commit_before>package gothub

import "testing"

func TestRepositories(t *testing.T) {
	repos, err := tgh.Repositories()
	if err != nil {
		t.Errorf("%s", err)
	} else {
		t.Logf("You have %d repositories:", len(repos))
		for _, repo := range repos {
			t.Logf("%#v", repo)
		}
	}
}

func TestUserRepositories(t *testing.T) {
	user, err := tgh.GetUser("octocat")
	if err != nil {
		t.Errorf("%s", err)
		return
	}

	repos, err := user.Repositories()
	if err != nil {
		t.Errorf("%s", err)
	} else {
		t.Logf("%s has the following repositories:", user.Login)
		for _, repo := range repos {
			t.Logf("%#v", repo)
		}
	}
	return
}
<commit_msg>Changed some of the logging output.<commit_after>package gothub

import "testing"

func TestRepositories(t *testing.T) {
	repos, err := tgh.Repositories()
	if err != nil {
		t.Errorf("%s", err)
	} else {
		t.Logf("You have %d repositories:", len(repos))
		for _, repo := range repos {
			t.Logf("%s", repo.FullName)
			//t.Logf("%#v", repo)
		}
	}
}

func TestUserRepositories(t *testing.T) {
	user, err := tgh.GetUser("octocat")
	if err != nil {
		t.Errorf("%s", err)
		return
	}

	repos, err := user.Repositories()
	if err != nil {
		t.Errorf("%s", err)
	} else {
		t.Logf("%s has the following repositories:", user.Login)
		for _, repo := range repos {
			t.Logf("%s", repo.FullName)
			//t.Logf("%#v", repo)
		}
	}
	return
}
<|endoftext|>


<commit_before>package resource

import (
	"fmt"
	"io"
)

// Resource is the interface type for resources
type Resource interface {
	// SetType sets the type for the resource
	// Primary usage of this method is by meta resources
	SetType(string)

	// ResourceID returns the unique identifier of a resource
	ResourceID() string

	// Returns the resources before which this resource shoud be processed
	WantBefore() []string

	// Returns the resources after which this resource should be processed
	WantAfter() []string

	// Evaluates the resource
	Evaluate() (State, error)

	// Creates the resource
	Create() error

	// Deletes the resource
	Delete() error

	// Updates the resource
	Update() error
}

// Config type contains various settings used by the resources
type Config struct {
	// The site repo which contains module and data files
	SiteRepo string

	// Writer used by the resources
	Writer io.Writer
}

// BaseResource is the base resource type for all resources
// The purpose of this type is to be embedded into other resources
// Partially implements the Resource interface
type BaseResource struct {
	// Type of the resource
	Type string `hcl:"-"`

	// Title of the resource
	Title string `hcl:"-"`

	// Resource configuration settings
	Config *Config `hcl:"-"`

	// Desired state of the resource
	State string `hcl:"state"`

	// Resources before which this resource should be processed
	Before []string `hcl:"before"`

	// Resources after which this resource should be processed
	After []string `hcl:"require"`
}

// SetType sets the type for the resource.
// This method is primarily being used by meta resources.
func (br *BaseResource) SetType(t string) {
	br.Type = t
}

// ResourceID returns the unique resource id
func (br *BaseResource) ResourceID() string {
	return fmt.Sprintf("%s[%s]", br.Type, br.Title)
}

// WantBefore returns the resources before which this resource
// should be processed
func (br *BaseResource) WantBefore() []string {
	return br.Before
}

// WantAfter returns the resources after which this resource
// should be processed
func (br *BaseResource) WantAfter() []string {
	return br.After
}

// Printf works just like fmt.Printf except that it writes to the
// given resource writer object and prepends the
// resource id to the output
func (br *BaseResource) Printf(format string, a ...interface{}) (int, error) {
	fmt.Fprintf(br.Config.Writer, "%s ", br.ResourceID())

	return fmt.Fprintf(br.Config.Writer, format, a...)
}
<commit_msg>resource: replace 'hcl' tags with 'luar' tags in BaseResource<commit_after>package resource

import (
	"fmt"
	"io"
)

// Resource is the interface type for resources
type Resource interface {
	// SetType sets the type for the resource
	// Primary usage of this method is by meta resources
	SetType(string)

	// ResourceID returns the unique identifier of a resource
	ResourceID() string

	// Returns the resources before which this resource shoud be processed
	WantBefore() []string

	// Returns the resources after which this resource should be processed
	WantAfter() []string

	// Evaluates the resource
	Evaluate() (State, error)

	// Creates the resource
	Create() error

	// Deletes the resource
	Delete() error

	// Updates the resource
	Update() error
}

// Config type contains various settings used by the resources
type Config struct {
	// The site repo which contains module and data files
	SiteRepo string

	// Writer used by the resources
	Writer io.Writer
}

// BaseResource is the base resource type for all resources
// The purpose of this type is to be embedded into other resources
// Partially implements the Resource interface
type BaseResource struct {
	// Type of the resource
	Type string `luar:"-"`

	// Title of the resource
	Title string `luar:"-"`

	// Resource configuration settings
	Config *Config `luar:"-"`

	// Desired state of the resource
	State string `luar:"state"`

	// Resources before which this resource should be processed
	Before []string `luar:"before"`

	// Resources after which this resource should be processed
	After []string `luar:"require"`
}

// SetType sets the type for the resource.
// This method is primarily being used by meta resources.
func (br *BaseResource) SetType(t string) {
	br.Type = t
}

// ResourceID returns the unique resource id
func (br *BaseResource) ResourceID() string {
	return fmt.Sprintf("%s[%s]", br.Type, br.Title)
}

// WantBefore returns the resources before which this resource
// should be processed
func (br *BaseResource) WantBefore() []string {
	return br.Before
}

// WantAfter returns the resources after which this resource
// should be processed
func (br *BaseResource) WantAfter() []string {
	return br.After
}

// Printf works just like fmt.Printf except that it writes to the
// given resource writer object and prepends the
// resource id to the output
func (br *BaseResource) Printf(format string, a ...interface{}) (int, error) {
	fmt.Fprintf(br.Config.Writer, "%s ", br.ResourceID())

	return fmt.Fprintf(br.Config.Writer, format, a...)
}
<|endoftext|>


<commit_before>// Copyright 2018 ETH Zurich
// Copyright 2019 ETH Zurich, Anapaya Systems
//
// 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 main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"net"
	"os"
	"time"

	"github.com/opentracing/opentracing-go/ext"

	"github.com/scionproto/scion/go/integration"
	"github.com/scionproto/scion/go/lib/addr"
	"github.com/scionproto/scion/go/lib/common"
	"github.com/scionproto/scion/go/lib/daemon"
	libint "github.com/scionproto/scion/go/lib/integration"
	"github.com/scionproto/scion/go/lib/log"
	"github.com/scionproto/scion/go/lib/serrors"
	"github.com/scionproto/scion/go/lib/snet"
	"github.com/scionproto/scion/go/lib/sock/reliable"
	"github.com/scionproto/scion/go/lib/topology"
	"github.com/scionproto/scion/go/lib/tracing"
	"github.com/scionproto/scion/go/lib/util"
)

const (
	ping = "ping:"
	pong = "pong:"
)

var (
	remote  snet.UDPAddr
	timeout = &util.DurWrap{Duration: 10 * time.Second}
)

func main() {
	os.Exit(realMain())
}

func realMain() int {
	defer log.HandlePanic()
	defer log.Flush()
	addFlags()
	integration.Setup()
	validateFlags()

	closeTracer, err := integration.InitTracer("end_2_end")
	if err != nil {
		log.Error("Tracer initialization failed", "err", err)
		return 1
	}
	defer closeTracer()
	if integration.Mode == integration.ModeServer {
		server{}.run()
		return 0
	}
	c := client{}
	return c.run()
}

func addFlags() {
	flag.Var(&remote, "remote", "(Mandatory for clients) address to connect to")
	flag.Var(timeout, "timeout", "The timeout for each attempt")
}

func validateFlags() {
	if integration.Mode == integration.ModeClient {
		if remote.Host == nil {
			integration.LogFatal("Missing remote address")
		}
		if remote.Host.Port == 0 {
			integration.LogFatal("Invalid remote port", "remote port", remote.Host.Port)
		}
		if timeout.Duration == 0 {
			integration.LogFatal("Invalid timeout provided", "timeout", timeout)
		}
	}
}

type server struct {
}

func (s server) run() {
	log.Info("Starting server", "isd_as", integration.Local.IA)
	defer log.Info("Finished server", "isd_as", integration.Local.IA)

	connFactory := &snet.DefaultPacketDispatcherService{
		Dispatcher: reliable.NewDispatcher(""),
		SCMPHandler: snet.DefaultSCMPHandler{
			RevocationHandler: daemon.RevHandler{Connector: integration.SDConn()},
		},
	}
	conn, port, err := connFactory.Register(context.Background(), integration.Local.IA,
		integration.Local.Host, addr.SvcNone)
	if err != nil {
		integration.LogFatal("Error listening", "err", err)
	}
	if len(os.Getenv(libint.GoIntegrationEnv)) > 0 {
		// Needed for integration test ready signal.
		fmt.Printf("Port=%d\n", port)
		fmt.Printf("%s%s\n\n", libint.ReadySignal, integration.Local.IA)
	}
	log.Info("Listening", "local", fmt.Sprintf("%v:%d", integration.Local.Host, port))

	// Receive ping message
	for {
		var p snet.Packet
		var ov net.UDPAddr
		if err := conn.ReadFrom(&p, &ov); err != nil {
			log.Error("Error reading packet", "err", err)
			var opErr *snet.OpError
			if errors.As(err, &opErr) && opErr.RevInfo() != nil {
				log.Info("Broken interface",
					"isd_as", opErr.RevInfo().IA(),
					"interface", opErr.RevInfo().IfID,
				)
			}
			continue
		}
		udp, ok := p.Payload.(snet.UDPPayload)
		if !ok {
			log.Error("Unexpected payload received",
				"source", p.Source,
				"destination", p.Destination,
				"type", common.TypeOf(p.Payload))
			continue
		}
		pld := string(udp.Payload)

		p.Destination, p.Source = p.Source, p.Destination
		p.Payload = snet.UDPPayload{
			DstPort: udp.SrcPort,
			SrcPort: udp.DstPort,
			Payload: pongMessage(integration.Local.IA, p.Destination.IA),
		}
		if pld != ping+integration.Local.IA.String() {
			log.Error("Unexpected data in payload",
				"source", p.Source,
				"destination", p.Destination,
				"data", pld,
			)
			continue
		}
		log.Info(fmt.Sprintf("Ping received from %s, sending pong.", p.Source))
		// reverse path
		if err := p.Path.Reverse(); err != nil {
			log.Info("Error reversing path",
				"source", p.Source,
				"destination", p.Destination,
				"err", err)
			continue
		}
		// Send pong
		if err := conn.WriteTo(&p, &ov); err != nil {
			log.Error("Unable to send reply", "err", err)
			continue
		}
		log.Info("Sent pong to", "client", p.Destination)
	}
}

type client struct {
	conn   snet.PacketConn
	port   uint16
	sdConn daemon.Connector

	errorPaths map[snet.PathFingerprint]struct{}
}

func (c *client) run() int {
	pair := fmt.Sprintf("%s -> %s", integration.Local.IA, remote.IA)
	log.Info("Starting", "pair", pair)
	defer log.Info("Finished", "pair", pair)
	defer integration.Done(integration.Local.IA, remote.IA)
	connFactory := &snet.DefaultPacketDispatcherService{
		Dispatcher: reliable.NewDispatcher(""),
		SCMPHandler: snet.DefaultSCMPHandler{
			RevocationHandler: daemon.RevHandler{Connector: integration.SDConn()},
		},
	}

	var err error
	c.conn, c.port, err = connFactory.Register(context.Background(), integration.Local.IA,
		integration.Local.Host, addr.SvcNone)
	if err != nil {
		integration.LogFatal("Unable to listen", "err", err)
	}
	log.Info("Send on", "local",
		fmt.Sprintf("%v,[%v]:%d", integration.Local.IA, integration.Local.Host.IP, c.port))
	c.sdConn = integration.SDConn()
	c.errorPaths = make(map[snet.PathFingerprint]struct{})
	return integration.AttemptRepeatedly("End2End", c.attemptRequest)
}

func (c *client) attemptRequest(n int) bool {
	timeoutCtx, cancel := context.WithTimeout(context.Background(), timeout.Duration)
	defer cancel()
	span, ctx := tracing.CtxWith(timeoutCtx, "run")
	span.SetTag("attempt", n)
	span.SetTag("src", integration.Local.IA)
	span.SetTag("dst", remote.IA)
	defer span.Finish()
	logger := log.FromCtx(ctx)

	// Send ping
	path, err := c.ping(ctx, n)
	if err != nil {
		logger.Error("Could not send packet", "err", err)
		ext.Error.Set(span, true)
		return false
	}
	// Receive pong
	if err := c.pong(ctx); err != nil {
		logger.Info("Error receiving pong", "err", err)
		ext.Error.Set(span, true)
		if path != nil {
			c.errorPaths[snet.Fingerprint(path)] = struct{}{}
		}
		return false
	}
	return true
}

func (c *client) ping(ctx context.Context, n int) (snet.Path, error) {
	path, err := c.getRemote(ctx, n)
	if err != nil {
		return nil, err
	}
	c.conn.SetWriteDeadline(getDeadline(ctx))
	if remote.NextHop == nil {
		remote.NextHop = &net.UDPAddr{
			IP:   remote.Host.IP,
			Port: topology.EndhostPort,
		}
	}
	pkt := &snet.Packet{
		PacketInfo: snet.PacketInfo{
			Destination: snet.SCIONAddress{
				IA:   remote.IA,
				Host: addr.HostFromIP(remote.Host.IP),
			},
			Source: snet.SCIONAddress{
				IA:   integration.Local.IA,
				Host: addr.HostFromIP(integration.Local.Host.IP),
			},
			Path: remote.Path,
			Payload: snet.UDPPayload{
				SrcPort: c.port,
				DstPort: uint16(remote.Host.Port),
				Payload: pingMessage(remote.IA),
			},
		},
	}
	log.Info("sending ping", "attempt", n, "path", path)
	return path, c.conn.WriteTo(pkt, remote.NextHop)
}

func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) {
	if remote.IA.Equal(integration.Local.IA) {
		return nil, nil
	}
	paths, err := c.sdConn.Paths(ctx, remote.IA, integration.Local.IA,
		daemon.PathReqFlags{Refresh: n != 0})
	if err != nil {
		return nil, serrors.WrapStr("Error requesting paths", err)
	}
	// if all paths had an error, let's try them again.
	if len(paths) <= len(c.errorPaths) {
		c.errorPaths = make(map[snet.PathFingerprint]struct{})
	}
	// select first path that didn't error before.
	var path snet.Path
	for _, p := range paths {
		if _, ok := c.errorPaths[snet.Fingerprint(p)]; ok {
			continue
		}
		path = p
		break
	}
	if path == nil {
		return nil, serrors.New("no path found",
			"candidates", len(paths), "errors", len(c.errorPaths))
	}
	// Extract forwarding path from the SCION Daemon response
	remote.Path = path.Path()
	remote.NextHop = path.UnderlayNextHop()
	return path, nil
}

func (c *client) pong(ctx context.Context) error {
	c.conn.SetReadDeadline(getDeadline(ctx))
	var p snet.Packet
	var ov net.UDPAddr
	if err := c.conn.ReadFrom(&p, &ov); err != nil {
		return serrors.WrapStr("Error reading packet", err)
	}
	expected := pong + remote.IA.String() + integration.Local.IA.String()
	udp, ok := p.Payload.(snet.UDPPayload)
	if !ok {
		return serrors.New("unexpected payload received", "type", common.TypeOf(p.Payload))
	}
	pld := string(udp.Payload)
	if pld != expected {
		return serrors.New("unexpected data received",
			"data", pld, "expected", expected)
	}
	log.Info("Received pong", "server", p.Source)
	return nil
}

func getDeadline(ctx context.Context) time.Time {
	dl, ok := ctx.Deadline()
	if !ok {
		integration.LogFatal("No deadline in context")
	}
	return dl
}

func pingMessage(server addr.IA) []byte {
	return []byte(ping + server.String())
}

func pongMessage(server, client addr.IA) []byte {
	return []byte(pong + server.String() + client.String())
}
<commit_msg>e2e: log broken interface for client and server<commit_after>// Copyright 2018 ETH Zurich
// Copyright 2019 ETH Zurich, Anapaya Systems
//
// 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 main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"net"
	"os"
	"time"

	"github.com/opentracing/opentracing-go/ext"

	"github.com/scionproto/scion/go/integration"
	"github.com/scionproto/scion/go/lib/addr"
	"github.com/scionproto/scion/go/lib/common"
	"github.com/scionproto/scion/go/lib/daemon"
	libint "github.com/scionproto/scion/go/lib/integration"
	"github.com/scionproto/scion/go/lib/log"
	"github.com/scionproto/scion/go/lib/serrors"
	"github.com/scionproto/scion/go/lib/snet"
	"github.com/scionproto/scion/go/lib/sock/reliable"
	"github.com/scionproto/scion/go/lib/topology"
	"github.com/scionproto/scion/go/lib/tracing"
	"github.com/scionproto/scion/go/lib/util"
)

const (
	ping = "ping:"
	pong = "pong:"
)

var (
	remote  snet.UDPAddr
	timeout = &util.DurWrap{Duration: 10 * time.Second}
)

func main() {
	os.Exit(realMain())
}

func realMain() int {
	defer log.HandlePanic()
	defer log.Flush()
	addFlags()
	integration.Setup()
	validateFlags()

	closeTracer, err := integration.InitTracer("end_2_end")
	if err != nil {
		log.Error("Tracer initialization failed", "err", err)
		return 1
	}
	defer closeTracer()
	if integration.Mode == integration.ModeServer {
		server{}.run()
		return 0
	}
	c := client{}
	return c.run()
}

func addFlags() {
	flag.Var(&remote, "remote", "(Mandatory for clients) address to connect to")
	flag.Var(timeout, "timeout", "The timeout for each attempt")
}

func validateFlags() {
	if integration.Mode == integration.ModeClient {
		if remote.Host == nil {
			integration.LogFatal("Missing remote address")
		}
		if remote.Host.Port == 0 {
			integration.LogFatal("Invalid remote port", "remote port", remote.Host.Port)
		}
		if timeout.Duration == 0 {
			integration.LogFatal("Invalid timeout provided", "timeout", timeout)
		}
	}
}

type server struct {
}

func (s server) run() {
	log.Info("Starting server", "isd_as", integration.Local.IA)
	defer log.Info("Finished server", "isd_as", integration.Local.IA)

	connFactory := &snet.DefaultPacketDispatcherService{
		Dispatcher: reliable.NewDispatcher(""),
		SCMPHandler: snet.DefaultSCMPHandler{
			RevocationHandler: daemon.RevHandler{Connector: integration.SDConn()},
		},
	}
	conn, port, err := connFactory.Register(context.Background(), integration.Local.IA,
		integration.Local.Host, addr.SvcNone)
	if err != nil {
		integration.LogFatal("Error listening", "err", err)
	}
	if len(os.Getenv(libint.GoIntegrationEnv)) > 0 {
		// Needed for integration test ready signal.
		fmt.Printf("Port=%d\n", port)
		fmt.Printf("%s%s\n\n", libint.ReadySignal, integration.Local.IA)
	}
	log.Info("Listening", "local", fmt.Sprintf("%v:%d", integration.Local.Host, port))

	// Receive ping message
	for {
		var p snet.Packet
		var ov net.UDPAddr
		if err := readFrom(conn, &p, &ov); err != nil {
			log.Error("Error reading packet", "err", err)
			continue
		}
		udp, ok := p.Payload.(snet.UDPPayload)
		if !ok {
			log.Error("Unexpected payload received",
				"source", p.Source,
				"destination", p.Destination,
				"type", common.TypeOf(p.Payload))
			continue
		}
		pld := string(udp.Payload)

		p.Destination, p.Source = p.Source, p.Destination
		p.Payload = snet.UDPPayload{
			DstPort: udp.SrcPort,
			SrcPort: udp.DstPort,
			Payload: pongMessage(integration.Local.IA, p.Destination.IA),
		}
		if pld != ping+integration.Local.IA.String() {
			log.Error("Unexpected data in payload",
				"source", p.Source,
				"destination", p.Destination,
				"data", pld,
			)
			continue
		}
		log.Info(fmt.Sprintf("Ping received from %s, sending pong.", p.Source))
		// reverse path
		if err := p.Path.Reverse(); err != nil {
			log.Info("Error reversing path",
				"source", p.Source,
				"destination", p.Destination,
				"err", err)
			continue
		}
		// Send pong
		if err := conn.WriteTo(&p, &ov); err != nil {
			log.Error("Unable to send reply", "err", err)
			continue
		}
		log.Info("Sent pong to", "client", p.Destination)
	}
}

type client struct {
	conn   snet.PacketConn
	port   uint16
	sdConn daemon.Connector

	errorPaths map[snet.PathFingerprint]struct{}
}

func (c *client) run() int {
	pair := fmt.Sprintf("%s -> %s", integration.Local.IA, remote.IA)
	log.Info("Starting", "pair", pair)
	defer log.Info("Finished", "pair", pair)
	defer integration.Done(integration.Local.IA, remote.IA)
	connFactory := &snet.DefaultPacketDispatcherService{
		Dispatcher: reliable.NewDispatcher(""),
		SCMPHandler: snet.DefaultSCMPHandler{
			RevocationHandler: daemon.RevHandler{Connector: integration.SDConn()},
		},
	}

	var err error
	c.conn, c.port, err = connFactory.Register(context.Background(), integration.Local.IA,
		integration.Local.Host, addr.SvcNone)
	if err != nil {
		integration.LogFatal("Unable to listen", "err", err)
	}
	log.Info("Send on", "local",
		fmt.Sprintf("%v,[%v]:%d", integration.Local.IA, integration.Local.Host.IP, c.port))
	c.sdConn = integration.SDConn()
	c.errorPaths = make(map[snet.PathFingerprint]struct{})
	return integration.AttemptRepeatedly("End2End", c.attemptRequest)
}

func (c *client) attemptRequest(n int) bool {
	timeoutCtx, cancel := context.WithTimeout(context.Background(), timeout.Duration)
	defer cancel()
	span, ctx := tracing.CtxWith(timeoutCtx, "run")
	span.SetTag("attempt", n)
	span.SetTag("src", integration.Local.IA)
	span.SetTag("dst", remote.IA)
	defer span.Finish()
	logger := log.FromCtx(ctx)

	// Send ping
	path, err := c.ping(ctx, n)
	if err != nil {
		logger.Error("Could not send packet", "err", err)
		ext.Error.Set(span, true)
		return false
	}
	// Receive pong
	if err := c.pong(ctx); err != nil {
		logger.Info("Error receiving pong", "err", err)
		ext.Error.Set(span, true)
		if path != nil {
			c.errorPaths[snet.Fingerprint(path)] = struct{}{}
		}
		return false
	}
	return true
}

func (c *client) ping(ctx context.Context, n int) (snet.Path, error) {
	path, err := c.getRemote(ctx, n)
	if err != nil {
		return nil, err
	}
	c.conn.SetWriteDeadline(getDeadline(ctx))
	if remote.NextHop == nil {
		remote.NextHop = &net.UDPAddr{
			IP:   remote.Host.IP,
			Port: topology.EndhostPort,
		}
	}
	pkt := &snet.Packet{
		PacketInfo: snet.PacketInfo{
			Destination: snet.SCIONAddress{
				IA:   remote.IA,
				Host: addr.HostFromIP(remote.Host.IP),
			},
			Source: snet.SCIONAddress{
				IA:   integration.Local.IA,
				Host: addr.HostFromIP(integration.Local.Host.IP),
			},
			Path: remote.Path,
			Payload: snet.UDPPayload{
				SrcPort: c.port,
				DstPort: uint16(remote.Host.Port),
				Payload: pingMessage(remote.IA),
			},
		},
	}
	log.Info("sending ping", "attempt", n, "path", path)
	return path, c.conn.WriteTo(pkt, remote.NextHop)
}

func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) {
	if remote.IA.Equal(integration.Local.IA) {
		return nil, nil
	}
	paths, err := c.sdConn.Paths(ctx, remote.IA, integration.Local.IA,
		daemon.PathReqFlags{Refresh: n != 0})
	if err != nil {
		return nil, serrors.WrapStr("Error requesting paths", err)
	}
	// if all paths had an error, let's try them again.
	if len(paths) <= len(c.errorPaths) {
		c.errorPaths = make(map[snet.PathFingerprint]struct{})
	}
	// select first path that didn't error before.
	var path snet.Path
	for _, p := range paths {
		if _, ok := c.errorPaths[snet.Fingerprint(p)]; ok {
			continue
		}
		path = p
		break
	}
	if path == nil {
		return nil, serrors.New("no path found",
			"candidates", len(paths), "errors", len(c.errorPaths))
	}
	// Extract forwarding path from the SCION Daemon response
	remote.Path = path.Path()
	remote.NextHop = path.UnderlayNextHop()
	return path, nil
}

func (c *client) pong(ctx context.Context) error {
	c.conn.SetReadDeadline(getDeadline(ctx))
	var p snet.Packet
	var ov net.UDPAddr
	if err := readFrom(c.conn, &p, &ov); err != nil {
		return serrors.WrapStr("Error reading packet", err)
	}
	expected := pong + remote.IA.String() + integration.Local.IA.String()
	udp, ok := p.Payload.(snet.UDPPayload)
	if !ok {
		return serrors.New("unexpected payload received", "type", common.TypeOf(p.Payload))
	}
	pld := string(udp.Payload)
	if pld != expected {
		return serrors.New("unexpected data received",
			"data", pld, "expected", expected)
	}
	log.Info("Received pong", "server", p.Source)
	return nil
}

func getDeadline(ctx context.Context) time.Time {
	dl, ok := ctx.Deadline()
	if !ok {
		integration.LogFatal("No deadline in context")
	}
	return dl
}

func readFrom(conn snet.PacketConn, pkt *snet.Packet, ov *net.UDPAddr) error {
	err := conn.ReadFrom(pkt, ov)
	// Attach more context to error
	var opErr *snet.OpError
	if !(errors.As(err, &opErr) && opErr.RevInfo() != nil) {
		return err
	}
	return serrors.WithCtx(err,
		"isd_as", opErr.RevInfo().IA(),
		"interface", opErr.RevInfo().IfID,
	)
}

func pingMessage(server addr.IA) []byte {
	return []byte(ping + server.String())
}

func pongMessage(server, client addr.IA) []byte {
	return []byte(pong + server.String() + client.String())
}
<|endoftext|>


<commit_before>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import (
	"sync"

	"github.com/keybase/client/go/kbfs/tlf"
	"github.com/keybase/client/go/logger"
	"golang.org/x/net/context"
	"golang.org/x/time/rate"
)

// When provisioning a new device from an existing device, the provisionee
// needs one of the existing devices to rekey for it, or it has to use paperkey
// for the rekey. For the case where an existing device does the rekey, there
// are three routines which eventually all go through this rekey queue. These
// three rekey routines are:
//
// 1. When a new device is added, the service on provisioner calls an RPC into
// KBFS, notifying the latter about the new device (provisionee) and that it
// needs rekey.
// 2. On KBFS client, a background routine runs once per hour. It asks the
// mdserver to check for TLFs that needs rekey. Note that this happens on all
// KBFS devices, no matter it has rekey capability or now.
//
// Both 1 and 2 do this by calling MDServerRemote.CheckForRekeys to send back a
// FoldersNeedRekey request.
//
// 3. When the provisionee gets provisioned, it goes through all TLFs and sends
// a MD update for each one of them, by merely copying (since it doesn't have
// access to the key yet) the existing MD revision while setting the rekey bit
// in the flag.

const (
	numConcurrentRekeys            = 64
	rekeysPerSecond     rate.Limit = 16
)

// RekeyQueueStandard implements the RekeyQueue interface.
type RekeyQueueStandard struct {
	config         Config
	log            logger.Logger
	queue          chan tlf.ID
	limiter        *rate.Limiter
	cancel         context.CancelFunc
	shutdownDoneCh chan struct{}

	mu       sync.RWMutex // guards everything below
	pendings map[tlf.ID]bool
}

// Test that RekeyQueueStandard fully implements the RekeyQueue interface.
var _ RekeyQueue = (*RekeyQueueStandard)(nil)

// NewRekeyQueueStandard creates a new rekey queue.
func NewRekeyQueueStandard(config Config) (rkq *RekeyQueueStandard) {
	ctx, cancel := context.WithCancel(context.Background())
	rkq = &RekeyQueueStandard{
		config:         config,
		log:            config.MakeLogger("RQ"),
		queue:          make(chan tlf.ID, config.Mode().RekeyQueueSize()),
		limiter:        rate.NewLimiter(rekeysPerSecond, numConcurrentRekeys),
		pendings:       make(map[tlf.ID]bool),
		cancel:         cancel,
		shutdownDoneCh: make(chan struct{}),
	}
	if config.Mode().RekeyWorkers() > 0 {
		rkq.start(ctx)
	} else {
		cancel()
		rkq.cancel = nil
	}
	return rkq
}

// start spawns a goroutine that dispatches rekey requests to correct folder
// branch ops while conforming to the rater limiter.
func (rkq *RekeyQueueStandard) start(ctx context.Context) {
	go func() {
		defer close(rkq.shutdownDoneCh)
		for {
			select {
			case id := <-rkq.queue:
				if err := rkq.limiter.Wait(ctx); err != nil {
					rkq.log.Debug("Waiting on rate limiter for tlf=%v error: %v", id, err)
					return
				}
				rkq.config.KBFSOps().RequestRekey(context.Background(), id)
				func(id tlf.ID) {
					rkq.mu.Lock()
					defer rkq.mu.Unlock()
					delete(rkq.pendings, id)
				}(id)
			case err := <-ctx.Done():
				rkq.log.Debug("Rekey queue background routine context done: %v", err)
				return
			}
		}
	}()
}

// Enqueue implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) Enqueue(id tlf.ID) {
	rkq.mu.Lock()
	defer rkq.mu.Unlock()
	rkq.pendings[id] = true

	select {
	case rkq.queue <- id:
	default:
		// The queue is full; drop this one for now until the next
		// request to the server for more rekeys.
		rkq.log.Debug("Rekey queue is full; dropping %s", id)
	}
}

// IsRekeyPending implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) IsRekeyPending(id tlf.ID) bool {
	rkq.mu.RLock()
	defer rkq.mu.RUnlock()
	return rkq.pendings[id]
}

// Shutdown implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) Shutdown() {
	rkq.mu.Lock()
	defer rkq.mu.Unlock()
	if rkq.cancel != nil {
		rkq.cancel()
		rkq.cancel = nil
		<-rkq.shutdownDoneCh
	}
}
<commit_msg>rekey_queue: avoid shutdown deadlock<commit_after>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import (
	"sync"

	"github.com/keybase/client/go/kbfs/tlf"
	"github.com/keybase/client/go/logger"
	"golang.org/x/net/context"
	"golang.org/x/time/rate"
)

// When provisioning a new device from an existing device, the provisionee
// needs one of the existing devices to rekey for it, or it has to use paperkey
// for the rekey. For the case where an existing device does the rekey, there
// are three routines which eventually all go through this rekey queue. These
// three rekey routines are:
//
// 1. When a new device is added, the service on provisioner calls an RPC into
// KBFS, notifying the latter about the new device (provisionee) and that it
// needs rekey.
// 2. On KBFS client, a background routine runs once per hour. It asks the
// mdserver to check for TLFs that needs rekey. Note that this happens on all
// KBFS devices, no matter it has rekey capability or now.
//
// Both 1 and 2 do this by calling MDServerRemote.CheckForRekeys to send back a
// FoldersNeedRekey request.
//
// 3. When the provisionee gets provisioned, it goes through all TLFs and sends
// a MD update for each one of them, by merely copying (since it doesn't have
// access to the key yet) the existing MD revision while setting the rekey bit
// in the flag.

const (
	numConcurrentRekeys            = 64
	rekeysPerSecond     rate.Limit = 16
)

// RekeyQueueStandard implements the RekeyQueue interface.
type RekeyQueueStandard struct {
	config         Config
	log            logger.Logger
	queue          chan tlf.ID
	limiter        *rate.Limiter
	cancel         context.CancelFunc
	shutdownDoneCh chan struct{}

	mu       sync.RWMutex // guards everything below
	pendings map[tlf.ID]bool
}

// Test that RekeyQueueStandard fully implements the RekeyQueue interface.
var _ RekeyQueue = (*RekeyQueueStandard)(nil)

// NewRekeyQueueStandard creates a new rekey queue.
func NewRekeyQueueStandard(config Config) (rkq *RekeyQueueStandard) {
	ctx, cancel := context.WithCancel(context.Background())
	rkq = &RekeyQueueStandard{
		config:         config,
		log:            config.MakeLogger("RQ"),
		queue:          make(chan tlf.ID, config.Mode().RekeyQueueSize()),
		limiter:        rate.NewLimiter(rekeysPerSecond, numConcurrentRekeys),
		pendings:       make(map[tlf.ID]bool),
		cancel:         cancel,
		shutdownDoneCh: make(chan struct{}),
	}
	if config.Mode().RekeyWorkers() > 0 {
		rkq.start(ctx)
	} else {
		cancel()
		rkq.cancel = nil
	}
	return rkq
}

// start spawns a goroutine that dispatches rekey requests to correct folder
// branch ops while conforming to the rater limiter.
func (rkq *RekeyQueueStandard) start(ctx context.Context) {
	go func() {
		defer close(rkq.shutdownDoneCh)
		for {
			select {
			case id := <-rkq.queue:
				if err := rkq.limiter.Wait(ctx); err != nil {
					rkq.log.Debug("Waiting on rate limiter for tlf=%v error: %v", id, err)
					return
				}
				rkq.config.KBFSOps().RequestRekey(context.Background(), id)
				func(id tlf.ID) {
					rkq.mu.Lock()
					defer rkq.mu.Unlock()
					delete(rkq.pendings, id)
				}(id)
			case err := <-ctx.Done():
				rkq.log.Debug("Rekey queue background routine context done: %v", err)
				return
			}
		}
	}()
}

// Enqueue implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) Enqueue(id tlf.ID) {
	rkq.mu.Lock()
	defer rkq.mu.Unlock()
	rkq.pendings[id] = true

	select {
	case rkq.queue <- id:
	default:
		// The queue is full; drop this one for now until the next
		// request to the server for more rekeys.
		rkq.log.Debug("Rekey queue is full; dropping %s", id)
	}
}

// IsRekeyPending implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) IsRekeyPending(id tlf.ID) bool {
	rkq.mu.RLock()
	defer rkq.mu.RUnlock()
	return rkq.pendings[id]
}

// Shutdown implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) Shutdown() {
	// Don't wait on the shutdown channel while holding the lock,
	// since that can cause deadlock.  Get it first, and then wait
	// after releasing the lock.
	cancel, shutdownDoneCh := func() (context.CancelFunc, <-chan struct{}) {
		rkq.mu.Lock()
		defer rkq.mu.Unlock()
		cancel := rkq.cancel
		rkq.cancel = nil
		return cancel, rkq.shutdownDoneCh
	}()

	if cancel != nil {
		cancel()
		<-shutdownDoneCh
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// +build !windows

package locale

import "os"

func getLocaleName() (string, error) {
	return os.Getenv("LC_ALL"), nil
}
<commit_msg>修正环境变量取值错误 不再取 LC_* 的相关值，而是 LANG<commit_after>// Copyright 2016 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// +build !windows

package locale

import "os"

func getLocaleName() (string, error) {
	return os.Getenv("LANG"), nil
}
<|endoftext|>


<commit_before>package gumble_ffmpeg

import (
	"encoding/binary"
	"errors"
	"io"
	"os/exec"
	"strconv"
	"time"

	"github.com/bontibon/gumble/gumble"
)

var (
	ErrState       = errors.New("invalid state")
	ErrUnsupported = errors.New("unsupported audio stream type")
)

type Stream struct {
	cmd        *exec.Cmd
	pipe       io.ReadCloser
	outgoing   gumble.AudioCallback
	sourceStop chan bool
}

func New() (*Stream, error) {
	stream := &Stream{}
	return stream, nil
}

func (s *Stream) OnAttach() error {
	return nil
}

func (s *Stream) OnAttachSource(outgoing gumble.AudioCallback) error {
	s.outgoing = outgoing
	return nil
}

func (s *Stream) OnAttachSink() (gumble.AudioCallback, error) {
	return nil, ErrUnsupported
}

func (s *Stream) OnDetach() {
}

func (s *Stream) Play(file string) error {
	if s.sourceStop != nil {
		return ErrState
	}
	s.cmd = exec.Command("ffmpeg", "-i", file, "-ac", "1", "-ar", strconv.Itoa(gumble.AudioSampleRate), "-f", "s16le", "-")
	if pipe, err := s.cmd.StdoutPipe(); err != nil {
		return nil
	} else {
		s.pipe = pipe
	}
	s.sourceStop = make(chan bool)
	go s.sourceRoutine()
	s.cmd.Start()
	return nil
}

func (s *Stream) Stop() error {
	if s.sourceStop != nil {
		close(s.sourceStop)
	}
	return nil
}

func (s *Stream) sourceRoutine() {
	ticker := time.NewTicker(10 * time.Millisecond)
	defer ticker.Stop()

	defer func() {
		s.pipe.Close()
		s.sourceStop = nil
	}()

	outgoing := s.outgoing
	stop := s.sourceStop
	int16Buffer := make([]int16, gumble.AudioDefaultFrameSize)
	packet := gumble.AudioPacket{
		Pcm: int16Buffer,
	}
	byteBuffer := make([]byte, gumble.AudioDefaultFrameSize*2)

	for {
		select {
		case <-stop:
			return
		case <-ticker.C:
			if _, err := io.ReadFull(s.pipe, byteBuffer); err != nil {
				return
			}
			for i := range int16Buffer {
				int16Buffer[i] = int16(binary.LittleEndian.Uint16(byteBuffer[i*2 : (i+1)*2]))
			}
			outgoing(&packet)
		}
	}
}
<commit_msg>minor gumble_ffmpeg change<commit_after>package gumble_ffmpeg

import (
	"encoding/binary"
	"errors"
	"io"
	"os/exec"
	"strconv"
	"time"

	"github.com/bontibon/gumble/gumble"
)

var (
	ErrState       = errors.New("invalid state")
	ErrUnsupported = errors.New("unsupported audio stream type")
)

type Stream struct {
	cmd        *exec.Cmd
	pipe       io.ReadCloser
	outgoing   gumble.AudioCallback
	sourceStop chan bool
}

func New() (*Stream, error) {
	stream := &Stream{}
	return stream, nil
}

func (s *Stream) OnAttach() error {
	return nil
}

func (s *Stream) OnAttachSource(outgoing gumble.AudioCallback) error {
	s.outgoing = outgoing
	return nil
}

func (s *Stream) OnAttachSink() (gumble.AudioCallback, error) {
	return nil, ErrUnsupported
}

func (s *Stream) OnDetach() {
}

func (s *Stream) Play(file string) error {
	if s.sourceStop != nil {
		return ErrState
	}
	s.cmd = exec.Command("ffmpeg", "-i", file, "-ac", "1", "-ar", strconv.Itoa(gumble.AudioSampleRate), "-f", "s16le", "-")
	if pipe, err := s.cmd.StdoutPipe(); err != nil {
		s.cmd = nil
		return nil
	} else {
		s.pipe = pipe
	}
	s.sourceStop = make(chan bool)
	go s.sourceRoutine()
	s.cmd.Start()
	return nil
}

func (s *Stream) Stop() error {
	if s.sourceStop != nil {
		close(s.sourceStop)
	}
	return nil
}

func (s *Stream) sourceRoutine() {
	ticker := time.NewTicker(10 * time.Millisecond)
	defer ticker.Stop()

	defer func() {
		s.pipe.Close()
		s.sourceStop = nil
	}()

	outgoing := s.outgoing
	stop := s.sourceStop
	int16Buffer := make([]int16, gumble.AudioDefaultFrameSize)
	packet := gumble.AudioPacket{
		Pcm: int16Buffer,
	}
	byteBuffer := make([]byte, gumble.AudioDefaultFrameSize*2)

	for {
		select {
		case <-stop:
			return
		case <-ticker.C:
			if _, err := io.ReadFull(s.pipe, byteBuffer); err != nil {
				return
			}
			for i := range int16Buffer {
				int16Buffer[i] = int16(binary.LittleEndian.Uint16(byteBuffer[i*2 : (i+1)*2]))
			}
			outgoing(&packet)
		}
	}
}
<|endoftext|>