<commit_before>package astilectron

import (
	"net"
	"os"
	"os/exec"
	"os/signal"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/asticode/go-astilog"
	"github.com/asticode/go-astitools/context"
	"github.com/asticode/go-astitools/exec"
	"github.com/pkg/errors"
)

// Versions
const (
	VersionAstilectron = "0.6.0"
	VersionElectron    = "1.6.5"
)

// Misc vars
var (
	validOSes = map[string]bool{
		"darwin":  true,
		"linux":   true,
		"windows": true,
	}
)

// App event names
const (
	EventNameAppClose         = "app.close"
	EventNameAppCmdStop       = "app.cmd.stop"
	EventNameAppCrash         = "app.crash"
	EventNameAppErrorAccept   = "app.error.accept"
	EventNameAppEventReady    = "app.event.ready"
	EventNameAppNoAccept      = "app.no.accept"
	EventNameAppTooManyAccept = "app.too.many.accept"
)

// Astilectron represents an object capable of interacting with Astilectron
// TODO Fix race conditions
type Astilectron struct {
	canceller    *asticontext.Canceller
	channelQuit  chan bool
	dispatcher   *Dispatcher
	displayPool  *displayPool
	identifier   *identifier
	listener     net.Listener
	options      Options
	paths        *Paths
	provisioner  Provisioner
	reader       *reader
	stderrWriter *astiexec.StdWriter
	stdoutWriter *astiexec.StdWriter
	writer       *writer
}

// Options represents Astilectron options
type Options struct {
	AppName            string
	AppIconDarwinPath  string // Darwin systems requires a specific .icns file
	AppIconDefaultPath string
	BaseDirectoryPath  string
}

// New creates a new Astilectron instance
func New(o Options) (a *Astilectron, err error) {
	// Validate the OS
	if !IsValidOS(runtime.GOOS) {
		err = errors.Wrapf(err, "OS %s is invalid")
		return
	}

	// Init
	a = &Astilectron{
		canceller:   asticontext.NewCanceller(),
		channelQuit: make(chan bool),
		dispatcher:  newDispatcher(),
		displayPool: newDisplayPool(),
		identifier:  newIdentifier(),
		options:     o,
		provisioner: DefaultProvisioner,
	}

	// Set paths
	if a.paths, err = newPaths(runtime.GOOS, runtime.GOARCH, o); err != nil {
		err = errors.Wrap(err, "creating new paths failed")
		return
	}

	// Add default listeners
	a.On(EventNameAppCmdStop, func(e Event) (deleteListener bool) {
		a.Stop()
		return
	})
	a.On(EventNameDisplayEventAdded, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventMetricsChanged, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventRemoved, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	return
}

// IsValidOS validates the OS
func IsValidOS(os string) (ok bool) {
	_, ok = validOSes[os]
	return
}

// SetProvisioner sets the provisioner
func (a *Astilectron) SetProvisioner(p Provisioner) *Astilectron {
	a.provisioner = p
	return a
}

// On implements the Listenable interface
func (a *Astilectron) On(eventName string, l Listener) {
	a.dispatcher.addListener(mainTargetID, eventName, l)
}

// Start starts Astilectron
func (a *Astilectron) Start() (err error) {
	// Log
	astilog.Debug("Starting...")

	// Start the dispatcher
	go a.dispatcher.start()

	// Provision
	if err = a.provision(); err != nil {
		return errors.Wrap(err, "provisioning failed")
	}

	// Unfortunately communicating with Electron through stdin/stdout doesn't work on Windows so all communications
	// will be done through TCP
	if err = a.listenTCP(); err != nil {
		return errors.Wrap(err, "listening failed")
	}

	// Execute
	if err = a.execute(); err != nil {
		err = errors.Wrap(err, "executing failed")
		return
	}
	return
}

// provision provisions Astilectron
func (a *Astilectron) provision() error {
	astilog.Debug("Provisioning...")
	var ctx, _ = a.canceller.NewContext()
	return a.provisioner.Provision(ctx, *a.dispatcher, a.options.AppName, runtime.GOOS, runtime.GOARCH, *a.paths)
}

// listenTCP listens to the first TCP connection coming its way (this should be Astilectron)
func (a *Astilectron) listenTCP() (err error) {
	// Log
	astilog.Debug("Listening...")

	// Listen
	if a.listener, err = net.Listen("tcp", "127.0.0.1:"); err != nil {
		return errors.Wrap(err, "tcp net.Listen failed")
	}

	// Check a connection has been accepted quickly enough
	var chanAccepted = make(chan bool)
	go a.watchNoAccept(30*time.Second, chanAccepted)

	// Accept connections
	go a.acceptTCP(chanAccepted)
	return
}

// watchNoAccept checks whether a TCP connection is accepted quickly enough
func (a *Astilectron) watchNoAccept(timeout time.Duration, chanAccepted chan bool) {
	var t = time.NewTimer(timeout)
	defer t.Stop()
	for {
		select {
		case <-chanAccepted:
			return
		case <-t.C:
			astilog.Errorf("No TCP connection has been accepted in the past %s", timeout)
			a.dispatcher.Dispatch(Event{Name: EventNameAppNoAccept, TargetID: mainTargetID})
			a.dispatcher.Dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
			return
		}
	}
}

// watchAcceptTCP accepts TCP connections
func (a *Astilectron) acceptTCP(chanAccepted chan bool) {
	for i := 0; i <= 1; i++ {
		// Accept
		var conn net.Conn
		var err error
		if conn, err = a.listener.Accept(); err != nil {
			astilog.Errorf("%s while TCP accepting", err)
			a.dispatcher.Dispatch(Event{Name: EventNameAppErrorAccept, TargetID: mainTargetID})
			a.dispatcher.Dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
			return
		}

		// We only accept the first connection which should be Astilectron, close the next one and stop
		// the app
		if i > 0 {
			astilog.Errorf("Too many TCP connections")
			a.dispatcher.Dispatch(Event{Name: EventNameAppTooManyAccept, TargetID: mainTargetID})
			a.dispatcher.Dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
			conn.Close()
			return
		}

		// Let the timer know a connection has been accepted
		chanAccepted <- true

		// Create reader and writer
		a.writer = newWriter(conn)
		a.reader = newReader(a.dispatcher, conn)
		go a.reader.read()
	}
}

// execute executes Astilectron in Electron
func (a *Astilectron) execute() (err error) {
	// Log
	astilog.Debug("Executing...")

	// Create command
	var ctx, _ = a.canceller.NewContext()
	var cmd = exec.CommandContext(ctx, a.paths.AppExecutable(), a.paths.AstilectronApplication(), a.listener.Addr().String())
	a.stderrWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stderr says: %s", i) })
	a.stdoutWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stdout says: %s", i) })
	cmd.Stderr = a.stderrWriter
	cmd.Stdout = a.stdoutWriter

	// Execute command
	if err = a.executeCmd(cmd); err != nil {
		return errors.Wrap(err, "executing cmd failed")
	}
	return
}

// executeCmd executes the command
func (a *Astilectron) executeCmd(cmd *exec.Cmd) (err error) {
	var e = synchronousFunc(a.canceller, a, func() {
		// Start command
		astilog.Debugf("Starting cmd %s", strings.Join(cmd.Args, " "))
		if err = cmd.Start(); err != nil {
			err = errors.Wrapf(err, "starting cmd %s failed", strings.Join(cmd.Args, " "))
			return
		}

		// Watch command
		go a.watchCmd(cmd)
	}, EventNameAppEventReady)

	// Update display pool
	if e.Displays != nil {
		a.displayPool.update(e.Displays)
	}
	return
}

// watchCmd watches the cmd execution
func (a *Astilectron) watchCmd(cmd *exec.Cmd) {
	// Wait
	cmd.Wait()

	// Check the canceller to check whether it was a crash
	if !a.canceller.Cancelled() {
		astilog.Debug("App has crashed")
		a.dispatcher.Dispatch(Event{Name: EventNameAppCrash, TargetID: mainTargetID})
	} else {
		astilog.Debug("App has closed")
		a.dispatcher.Dispatch(Event{Name: EventNameAppClose, TargetID: mainTargetID})
	}
	a.dispatcher.Dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
}

// Close closes Astilectron properly
func (a *Astilectron) Close() {
	astilog.Debug("Closing...")
	a.canceller.Cancel()
	a.dispatcher.close()
	if a.listener != nil {
		a.listener.Close()
	}
	if a.reader != nil {
		a.reader.close()
	}
	if a.stderrWriter != nil {
		a.stderrWriter.Close()
	}
	if a.stdoutWriter != nil {
		a.stdoutWriter.Close()
	}
	if a.writer != nil {
		a.writer.close()
	}
}

// HandleSignals handles signals
func (a *Astilectron) HandleSignals() {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGABRT, syscall.SIGKILL, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
	go func() {
		for sig := range ch {
			astilog.Debugf("Received signal %s", sig)
			a.Stop()
		}
	}()
}

// Stop orders Astilectron to stop
func (a *Astilectron) Stop() {
	astilog.Debug("Stopping...")
	a.canceller.Cancel()
	if a.channelQuit != nil {
		close(a.channelQuit)
		a.channelQuit = nil
	}
}

// Wait is a blocking pattern
func (a *Astilectron) Wait() {
	if a.channelQuit == nil {
		return
	}
	<-a.channelQuit
}

// Paths returns the paths
func (a *Astilectron) Paths() Paths {
	return *a.paths
}

// Displays returns the displays
func (a *Astilectron) Displays() []*Display {
	return a.displayPool.all()
}

// PrimaryDisplay returns the primary display
func (a *Astilectron) PrimaryDisplay() *Display {
	return a.displayPool.primary()
}

// NewMenu creates a new app menu
func (a *Astilectron) NewMenu(i []*MenuItemOptions) *Menu {
	return newMenu(nil, a, i, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindow creates a new window
func (a *Astilectron) NewWindow(url string, o *WindowOptions) (*Window, error) {
	return newWindow(a.options, url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindowInDisplay creates a new window in a specific display
// This overrides the center attribute
func (a *Astilectron) NewWindowInDisplay(d *Display, url string, o *WindowOptions) (*Window, error) {
	if o.X != nil {
		*o.X += d.Bounds().X
	} else {
		o.X = PtrInt(d.Bounds().X)
	}
	if o.Y != nil {
		*o.Y += d.Bounds().Y
	} else {
		o.Y = PtrInt(d.Bounds().Y)
	}
	return newWindow(a.options, url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewTray creates a new tray
func (a *Astilectron) NewTray(o *TrayOptions) *Tray {
	return newTray(o, a.canceller, a.dispatcher, a.identifier, a.writer)
}
<commit_msg>Bumped astilectron version<commit_after>package astilectron

import (
	"net"
	"os"
	"os/exec"
	"os/signal"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/asticode/go-astilog"
	"github.com/asticode/go-astitools/context"
	"github.com/asticode/go-astitools/exec"
	"github.com/pkg/errors"
)

// Versions
const (
	VersionAstilectron = "0.7.0"
	VersionElectron    = "1.6.5"
)

// Misc vars
var (
	validOSes = map[string]bool{
		"darwin":  true,
		"linux":   true,
		"windows": true,
	}
)

// App event names
const (
	EventNameAppClose         = "app.close"
	EventNameAppCmdStop       = "app.cmd.stop"
	EventNameAppCrash         = "app.crash"
	EventNameAppErrorAccept   = "app.error.accept"
	EventNameAppEventReady    = "app.event.ready"
	EventNameAppNoAccept      = "app.no.accept"
	EventNameAppTooManyAccept = "app.too.many.accept"
)

// Astilectron represents an object capable of interacting with Astilectron
// TODO Fix race conditions
type Astilectron struct {
	canceller    *asticontext.Canceller
	channelQuit  chan bool
	dispatcher   *Dispatcher
	displayPool  *displayPool
	identifier   *identifier
	listener     net.Listener
	options      Options
	paths        *Paths
	provisioner  Provisioner
	reader       *reader
	stderrWriter *astiexec.StdWriter
	stdoutWriter *astiexec.StdWriter
	writer       *writer
}

// Options represents Astilectron options
type Options struct {
	AppName            string
	AppIconDarwinPath  string // Darwin systems requires a specific .icns file
	AppIconDefaultPath string
	BaseDirectoryPath  string
}

// New creates a new Astilectron instance
func New(o Options) (a *Astilectron, err error) {
	// Validate the OS
	if !IsValidOS(runtime.GOOS) {
		err = errors.Wrapf(err, "OS %s is invalid")
		return
	}

	// Init
	a = &Astilectron{
		canceller:   asticontext.NewCanceller(),
		channelQuit: make(chan bool),
		dispatcher:  newDispatcher(),
		displayPool: newDisplayPool(),
		identifier:  newIdentifier(),
		options:     o,
		provisioner: DefaultProvisioner,
	}

	// Set paths
	if a.paths, err = newPaths(runtime.GOOS, runtime.GOARCH, o); err != nil {
		err = errors.Wrap(err, "creating new paths failed")
		return
	}

	// Add default listeners
	a.On(EventNameAppCmdStop, func(e Event) (deleteListener bool) {
		a.Stop()
		return
	})
	a.On(EventNameDisplayEventAdded, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventMetricsChanged, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventRemoved, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	return
}

// IsValidOS validates the OS
func IsValidOS(os string) (ok bool) {
	_, ok = validOSes[os]
	return
}

// SetProvisioner sets the provisioner
func (a *Astilectron) SetProvisioner(p Provisioner) *Astilectron {
	a.provisioner = p
	return a
}

// On implements the Listenable interface
func (a *Astilectron) On(eventName string, l Listener) {
	a.dispatcher.addListener(mainTargetID, eventName, l)
}

// Start starts Astilectron
func (a *Astilectron) Start() (err error) {
	// Log
	astilog.Debug("Starting...")

	// Start the dispatcher
	go a.dispatcher.start()

	// Provision
	if err = a.provision(); err != nil {
		return errors.Wrap(err, "provisioning failed")
	}

	// Unfortunately communicating with Electron through stdin/stdout doesn't work on Windows so all communications
	// will be done through TCP
	if err = a.listenTCP(); err != nil {
		return errors.Wrap(err, "listening failed")
	}

	// Execute
	if err = a.execute(); err != nil {
		err = errors.Wrap(err, "executing failed")
		return
	}
	return
}

// provision provisions Astilectron
func (a *Astilectron) provision() error {
	astilog.Debug("Provisioning...")
	var ctx, _ = a.canceller.NewContext()
	return a.provisioner.Provision(ctx, *a.dispatcher, a.options.AppName, runtime.GOOS, runtime.GOARCH, *a.paths)
}

// listenTCP listens to the first TCP connection coming its way (this should be Astilectron)
func (a *Astilectron) listenTCP() (err error) {
	// Log
	astilog.Debug("Listening...")

	// Listen
	if a.listener, err = net.Listen("tcp", "127.0.0.1:"); err != nil {
		return errors.Wrap(err, "tcp net.Listen failed")
	}

	// Check a connection has been accepted quickly enough
	var chanAccepted = make(chan bool)
	go a.watchNoAccept(30*time.Second, chanAccepted)

	// Accept connections
	go a.acceptTCP(chanAccepted)
	return
}

// watchNoAccept checks whether a TCP connection is accepted quickly enough
func (a *Astilectron) watchNoAccept(timeout time.Duration, chanAccepted chan bool) {
	var t = time.NewTimer(timeout)
	defer t.Stop()
	for {
		select {
		case <-chanAccepted:
			return
		case <-t.C:
			astilog.Errorf("No TCP connection has been accepted in the past %s", timeout)
			a.dispatcher.Dispatch(Event{Name: EventNameAppNoAccept, TargetID: mainTargetID})
			a.dispatcher.Dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
			return
		}
	}
}

// watchAcceptTCP accepts TCP connections
func (a *Astilectron) acceptTCP(chanAccepted chan bool) {
	for i := 0; i <= 1; i++ {
		// Accept
		var conn net.Conn
		var err error
		if conn, err = a.listener.Accept(); err != nil {
			astilog.Errorf("%s while TCP accepting", err)
			a.dispatcher.Dispatch(Event{Name: EventNameAppErrorAccept, TargetID: mainTargetID})
			a.dispatcher.Dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
			return
		}

		// We only accept the first connection which should be Astilectron, close the next one and stop
		// the app
		if i > 0 {
			astilog.Errorf("Too many TCP connections")
			a.dispatcher.Dispatch(Event{Name: EventNameAppTooManyAccept, TargetID: mainTargetID})
			a.dispatcher.Dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
			conn.Close()
			return
		}

		// Let the timer know a connection has been accepted
		chanAccepted <- true

		// Create reader and writer
		a.writer = newWriter(conn)
		a.reader = newReader(a.dispatcher, conn)
		go a.reader.read()
	}
}

// execute executes Astilectron in Electron
func (a *Astilectron) execute() (err error) {
	// Log
	astilog.Debug("Executing...")

	// Create command
	var ctx, _ = a.canceller.NewContext()
	var cmd = exec.CommandContext(ctx, a.paths.AppExecutable(), a.paths.AstilectronApplication(), a.listener.Addr().String())
	a.stderrWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stderr says: %s", i) })
	a.stdoutWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stdout says: %s", i) })
	cmd.Stderr = a.stderrWriter
	cmd.Stdout = a.stdoutWriter

	// Execute command
	if err = a.executeCmd(cmd); err != nil {
		return errors.Wrap(err, "executing cmd failed")
	}
	return
}

// executeCmd executes the command
func (a *Astilectron) executeCmd(cmd *exec.Cmd) (err error) {
	var e = synchronousFunc(a.canceller, a, func() {
		// Start command
		astilog.Debugf("Starting cmd %s", strings.Join(cmd.Args, " "))
		if err = cmd.Start(); err != nil {
			err = errors.Wrapf(err, "starting cmd %s failed", strings.Join(cmd.Args, " "))
			return
		}

		// Watch command
		go a.watchCmd(cmd)
	}, EventNameAppEventReady)

	// Update display pool
	if e.Displays != nil {
		a.displayPool.update(e.Displays)
	}
	return
}

// watchCmd watches the cmd execution
func (a *Astilectron) watchCmd(cmd *exec.Cmd) {
	// Wait
	cmd.Wait()

	// Check the canceller to check whether it was a crash
	if !a.canceller.Cancelled() {
		astilog.Debug("App has crashed")
		a.dispatcher.Dispatch(Event{Name: EventNameAppCrash, TargetID: mainTargetID})
	} else {
		astilog.Debug("App has closed")
		a.dispatcher.Dispatch(Event{Name: EventNameAppClose, TargetID: mainTargetID})
	}
	a.dispatcher.Dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
}

// Close closes Astilectron properly
func (a *Astilectron) Close() {
	astilog.Debug("Closing...")
	a.canceller.Cancel()
	a.dispatcher.close()
	if a.listener != nil {
		a.listener.Close()
	}
	if a.reader != nil {
		a.reader.close()
	}
	if a.stderrWriter != nil {
		a.stderrWriter.Close()
	}
	if a.stdoutWriter != nil {
		a.stdoutWriter.Close()
	}
	if a.writer != nil {
		a.writer.close()
	}
}

// HandleSignals handles signals
func (a *Astilectron) HandleSignals() {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGABRT, syscall.SIGKILL, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
	go func() {
		for sig := range ch {
			astilog.Debugf("Received signal %s", sig)
			a.Stop()
		}
	}()
}

// Stop orders Astilectron to stop
func (a *Astilectron) Stop() {
	astilog.Debug("Stopping...")
	a.canceller.Cancel()
	if a.channelQuit != nil {
		close(a.channelQuit)
		a.channelQuit = nil
	}
}

// Wait is a blocking pattern
func (a *Astilectron) Wait() {
	if a.channelQuit == nil {
		return
	}
	<-a.channelQuit
}

// Paths returns the paths
func (a *Astilectron) Paths() Paths {
	return *a.paths
}

// Displays returns the displays
func (a *Astilectron) Displays() []*Display {
	return a.displayPool.all()
}

// PrimaryDisplay returns the primary display
func (a *Astilectron) PrimaryDisplay() *Display {
	return a.displayPool.primary()
}

// NewMenu creates a new app menu
func (a *Astilectron) NewMenu(i []*MenuItemOptions) *Menu {
	return newMenu(nil, a, i, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindow creates a new window
func (a *Astilectron) NewWindow(url string, o *WindowOptions) (*Window, error) {
	return newWindow(a.options, url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindowInDisplay creates a new window in a specific display
// This overrides the center attribute
func (a *Astilectron) NewWindowInDisplay(d *Display, url string, o *WindowOptions) (*Window, error) {
	if o.X != nil {
		*o.X += d.Bounds().X
	} else {
		o.X = PtrInt(d.Bounds().X)
	}
	if o.Y != nil {
		*o.Y += d.Bounds().Y
	} else {
		o.Y = PtrInt(d.Bounds().Y)
	}
	return newWindow(a.options, url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewTray creates a new tray
func (a *Astilectron) NewTray(o *TrayOptions) *Tray {
	return newTray(o, a.canceller, a.dispatcher, a.identifier, a.writer)
}
<|endoftext|>


<commit_before><commit_msg>Fix androidbuildinternal yet again.<commit_after><|endoftext|>


<commit_before>package cmrdr

import (
	"errors"
	"fmt"
	"os"
	"testing"
	"time"

	curl "github.com/andelf/go-curl"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

type CurlerMock struct {
	mock.Mock
}

func (m *CurlerMock) Setopt(opt int, param interface{}) error {
	args := m.Called(opt, param)
	return args.Error(0)
}

func (m *CurlerMock) Perform() error {
	args := m.Called()
	return args.Error(0)
}

func (m *CurlerMock) Getinfo(info curl.CurlInfo) (interface{}, error) {
	args := m.Called(info)
	return args.Int(0), args.Error(1)
}

func (m *CurlerMock) Duphandle() Curler {
	return m
}

func TestAttackCredentials(t *testing.T) {
	validStream1 := Stream{
		Device:  "fakeDevice",
		Address: "fakeAddress",
		Port:    1337,
	}

	validStream2 := Stream{
		Device:  "fakeDevice",
		Address: "differentFakeAddress",
		Port:    1337,
	}

	invalidStream := Stream{
		Device: "InvalidDevice",
	}

	fakeTargets := []Stream{validStream1, validStream2}
	invalidTargets := []Stream{invalidStream}
	fakeCredentials := Credentials{
		Usernames: []string{"admin", "root"},
		Passwords: []string{"12345", "root"},
	}

	testCases := []struct {
		targets     []Stream
		credentials Credentials
		timeout     time.Duration
		log         bool

		status int

		performErr     error
		getInfoErr     error
		invalidTargets bool

		expectedStreams []Stream
		expectedErrMsg  string
	}{
		// Credentials found
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,

			status: 404,

			expectedStreams: fakeTargets,
		},
		// Camera accessed
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,

			status: 200,

			expectedStreams: fakeTargets,
		},
		// Invalid targets
		{
			targets:     invalidTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,

			invalidTargets: true,

			expectedErrMsg:  "invalid targets",
			expectedStreams: invalidTargets,
		},
		// curl perform fails
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,

			performErr: errors.New("dummy error"),

			expectedStreams: fakeTargets,
		},
		// curl getinfo fails
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,

			getInfoErr: errors.New("dummy error"),

			expectedStreams: fakeTargets,
		},
		// Logging disabled
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,
			log:         false,

			status: 403,

			expectedStreams: fakeTargets,
		},
		// Logging enabled
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,
			log:         true,

			status: 403,

			expectedStreams: fakeTargets,
		},
	}
	for i, test := range testCases {
		curlerMock := &CurlerMock{}

		if !test.invalidTargets {
			curlerMock.On("Setopt", mock.Anything, mock.Anything).Return(nil)
			curlerMock.On("Perform").Return(test.performErr)
			if test.performErr == nil {
				curlerMock.On("Getinfo", mock.Anything).Return(test.status, test.getInfoErr)
			}
		}

		results, err := AttackCredentials(curlerMock, test.targets, test.credentials, test.timeout, test.log)

		if len(test.expectedErrMsg) > 0 {
			if err == nil {
				fmt.Printf("unexpected success in AttackCredentials test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
				os.Exit(1)
			}
			assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
		} else {
			if err != nil {
				fmt.Printf("unexpected error in AttackCredentials test, iteration %d: %v\n", i, err)
				os.Exit(1)
			}
			for _, stream := range test.expectedStreams {
				foundStream := false
				for _, result := range results {
					if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
						foundStream = true
					}
				}
				assert.Equal(t, true, foundStream, "wrong streams parsed")
			}
		}
		assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
		curlerMock.AssertExpectations(t)
	}
}

func TestAttackRoute(t *testing.T) {
	validStream1 := Stream{
		Device:  "fakeDevice",
		Address: "fakeAddress",
		Port:    1337,
	}

	validStream2 := Stream{
		Device:  "fakeDevice",
		Address: "differentFakeAddress",
		Port:    1337,
	}

	invalidStream := Stream{
		Device: "InvalidDevice",
	}

	fakeTargets := []Stream{validStream1, validStream2}
	fakeRoutes := Routes{"live.sdp", "media.amp"}
	invalidTargets := []Stream{invalidStream}

	testCases := []struct {
		targets []Stream
		routes  Routes
		timeout time.Duration
		log     bool

		status int

		performErr     error
		getInfoErr     error
		invalidTargets bool

		expectedStreams []Stream
		expectedErrMsg  string
	}{
		// Route found
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,

			status: 403,

			expectedStreams: fakeTargets,
		},
		// Route found
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,

			status: 401,

			expectedStreams: fakeTargets,
		},
		// Camera accessed
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,

			status: 200,

			expectedStreams: fakeTargets,
		},
		// Invalid targets
		{
			targets:        invalidTargets,
			routes:         fakeRoutes,
			timeout:        1 * time.Millisecond,
			invalidTargets: true,

			expectedErrMsg:  "invalid targets",
			expectedStreams: invalidTargets,
		},
		// curl perform fails
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,

			performErr: errors.New("dummy error"),

			expectedStreams: fakeTargets,
		},
		// curl getinfo fails
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,

			getInfoErr: errors.New("dummy error"),

			expectedStreams: fakeTargets,
		},
		// Logs disabled
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,
			log:     false,

			expectedStreams: fakeTargets,
		},
		// Logs enabled
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,
			log:     true,

			expectedStreams: fakeTargets,
		},
	}
	for i, test := range testCases {
		curlerMock := &CurlerMock{}

		if !test.invalidTargets {
			curlerMock.On("Setopt", mock.Anything, mock.Anything).Return(nil)
			curlerMock.On("Perform").Return(test.performErr)
			if test.performErr == nil {
				curlerMock.On("Getinfo", mock.Anything).Return(test.status, test.getInfoErr)
			}
		}

		results, err := AttackRoute(curlerMock, test.targets, test.routes, test.timeout, test.log)

		if len(test.expectedErrMsg) > 0 {
			if err == nil {
				fmt.Printf("unexpected success in AttackRoute test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
				os.Exit(1)
			}
			assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
		} else {
			if err != nil {
				fmt.Printf("unexpected error in AttackRoute test, iteration %d: %v\n", i, err)
				os.Exit(1)
			}
			for _, stream := range test.expectedStreams {
				foundStream := false
				for _, result := range results {
					if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
						foundStream = true
					}
				}
				assert.Equal(t, true, foundStream, "wrong streams parsed")
			}
		}
		assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
		curlerMock.AssertExpectations(t)
	}
}

func TestDotWrite(t *testing.T) {
	assert.Equal(t, true, doNotWrite(nil, nil))
}
<commit_msg>Add unit tests for stream validation<commit_after>package cmrdr

import (
	"errors"
	"fmt"
	"os"
	"testing"
	"time"

	curl "github.com/andelf/go-curl"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

type CurlerMock struct {
	mock.Mock
}

func (m *CurlerMock) Setopt(opt int, param interface{}) error {
	args := m.Called(opt, param)
	return args.Error(0)
}

func (m *CurlerMock) Perform() error {
	args := m.Called()
	return args.Error(0)
}

func (m *CurlerMock) Getinfo(info curl.CurlInfo) (interface{}, error) {
	args := m.Called(info)
	return args.Int(0), args.Error(1)
}

func (m *CurlerMock) Duphandle() Curler {
	return m
}

func TestAttackCredentials(t *testing.T) {
	validStream1 := Stream{
		Device:  "fakeDevice",
		Address: "fakeAddress",
		Port:    1337,
	}

	validStream2 := Stream{
		Device:  "fakeDevice",
		Address: "differentFakeAddress",
		Port:    1337,
	}

	invalidStream := Stream{
		Device: "InvalidDevice",
	}

	fakeTargets := []Stream{validStream1, validStream2}
	invalidTargets := []Stream{invalidStream}
	fakeCredentials := Credentials{
		Usernames: []string{"admin", "root"},
		Passwords: []string{"12345", "root"},
	}

	testCases := []struct {
		targets     []Stream
		credentials Credentials
		timeout     time.Duration
		log         bool

		status int

		performErr     error
		getInfoErr     error
		invalidTargets bool

		expectedStreams []Stream
		expectedErrMsg  string
	}{
		// Credentials found
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,

			status: 404,

			expectedStreams: fakeTargets,
		},
		// Camera accessed
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,

			status: 200,

			expectedStreams: fakeTargets,
		},
		// Invalid targets
		{
			targets:     invalidTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,

			invalidTargets: true,

			expectedErrMsg:  "invalid targets",
			expectedStreams: invalidTargets,
		},
		// curl perform fails
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,

			performErr: errors.New("dummy error"),

			expectedStreams: fakeTargets,
		},
		// curl getinfo fails
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,

			getInfoErr: errors.New("dummy error"),

			expectedStreams: fakeTargets,
		},
		// Logging disabled
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,
			log:         false,

			status: 403,

			expectedStreams: fakeTargets,
		},
		// Logging enabled
		{
			targets:     fakeTargets,
			credentials: fakeCredentials,
			timeout:     1 * time.Millisecond,
			log:         true,

			status: 403,

			expectedStreams: fakeTargets,
		},
	}
	for i, test := range testCases {
		curlerMock := &CurlerMock{}

		if !test.invalidTargets {
			curlerMock.On("Setopt", mock.Anything, mock.Anything).Return(nil)
			curlerMock.On("Perform").Return(test.performErr)
			if test.performErr == nil {
				curlerMock.On("Getinfo", mock.Anything).Return(test.status, test.getInfoErr)
			}
		}

		results, err := AttackCredentials(curlerMock, test.targets, test.credentials, test.timeout, test.log)

		if len(test.expectedErrMsg) > 0 {
			if err == nil {
				fmt.Printf("unexpected success in AttackCredentials test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
				os.Exit(1)
			}
			assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
		} else {
			if err != nil {
				fmt.Printf("unexpected error in AttackCredentials test, iteration %d: %v\n", i, err)
				os.Exit(1)
			}
			for _, stream := range test.expectedStreams {
				foundStream := false
				for _, result := range results {
					if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
						foundStream = true
					}
				}
				assert.Equal(t, true, foundStream, "wrong streams parsed")
			}
		}
		assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
		curlerMock.AssertExpectations(t)
	}
}

func TestAttackRoute(t *testing.T) {
	validStream1 := Stream{
		Device:  "fakeDevice",
		Address: "fakeAddress",
		Port:    1337,
	}

	validStream2 := Stream{
		Device:  "fakeDevice",
		Address: "differentFakeAddress",
		Port:    1337,
	}

	invalidStream := Stream{
		Device: "InvalidDevice",
	}

	fakeTargets := []Stream{validStream1, validStream2}
	fakeRoutes := Routes{"live.sdp", "media.amp"}
	invalidTargets := []Stream{invalidStream}

	testCases := []struct {
		targets []Stream
		routes  Routes
		timeout time.Duration
		log     bool

		status int

		performErr     error
		getInfoErr     error
		invalidTargets bool

		expectedStreams []Stream
		expectedErrMsg  string
	}{
		// Route found
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,

			status: 403,

			expectedStreams: fakeTargets,
		},
		// Route found
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,

			status: 401,

			expectedStreams: fakeTargets,
		},
		// Camera accessed
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,

			status: 200,

			expectedStreams: fakeTargets,
		},
		// Invalid targets
		{
			targets:        invalidTargets,
			routes:         fakeRoutes,
			timeout:        1 * time.Millisecond,
			invalidTargets: true,

			expectedErrMsg:  "invalid targets",
			expectedStreams: invalidTargets,
		},
		// curl perform fails
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,

			performErr: errors.New("dummy error"),

			expectedStreams: fakeTargets,
		},
		// curl getinfo fails
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,

			getInfoErr: errors.New("dummy error"),

			expectedStreams: fakeTargets,
		},
		// Logs disabled
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,
			log:     false,

			expectedStreams: fakeTargets,
		},
		// Logs enabled
		{
			targets: fakeTargets,
			routes:  fakeRoutes,
			timeout: 1 * time.Millisecond,
			log:     true,

			expectedStreams: fakeTargets,
		},
	}
	for i, test := range testCases {
		curlerMock := &CurlerMock{}

		if !test.invalidTargets {
			curlerMock.On("Setopt", mock.Anything, mock.Anything).Return(nil)
			curlerMock.On("Perform").Return(test.performErr)
			if test.performErr == nil {
				curlerMock.On("Getinfo", mock.Anything).Return(test.status, test.getInfoErr)
			}
		}

		results, err := AttackRoute(curlerMock, test.targets, test.routes, test.timeout, test.log)

		if len(test.expectedErrMsg) > 0 {
			if err == nil {
				fmt.Printf("unexpected success in AttackRoute test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
				os.Exit(1)
			}
			assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
		} else {
			if err != nil {
				fmt.Printf("unexpected error in AttackRoute test, iteration %d: %v\n", i, err)
				os.Exit(1)
			}
			for _, stream := range test.expectedStreams {
				foundStream := false
				for _, result := range results {
					if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
						foundStream = true
					}
				}
				assert.Equal(t, true, foundStream, "wrong streams parsed")
			}
		}
		assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
		curlerMock.AssertExpectations(t)
	}
}

func TestValidateStreams(t *testing.T) {
	validStream1 := Stream{
		Device:    "fakeDevice",
		Address:   "fakeAddress",
		Port:      1337,
		Available: true,
	}

	validStream2 := Stream{
		Device:    "fakeDevice",
		Address:   "differentFakeAddress",
		Port:      1337,
		Available: true,
	}

	unavailableStream := Stream{
		Device:    "fakeDevice",
		Available: false,
	}

	fakeTargets := []Stream{validStream1, validStream2}
	unavailableTargets := []Stream{unavailableStream}

	testCases := []struct {
		desc string

		targets []Stream
		timeout time.Duration
		log     bool

		status int

		performErr error
		getInfoErr error

		expectedStreams []Stream
		expectedErrMsg  string
	}{
		// Route found
		{
			desc: "route found",

			targets: fakeTargets,
			timeout: 1 * time.Millisecond,

			status: 403,

			expectedStreams: fakeTargets,
		},
		// Route found
		{
			desc: "route found",

			targets: fakeTargets,
			timeout: 1 * time.Millisecond,

			status: 401,

			expectedStreams: fakeTargets,
		},
		// Camera accessed
		{
			desc: "camera accessed",

			targets: fakeTargets,
			timeout: 1 * time.Millisecond,

			status: 200,

			expectedStreams: fakeTargets,
		},
		// Unavailable stream
		{
			desc: "unavailable stream",

			targets: unavailableTargets,
			timeout: 1 * time.Millisecond,

			status: 400,

			expectedStreams: unavailableTargets,
		},
		// curl perform fails
		{
			desc: "curl perform fails",

			targets: fakeTargets,
			timeout: 1 * time.Millisecond,

			performErr: errors.New("dummy error"),

			expectedStreams: fakeTargets,
		},
		// curl getinfo fails
		{
			desc: "curl getinfo fails",

			targets: fakeTargets,
			timeout: 1 * time.Millisecond,

			getInfoErr: errors.New("dummy error"),

			expectedStreams: fakeTargets,
		},
		// Logs disabled
		{
			desc: "logs disabled",

			targets: fakeTargets,
			timeout: 1 * time.Millisecond,
			log:     false,

			expectedStreams: fakeTargets,
		},
		// Logs enabled
		{
			desc: "logs enabled",

			targets: fakeTargets,
			timeout: 1 * time.Millisecond,
			log:     true,

			expectedStreams: fakeTargets,
		},
	}
	for i, tC := range testCases {
		t.Run(tC.desc, func(t *testing.T) {
			curlerMock := &CurlerMock{}

			curlerMock.On("Setopt", mock.Anything, mock.Anything).Return(nil)
			curlerMock.On("Perform").Return(tC.performErr)
			if tC.performErr == nil {
				curlerMock.On("Getinfo", mock.Anything).Return(tC.status, tC.getInfoErr)
			}

			results, err := ValidateStreams(curlerMock, tC.targets, tC.timeout, tC.log)

			if len(tC.expectedErrMsg) > 0 {
				if err == nil {
					fmt.Printf("unexpected success in ValidateStream test, iteration %d. expected error: %s\n", i, tC.expectedErrMsg)
					os.Exit(1)
				}
				assert.Contains(t, err.Error(), tC.expectedErrMsg, "wrong error message")
			} else {
				if err != nil {
					fmt.Printf("unexpected error in ValidateStream test, iteration %d: %v\n", i, err)
					os.Exit(1)
				}
				for _, stream := range tC.expectedStreams {
					foundStream := false
					for _, result := range results {
						if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
							foundStream = true
						}
					}
					assert.Equal(t, true, foundStream, "wrong streams parsed")
				}
			}
			assert.Equal(t, len(tC.expectedStreams), len(results), "wrong streams parsed")
			curlerMock.AssertExpectations(t)
		})
	}
}

func TestDotWrite(t *testing.T) {
	assert.Equal(t, true, doNotWrite(nil, nil))
}
<|endoftext|>


<commit_before>package auth

import (
	"crypto/tls"
	"errors"
	"fmt"
	"log"
	"os"
	"time"

	"upspin.io/bind"
	"upspin.io/upspin"
)

// Config holds the configuration parameters for instantiating a server (HTTP or gRPC).
type Config struct {
	// Lookup looks up user keys.
	Lookup func(userName upspin.UserName) ([]upspin.PublicKey, error)

	// AllowUnauthenticatedConnections allows unauthenticated connections, making it the caller's
	// responsibility to check Handler.IsAuthenticated.
	AllowUnauthenticatedConnections bool

	// TimeFunc returns the current time. If nil, time.Now() will be used. Mostly only used for testing.
	TimeFunc func() time.Time
}

const (
	userServiceAddr = "https://upspin.io:8082"
)

// NewDefaultTLSConfig creates a new TLS config based on the certificate files given.
func NewDefaultTLSConfig(certFile string, certKeyFile string) (*tls.Config, error) {
	certReadable, err := isReadableFile(certFile)
	if err != nil {
		return nil, fmt.Errorf("Problem with SSL certificate in %q: %q", certFile, err)
	}
	if !certReadable {
		return nil, fmt.Errorf("Certificate %q not readable", certFile)
	}
	keyReadable, err := isReadableFile(certKeyFile)
	if err != nil {
		return nil, fmt.Errorf("Problem with SSL key %q: %v", certKeyFile, err)
	}
	if !keyReadable {
		return nil, fmt.Errorf("Certificate key %q not readable", certKeyFile)
	}

	cert, err := tls.LoadX509KeyPair(certFile, certKeyFile)

	tlsConfig := &tls.Config{
		CipherSuites: []uint16{
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		},
		MinVersion:               tls.VersionTLS12,
		PreferServerCipherSuites: true, // Use our choice, not the client's choice
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
		Certificates:             []tls.Certificate{cert},
	}
	tlsConfig.BuildNameToCertificate()
	return tlsConfig, nil
}

// PublicUserKeyService returns a Lookup function that looks up users public keys.
// The lookup function returned is bound to a well-known public Upspin user service.
func PublicUserKeyService() func(userName upspin.UserName) ([]upspin.PublicKey, error) {
	context := &upspin.Context{}
	e := upspin.Endpoint{
		Transport: upspin.GCP,
		NetAddr:   upspin.NetAddr(userServiceAddr),
	}
	u, err := bind.User(context, e)
	if err != nil {
		log.Fatalf("Can't bind to User service: %v", err)
	}
	return func(userName upspin.UserName) ([]upspin.PublicKey, error) {
		_, keys, err := u.Lookup(userName)
		return keys, err
	}
}

// isReadableFile reports whether the file exists and is readable.
// If the error is non-nil, it means there might be a file or directory
// with that name but we cannot read it.
func isReadableFile(path string) (bool, error) {
	// Is it stattable and is it a plain file?
	info, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil // Item does not exist.
		}
		return false, err // Item is problematic.
	}
	if info.IsDir() {
		return false, errors.New("is directory")
	}
	// Is it readable?
	fd, err := os.Open(path)
	if err != nil {
		return false, errors.New("permission denied")
	}
	fd.Close()
	return true, nil // Item exists and is readable.
}
<commit_msg>auth: check error when loading ceritificates<commit_after>package auth

import (
	"crypto/tls"
	"errors"
	"fmt"
	"log"
	"os"
	"time"

	"upspin.io/bind"
	"upspin.io/upspin"
)

// Config holds the configuration parameters for instantiating a server (HTTP or gRPC).
type Config struct {
	// Lookup looks up user keys.
	Lookup func(userName upspin.UserName) ([]upspin.PublicKey, error)

	// AllowUnauthenticatedConnections allows unauthenticated connections, making it the caller's
	// responsibility to check Handler.IsAuthenticated.
	AllowUnauthenticatedConnections bool

	// TimeFunc returns the current time. If nil, time.Now() will be used. Mostly only used for testing.
	TimeFunc func() time.Time
}

const (
	userServiceAddr = "https://upspin.io:8082"
)

// NewDefaultTLSConfig creates a new TLS config based on the certificate files given.
func NewDefaultTLSConfig(certFile string, certKeyFile string) (*tls.Config, error) {
	certReadable, err := isReadableFile(certFile)
	if err != nil {
		return nil, fmt.Errorf("Problem with SSL certificate in %q: %q", certFile, err)
	}
	if !certReadable {
		return nil, fmt.Errorf("Certificate %q not readable", certFile)
	}
	keyReadable, err := isReadableFile(certKeyFile)
	if err != nil {
		return nil, fmt.Errorf("Problem with SSL key %q: %v", certKeyFile, err)
	}
	if !keyReadable {
		return nil, fmt.Errorf("Certificate key %q not readable", certKeyFile)
	}

	cert, err := tls.LoadX509KeyPair(certFile, certKeyFile)
	if err != nil {
		return nil, err
	}

	tlsConfig := &tls.Config{
		CipherSuites: []uint16{
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		},
		MinVersion:               tls.VersionTLS12,
		PreferServerCipherSuites: true, // Use our choice, not the client's choice
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
		Certificates:             []tls.Certificate{cert},
	}
	tlsConfig.BuildNameToCertificate()
	return tlsConfig, nil
}

// PublicUserKeyService returns a Lookup function that looks up users public keys.
// The lookup function returned is bound to a well-known public Upspin user service.
func PublicUserKeyService() func(userName upspin.UserName) ([]upspin.PublicKey, error) {
	context := &upspin.Context{}
	e := upspin.Endpoint{
		Transport: upspin.GCP,
		NetAddr:   upspin.NetAddr(userServiceAddr),
	}
	u, err := bind.User(context, e)
	if err != nil {
		log.Fatalf("Can't bind to User service: %v", err)
	}
	return func(userName upspin.UserName) ([]upspin.PublicKey, error) {
		_, keys, err := u.Lookup(userName)
		return keys, err
	}
}

// isReadableFile reports whether the file exists and is readable.
// If the error is non-nil, it means there might be a file or directory
// with that name but we cannot read it.
func isReadableFile(path string) (bool, error) {
	// Is it stattable and is it a plain file?
	info, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil // Item does not exist.
		}
		return false, err // Item is problematic.
	}
	if info.IsDir() {
		return false, errors.New("is directory")
	}
	// Is it readable?
	fd, err := os.Open(path)
	if err != nil {
		return false, errors.New("permission denied")
	}
	fd.Close()
	return true, nil // Item exists and is readable.
}
<|endoftext|>


<commit_before>package autorestart

import (
	"github.com/howeyc/fsnotify"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
)

const logPrefix = "@(dim:[autorebuild]) "

func RestartOnChange() {
	logger := log.New(os.Stderr, "[autorestart.RestartOnChange] ", log.LstdFlags)
	exePath, err := exec.LookPath(os.Args[0])
	if err != nil {
		logger.Printf("Failed to resolve path to current program: %s\n", err)
		return
	}
	exePath, err = filepath.Abs(exePath)
	if err != nil {
		logger.Printf("Failed to resolve absolute path to current program: %s\n", err)
	}
	exePath = filepath.Clean(exePath)
	exeDir := filepath.Dir(exePath)
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		logger.Printf("Failed to initialize howeyc/fsnotify watcher: %s\n", err)
		return
	}
	abs, _ := filepath.Abs(exeDir)
	err = watcher.Watch(abs)
	if err != nil {
		logger.Printf("Failed to start filesystem watcher on %s: %s\n", exeDir, err)
	}
	for {
		select {
		case err := <-watcher.Error:
			logger.Printf("Watcher error: %s\n", err)
		case ev := <-watcher.Event:
			log.Println("change", ev.Name, exePath, ev)
			if ev.Name == exePath && (ev.IsModify() || ev.IsCreate()) {
				logger.Printf("%s changed. Restarting via exec.\n", exePath)
				syscall.Exec(exePath, os.Args, os.Environ())
			}
		}
	}
}
<commit_msg>Comment out some debug logging<commit_after>package autorestart

import (
	"github.com/howeyc/fsnotify"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
)

const logPrefix = "@(dim:[autorebuild]) "

func RestartOnChange() {
	logger := log.New(os.Stderr, "[autorestart.RestartOnChange] ", log.LstdFlags)
	exePath, err := exec.LookPath(os.Args[0])
	if err != nil {
		logger.Printf("Failed to resolve path to current program: %s\n", err)
		return
	}
	exePath, err = filepath.Abs(exePath)
	if err != nil {
		logger.Printf("Failed to resolve absolute path to current program: %s\n", err)
	}
	exePath = filepath.Clean(exePath)
	exeDir := filepath.Dir(exePath)
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		logger.Printf("Failed to initialize howeyc/fsnotify watcher: %s\n", err)
		return
	}
	abs, _ := filepath.Abs(exeDir)
	err = watcher.Watch(abs)
	if err != nil {
		logger.Printf("Failed to start filesystem watcher on %s: %s\n", exeDir, err)
	}
	for {
		select {
		case err := <-watcher.Error:
			logger.Printf("Watcher error: %s\n", err)
		case ev := <-watcher.Event:
			// log.Println("change", ev.Name, exePath, ev)
			if ev.Name == exePath && (ev.IsModify() || ev.IsCreate()) {
				logger.Printf("%s changed. Restarting via exec.\n", exePath)
				syscall.Exec(exePath, os.Args, os.Environ())
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"path"
	"runtime"

	"go.skia.org/infra/go/androidbuildinternal/v2beta1"
	"go.skia.org/infra/go/auth"
	"go.skia.org/infra/go/gce"
	"go.skia.org/infra/go/gce/server"
)

func AutoRollBase(name, ipAddress string) *gce.Instance {
	vm := server.AddGitConfigs(server.Server20170518(name), name)
	vm.DataDisk.SizeGb = 64
	vm.DataDisk.Type = gce.DISK_TYPE_PERSISTENT_STANDARD
	vm.ExternalIpAddress = ipAddress
	vm.MachineType = gce.MACHINE_TYPE_STANDARD_2
	vm.Metadata["owner_primary"] = "borenet"
	vm.Metadata["owner_secondary"] = "rmistry"
	vm.Scopes = append(vm.Scopes,
		auth.SCOPE_GERRIT,
	)
	return vm
}

func Skia() *gce.Instance {
	return AutoRollBase("skia-autoroll", "104.154.112.12")
}

func SkiaInternal() *gce.Instance {
	return AutoRollBase("skia-internal-autoroll", "104.154.112.129")
}

func Catapult() *gce.Instance {
	return AutoRollBase("catapult-autoroll", "104.154.112.121")
}

func NaCl() *gce.Instance {
	return AutoRollBase("nacl-autoroll", "104.154.112.123")
}

func PDFium() *gce.Instance {
	return AutoRollBase("pdfium-autoroll", "104.154.123.210")
}

func AddAndroidConfigs(vm *gce.Instance) *gce.Instance {
	vm.DataDisk.SizeGb = 512
	vm.MachineType = gce.MACHINE_TYPE_HIGHMEM_16
	vm.Scopes = append(vm.Scopes, androidbuildinternal.AndroidbuildInternalScope)

	_, filename, _, _ := runtime.Caller(0)
	dir := path.Dir(filename)
	vm.SetupScript = path.Join(dir, "setup-script-android.sh")
	return vm
}

func AndroidMaster() *gce.Instance {
	return AddAndroidConfigs(AutoRollBase("android-master-autoroll", "104.154.123.206"))
}

func AndroidO() *gce.Instance {
	return AddAndroidConfigs(AutoRollBase("android-o-autoroll", "104.154.123.208"))
}

func Test() *gce.Instance {
	vm := AutoRollBase("borenet-instance-creation-test", "")
	server.AddGitConfigs(vm, "skia-autoroll") // Hack. Copy the skia-autoroll creds.
	return vm
}

func main() {
	server.Main(gce.ZONE_DEFAULT, map[string]*gce.Instance{
		"skia":           Skia(),
		"skia-internal":  SkiaInternal(),
		"catapult":       Catapult(),
		"nacl":           NaCl(),
		"pdfium":         PDFium(),
		"android-master": AndroidMaster(),
		"android-o":      AndroidO(),
		"test":           Test(),
	})
}
<commit_msg>Change external IPs for android roller and use ephemeral IPs for other rollers<commit_after>package main

import (
	"path"
	"runtime"

	"go.skia.org/infra/go/androidbuildinternal/v2beta1"
	"go.skia.org/infra/go/auth"
	"go.skia.org/infra/go/gce"
	"go.skia.org/infra/go/gce/server"
)

func AutoRollBase(name, ipAddress string) *gce.Instance {
	vm := server.AddGitConfigs(server.Server20170518(name), name)
	vm.DataDisk.SizeGb = 64
	vm.DataDisk.Type = gce.DISK_TYPE_PERSISTENT_STANDARD
	if ipAddress != "" {
		vm.ExternalIpAddress = ipAddress
	}
	vm.MachineType = gce.MACHINE_TYPE_STANDARD_2
	vm.Metadata["owner_primary"] = "borenet"
	vm.Metadata["owner_secondary"] = "rmistry"
	vm.Scopes = append(vm.Scopes,
		auth.SCOPE_GERRIT,
	)
	return vm
}

func Skia() *gce.Instance {
	return AutoRollBase("skia-autoroll", "" /* Use ephemeral IP */)
}

func SkiaInternal() *gce.Instance {
	return AutoRollBase("skia-internal-autoroll", "" /* Use ephemeral IP */)
}

func Catapult() *gce.Instance {
	return AutoRollBase("catapult-autoroll", "" /* Use ephemeral IP */)
}

func NaCl() *gce.Instance {
	return AutoRollBase("nacl-autoroll", "" /* Use ephemeral IP */)
}

func PDFium() *gce.Instance {
	return AutoRollBase("pdfium-autoroll", "" /* Use ephemeral IP */)
}

func AddAndroidConfigs(vm *gce.Instance) *gce.Instance {
	vm.DataDisk.SizeGb = 512
	vm.MachineType = gce.MACHINE_TYPE_HIGHMEM_16
	vm.Scopes = append(vm.Scopes, androidbuildinternal.AndroidbuildInternalScope)

	_, filename, _, _ := runtime.Caller(0)
	dir := path.Dir(filename)
	vm.SetupScript = path.Join(dir, "setup-script-android.sh")
	return vm
}

func AndroidMaster() *gce.Instance {
	return AddAndroidConfigs(AutoRollBase("android-master-autoroll", "130.211.199.63" /* Needs whitelisted static IP */))
}

func AndroidO() *gce.Instance {
	return AddAndroidConfigs(AutoRollBase("android-o-autoroll", "104.198.73.244" /* Needs whitelisted static IP */))
}

func Test() *gce.Instance {
	vm := AutoRollBase("borenet-instance-creation-test", "")
	server.AddGitConfigs(vm, "skia-autoroll") // Hack. Copy the skia-autoroll creds.
	return vm
}

func main() {
	server.Main(gce.ZONE_DEFAULT, map[string]*gce.Instance{
		"skia":           Skia(),
		"skia-internal":  SkiaInternal(),
		"catapult":       Catapult(),
		"nacl":           NaCl(),
		"pdfium":         PDFium(),
		"android-master": AndroidMaster(),
		"android-o":      AndroidO(),
		"test":           Test(),
	})
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"

	"github.com/google/uuid"
	"github.com/gorilla/sessions"
	"github.com/gorilla/websocket"
	garbler "github.com/michaelbironneau/garbler/lib"
	"github.com/tejpbit/talarlista/backend/messages"
)

const SESSION_KEY = "talarlista_session"
const UUID_KEY = "uuid"

const (
	NoUserForSession   = "The provided session have no corresponding user in the state."
	NoUUIDInSession    = "The provided session does not contain a UUID for the user."
	NoUserForUUID      = "The UUID provided from the session does not correspond to a user in the state."
	UserIsNotAdmin     = "User is not admin"
	UserAlreadyInList  = "User already in list. Cannot be added twice."
	UserNotFoundInList = "User not in current list"
	NoListForUUID      = "The UUID does not correspond to any list"
)

type Hub struct {
	Users             map[uuid.UUID]*User
	AdminCreatedUsers map[uuid.UUID]*User
	SpeakerLists      []*SpeakerList
	connectedUsers    map[uuid.UUID]*User
	oneTimePasswords  []string
	input             chan UserEvent
	messageHandlers   map[string]MessageHandler
}

var reqs = garbler.PasswordStrengthRequirements{
	MinimumTotalLength: 8,
	MaximumTotalLength: 8,
	Uppercase:          3,
	Digits:             2,
}

var store = sessions.NewCookieStore([]byte("this is the secret stuff"))

func CreateHub() Hub {
	store.Options = &sessions.Options{
		MaxAge:   86400,
		HttpOnly: true,
	}

	speakerLists := []*SpeakerList{}

	hub := Hub{
		Users:             make(map[uuid.UUID]*User),
		AdminCreatedUsers: make(map[uuid.UUID]*User),
		SpeakerLists:      speakerLists,
		connectedUsers:    make(map[uuid.UUID]*User),
	}

	hub.generateNewPassword()

	hub.messageHandlers = CreateHandlers(&hub)
	return hub
}

func (hub *Hub) Start() error {
	if hub.input != nil {
		return errors.New("Hub already running")
	}
	hub.input = make(chan UserEvent, 10)
	for _, user := range hub.Users {
		user.hubChannel = hub.input
	}

	go hub.listenForUserEvents()
	return nil
}

func (hub *Hub) Broadcast(sendEvent messages.SendEvent) {
	for _, user := range hub.Users {
		user.input <- sendEvent
	}
}

func (hub *Hub) AdminBroadcast(sendEvent messages.SendEvent) {
	for _, user := range hub.Users {
		if user.IsAdmin {
			user.input <- sendEvent
		}
	}
}

func (hub *Hub) listenForUserEvents() {
	for {
		event := <-hub.input
		handler, ok := hub.messageHandlers[event.messageType]
		if !ok {
			sendError(event.user.input, fmt.Sprintf("No handler for the topic '%s'.", event.messageType))
			log.Printf("No handler for the topic '%s'", event.messageType)
			continue
		}
		handler.handle(event)
	}
}

func (hub *Hub) addUserToList(id uuid.UUID, user *User) error {
	listIndex := -1
	for i, list := range hub.SpeakerLists {
		if list.Id == id {
			listIndex = i
			break
		}
	}
	if listIndex == -1 {
		return errors.New("Could not find list for provided id")
	}
	list := hub.SpeakerLists[listIndex]
	ok := list.AddUser(user)
	if !ok {
		return errors.New("User already in list")
	}
	return nil
}

func (hub *Hub) removeUserFromList(id uuid.UUID, user *User) error {
	listIndex := -1
	for i, list := range hub.SpeakerLists {
		if list.Id == id {
			listIndex = i
			break
		}
	}
	if listIndex == -1 {
		return errors.New("Could not find list for provided id")
	}
	list := hub.SpeakerLists[listIndex]
	ok := list.RemoveUser(user)
	if !ok {
		return errors.New("User not in list")
	}
	return nil
}

func (hub *Hub) deleteList(id uuid.UUID) error {
	i := -1
	for index, list := range hub.SpeakerLists {
		if list.Id == id {
			i = index
		}
	}
	if i == -1 {
		return errors.New("List not found")
	}

	copy(hub.SpeakerLists[i:], hub.SpeakerLists[i+1:])
	hub.SpeakerLists[len(hub.SpeakerLists)-1] = nil
	hub.SpeakerLists = hub.SpeakerLists[:len(hub.SpeakerLists)-1]
	return nil
}

func (hub *Hub) getList(id uuid.UUID) (*SpeakerList, error) {

	for _, list := range hub.SpeakerLists {
		if id == list.Id {
			return list, nil
		}
	}
	return nil, errors.New(NoListForUUID)
}

func (hub Hub) getUserFromRequest(req *http.Request) (*User, error) {
	session, err := store.Get(req, SESSION_KEY)
	if err != nil {
		return nil, errors.New("Could not get session from storage")
	}
	return hub.getUserFromSession(session)
}

func (hub Hub) getUserFromSession(session *sessions.Session) (*User, error) {
	id, err := getUUIDfromSession(session)

	if err != nil {
		return nil, err
	}

	return hub.getUser(id)
}

func (hub Hub) getUser(id uuid.UUID) (*User, error) {
	user, ok := hub.Users[id]

	if !ok {
		return nil, errors.New(NoUserForUUID)
	}
	return user, nil
}

func (hub Hub) isUserNickTaken(nick string) bool {
	for _, user := range hub.Users {
		if user.Nick == nick {
			return true
		}
	}
	for _, user := range hub.AdminCreatedUsers {
		if user.Nick == nick {
			return true
		}
	}
	return false
}

func (hub Hub) addUser(user *User) bool {
	_, ok := hub.Users[user.Id]
	if ok {
		return false
	}
	hub.Users[user.Id] = user
	return true
}

func (hub Hub) updateUser(updatedUser *User) (success bool) {
	user, inUsers := hub.Users[updatedUser.Id]
	adminCreatedUser, inAdminCreatedUsers := hub.AdminCreatedUsers[updatedUser.Id]
	success = inUsers != inAdminCreatedUsers
	if !success {
		return
	}

	if inUsers {
		user.Nick = updatedUser.Nick
	} else if inAdminCreatedUsers {
		adminCreatedUser.Nick = updatedUser.Nick
	}

	return
}

func (hub Hub) deleteUser(user *User) {
	for _, list := range hub.SpeakerLists {
		list.RemoveUser(user)
	}
	delete(hub.AdminCreatedUsers, user.Id)
	delete(hub.Users, user.Id)
	delete(hub.connectedUsers, user.Id)
}

func (hub Hub) addAdminCreatedUser(user *User) bool {
	_, ok := hub.AdminCreatedUsers[user.Id]
	if ok {
		return false
	}
	user.Id = uuid.New()
	hub.AdminCreatedUsers[user.Id] = user
	return true
}

func (hub *Hub) tryAdminLogin(user *User, password string) bool {
	passwordIndex := -1
	for i, k := range hub.oneTimePasswords {
		if k == password {
			passwordIndex = i
			break
		}
	}

	ok := passwordIndex != -1
	if ok {
		user.IsAdmin = true
		hub.oneTimePasswords = append(hub.oneTimePasswords[:passwordIndex], hub.oneTimePasswords[passwordIndex+1:]...)
	}
	return ok
}

}

func (hub *Hub) generateNewPassword() {
	newPassword, err := garbler.NewPassword(&reqs)
	if err != nil {
		log.Panicf("Could not generate initial password: %v", err)
	}
	log.Printf("Generated password: %s", newPassword)
	hub.oneTimePasswords = append(hub.oneTimePasswords, newPassword)
}

func getUUIDfromSession(session *sessions.Session) (uuid.UUID, error) {
	storedValue, ok := session.Values[UUID_KEY]
	if !ok {
		return uuid.UUID{}, errors.New(NoUUIDInSession)
	}

	stringId, ok := storedValue.(string)
	if !ok {
		return uuid.UUID{}, errors.New("Could not cast stored value to string")
	}

	id, err := uuid.Parse(stringId)
	if err != nil {
		return uuid.UUID{}, errors.New("Could not parse stored string into uuid.UUID")
	}

	return id, nil
}

func (hub *Hub) ServeWs(w http.ResponseWriter, r *http.Request) {
	session, err := store.Get(r, SESSION_KEY)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if session.IsNew {
		newUser := CreateUser()
		session.Values[UUID_KEY] = newUser.Id.String()
		newUser.hubChannel = hub.input
		hub.addUser(newUser)

		err = session.Save(r, w)

		if err != nil {
			log.Printf("Error when saving session to storage: %v\n", err)
		}
	}

	conn, err := upgrader.Upgrade(w, r, w.Header())
	if err != nil {
		log.Println(err)
		return
	}

	user, err := hub.getUserFromRequest(r)
	if err != nil {
		errorResp, err := json.Marshal(messages.JsonMessage{Message: err.Error()})

		if err != nil {
			errorEvent := messages.SendEvent{Topic: messages.ERROR, Content: []byte(err.Error())}
			conn.WriteMessage(websocket.TextMessage, []byte(errorEvent.String()))
		} else {
			errorEvent := messages.SendEvent{Topic: messages.ERROR, Content: errorResp}
			conn.WriteMessage(websocket.TextMessage, []byte(errorEvent.String()))
		}
		conn.Close()
		return
	}

	user.ServeWS(conn)
}

func createListsResponse(lists []*SpeakerList) (messages.SendEvent, error) {
	listsObj, err := json.Marshal(lists)
	if err != nil {
		return messages.SendEvent{}, err
	} else {
		return messages.SendEvent{messages.LISTS_UPDATE, listsObj}, nil
	}
}

type UsersResponse struct {
	Users             []*User `json:"users"`
	AdminCreatedUsers []*User `json:"adminCreatedUsers"`
}

func (hub *Hub) createUsersResponse() (messages.SendEvent, error) {
	var users, adminCreatedUsers []*User
	for _, u := range hub.Users {
		users = append(users, u)
	}
	for _, u := range hub.AdminCreatedUsers {
		adminCreatedUsers = append(adminCreatedUsers, u)
	}

	usersUbj, err := json.Marshal(UsersResponse{Users: users, AdminCreatedUsers: adminCreatedUsers})
	if err != nil {
		return messages.SendEvent{}, err
	} else {
		return messages.SendEvent{messages.USERS_UPDATE, usersUbj}, nil
	}
}

func createListResponse(list *SpeakerList) (messages.SendEvent, error) {
	listObj, err := json.Marshal(list)
	if err != nil {
		return messages.SendEvent{}, err
	} else {
		return messages.SendEvent{messages.LIST_UPDATE, listObj}, nil
	}
}

func sendNotification(userChannel chan messages.SendEvent, topic, message string) {
	respObj, err := json.Marshal(messages.JsonMessage{message})
	if err != nil {
		log.Printf("Could not marshal error message: %v", err)
	}
	userChannel <- messages.SendEvent{topic, respObj}
}

func sendError(userChannel chan messages.SendEvent, message string) {
	sendNotification(userChannel, messages.ERROR, message)
}

func sendSuccess(userChannel chan messages.SendEvent, message string) {
	sendNotification(userChannel, messages.SUCCESS, message)
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}
<commit_msg>Start with three one-time passwords instead of one<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"

	"github.com/google/uuid"
	"github.com/gorilla/sessions"
	"github.com/gorilla/websocket"
	garbler "github.com/michaelbironneau/garbler/lib"
	"github.com/tejpbit/talarlista/backend/messages"
)

const SESSION_KEY = "talarlista_session"
const UUID_KEY = "uuid"

const (
	NoUserForSession   = "The provided session have no corresponding user in the state."
	NoUUIDInSession    = "The provided session does not contain a UUID for the user."
	NoUserForUUID      = "The UUID provided from the session does not correspond to a user in the state."
	UserIsNotAdmin     = "User is not admin"
	UserAlreadyInList  = "User already in list. Cannot be added twice."
	UserNotFoundInList = "User not in current list"
	NoListForUUID      = "The UUID does not correspond to any list"
)

type Hub struct {
	Users             map[uuid.UUID]*User
	AdminCreatedUsers map[uuid.UUID]*User
	SpeakerLists      []*SpeakerList
	connectedUsers    map[uuid.UUID]*User
	oneTimePasswords  []string
	input             chan UserEvent
	messageHandlers   map[string]MessageHandler
}

var reqs = garbler.PasswordStrengthRequirements{
	MinimumTotalLength: 8,
	MaximumTotalLength: 8,
	Uppercase:          3,
	Digits:             2,
}

var store = sessions.NewCookieStore([]byte("this is the secret stuff"))

func CreateHub() Hub {
	store.Options = &sessions.Options{
		MaxAge:   86400,
		HttpOnly: true,
	}

	speakerLists := []*SpeakerList{}

	hub := Hub{
		Users:             make(map[uuid.UUID]*User),
		AdminCreatedUsers: make(map[uuid.UUID]*User),
		SpeakerLists:      speakerLists,
		connectedUsers:    make(map[uuid.UUID]*User),
	}

	hub.generateNewPassword()
	hub.generateNewPassword()
	hub.generateNewPassword()

	hub.messageHandlers = CreateHandlers(&hub)
	return hub
}

func (hub *Hub) Start() error {
	if hub.input != nil {
		return errors.New("Hub already running")
	}
	hub.input = make(chan UserEvent, 10)
	for _, user := range hub.Users {
		user.hubChannel = hub.input
	}

	go hub.listenForUserEvents()
	return nil
}

func (hub *Hub) Broadcast(sendEvent messages.SendEvent) {
	for _, user := range hub.Users {
		user.input <- sendEvent
	}
}

func (hub *Hub) AdminBroadcast(sendEvent messages.SendEvent) {
	for _, user := range hub.Users {
		if user.IsAdmin {
			user.input <- sendEvent
		}
	}
}

func (hub *Hub) listenForUserEvents() {
	for {
		event := <-hub.input
		handler, ok := hub.messageHandlers[event.messageType]
		if !ok {
			sendError(event.user.input, fmt.Sprintf("No handler for the topic '%s'.", event.messageType))
			log.Printf("No handler for the topic '%s'", event.messageType)
			continue
		}
		handler.handle(event)
	}
}

func (hub *Hub) addUserToList(id uuid.UUID, user *User) error {
	listIndex := -1
	for i, list := range hub.SpeakerLists {
		if list.Id == id {
			listIndex = i
			break
		}
	}
	if listIndex == -1 {
		return errors.New("Could not find list for provided id")
	}
	list := hub.SpeakerLists[listIndex]
	ok := list.AddUser(user)
	if !ok {
		return errors.New("User already in list")
	}
	return nil
}

func (hub *Hub) removeUserFromList(id uuid.UUID, user *User) error {
	listIndex := -1
	for i, list := range hub.SpeakerLists {
		if list.Id == id {
			listIndex = i
			break
		}
	}
	if listIndex == -1 {
		return errors.New("Could not find list for provided id")
	}
	list := hub.SpeakerLists[listIndex]
	ok := list.RemoveUser(user)
	if !ok {
		return errors.New("User not in list")
	}
	return nil
}

func (hub *Hub) deleteList(id uuid.UUID) error {
	i := -1
	for index, list := range hub.SpeakerLists {
		if list.Id == id {
			i = index
		}
	}
	if i == -1 {
		return errors.New("List not found")
	}

	copy(hub.SpeakerLists[i:], hub.SpeakerLists[i+1:])
	hub.SpeakerLists[len(hub.SpeakerLists)-1] = nil
	hub.SpeakerLists = hub.SpeakerLists[:len(hub.SpeakerLists)-1]
	return nil
}

func (hub *Hub) getList(id uuid.UUID) (*SpeakerList, error) {

	for _, list := range hub.SpeakerLists {
		if id == list.Id {
			return list, nil
		}
	}
	return nil, errors.New(NoListForUUID)
}

func (hub Hub) getUserFromRequest(req *http.Request) (*User, error) {
	session, err := store.Get(req, SESSION_KEY)
	if err != nil {
		return nil, errors.New("Could not get session from storage")
	}
	return hub.getUserFromSession(session)
}

func (hub Hub) getUserFromSession(session *sessions.Session) (*User, error) {
	id, err := getUUIDfromSession(session)

	if err != nil {
		return nil, err
	}

	return hub.getUser(id)
}

func (hub Hub) getUser(id uuid.UUID) (*User, error) {
	user, ok := hub.Users[id]

	if !ok {
		return nil, errors.New(NoUserForUUID)
	}
	return user, nil
}

func (hub Hub) isUserNickTaken(nick string) bool {
	for _, user := range hub.Users {
		if user.Nick == nick {
			return true
		}
	}
	for _, user := range hub.AdminCreatedUsers {
		if user.Nick == nick {
			return true
		}
	}
	return false
}

func (hub Hub) addUser(user *User) bool {
	_, ok := hub.Users[user.Id]
	if ok {
		return false
	}
	hub.Users[user.Id] = user
	return true
}

func (hub Hub) updateUser(updatedUser *User) (success bool) {
	user, inUsers := hub.Users[updatedUser.Id]
	adminCreatedUser, inAdminCreatedUsers := hub.AdminCreatedUsers[updatedUser.Id]
	success = inUsers != inAdminCreatedUsers
	if !success {
		return
	}

	if inUsers {
		user.Nick = updatedUser.Nick
	} else if inAdminCreatedUsers {
		adminCreatedUser.Nick = updatedUser.Nick
	}

	return
}

func (hub Hub) deleteUser(user *User) {
	for _, list := range hub.SpeakerLists {
		list.RemoveUser(user)
	}
	delete(hub.AdminCreatedUsers, user.Id)
	delete(hub.Users, user.Id)
	delete(hub.connectedUsers, user.Id)
}

func (hub Hub) addAdminCreatedUser(user *User) bool {
	_, ok := hub.AdminCreatedUsers[user.Id]
	if ok {
		return false
	}
	user.Id = uuid.New()
	hub.AdminCreatedUsers[user.Id] = user
	return true
}

func (hub *Hub) tryAdminLogin(user *User, password string) bool {
	passwordIndex := -1
	for i, k := range hub.oneTimePasswords {
		if k == password {
			passwordIndex = i
			break
		}
	}

	ok := passwordIndex != -1
	if ok {
		user.IsAdmin = true
		hub.oneTimePasswords = append(hub.oneTimePasswords[:passwordIndex], hub.oneTimePasswords[passwordIndex+1:]...)
	}
	return ok
}

}

func (hub *Hub) generateNewPassword() {
	newPassword, err := garbler.NewPassword(&reqs)
	if err != nil {
		log.Panicf("Could not generate initial password: %v", err)
	}
	log.Printf("Generated password: %s", newPassword)
	hub.oneTimePasswords = append(hub.oneTimePasswords, newPassword)
}

func getUUIDfromSession(session *sessions.Session) (uuid.UUID, error) {
	storedValue, ok := session.Values[UUID_KEY]
	if !ok {
		return uuid.UUID{}, errors.New(NoUUIDInSession)
	}

	stringId, ok := storedValue.(string)
	if !ok {
		return uuid.UUID{}, errors.New("Could not cast stored value to string")
	}

	id, err := uuid.Parse(stringId)
	if err != nil {
		return uuid.UUID{}, errors.New("Could not parse stored string into uuid.UUID")
	}

	return id, nil
}

func (hub *Hub) ServeWs(w http.ResponseWriter, r *http.Request) {
	session, err := store.Get(r, SESSION_KEY)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if session.IsNew {
		newUser := CreateUser()
		session.Values[UUID_KEY] = newUser.Id.String()
		newUser.hubChannel = hub.input
		hub.addUser(newUser)

		err = session.Save(r, w)

		if err != nil {
			log.Printf("Error when saving session to storage: %v\n", err)
		}
	}

	conn, err := upgrader.Upgrade(w, r, w.Header())
	if err != nil {
		log.Println(err)
		return
	}

	user, err := hub.getUserFromRequest(r)
	if err != nil {
		errorResp, err := json.Marshal(messages.JsonMessage{Message: err.Error()})

		if err != nil {
			errorEvent := messages.SendEvent{Topic: messages.ERROR, Content: []byte(err.Error())}
			conn.WriteMessage(websocket.TextMessage, []byte(errorEvent.String()))
		} else {
			errorEvent := messages.SendEvent{Topic: messages.ERROR, Content: errorResp}
			conn.WriteMessage(websocket.TextMessage, []byte(errorEvent.String()))
		}
		conn.Close()
		return
	}

	user.ServeWS(conn)
}

func createListsResponse(lists []*SpeakerList) (messages.SendEvent, error) {
	listsObj, err := json.Marshal(lists)
	if err != nil {
		return messages.SendEvent{}, err
	} else {
		return messages.SendEvent{messages.LISTS_UPDATE, listsObj}, nil
	}
}

type UsersResponse struct {
	Users             []*User `json:"users"`
	AdminCreatedUsers []*User `json:"adminCreatedUsers"`
}

func (hub *Hub) createUsersResponse() (messages.SendEvent, error) {
	var users, adminCreatedUsers []*User
	for _, u := range hub.Users {
		users = append(users, u)
	}
	for _, u := range hub.AdminCreatedUsers {
		adminCreatedUsers = append(adminCreatedUsers, u)
	}

	usersUbj, err := json.Marshal(UsersResponse{Users: users, AdminCreatedUsers: adminCreatedUsers})
	if err != nil {
		return messages.SendEvent{}, err
	} else {
		return messages.SendEvent{messages.USERS_UPDATE, usersUbj}, nil
	}
}

func createListResponse(list *SpeakerList) (messages.SendEvent, error) {
	listObj, err := json.Marshal(list)
	if err != nil {
		return messages.SendEvent{}, err
	} else {
		return messages.SendEvent{messages.LIST_UPDATE, listObj}, nil
	}
}

func sendNotification(userChannel chan messages.SendEvent, topic, message string) {
	respObj, err := json.Marshal(messages.JsonMessage{message})
	if err != nil {
		log.Printf("Could not marshal error message: %v", err)
	}
	userChannel <- messages.SendEvent{topic, respObj}
}

func sendError(userChannel chan messages.SendEvent, message string) {
	sendNotification(userChannel, messages.ERROR, message)
}

func sendSuccess(userChannel chan messages.SendEvent, message string) {
	sendNotification(userChannel, messages.SUCCESS, message)
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}
<|endoftext|>


<commit_before>package backend

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"log"

	"github.com/garyburd/redigo/redis"
	gosocketio "github.com/graarh/golang-socketio"
)

type scanInfoStruct struct {
	Key  string `json:"key"`
	Iter int64  `json:"iter"`
}

type scanResultStruct struct {
	Keys []string `json:"keys"`
	Iter int64    `json:"iter"`
}

func scan(conn *gosocketio.Channel, c redis.Conn, key, field string, t scanType, iterate int64, maxScanLimits int) (int64, []string) {

	var method, cmd string
	var ret []interface{}
	var keys, tmp []string
	var err error
	switch t {
	case keyScan:
		method = "scan"
	case setScan:
		method = "sscan"
	case hashScan:
		method = "hscan"
	case zsetScan:
		method = "zscan"
	default:
		log.Println("type not exists!")
		return 0, nil
	}

	for {

		if t == keyScan {
			if !strings.ContainsAny(key, "*") {
				key = key + "*"
			}
			cmd = fmt.Sprintf("%s %d MATCH %s COUNT %d", method, iterate, key, _globalConfigs.System.KeyScanLimits)
			ret, err = redis.Values(c.Do(method, iterate, "MATCH", key, "COUNT", _globalConfigs.System.KeyScanLimits))

		} else {
			if key == "" {
				sendCmdError(conn, "key can't not be empty")
				return 0, nil
			}
			if !strings.ContainsAny(field, "*") {
				field = field + "*"
			}
			cmd = fmt.Sprintf("%s %s %d MATCH %s COUNT %d", method, key, iterate, field, _globalConfigs.System.KeyScanLimits)
			ret, err = redis.Values(c.Do(method, key, iterate, "MATCH", field, "COUNT", _globalConfigs.System.KeyScanLimits))
		}

		sendCmd(conn, cmd)
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return 0, nil
		}
		sendCmdReceive(conn, ret)
		tmp, err = redis.Strings(ret[1], nil)
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return 0, nil
		}
		keys = append(keys, tmp...)
		iterate, err = redis.Int64(ret[0], nil)
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return 0, nil
		}
		if iterate == 0 || len(keys) >= maxScanLimits {
			break
		}
	}

	return iterate, keys
}

// ScanKeys scan redis key
func ScanKeys(conn *gosocketio.Channel, data json.RawMessage) {
	if info, ok := checkOperData(conn, data); ok {
		tmpBytes, _ := json.Marshal(info.Data)
		var scanInfo scanInfoStruct
		err := json.Unmarshal(tmpBytes, &scanInfo)
		if err != nil {
			sendCmdError(conn, "Unmarshal error: "+err.Error())
			return
		}
		c, err := getRedisClient(info.ServerID, info.DB)
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return
		}
		defer c.Close()
		iter, keys := scan(conn, c, scanInfo.Key, "", keyScan, scanInfo.Iter, _globalConfigs.System.KeyScanLimits)
		result := scanResultStruct{
			keys, iter,
		}
		conn.Emit("LoadKeys", result)
	}
}

// ScanRemote scan remote
func ScanRemote(conn *gosocketio.Channel, data json.RawMessage) {
	if c, _redisValue, ok := checkRedisValue(conn, data); ok {
		defer c.Close()
		var key = _redisValue.Key
		var field string

		field, ok := (_redisValue.Val).(string)
		if !ok {
			sendCmdError(conn, "val should be string")
			return
		}
		var size int
		size = _redisValue.Size
		if size == 0 {
			size = _globalConfigs.System.RowScanLimits
		}
		if t, err := keyType(conn, c, key); err == nil {
			if t == "none" {
				sendCmdError(conn, "key is not exists")
				return
			}
			switch t {
			case "string", "list":
				sendCmdError(conn, t+" not support this method!")
				return
			case "set":
				_, vals := scan(conn, c, key, field, setScan, 0, size)
				conn.Emit("ReloadDatas", formatSetAndList(vals))
			case "zset":
				_, vals := scan(conn, c, key, field, zsetScan, 0, size)
				conn.Emit("ReloadDatas", formatZset(vals))
			case "hash":
				_, vals := scan(conn, c, key, field, hashScan, 0, size)
				conn.Emit("ReloadDatas", formatHash(vals))
			default:
				sendCmdError(conn, "underfined type:"+t)
				return
			}
		}
	}
}

// GetKey get value of the key
func GetKey(conn *gosocketio.Channel, data json.RawMessage) {
	if c, _redisValue, ok := checkRedisValue(conn, data); ok {
		defer c.Close()
		// type, ttl, data
		var key = _redisValue.Key
		extra, ok := (_redisValue.Val).(map[string]string)
		var field = ""
		if ok {
			field = extra["field"]
		}
		var size int
		size = _redisValue.Size
		if size == 0 {
			size = _globalConfigs.System.RowScanLimits
		}
		if t, err := keyType(conn, c, key); err == nil {
			if t == "none" {
				sendCmdError(conn, "key is not exists")
				return
			}
			_redisValue.T = t
			// ttl
			cmd := "TTL " + key
			sendCmd(conn, cmd)
			expire, err := redis.Int64(c.Do("TTL", key))
			if err != nil {
				sendCmdError(conn, err.Error())
				return
			}
			sendCmdReceive(conn, expire)
			_redisValue.TTL = expire

			switch t {
			case "string":
				cmd = "GET " + key
				sendCmd(conn, cmd)
				s, err := redis.String(c.Do("GET", key))
				if err != nil {
					sendCmdError(conn, err.Error())
					return
				}
				sendCmdReceive(conn, s)
				_redisValue.Size = 1
				_redisValue.Val = s
			case "list":
				cmd = "LRANGE " + key + " 0 " + strconv.Itoa(_globalConfigs.System.RowScanLimits)
				sendCmd(conn, cmd)
				list, err := redis.Strings(c.Do("LRANGE", key, 0, _globalConfigs.System.RowScanLimits))
				if err != nil {
					sendCmdError(conn, err.Error())
					return
				}
				cmd = "LLEN " + key
				sendCmd(conn, cmd)
				l, err := redis.Int(c.Do("LLEN", key))
				if err != nil {
					sendCmdError(conn, err.Error())
					return
				}
				sendCmdReceive(conn, l)
				_redisValue.Size = l
				_redisValue.Val = formatSetAndList(list)
			case "set":
				_, vals := scan(conn, c, key, field, setScan, 0, size)

				cmd = "SCARD " + key
				sendCmd(conn, cmd)
				l, err := redis.Int(c.Do("SCARD", key))
				if err != nil {
					sendCmdError(conn, err.Error())
					return
				}
				sendCmdReceive(conn, l)
				_redisValue.Size = l
				_redisValue.Val = formatSetAndList(vals)
			case "zset", "hash":
				var method scanType
				var lenMethod string
				if t == "zset" {
					method = zsetScan
					lenMethod = "ZCOUNT"
				} else {
					lenMethod = "HLEN"
					method = hashScan
				}
				_, vals := scan(conn, c, key, field, method, 0, size)
				cmd = lenMethod + " " + key
				var l int
				var err error
				if t == "zset" {
					_redisValue.Val = formatZset(vals)
					cmd += " -inf +inf"
					sendCmd(conn, cmd)
					l, err = redis.Int(c.Do(lenMethod, key, "-inf", "+inf"))
				} else {
					_redisValue.Val = formatHash(vals)
					sendCmd(conn, cmd)
					l, err = redis.Int(c.Do(lenMethod, key))
				}

				if err != nil {
					sendCmdError(conn, err.Error())
					return
				}
				sendCmdReceive(conn, l)
				_redisValue.Size = l
			}

			conn.Emit("ShowRedisValue", _redisValue)
		}
	}
}

func formatSetAndList(datas []string) []map[string]interface{} {
	tmp := make([]map[string]interface{}, 0, 100)
	for i := 0; i < len(datas); i++ {
		row := make(map[string]interface{})
		row["val"] = datas[i]
		row["index"] = i
		tmp = append(tmp, row)
	}
	return tmp
}

func formatHash(datas []string) []map[string]string {
	tmp := make([]map[string]string, 0, 100)
	for i := 0; i < len(datas); i = i + 2 {
		row := make(map[string]string)
		row["field"] = datas[i]
		row["val"] = datas[i+1]
		tmp = append(tmp, row)
	}
	return tmp
}

func formatZset(datas []string) []map[string]interface{} {
	tmp := make([]map[string]interface{}, 0, 100)
	for i := 0; i < len(datas); i = i + 2 {
		row := make(map[string]interface{})
		row["val"] = datas[i]
		row["score"], _ = strconv.ParseFloat(datas[i+1], 10)
		tmp = append(tmp, row)
	}
	return tmp
}

// SetTTL set ttl
func SetTTL(conn *gosocketio.Channel, data json.RawMessage) {
	if c, _redisValue, ok := checkRedisValue(conn, data); ok {
		defer c.Close()
		cmd := "EXPIRE " + _redisValue.Key + " " + strconv.FormatInt(_redisValue.TTL, 10)
		sendCmd(conn, cmd)
		expire, err := redis.Int(c.Do("EXPIRE", _redisValue.Key, _redisValue.TTL))
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return
		}
		sendCmdReceive(conn, expire)

		conn.Emit("ReloadTTL", _redisValue.TTL)
		conn.Emit("tip", &info{"success", "set ttl success!", 2})
	}
}

// KeyType type of the key
func KeyType(conn *gosocketio.Channel, data json.RawMessage) {
	if c, _redisValue, ok := checkRedisValue(conn, data); ok {
		defer c.Close()
		s, err := keyType(conn, c, _redisValue.Key)
		if err == nil {
			conn.Emit("KeyType", s)
		}
	}
}

// Rename rename a key
func Rename(conn *gosocketio.Channel, data json.RawMessage) {
	if c, _redisValue, ok := checkRedisValue(conn, data); ok {
		defer c.Close()
		var newKey string
		newKey, ok = _redisValue.Val.(string)
		if !ok || newKey == "" {
			sendCmdError(conn, "key must be string")
			return
		}
		if newKey == "" {
			sendCmdError(conn, "new key can not be empty!")
			return
		}
		var err error
		cmd := "RENAME " + _redisValue.Key + " " + newKey
		sendCmd(conn, cmd)
		_, err = c.Do("RENAME", _redisValue.Key, newKey)
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return
		}
		conn.Emit("ReNameSuccess", newKey)
		conn.Emit("tip", &info{"success", "rename success!", 2})
	}
}

/*
 none (key不存在)
 string (字符串)
 list (列表)
 set (集合)
 zset (有序集)
 hash (哈希表)
*/
func keyType(conn *gosocketio.Channel, c redis.Conn, key string) (string, error) {
	cmd := "TYPE " + key
	sendCmd(conn, cmd)
	s, err := redis.String(c.Do("TYPE", key))
	if err != nil {
		sendCmdError(conn, err.Error())
		return "", err
	}
	sendCmdReceive(conn, s)
	return s, err
}
<commit_msg>upate<commit_after>package backend

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"log"

	"github.com/garyburd/redigo/redis"
	gosocketio "github.com/graarh/golang-socketio"
)

type scanInfoStruct struct {
	Key  string `json:"key"`
	Iter int64  `json:"iter"`
}

type scanResultStruct struct {
	Keys []string `json:"keys"`
	Iter int64    `json:"iter"`
}

func scan(conn *gosocketio.Channel, c redis.Conn, key, field string, t scanType, iterate int64, maxScanLimits int) (int64, []string) {

	var method, cmd string
	var ret []interface{}
	var keys, tmp []string
	var err error
	switch t {
	case keyScan:
		method = "scan"
	case setScan:
		method = "sscan"
	case hashScan:
		method = "hscan"
	case zsetScan:
		method = "zscan"
	default:
		log.Println("type not exists!")
		return 0, nil
	}

	for {

		if t == keyScan {
			if !strings.ContainsAny(key, "*") {
				key = key + "*"
			}
			cmd = fmt.Sprintf("%s %d MATCH %s COUNT %d", method, iterate, key, _globalConfigs.System.KeyScanLimits)
			ret, err = redis.Values(c.Do(method, iterate, "MATCH", key, "COUNT", _globalConfigs.System.KeyScanLimits))

		} else {
			if key == "" {
				sendCmdError(conn, "key can't not be empty")
				return 0, nil
			}
			if !strings.ContainsAny(field, "*") {
				field = field + "*"
			}
			cmd = fmt.Sprintf("%s %s %d MATCH %s COUNT %d", method, key, iterate, field, _globalConfigs.System.KeyScanLimits)
			ret, err = redis.Values(c.Do(method, key, iterate, "MATCH", field, "COUNT", _globalConfigs.System.KeyScanLimits))
		}

		sendCmd(conn, cmd)
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return 0, nil
		}
		sendCmdReceive(conn, ret)
		tmp, err = redis.Strings(ret[1], nil)
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return 0, nil
		}
		keys = append(keys, tmp...)
		iterate, err = redis.Int64(ret[0], nil)
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return 0, nil
		}
		if iterate == 0 || len(keys) >= maxScanLimits {
			break
		}
	}

	return iterate, keys
}

// ScanKeys scan redis key
func ScanKeys(conn *gosocketio.Channel, data json.RawMessage) {
	if info, ok := checkOperData(conn, data); ok {
		var scanInfo scanInfoStruct
		err := json.Unmarshal(info.Data, &scanInfo)
		if err != nil {
			sendCmdError(conn, "Unmarshal error: "+err.Error())
			return
		}
		c, err := getRedisClient(info.ServerID, info.DB)
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return
		}
		defer c.Close()
		iter, keys := scan(conn, c, scanInfo.Key, "", keyScan, scanInfo.Iter, _globalConfigs.System.KeyScanLimits)
		result := scanResultStruct{
			keys, iter,
		}
		conn.Emit("LoadKeys", result)
	}
}

// ScanRemote scan remote
func ScanRemote(conn *gosocketio.Channel, data json.RawMessage) {
	if c, _redisValue, ok := checkRedisValue(conn, data); ok {
		defer c.Close()
		var key = _redisValue.Key
		var field string

		field, ok := (_redisValue.Val).(string)
		if !ok {
			sendCmdError(conn, "val should be string")
			return
		}
		var size int
		size = _redisValue.Size
		if size == 0 {
			size = _globalConfigs.System.RowScanLimits
		}
		if t, err := keyType(conn, c, key); err == nil {
			if t == "none" {
				sendCmdError(conn, "key is not exists")
				return
			}
			switch t {
			case "string", "list":
				sendCmdError(conn, t+" not support this method!")
				return
			case "set":
				_, vals := scan(conn, c, key, field, setScan, 0, size)
				conn.Emit("ReloadDatas", formatSetAndList(vals))
			case "zset":
				_, vals := scan(conn, c, key, field, zsetScan, 0, size)
				conn.Emit("ReloadDatas", formatZset(vals))
			case "hash":
				_, vals := scan(conn, c, key, field, hashScan, 0, size)
				conn.Emit("ReloadDatas", formatHash(vals))
			default:
				sendCmdError(conn, "underfined type:"+t)
				return
			}
		}
	}
}

// GetKey get value of the key
func GetKey(conn *gosocketio.Channel, data json.RawMessage) {
	if c, _redisValue, ok := checkRedisValue(conn, data); ok {
		defer c.Close()
		// type, ttl, data
		var key = _redisValue.Key
		extra, ok := (_redisValue.Val).(map[string]string)
		var field = ""
		if ok {
			field = extra["field"]
		}
		var size int
		size = _redisValue.Size
		if size == 0 {
			size = _globalConfigs.System.RowScanLimits
		}
		if t, err := keyType(conn, c, key); err == nil {
			if t == "none" {
				sendCmdError(conn, "key is not exists")
				return
			}
			_redisValue.T = t
			// ttl
			cmd := "TTL " + key
			sendCmd(conn, cmd)
			expire, err := redis.Int64(c.Do("TTL", key))
			if err != nil {
				sendCmdError(conn, err.Error())
				return
			}
			sendCmdReceive(conn, expire)
			_redisValue.TTL = expire

			switch t {
			case "string":
				cmd = "GET " + key
				sendCmd(conn, cmd)
				s, err := redis.String(c.Do("GET", key))
				if err != nil {
					sendCmdError(conn, err.Error())
					return
				}
				sendCmdReceive(conn, s)
				_redisValue.Size = 1
				_redisValue.Val = s
			case "list":
				cmd = "LRANGE " + key + " 0 " + strconv.Itoa(_globalConfigs.System.RowScanLimits)
				sendCmd(conn, cmd)
				list, err := redis.Strings(c.Do("LRANGE", key, 0, _globalConfigs.System.RowScanLimits))
				if err != nil {
					sendCmdError(conn, err.Error())
					return
				}
				cmd = "LLEN " + key
				sendCmd(conn, cmd)
				l, err := redis.Int(c.Do("LLEN", key))
				if err != nil {
					sendCmdError(conn, err.Error())
					return
				}
				sendCmdReceive(conn, l)
				_redisValue.Size = l
				_redisValue.Val = formatSetAndList(list)
			case "set":
				_, vals := scan(conn, c, key, field, setScan, 0, size)

				cmd = "SCARD " + key
				sendCmd(conn, cmd)
				l, err := redis.Int(c.Do("SCARD", key))
				if err != nil {
					sendCmdError(conn, err.Error())
					return
				}
				sendCmdReceive(conn, l)
				_redisValue.Size = l
				_redisValue.Val = formatSetAndList(vals)
			case "zset", "hash":
				var method scanType
				var lenMethod string
				if t == "zset" {
					method = zsetScan
					lenMethod = "ZCOUNT"
				} else {
					lenMethod = "HLEN"
					method = hashScan
				}
				_, vals := scan(conn, c, key, field, method, 0, size)
				cmd = lenMethod + " " + key
				var l int
				var err error
				if t == "zset" {
					_redisValue.Val = formatZset(vals)
					cmd += " -inf +inf"
					sendCmd(conn, cmd)
					l, err = redis.Int(c.Do(lenMethod, key, "-inf", "+inf"))
				} else {
					_redisValue.Val = formatHash(vals)
					sendCmd(conn, cmd)
					l, err = redis.Int(c.Do(lenMethod, key))
				}

				if err != nil {
					sendCmdError(conn, err.Error())
					return
				}
				sendCmdReceive(conn, l)
				_redisValue.Size = l
			}

			conn.Emit("ShowRedisValue", _redisValue)
		}
	}
}

func formatSetAndList(datas []string) []map[string]interface{} {
	tmp := make([]map[string]interface{}, 0, 100)
	for i := 0; i < len(datas); i++ {
		row := make(map[string]interface{})
		row["val"] = datas[i]
		row["index"] = i
		tmp = append(tmp, row)
	}
	return tmp
}

func formatHash(datas []string) []map[string]string {
	tmp := make([]map[string]string, 0, 100)
	for i := 0; i < len(datas); i = i + 2 {
		row := make(map[string]string)
		row["field"] = datas[i]
		row["val"] = datas[i+1]
		tmp = append(tmp, row)
	}
	return tmp
}

func formatZset(datas []string) []map[string]interface{} {
	tmp := make([]map[string]interface{}, 0, 100)
	for i := 0; i < len(datas); i = i + 2 {
		row := make(map[string]interface{})
		row["val"] = datas[i]
		row["score"], _ = strconv.ParseFloat(datas[i+1], 10)
		tmp = append(tmp, row)
	}
	return tmp
}

// SetTTL set ttl
func SetTTL(conn *gosocketio.Channel, data json.RawMessage) {
	if c, _redisValue, ok := checkRedisValue(conn, data); ok {
		defer c.Close()
		cmd := "EXPIRE " + _redisValue.Key + " " + strconv.FormatInt(_redisValue.TTL, 10)
		sendCmd(conn, cmd)
		expire, err := redis.Int(c.Do("EXPIRE", _redisValue.Key, _redisValue.TTL))
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return
		}
		sendCmdReceive(conn, expire)

		conn.Emit("ReloadTTL", _redisValue.TTL)
		conn.Emit("tip", &info{"success", "set ttl success!", 2})
	}
}

// KeyType type of the key
func KeyType(conn *gosocketio.Channel, data json.RawMessage) {
	if c, _redisValue, ok := checkRedisValue(conn, data); ok {
		defer c.Close()
		s, err := keyType(conn, c, _redisValue.Key)
		if err == nil {
			conn.Emit("KeyType", s)
		}
	}
}

// Rename rename a key
func Rename(conn *gosocketio.Channel, data json.RawMessage) {
	if c, _redisValue, ok := checkRedisValue(conn, data); ok {
		defer c.Close()
		var newKey string
		newKey, ok = _redisValue.Val.(string)
		if !ok || newKey == "" {
			sendCmdError(conn, "key must be string")
			return
		}
		if newKey == "" {
			sendCmdError(conn, "new key can not be empty!")
			return
		}
		var err error
		cmd := "RENAME " + _redisValue.Key + " " + newKey
		sendCmd(conn, cmd)
		_, err = c.Do("RENAME", _redisValue.Key, newKey)
		if err != nil {
			sendCmdError(conn, "redis error: "+err.Error())
			return
		}
		conn.Emit("ReNameSuccess", newKey)
		conn.Emit("tip", &info{"success", "rename success!", 2})
	}
}

/*
 none (key不存在)
 string (字符串)
 list (列表)
 set (集合)
 zset (有序集)
 hash (哈希表)
*/
func keyType(conn *gosocketio.Channel, c redis.Conn, key string) (string, error) {
	cmd := "TYPE " + key
	sendCmd(conn, cmd)
	s, err := redis.String(c.Do("TYPE", key))
	if err != nil {
		sendCmdError(conn, err.Error())
		return "", err
	}
	sendCmdReceive(conn, s)
	return s, err
}
<|endoftext|>