<commit_before>package main

import (
	"log"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
)

const (
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer.
	maxMessageSize = 1024
)

type NamedWebSocket struct {
	serviceName string

	// The current websocket connection instances to this named websocket
	connections []*Connection

	// Buffered channel of outbound service messages.
	broadcastBuffer chan *Message

	// Buffered channel of outbound connect/disconnect messages
	controlBuffer chan *Message

	// Attached DNS-SD discovery registration and browser for this Named Web Socket
	discoveryClient *DiscoveryClient
}

type Connection struct {
	ws      *websocket.Conn
	isProxy bool
}

// Send a message to the target websocket connection
func (conn *Connection) write(mt int, payload []byte) {
	conn.ws.SetWriteDeadline(time.Now().Add(writeWait))
	conn.ws.WriteMessage(mt, payload)
}

type Message struct {
	source  *Connection
	payload []byte
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  2048,
	WriteBufferSize: 2048,
	CheckOrigin: func(r *http.Request) bool {
		return true // allow all origins
	},
}

// Create a new NamedWebSocket instance (local or broadcast-based) with a given service type
func NewNamedWebSocket(serviceName string, isBroadcast bool) *NamedWebSocket {
	scope := "broadcast"
	if isBroadcast == false {
		scope = "local"
	}

	sock := &NamedWebSocket{
		serviceName:     serviceName,
		connections:     make([]*Connection, 0),
		broadcastBuffer: make(chan *Message, 512),
		controlBuffer:   make(chan *Message, 512),
	}

	go sock.messageDispatcher()

	log.Print("New " + scope + " web socket '" + serviceName + "' created.")

	if isBroadcast {
		go sock.advertise()
	}

	return sock
}

func (sock *NamedWebSocket) advertise() {
	if sock.discoveryClient == nil {
		// Advertise new socket type on the local network
		sock.discoveryClient = NewDiscoveryClient(sock.serviceName)
	}
}

// Set up a new web socket connection
func (sock *NamedWebSocket) serve(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	isProxy := false
	proxyHeader := r.Header.Get("X-BroadcastWebSocket-Proxy")
	if proxyHeader == "true" {
		isProxy = true
	}

	ws, err := upgrader.Upgrade(w, r, map[string][]string{
		"Access-Control-Allow-Origin":      []string{"*"},
		"Access-Control-Allow-Credentials": []string{"true"},
		"Access-Control-Allow-Headers":     []string{"content-type"},
	})
	if err != nil {
		if _, ok := err.(websocket.HandshakeError); !ok {
			log.Println(err)
		}
		return
	}

	conn := &Connection{
		ws:      ws,
		isProxy: isProxy,
	}

	sock.addConnection(conn, true)
}

// readConnectionPump pumps messages from an individual websocket connection to the dispatcher
func (sock *NamedWebSocket) readConnectionPump(conn *Connection) {
	defer func() {
		sock.removeConnection(conn)
	}()
	conn.ws.SetReadLimit(maxMessageSize)
	conn.ws.SetReadDeadline(time.Now().Add(pongWait))
	conn.ws.SetPongHandler(func(string) error { conn.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
	for {
		_, message, err := conn.ws.ReadMessage()
		if err != nil {
			break
		}

		wsBroadcast := &Message{
			source:  conn,
			payload: message,
		}

		sock.broadcastBuffer <- wsBroadcast
	}
}

// writeConnectionPump keeps an individual websocket connection alive
func (sock *NamedWebSocket) writeConnectionPump(conn *Connection) {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		sock.removeConnection(conn)
	}()
	for {
		select {
		case <-ticker.C:
			conn.write(websocket.PingMessage, []byte{})
		}
	}
}

// Handle broadcast of control and service messaging on NamedWebSocket connections
func (sock *NamedWebSocket) messageDispatcher() {
	for {
		select {
		case wsConnect, ok := <-sock.controlBuffer:
			if !ok {
				wsConnect.source.write(websocket.CloseMessage, []byte{})
				return
			}
			sock.broadcast(wsConnect)
		case wsBroadcast, ok := <-sock.broadcastBuffer:
			if !ok {
				wsBroadcast.source.write(websocket.CloseMessage, []byte{})
				return
			}
			sock.broadcast(wsBroadcast)
		}
	}
}

// Set up a new NamedWebSocket connection instance
func (sock *NamedWebSocket) addConnection(conn *Connection, writable bool) {
	connectPayload := []byte("____connect")

	// Notify new websocket connection of existing websocket connections
	for _, oConn := range sock.connections {
		// don't relay connect messages infinitely between proxy connections
		if conn.isProxy && oConn.isProxy {
			continue
		}
		conn.write(websocket.TextMessage, connectPayload)
	}

	if !conn.isProxy {
		// Connect message
		wsConnect := &Message{
			source:  conn,
			payload: []byte("____connect"),
		}

		// Broadcast new connect event to all existing named websocket connections
		sock.controlBuffer <- wsConnect
	}

	// Add this websocket instance to Named WebSocket broadcast list
	if writable {
		sock.connections = append(sock.connections, conn)
	}

	// Start connection read/write pumps
	go sock.writeConnectionPump(conn)
	sock.readConnectionPump(conn)
}

// Broadcast a message to all websocket connections for this NamedWebSocket
// instance (except to the src websocket connection)
func (sock *NamedWebSocket) broadcast(broadcast *Message) {
	for _, conn := range sock.connections {
		if conn.ws != broadcast.source.ws {
			// don't relay broadcast messages infinitely between proxy connections
			if conn.isProxy && broadcast.source.isProxy {
				continue
			}
			conn.write(websocket.TextMessage, broadcast.payload)
		}
	}
}

// Tear down an existing NamedWebSocket connection instance
func (sock *NamedWebSocket) removeConnection(conn *Connection) {
	conn.ws.Close()

	for i, oConn := range sock.connections {
		if oConn.ws == conn.ws {
			sock.connections = append(sock.connections[:i], sock.connections[i+1:]...)
			break
		}
	}

	if !conn.isProxy {
		// Broadcast new disconnect event to all existing named websocket connections
		wsDisconnect := &Message{
			source:  conn,
			payload: []byte("____disconnect"),
		}

		sock.controlBuffer <- wsDisconnect
	}
}
<commit_msg>Remove connection from broadcast connections before closing websocket<commit_after>package main

import (
	"log"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
)

const (
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer.
	maxMessageSize = 1024
)

type NamedWebSocket struct {
	serviceName string

	// The current websocket connection instances to this named websocket
	connections []*Connection

	// Buffered channel of outbound service messages.
	broadcastBuffer chan *Message

	// Buffered channel of outbound connect/disconnect messages
	controlBuffer chan *Message

	// Attached DNS-SD discovery registration and browser for this Named Web Socket
	discoveryClient *DiscoveryClient
}

type Connection struct {
	ws      *websocket.Conn
	isProxy bool
}

// Send a message to the target websocket connection
func (conn *Connection) write(mt int, payload []byte) {
	conn.ws.SetWriteDeadline(time.Now().Add(writeWait))
	conn.ws.WriteMessage(mt, payload)
}

type Message struct {
	source  *Connection
	payload []byte
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  2048,
	WriteBufferSize: 2048,
	CheckOrigin: func(r *http.Request) bool {
		return true // allow all origins
	},
}

// Create a new NamedWebSocket instance (local or broadcast-based) with a given service type
func NewNamedWebSocket(serviceName string, isBroadcast bool) *NamedWebSocket {
	scope := "broadcast"
	if isBroadcast == false {
		scope = "local"
	}

	sock := &NamedWebSocket{
		serviceName:     serviceName,
		connections:     make([]*Connection, 0),
		broadcastBuffer: make(chan *Message, 512),
		controlBuffer:   make(chan *Message, 512),
	}

	go sock.messageDispatcher()

	log.Print("New " + scope + " web socket '" + serviceName + "' created.")

	if isBroadcast {
		go sock.advertise()
	}

	return sock
}

func (sock *NamedWebSocket) advertise() {
	if sock.discoveryClient == nil {
		// Advertise new socket type on the local network
		sock.discoveryClient = NewDiscoveryClient(sock.serviceName)
	}
}

// Set up a new web socket connection
func (sock *NamedWebSocket) serve(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	isProxy := false
	proxyHeader := r.Header.Get("X-BroadcastWebSocket-Proxy")
	if proxyHeader == "true" {
		isProxy = true
	}

	ws, err := upgrader.Upgrade(w, r, map[string][]string{
		"Access-Control-Allow-Origin":      []string{"*"},
		"Access-Control-Allow-Credentials": []string{"true"},
		"Access-Control-Allow-Headers":     []string{"content-type"},
	})
	if err != nil {
		if _, ok := err.(websocket.HandshakeError); !ok {
			log.Println(err)
		}
		return
	}

	conn := &Connection{
		ws:      ws,
		isProxy: isProxy,
	}

	sock.addConnection(conn, true)
}

// readConnectionPump pumps messages from an individual websocket connection to the dispatcher
func (sock *NamedWebSocket) readConnectionPump(conn *Connection) {
	defer func() {
		sock.removeConnection(conn)
	}()
	conn.ws.SetReadLimit(maxMessageSize)
	conn.ws.SetReadDeadline(time.Now().Add(pongWait))
	conn.ws.SetPongHandler(func(string) error { conn.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
	for {
		_, message, err := conn.ws.ReadMessage()
		if err != nil {
			break
		}

		wsBroadcast := &Message{
			source:  conn,
			payload: message,
		}

		sock.broadcastBuffer <- wsBroadcast
	}
}

// writeConnectionPump keeps an individual websocket connection alive
func (sock *NamedWebSocket) writeConnectionPump(conn *Connection) {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		sock.removeConnection(conn)
	}()
	for {
		select {
		case <-ticker.C:
			conn.write(websocket.PingMessage, []byte{})
		}
	}
}

// Handle broadcast of control and service messaging on NamedWebSocket connections
func (sock *NamedWebSocket) messageDispatcher() {
	for {
		select {
		case wsConnect, ok := <-sock.controlBuffer:
			if !ok {
				wsConnect.source.write(websocket.CloseMessage, []byte{})
				return
			}
			sock.broadcast(wsConnect)
		case wsBroadcast, ok := <-sock.broadcastBuffer:
			if !ok {
				wsBroadcast.source.write(websocket.CloseMessage, []byte{})
				return
			}
			sock.broadcast(wsBroadcast)
		}
	}
}

// Set up a new NamedWebSocket connection instance
func (sock *NamedWebSocket) addConnection(conn *Connection, writable bool) {
	connectPayload := []byte("____connect")

	// Notify new websocket connection of existing websocket connections
	for _, oConn := range sock.connections {
		// don't relay connect messages infinitely between proxy connections
		if conn.isProxy && oConn.isProxy {
			continue
		}
		conn.write(websocket.TextMessage, connectPayload)
	}

	if !conn.isProxy {
		// Connect message
		wsConnect := &Message{
			source:  conn,
			payload: []byte("____connect"),
		}

		// Broadcast new connect event to all existing named websocket connections
		sock.controlBuffer <- wsConnect
	}

	// Add this websocket instance to Named WebSocket broadcast list
	if writable {
		sock.connections = append(sock.connections, conn)
	}

	// Start connection read/write pumps
	go sock.writeConnectionPump(conn)
	sock.readConnectionPump(conn)
}

// Broadcast a message to all websocket connections for this NamedWebSocket
// instance (except to the src websocket connection)
func (sock *NamedWebSocket) broadcast(broadcast *Message) {
	for _, conn := range sock.connections {
		if conn.ws != broadcast.source.ws {
			// don't relay broadcast messages infinitely between proxy connections
			if conn.isProxy && broadcast.source.isProxy {
				continue
			}
			conn.write(websocket.TextMessage, broadcast.payload)
		}
	}
}

// Tear down an existing NamedWebSocket connection instance
func (sock *NamedWebSocket) removeConnection(conn *Connection) {
	for i, oConn := range sock.connections {
		if oConn.ws == conn.ws {
			sock.connections = append(sock.connections[:i], sock.connections[i+1:]...)
			break
		}
	}

	conn.ws.Close()

	if !conn.isProxy {
		// Broadcast new disconnect event to all existing named websocket connections
		wsDisconnect := &Message{
			source:  conn,
			payload: []byte("____disconnect"),
		}

		sock.controlBuffer <- wsDisconnect
	}
}
<|endoftext|>


<commit_before>package app

import (
	"github.com/moncho/dry/ui"
	"github.com/nsf/termbox-go"
)

//eventHandler maps a key to an app action
type eventHandler interface {
	handle(event termbox.Event) (refresh bool, focus bool)
}

type containersScreenEventHandler struct {
	dry                  *Dry
	screen               *ui.Screen
	keyboardQueueForView chan termbox.Event
	viewClosed           chan struct{}
}

func (h containersScreenEventHandler) handle(event termbox.Event) (refresh bool, focus bool) {
	focus = true
	dry := h.dry
	screen := h.screen
	cursorPos := screen.CursorPosition()
	//Controls if the event has been handled by the first switch statement
	handled := true
	switch event.Key {
	case termbox.KeyArrowUp: //cursor up
		screen.ScrollCursorUp()
		refresh = true
	case termbox.KeyArrowDown: // cursor down
		screen.ScrollCursorDown()
		refresh = true
	case termbox.KeyF1: //sort
		dry.Sort()
	case termbox.KeyF2: //show all containers
		screen.Cursor.Line = 0
		dry.ToggleShowAllContainers()
	case termbox.KeyF5: // refresh
		dry.Refresh()
	case termbox.KeyF10: // docker info
		dry.ShowInfo()
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	case termbox.KeyCtrlE: //remove all stopped
		dry.RemoveAllStoppedContainers()
	case termbox.KeyCtrlK: //kill
		dry.Kill(cursorPos)
	case termbox.KeyCtrlR: //start
		dry.RestartContainer(cursorPos)
	case termbox.KeyCtrlT: //stop
		dry.StopContainer(cursorPos)
	case termbox.KeyEnter: //inspect
		dry.Inspect(cursorPos)
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	default: //Not handled
		handled = false
	}
	if !handled {
		switch event.Ch {
		case 's', 'S': //stats
			done, errC, err := dry.Stats(cursorPos)
			if err == nil {
				focus = false
				go autorefresh(dry, screen, h.keyboardQueueForView, h.viewClosed, done, errC)
			}
		case 'l', 'L': //logs
			if logs, err := dry.Logs(cursorPos); err == nil {
				focus = false
				dry.ShowContainers()
				go stream(screen, logs, h.keyboardQueueForView, h.viewClosed)
			}
		case '?', 'h', 'H': //help
			focus = false
			dry.ShowHelp()
			go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
		case '1':
			screen.Cursor.Line = 0
			dry.ShowImages()
		case '2':
			screen.Cursor.Line = 0
			dry.ShowNetworks()
		case 'e', 'E': //remove
			dry.Rm(cursorPos)
			screen.ScrollCursorUp()
		}
	}
	return (refresh || dry.Changed()), focus
}

type imagesScreenEventHandler struct {
	dry                  *Dry
	screen               *ui.Screen
	keyboardQueueForView chan termbox.Event
	viewClosed           chan struct{}
}

func (h imagesScreenEventHandler) handle(event termbox.Event) (refresh bool, focus bool) {
	focus = true
	dry := h.dry
	screen := h.screen
	cursorPos := screen.CursorPosition()
	//Controls if the event has been handled by the first switch statement
	handled := true

	switch event.Key {
	case termbox.KeyArrowUp: //cursor up
		screen.ScrollCursorUp()
		refresh = true
	case termbox.KeyArrowDown: // cursor down
		screen.ScrollCursorDown()
		refresh = true
	case termbox.KeyF1: //sort
		dry.SortImages()
	case termbox.KeyF5: // refresh
		dry.Refresh()
	case termbox.KeyF10: // docker info
		dry.ShowInfo()
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)

	case termbox.KeyCtrlE: //remove image
		go dry.RemoveImage(cursorPos, false)
		screen.ScrollCursorUp()
	case termbox.KeyCtrlF: //force remove image
		go dry.RemoveImage(cursorPos, true)
		screen.ScrollCursorUp()
	case termbox.KeyEnter: //inspect image
		dry.InspectImage(cursorPos)
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	default:
		handled = false
	}

	if !handled {
		switch event.Ch {
		case '?', 'h', 'H': //help
			focus = false
			dry.ShowHelp()
			go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
		case '1':
			screen.Cursor.Line = 0
			dry.ShowContainers()
		case '2':
			screen.Cursor.Line = 0
			dry.ShowNetworks()
		case 'i', 'I': //image history
			dry.History(cursorPos)
			focus = false
			go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
		}

	}

	return (refresh || dry.Changed()), focus
}

type networksScreenEventHandler struct {
	dry                  *Dry
	screen               *ui.Screen
	keyboardQueueForView chan termbox.Event
	viewClosed           chan struct{}
}

func (h networksScreenEventHandler) handle(event termbox.Event) (refresh bool, focus bool) {
	focus = true
	dry := h.dry
	screen := h.screen
	cursorPos := screen.CursorPosition()
	switch event.Key {
	case termbox.KeyArrowUp: //cursor up
		screen.ScrollCursorUp()
		refresh = true
	case termbox.KeyArrowDown: // cursor down
		screen.ScrollCursorDown()
		refresh = true
	case termbox.KeyF1: //sort
		dry.SortNetworks()
	case termbox.KeyF5: // refresh
		screen.Cursor.Line = 0
		dry.Refresh()
	case termbox.KeyF10: // docker info
		dry.ShowInfo()
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	case termbox.KeyEnter: //inspect
		dry.InspectNetwork(cursorPos)
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	}

	switch event.Ch {
	case '?', 'h', 'H': //help
		focus = false
		dry.ShowHelp()
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	case '1':
		screen.Cursor.Line = 0
		dry.ShowContainers()
	case '2':
		screen.Cursor.Line = 0
		dry.ShowImages()
	}
	return (refresh || dry.Changed()), focus
}

func eventHandlerFactory(dry *Dry, screen *ui.Screen,
	keyboardQueueForView chan termbox.Event,
	viewClosed chan struct{}) eventHandler {
	switch dry.viewMode() {
	case Images:
		return imagesScreenEventHandler{dry, screen, keyboardQueueForView, viewClosed}
	case Networks:
		return networksScreenEventHandler{dry, screen, keyboardQueueForView, viewClosed}
	default:
		return containersScreenEventHandler{dry, screen, keyboardQueueForView, viewClosed}
	}
}
<commit_msg>Use cursor directly, not going through screen<commit_after>package app

import (
	"github.com/moncho/dry/ui"
	"github.com/nsf/termbox-go"
)

//eventHandler maps a key to an app action
type eventHandler interface {
	handle(event termbox.Event) (refresh bool, focus bool)
}

type containersScreenEventHandler struct {
	dry                  *Dry
	screen               *ui.Screen
	keyboardQueueForView chan termbox.Event
	viewClosed           chan struct{}
}

func (h containersScreenEventHandler) handle(event termbox.Event) (refresh bool, focus bool) {
	focus = true
	dry := h.dry
	screen := h.screen
	cursor := screen.Cursor
	cursorPos := cursor.Position()
	//Controls if the event has been handled by the first switch statement
	handled := true
	switch event.Key {
	case termbox.KeyArrowUp: //cursor up
		cursor.ScrollCursorUp()
		refresh = true
	case termbox.KeyArrowDown: // cursor down
		cursor.ScrollCursorDown()
		refresh = true
	case termbox.KeyF1: //sort
		dry.Sort()
	case termbox.KeyF2: //show all containers
		cursor.Reset()
		dry.ToggleShowAllContainers()
	case termbox.KeyF5: // refresh
		dry.Refresh()
	case termbox.KeyF10: // docker info
		dry.ShowInfo()
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	case termbox.KeyCtrlE: //remove all stopped
		dry.RemoveAllStoppedContainers()
	case termbox.KeyCtrlK: //kill
		dry.Kill(cursorPos)
	case termbox.KeyCtrlR: //start
		dry.RestartContainer(cursorPos)
	case termbox.KeyCtrlT: //stop
		dry.StopContainer(cursorPos)
	case termbox.KeyEnter: //inspect
		dry.Inspect(cursorPos)
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	default: //Not handled
		handled = false
	}
	if !handled {
		switch event.Ch {
		case 's', 'S': //stats
			done, errC, err := dry.Stats(cursorPos)
			if err == nil {
				focus = false
				go autorefresh(dry, screen, h.keyboardQueueForView, h.viewClosed, done, errC)
			}
		case 'l', 'L': //logs
			if logs, err := dry.Logs(cursorPos); err == nil {
				focus = false
				dry.ShowContainers()
				go stream(screen, logs, h.keyboardQueueForView, h.viewClosed)
			}
		case '?', 'h', 'H': //help
			focus = false
			dry.ShowHelp()
			go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
		case '1':
			cursor.Reset()
			dry.ShowImages()
		case '2':
			cursor.Reset()
			dry.ShowNetworks()
		case 'e', 'E': //remove
			dry.Rm(cursorPos)
			cursor.ScrollCursorUp()
		}
	}
	return (refresh || dry.Changed()), focus
}

type imagesScreenEventHandler struct {
	dry                  *Dry
	screen               *ui.Screen
	keyboardQueueForView chan termbox.Event
	viewClosed           chan struct{}
}

func (h imagesScreenEventHandler) handle(event termbox.Event) (refresh bool, focus bool) {
	focus = true
	dry := h.dry
	screen := h.screen
	cursor := screen.Cursor
	cursorPos := cursor.Position()
	//Controls if the event has been handled by the first switch statement
	handled := true

	switch event.Key {
	case termbox.KeyArrowUp: //cursor up
		cursor.ScrollCursorUp()
		refresh = true
	case termbox.KeyArrowDown: // cursor down
		cursor.ScrollCursorDown()
		refresh = true
	case termbox.KeyF1: //sort
		dry.SortImages()
	case termbox.KeyF5: // refresh
		dry.Refresh()
	case termbox.KeyF10: // docker info
		dry.ShowInfo()
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)

	case termbox.KeyCtrlE: //remove image
		dry.RemoveImage(cursorPos, false)
		cursor.ScrollCursorUp()
	case termbox.KeyCtrlF: //force remove image
		dry.RemoveImage(cursorPos, true)
		cursor.ScrollCursorUp()
	case termbox.KeyEnter: //inspect image
		dry.InspectImage(cursorPos)
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	default:
		handled = false
	}

	if !handled {
		switch event.Ch {
		case '?', 'h', 'H': //help
			focus = false
			dry.ShowHelp()
			go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
		case '1':
			cursor.Reset()
			dry.ShowContainers()
		case '2':
			cursor.Reset()
			dry.ShowNetworks()
		case 'i', 'I': //image history
			dry.History(cursorPos)
			focus = false
			go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
		}

	}

	return (refresh || dry.Changed()), focus
}

type networksScreenEventHandler struct {
	dry                  *Dry
	screen               *ui.Screen
	keyboardQueueForView chan termbox.Event
	viewClosed           chan struct{}
}

func (h networksScreenEventHandler) handle(event termbox.Event) (refresh bool, focus bool) {
	focus = true
	dry := h.dry
	screen := h.screen
	cursor := screen.Cursor
	cursorPos := cursor.Position()
	switch event.Key {
	case termbox.KeyArrowUp: //cursor up
		cursor.ScrollCursorUp()
		refresh = true
	case termbox.KeyArrowDown: // cursor down
		cursor.ScrollCursorDown()
		refresh = true
	case termbox.KeyF1: //sort
		dry.SortNetworks()
	case termbox.KeyF5: // refresh
		cursor.Reset()
		dry.Refresh()
	case termbox.KeyF10: // docker info
		dry.ShowInfo()
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	case termbox.KeyEnter: //inspect
		dry.InspectNetwork(cursorPos)
		focus = false
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	}

	switch event.Ch {
	case '?', 'h', 'H': //help
		focus = false
		dry.ShowHelp()
		go less(dry, screen, h.keyboardQueueForView, h.viewClosed)
	case '1':
		cursor.Reset()
		dry.ShowContainers()
	case '2':
		cursor.Reset()
		dry.ShowImages()
	}
	return (refresh || dry.Changed()), focus
}

func eventHandlerFactory(dry *Dry, screen *ui.Screen,
	keyboardQueueForView chan termbox.Event,
	viewClosed chan struct{}) eventHandler {
	switch dry.viewMode() {
	case Images:
		return imagesScreenEventHandler{dry, screen, keyboardQueueForView, viewClosed}
	case Networks:
		return networksScreenEventHandler{dry, screen, keyboardQueueForView, viewClosed}
	default:
		return containersScreenEventHandler{dry, screen, keyboardQueueForView, viewClosed}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 Google LLC
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 api

import (
	"archive/zip"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"path/filepath"
	generator "shifter/generators"
	lib "shifter/lib"
	os "shifter/openshift"
	ops "shifter/ops"
	"shifter/processor"

	"github.com/gin-gonic/gin"
)

func (server *Server) Convert(ctx *gin.Context) {
	var openshift os.Openshift

	// Create API Unique RUN ID
	//uuid := uuid.New().String()
	suid := ops.CreateSUID("")
	convert := Convert{}
	// using BindJson method to serialize body with struct
	if err := ctx.BindJSON(&convert); err != nil {
		ctx.AbortWithError(http.StatusBadRequest, err)
		return
	}

	var openshift os.Openshift
	openshift.Endpoint = convert.Shifter.ClusterConfig.BaseUrl
	openshift.AuthToken = convert.Shifter.ClusterConfig.BearerToken

	// Process Each Item
	for _, item := range convert.Items {
		// Confirm Project/Namespace Exists
		deploymentConfig := openshift.GetDeploymentConfig(item.Namespace.ObjectMeta.Name, item.DeploymentConfig.ObjectMeta.Name)

		u, err := json.Marshal(deploymentConfig)
		if err != nil {
			panic(err)
		}

		// Handle the Conversion of the Manifests and File Writing
		var generator generator.Generator
		var objs []lib.K8sobject
		obj := processor.Processor(u, "DeploymentConfig", nil)
		objs = append(objs, obj)
		convertedObjects := generator.Yaml(item.DeploymentConfig.ObjectMeta.Name, objs)
		for _, conObj := range convertedObjects {
			fileObj := &ops.FileObject{
				//StorageType: "GCS",
				//SourcePath:  ("gs://shifter-lz-002-sample-files/" + uuid + "/" + item.Namespace.ObjectMeta.Name + "/" + item.DeploymentConfig.ObjectMeta.Name),
				StorageType:   server.config.serverStorage.storageType,
				SourcePath:    (server.config.serverStorage.sourcePath + "/" + suid.DirectoryName + "/" + item.Namespace.ObjectMeta.Name + "/" + item.DeploymentConfig.ObjectMeta.Name),
				Ext:           "yaml",
				Content:       conObj.Payload,
				ContentLength: conObj.Payload.Len(),
			}
			fileObj.WriteFile()
		}
	}

	// Zip / Package Converted Objects
	err := server.PackageConversionObjects(suid)
	if err != nil {
		ctx.AbortWithError(http.StatusBadRequest, err)
	}

	// Construct API Endpoint Response
	r := ResponseConvert{
		SUID:    suid,
		Message: "Converted..." + string(len(convert.Items)) + " Objects",
	}
	ctx.JSON(http.StatusOK, r)
}

func (server *Server) PackageConversionObjects(suid ops.SUID) error {

	file, err := os.Create(server.config.serverStorage.outputPath + "/" + suid.DownloadId + ".zip")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	w := zip.NewWriter(file)
	defer w.Close()

	walker := func(path string, info os.FileInfo, err error) error {
		//fmt.Printf("Crawling: %#v\n", path)
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()

		// Ensure that `path` is not absolute; it should not start with "/".
		// This snippet happens to work because I don't use
		// absolute paths, but ensure your real-world code
		// transforms path into a zip-root relative path.
		f, err := w.Create(path)
		if err != nil {
			return err
		}

		_, err = io.Copy(f, file)
		if err != nil {
			return err
		}
		return nil
	}
	err = filepath.Walk(server.config.serverStorage.sourcePath+"/"+suid.DirectoryName+"/", walker)
	if err != nil {
		return errors.New("Unable to resolve or find Download ID")
	}
	return nil
}
<commit_msg>Downloads is working, downloading a file from the local server works. Zipping a GCS object does not yet work<commit_after>/*
Copyright 2019 Google LLC
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 api

import (
	"archive/zip"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	generator "shifter/generators"
	lib "shifter/lib"
	ops "shifter/ops"
	"shifter/processor"

	"github.com/gin-gonic/gin"
)

func (server *Server) Convert(ctx *gin.Context) {
	var openshift os.Openshift

	// Create API Unique RUN ID
	//uuid := uuid.New().String()
	suid := ops.CreateSUID("")
	convert := Convert{}
	// using BindJson method to serialize body with struct
	if err := ctx.BindJSON(&convert); err != nil {
		ctx.AbortWithError(http.StatusBadRequest, err)
		return
	}

	var openshift os.Openshift
	openshift.Endpoint = convert.Shifter.ClusterConfig.BaseUrl
	openshift.AuthToken = convert.Shifter.ClusterConfig.BearerToken

	// Process Each Item
	for _, item := range convert.Items {
		// Create OpenShift Client
		openshift := osh.NewClient(http.DefaultClient)
		// Configure Authorization
		openshift.AuthOptions = &osh.AuthOptions{
			BearerToken: convert.Shifter.ClusterConfig.BearerToken,
		}
		// Configure Base URL
		var err error
		openshift.BaseURL, err = url.Parse(convert.Shifter.ClusterConfig.BaseUrl)
		if err != nil {
			panic(err)
		}

		// Confirm Project/Namespace Exists
		deploymentConfig := openshift.GetDeploymentConfig(item.Namespace.ObjectMeta.Name, item.DeploymentConfig.ObjectMeta.Name)

		u, err := json.Marshal(deploymentConfig)
		if err != nil {
			panic(err)
		}

		// Handle the Conversion of the Manifests and File Writing
		var generator generator.Generator
		var objs []lib.K8sobject
		obj := processor.Processor(u, "DeploymentConfig", nil)
		objs = append(objs, obj)
		convertedObjects := generator.Yaml(item.DeploymentConfig.ObjectMeta.Name, objs)
		for _, conObj := range convertedObjects {
			fileObj := &ops.FileObject{
				//StorageType: "GCS",
				//SourcePath:  ("gs://shifter-lz-002-sample-files/" + uuid + "/" + item.Namespace.ObjectMeta.Name + "/" + item.DeploymentConfig.ObjectMeta.Name),
				StorageType:   server.config.serverStorage.storageType,
				SourcePath:    (server.config.serverStorage.sourcePath + "/" + suid.DirectoryName + "/" + item.Namespace.ObjectMeta.Name + "/" + item.DeploymentConfig.ObjectMeta.Name),
				Ext:           "yaml",
				Content:       conObj.Payload,
				ContentLength: conObj.Payload.Len(),
			}
			fileObj.WriteFile()
		}
	}

	// Zip / Package Converted Objects
	err := server.PackageConversionObjects(suid)
	if err != nil {
		ctx.AbortWithError(http.StatusBadRequest, err)
	}

	// Construct API Endpoint Response
	r := ResponseConvert{
		SUID:    suid,
		Message: "Converted..." + string(len(convert.Items)) + " Objects",
	}
	ctx.JSON(http.StatusOK, r)
}

func (server *Server) PackageConversionObjects(suid ops.SUID) error {

	file, err := os.Create(server.config.serverStorage.outputPath + "/" + suid.DownloadId + ".zip")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	w := zip.NewWriter(file)
	defer w.Close()

	walker := func(path string, info os.FileInfo, err error) error {
		//fmt.Printf("Crawling: %#v\n", path)
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()

		// Ensure that `path` is not absolute; it should not start with "/".
		// This snippet happens to work because I don't use
		// absolute paths, but ensure your real-world code
		// transforms path into a zip-root relative path.
		f, err := w.Create(path)
		if err != nil {
			return err
		}

		_, err = io.Copy(f, file)
		if err != nil {
			return err
		}
		return nil
	}
	err = filepath.Walk(server.config.serverStorage.sourcePath+"/"+suid.DirectoryName+"/", walker)
	if err != nil {
		return errors.New("Unable to resolve or find Download ID")
	}
	return nil
}
<|endoftext|>


<commit_before>package libgobuster

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"log"
	"os"
	"strings"
	"sync"
)

const (
	// VERSION contains the current gobuster version
	VERSION = "1.4.1"
)

// SetupFunc is the "setup" function prototype for implementations
type SetupFunc func(*Gobuster) error

// ProcessFunc is the "process" function prototype for implementations
type ProcessFunc func(*Gobuster, string) ([]Result, error)

// ResultToStringFunc is the "to string" function prototype for implementations
type ResultToStringFunc func(*Gobuster, *Result) (*string, error)

// Gobuster is the main object when creating a new run
type Gobuster struct {
	Opts             *Options
	http             *httpClient
	WildcardIps      stringSet
	context          context.Context
	requestsExpected int
	requestsIssued   int
	mu               *sync.RWMutex
	funcResToString  ResultToStringFunc
	funcProcessor    ProcessFunc
	funcSetup        SetupFunc
	IsWildcard       bool
	resultChan       chan Result
}

// NewGobuster returns a new Gobuster object
func NewGobuster(c context.Context, opts *Options, setupFunc SetupFunc, processFunc ProcessFunc, resultFunc ResultToStringFunc) (*Gobuster, error) {
	// validate given options
	multiErr := opts.validate()
	if multiErr != nil {
		return nil, multiErr
	}

	var g Gobuster
	g.WildcardIps = stringSet{Set: map[string]bool{}}
	g.context = c
	g.Opts = opts
	h, err := newHTTPClient(c, opts)
	if err != nil {
		return nil, err
	}
	g.http = h

	g.funcSetup = setupFunc
	g.funcProcessor = processFunc
	g.funcResToString = resultFunc
	g.mu = new(sync.RWMutex)

	g.resultChan = make(chan Result)

	return &g, nil
}

// Results returns a channel of Results
func (g *Gobuster) Results() <-chan Result {
	return g.resultChan
}

func (g *Gobuster) incrementRequests() {
	g.mu.Lock()
	g.requestsIssued++
	g.mu.Unlock()
}

// PrintProgress outputs the current wordlist progress to stderr
func (g *Gobuster) PrintProgress() {
	g.mu.RLock()
	if g.Opts.Wordlist == "-" {
		fmt.Fprintf(os.Stderr, "\rProgress: %d", g.requestsIssued)
	} else {
		fmt.Fprintf(os.Stderr, "\rProgress: %d / %d", g.requestsIssued, g.requestsExpected)
	}
	g.mu.RUnlock()
}

// ClearProgress removes the last status line from stderr
func (g *Gobuster) ClearProgress() {
	fmt.Fprint(os.Stderr, "\r")
}

// GetRequest issues a GET request to the target and returns
// the status code, length and an error
func (g *Gobuster) GetRequest(url string) (*int, *int64, error) {
	g.incrementRequests()
	return g.http.makeRequest(url, g.Opts.Cookies)
}

func (g *Gobuster) worker(wordChan <-chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		select {
		case <-g.context.Done():
			return
		case word := <-wordChan:
			// Mode-specific processing
			res, err := g.funcProcessor(g, word)
			if err != nil {
				// do not exit and continue
				log.Printf("error on word %s: %v", word, err)
				continue
			} else {
				for _, r := range res {
					g.resultChan <- r
				}
			}
		}
	}
}

func (g *Gobuster) getWordlist() (*bufio.Scanner, error) {
	if g.Opts.Wordlist == "-" {
		// Read directly from stdin
		return bufio.NewScanner(os.Stdin), nil
	}
	// Pull content from the wordlist
	wordlist, err := os.Open(g.Opts.Wordlist)
	if err != nil {
		return nil, fmt.Errorf("failed to open wordlist: %v", err)
	}

	lines, err := lineCounter(wordlist)
	if err != nil {
		return nil, fmt.Errorf("failed to get number of lines: %v", err)
	}

	// mutiply by extensions to get the total number of requests
	if len(g.Opts.ExtensionsParsed) > 0 {
		lines = lines + (lines * len(g.Opts.ExtensionsParsed))
	}
	g.requestsExpected = lines
	g.requestsIssued = 0

	// rewind wordlist
	_, err = wordlist.Seek(0, 0)
	if err != nil {
		return nil, fmt.Errorf("failed to rewind wordlist: %v", err)
	}
	return bufio.NewScanner(wordlist), nil
}

// Start the busting of the website with the given
// set of settings from the command line.
func (g *Gobuster) Start() error {
	if err := g.funcSetup(g); err != nil {
		return err
	}

	var printerGroup sync.WaitGroup
	printerGroup.Add(1)
	var workerGroup sync.WaitGroup
	workerGroup.Add(g.Opts.Threads)

	wordChan := make(chan string, g.Opts.Threads)

	// Create goroutines for each of the number of threads
	// specified.
	for i := 0; i < g.Opts.Threads; i++ {
		go g.worker(wordChan, &workerGroup)
	}

	scanner, err := g.getWordlist()
	if err != nil {
		return err
	}

Scan:
	for scanner.Scan() {
		select {
		case <-g.context.Done():
			break Scan
		default:
			word := strings.TrimSpace(scanner.Text())
			// Skip "comment" (starts with #), as well as empty lines
			if !strings.HasPrefix(word, "#") && len(word) > 0 {
				wordChan <- word
			}
		}
	}
	close(wordChan)
	return nil
}

// GetConfigString returns the current config as a printable string
func (g *Gobuster) GetConfigString() (string, error) {
	buf := &bytes.Buffer{}
	o := g.Opts
	if _, err := fmt.Fprintf(buf, "[+] Mode         : %s\n", o.Mode); err != nil {
		return "", err
	}
	if _, err := fmt.Fprintf(buf, "[+] Url/Domain   : %s\n", o.URL); err != nil {
		return "", err
	}
	if _, err := fmt.Fprintf(buf, "[+] Threads      : %d\n", o.Threads); err != nil {
		return "", err
	}

	wordlist := "stdin (pipe)"
	if o.Wordlist != "-" {
		wordlist = o.Wordlist
	}
	if _, err := fmt.Fprintf(buf, "[+] Wordlist     : %s\n", wordlist); err != nil {
		return "", err
	}

	if o.Mode == ModeDir {
		if _, err := fmt.Fprintf(buf, "[+] Status codes : %s\n", o.StatusCodesParsed.Stringify()); err != nil {
			return "", err
		}

		if o.Proxy != "" {
			if _, err := fmt.Fprintf(buf, "[+] Proxy        : %s\n", o.Proxy); err != nil {
				return "", err
			}
		}

		if o.Cookies != "" {
			if _, err := fmt.Fprintf(buf, "[+] Cookies      : %s\n", o.Cookies); err != nil {
				return "", err
			}
		}

		if o.UserAgent != "" {
			if _, err := fmt.Fprintf(buf, "[+] User Agent   : %s\n", o.UserAgent); err != nil {
				return "", err
			}
		}

		if o.IncludeLength {
			if _, err := fmt.Fprintf(buf, "[+] Show length  : true\n"); err != nil {
				return "", err
			}
		}

		if o.Username != "" {
			if _, err := fmt.Fprintf(buf, "[+] Auth User    : %s\n", o.Username); err != nil {
				return "", err
			}
		}

		if len(o.Extensions) > 0 {
			if _, err := fmt.Fprintf(buf, "[+] Extensions   : %s\n", strings.Join(o.ExtensionsParsed, ",")); err != nil {
				return "", err
			}
		}

		if o.UseSlash {
			if _, err := fmt.Fprintf(buf, "[+] Add Slash    : true\n"); err != nil {
				return "", err
			}
		}

		if o.FollowRedirect {
			if _, err := fmt.Fprintf(buf, "[+] Follow Redir : true\n"); err != nil {
				return "", err
			}
		}

		if o.Expanded {
			if _, err := fmt.Fprintf(buf, "[+] Expanded     : true\n"); err != nil {
				return "", err
			}
		}

		if o.NoStatus {
			if _, err := fmt.Fprintf(buf, "[+] No status    : true\n"); err != nil {
				return "", err
			}
		}

		if o.Verbose {
			if _, err := fmt.Fprintf(buf, "[+] Verbose      : true\n"); err != nil {
				return "", err
			}
		}

		if _, err := fmt.Fprintf(buf, "[+] Timeout      : %s\n", o.Timeout.String()); err != nil {
			return "", err
		}
	}

	return strings.TrimSpace(buf.String()), nil
}
<commit_msg>don't print status on first connect<commit_after>package libgobuster

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"log"
	"os"
	"strings"
	"sync"
)

const (
	// VERSION contains the current gobuster version
	VERSION = "1.4.1"
)

// SetupFunc is the "setup" function prototype for implementations
type SetupFunc func(*Gobuster) error

// ProcessFunc is the "process" function prototype for implementations
type ProcessFunc func(*Gobuster, string) ([]Result, error)

// ResultToStringFunc is the "to string" function prototype for implementations
type ResultToStringFunc func(*Gobuster, *Result) (*string, error)

// Gobuster is the main object when creating a new run
type Gobuster struct {
	Opts             *Options
	http             *httpClient
	WildcardIps      stringSet
	context          context.Context
	requestsExpected int
	requestsIssued   int
	mu               *sync.RWMutex
	funcResToString  ResultToStringFunc
	funcProcessor    ProcessFunc
	funcSetup        SetupFunc
	IsWildcard       bool
	resultChan       chan Result
}

// NewGobuster returns a new Gobuster object
func NewGobuster(c context.Context, opts *Options, setupFunc SetupFunc, processFunc ProcessFunc, resultFunc ResultToStringFunc) (*Gobuster, error) {
	// validate given options
	multiErr := opts.validate()
	if multiErr != nil {
		return nil, multiErr
	}

	var g Gobuster
	g.WildcardIps = stringSet{Set: map[string]bool{}}
	g.context = c
	g.Opts = opts
	h, err := newHTTPClient(c, opts)
	if err != nil {
		return nil, err
	}
	g.http = h

	g.funcSetup = setupFunc
	g.funcProcessor = processFunc
	g.funcResToString = resultFunc
	g.mu = new(sync.RWMutex)

	g.resultChan = make(chan Result)

	return &g, nil
}

// Results returns a channel of Results
func (g *Gobuster) Results() <-chan Result {
	return g.resultChan
}

func (g *Gobuster) incrementRequests() {
	g.mu.Lock()
	g.requestsIssued++
	g.mu.Unlock()
}

// PrintProgress outputs the current wordlist progress to stderr
func (g *Gobuster) PrintProgress() {
	g.mu.RLock()
	if g.Opts.Wordlist == "-" {
		fmt.Fprintf(os.Stderr, "\rProgress: %d", g.requestsIssued)
		// only print status if we already read in the wordlist
	} else if g.requestsExpected > 0 {
		fmt.Fprintf(os.Stderr, "\rProgress: %d / %d", g.requestsIssued, g.requestsExpected)
	}
	g.mu.RUnlock()
}

// ClearProgress removes the last status line from stderr
func (g *Gobuster) ClearProgress() {
	fmt.Fprint(os.Stderr, "\r")
}

// GetRequest issues a GET request to the target and returns
// the status code, length and an error
func (g *Gobuster) GetRequest(url string) (*int, *int64, error) {
	g.incrementRequests()
	return g.http.makeRequest(url, g.Opts.Cookies)
}

func (g *Gobuster) worker(wordChan <-chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		select {
		case <-g.context.Done():
			return
		case word := <-wordChan:
			// Mode-specific processing
			res, err := g.funcProcessor(g, word)
			if err != nil {
				// do not exit and continue
				log.Printf("error on word %s: %v", word, err)
				continue
			} else {
				for _, r := range res {
					g.resultChan <- r
				}
			}
		}
	}
}

func (g *Gobuster) getWordlist() (*bufio.Scanner, error) {
	if g.Opts.Wordlist == "-" {
		// Read directly from stdin
		return bufio.NewScanner(os.Stdin), nil
	}
	// Pull content from the wordlist
	wordlist, err := os.Open(g.Opts.Wordlist)
	if err != nil {
		return nil, fmt.Errorf("failed to open wordlist: %v", err)
	}

	lines, err := lineCounter(wordlist)
	if err != nil {
		return nil, fmt.Errorf("failed to get number of lines: %v", err)
	}

	// mutiply by extensions to get the total number of requests
	if len(g.Opts.ExtensionsParsed) > 0 {
		lines = lines + (lines * len(g.Opts.ExtensionsParsed))
	}
	g.requestsExpected = lines
	g.requestsIssued = 0

	// rewind wordlist
	_, err = wordlist.Seek(0, 0)
	if err != nil {
		return nil, fmt.Errorf("failed to rewind wordlist: %v", err)
	}
	return bufio.NewScanner(wordlist), nil
}

// Start the busting of the website with the given
// set of settings from the command line.
func (g *Gobuster) Start() error {
	if err := g.funcSetup(g); err != nil {
		return err
	}

	var printerGroup sync.WaitGroup
	printerGroup.Add(1)
	var workerGroup sync.WaitGroup
	workerGroup.Add(g.Opts.Threads)

	wordChan := make(chan string, g.Opts.Threads)

	// Create goroutines for each of the number of threads
	// specified.
	for i := 0; i < g.Opts.Threads; i++ {
		go g.worker(wordChan, &workerGroup)
	}

	scanner, err := g.getWordlist()
	if err != nil {
		return err
	}

Scan:
	for scanner.Scan() {
		select {
		case <-g.context.Done():
			break Scan
		default:
			word := strings.TrimSpace(scanner.Text())
			// Skip "comment" (starts with #), as well as empty lines
			if !strings.HasPrefix(word, "#") && len(word) > 0 {
				wordChan <- word
			}
		}
	}
	close(wordChan)
	return nil
}

// GetConfigString returns the current config as a printable string
func (g *Gobuster) GetConfigString() (string, error) {
	buf := &bytes.Buffer{}
	o := g.Opts
	if _, err := fmt.Fprintf(buf, "[+] Mode         : %s\n", o.Mode); err != nil {
		return "", err
	}
	if _, err := fmt.Fprintf(buf, "[+] Url/Domain   : %s\n", o.URL); err != nil {
		return "", err
	}
	if _, err := fmt.Fprintf(buf, "[+] Threads      : %d\n", o.Threads); err != nil {
		return "", err
	}

	wordlist := "stdin (pipe)"
	if o.Wordlist != "-" {
		wordlist = o.Wordlist
	}
	if _, err := fmt.Fprintf(buf, "[+] Wordlist     : %s\n", wordlist); err != nil {
		return "", err
	}

	if o.Mode == ModeDir {
		if _, err := fmt.Fprintf(buf, "[+] Status codes : %s\n", o.StatusCodesParsed.Stringify()); err != nil {
			return "", err
		}

		if o.Proxy != "" {
			if _, err := fmt.Fprintf(buf, "[+] Proxy        : %s\n", o.Proxy); err != nil {
				return "", err
			}
		}

		if o.Cookies != "" {
			if _, err := fmt.Fprintf(buf, "[+] Cookies      : %s\n", o.Cookies); err != nil {
				return "", err
			}
		}

		if o.UserAgent != "" {
			if _, err := fmt.Fprintf(buf, "[+] User Agent   : %s\n", o.UserAgent); err != nil {
				return "", err
			}
		}

		if o.IncludeLength {
			if _, err := fmt.Fprintf(buf, "[+] Show length  : true\n"); err != nil {
				return "", err
			}
		}

		if o.Username != "" {
			if _, err := fmt.Fprintf(buf, "[+] Auth User    : %s\n", o.Username); err != nil {
				return "", err
			}
		}

		if len(o.Extensions) > 0 {
			if _, err := fmt.Fprintf(buf, "[+] Extensions   : %s\n", strings.Join(o.ExtensionsParsed, ",")); err != nil {
				return "", err
			}
		}

		if o.UseSlash {
			if _, err := fmt.Fprintf(buf, "[+] Add Slash    : true\n"); err != nil {
				return "", err
			}
		}

		if o.FollowRedirect {
			if _, err := fmt.Fprintf(buf, "[+] Follow Redir : true\n"); err != nil {
				return "", err
			}
		}

		if o.Expanded {
			if _, err := fmt.Fprintf(buf, "[+] Expanded     : true\n"); err != nil {
				return "", err
			}
		}

		if o.NoStatus {
			if _, err := fmt.Fprintf(buf, "[+] No status    : true\n"); err != nil {
				return "", err
			}
		}

		if o.Verbose {
			if _, err := fmt.Fprintf(buf, "[+] Verbose      : true\n"); err != nil {
				return "", err
			}
		}

		if _, err := fmt.Fprintf(buf, "[+] Timeout      : %s\n", o.Timeout.String()); err != nil {
			return "", err
		}
	}

	return strings.TrimSpace(buf.String()), nil
}
<|endoftext|>


<commit_before>//go:generate stringer -type=DynoState,DynoInput
package hsup

import (
	"errors"
	"log"
	"os/exec"

	"fmt"

	"github.com/fsouza/go-dockerclient"
)

type DynoState int

const (
	Stopped DynoState = iota
	Started
	Retiring
	Retired
)

type DynoInput int

const (
	Retire DynoInput = iota
	Restart
	Exited
	StayStarted
)

var ErrExecutorComplete = errors.New("Executor complete")

type Executor struct {
	Args        []string
	DynoDriver  DynoDriver
	Release     *Release
	ProcessID   string
	ProcessType string
	Status      chan *ExitStatus
	Complete    chan struct{}

	// simple, abspath, and libcontainer dyno driver properties
	cmd     *exec.Cmd
	waiting chan struct{}

	// docker dyno driver properties
	container *docker.Container

	// libcontainer dyno driver properties
	lcStatus      chan *ExitStatus
	waitStartup   chan struct{}
	waitWait      chan struct{}
	containerUUID string

	// FSM Fields
	OneShot  bool
	State    DynoState
	NewInput chan DynoInput
}

func (e *Executor) Trigger(input DynoInput) {
	log.Println("triggering", input)
	select {
	case e.NewInput <- input:
	case <-e.Complete:
	}
}

func (e *Executor) wait() {
	if s := e.DynoDriver.Wait(e); e.Status != nil {
		log.Println("Executor exits:", e.Name(), "exit code:", s.Code)
		e.Status <- s
	}
	e.Trigger(Exited)
}

func (e *Executor) Tick() (err error) {
	log.Println(e.Name(), "waiting for tick...", e.State)
	input := <-e.NewInput
	log.Println(e.Name(), "ticking with input", input)

	start := func() error {
		log.Printf("%v: starting\n", e.Name())
		if err = e.DynoDriver.Start(e); err != nil {
			log.Printf("%v: start fails: %v", e.Name(), err)
			if e.OneShot {
				go e.Trigger(Retire)
			} else {
				go e.Trigger(Restart)
			}
			return err
		}

		log.Printf("%v: started\n", e.Name())
		e.State = Started
		go e.wait()
		return nil
	}

again:
	switch e.State {
	case Retired:
		close(e.Complete)
		return ErrExecutorComplete
	case Retiring:
		if err = e.DynoDriver.Stop(e); err != nil {
			return err
		}

		e.State = Retired
		goto again
	case Stopped:
		switch input {
		case Retire:
			e.State = Retired
			goto again
		case Exited:
			if e.OneShot {
				e.State = Retired
				goto again
			}

			return start()
		case StayStarted:
			fallthrough
		case Restart:
			return start()
		default:
			panic(fmt.Sprintln("Invalid input", input))
		}
	case Started:
		switch input {
		case Retire:
			e.State = Retiring
			goto again
		case Exited:
			e.State = Stopped
			goto again
		case Restart:
			if err = e.DynoDriver.Stop(e); err != nil {
				return err
			}
			goto again
		default:
			panic(fmt.Sprintln("Invalid input", input))
		}
	default:
		panic(fmt.Sprintln("Invalid state", e.State))
	}
}

func (e *Executor) Name() string {
	return e.ProcessType + "." + e.ProcessID
}
<commit_msg>avoid hiding details about the error<commit_after>//go:generate stringer -type=DynoState,DynoInput
package hsup

import (
	"errors"
	"log"
	"os/exec"

	"fmt"

	"github.com/fsouza/go-dockerclient"
)

type DynoState int

const (
	Stopped DynoState = iota
	Started
	Retiring
	Retired
)

type DynoInput int

const (
	Retire DynoInput = iota
	Restart
	Exited
	StayStarted
)

var ErrExecutorComplete = errors.New("Executor complete")

type Executor struct {
	Args        []string
	DynoDriver  DynoDriver
	Release     *Release
	ProcessID   string
	ProcessType string
	Status      chan *ExitStatus
	Complete    chan struct{}

	// simple, abspath, and libcontainer dyno driver properties
	cmd     *exec.Cmd
	waiting chan struct{}

	// docker dyno driver properties
	container *docker.Container

	// libcontainer dyno driver properties
	lcStatus      chan *ExitStatus
	waitStartup   chan struct{}
	waitWait      chan struct{}
	containerUUID string

	// FSM Fields
	OneShot  bool
	State    DynoState
	NewInput chan DynoInput
}

func (e *Executor) Trigger(input DynoInput) {
	log.Println("triggering", input)
	select {
	case e.NewInput <- input:
	case <-e.Complete:
	}
}

func (e *Executor) wait() {
	if s := e.DynoDriver.Wait(e); e.Status != nil {
		log.Println("Executor exits:", e.Name(), "exit code:", s.Code)
		e.Status <- s
	}
	e.Trigger(Exited)
}

func (e *Executor) Tick() (err error) {
	log.Println(e.Name(), "waiting for tick...", e.State)
	input := <-e.NewInput
	log.Println(e.Name(), "ticking with input", input)

	start := func() error {
		log.Printf("%v: starting\n", e.Name())
		if err = e.DynoDriver.Start(e); err != nil {
			log.Printf("%v: start fails: %q", e.Name(), err.Error())
			if e.OneShot {
				go e.Trigger(Retire)
			} else {
				go e.Trigger(Restart)
			}
			return err
		}

		log.Printf("%v: started\n", e.Name())
		e.State = Started
		go e.wait()
		return nil
	}

again:
	switch e.State {
	case Retired:
		close(e.Complete)
		return ErrExecutorComplete
	case Retiring:
		if err = e.DynoDriver.Stop(e); err != nil {
			return err
		}

		e.State = Retired
		goto again
	case Stopped:
		switch input {
		case Retire:
			e.State = Retired
			goto again
		case Exited:
			if e.OneShot {
				e.State = Retired
				goto again
			}

			return start()
		case StayStarted:
			fallthrough
		case Restart:
			return start()
		default:
			panic(fmt.Sprintln("Invalid input", input))
		}
	case Started:
		switch input {
		case Retire:
			e.State = Retiring
			goto again
		case Exited:
			e.State = Stopped
			goto again
		case Restart:
			if err = e.DynoDriver.Stop(e); err != nil {
				return err
			}
			goto again
		default:
			panic(fmt.Sprintln("Invalid input", input))
		}
	default:
		panic(fmt.Sprintln("Invalid state", e.State))
	}
}

func (e *Executor) Name() string {
	return e.ProcessType + "." + e.ProcessID
}
<|endoftext|>


<commit_before>// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package stun

import (
	"context"
	"net"
	"sync/atomic"
	"time"

	"github.com/AudriusButkevicius/pfilter"
	"github.com/ccding/go-stun/stun"

	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/util"
)

const stunRetryInterval = 5 * time.Minute

type Host = stun.Host
type NATType = stun.NATType

// NAT types.

const (
	NATError                = stun.NATError
	NATUnknown              = stun.NATUnknown
	NATNone                 = stun.NATNone
	NATBlocked              = stun.NATBlocked
	NATFull                 = stun.NATFull
	NATSymmetric            = stun.NATSymmetric
	NATRestricted           = stun.NATRestricted
	NATPortRestricted       = stun.NATPortRestricted
	NATSymmetricUDPFirewall = stun.NATSymmetricUDPFirewall
)

type writeTrackingPacketConn struct {
	lastWrite int64 // atomic, must remain 64-bit aligned
	net.PacketConn
}

func (c *writeTrackingPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
	atomic.StoreInt64(&c.lastWrite, time.Now().Unix())
	return c.PacketConn.WriteTo(p, addr)
}

func (c *writeTrackingPacketConn) getLastWrite() time.Time {
	unix := atomic.LoadInt64(&c.lastWrite)
	return time.Unix(unix, 0)
}

type Subscriber interface {
	OnNATTypeChanged(natType NATType)
	OnExternalAddressChanged(address *Host, via string)
}

type Service struct {
	name       string
	cfg        config.Wrapper
	subscriber Subscriber
	stunConn   net.PacketConn
	client     *stun.Client

	writeTrackingPacketConn *writeTrackingPacketConn

	natType NATType
	addr    *Host
}

func New(cfg config.Wrapper, subscriber Subscriber, conn net.PacketConn) (*Service, net.PacketConn) {
	// Wrap the original connection to track writes on it
	writeTrackingPacketConn := &writeTrackingPacketConn{lastWrite: 0, PacketConn: conn}

	// Wrap it in a filter and split it up, so that stun packets arrive on stun conn, others arrive on the data conn
	filterConn := pfilter.NewPacketFilter(writeTrackingPacketConn)
	otherDataConn := filterConn.NewConn(otherDataPriority, nil)
	stunConn := filterConn.NewConn(stunFilterPriority, &stunFilter{
		ids: make(map[string]time.Time),
	})

	filterConn.Start()

	// Construct the client to use the stun conn
	client := stun.NewClientWithConnection(stunConn)
	client.SetSoftwareName("") // Explicitly unset this, seems to freak some servers out.

	// Return the service and the other conn to the client
	s := &Service{
		name: "Stun@" + conn.LocalAddr().Network() + "://" + conn.LocalAddr().String(),

		cfg:        cfg,
		subscriber: subscriber,
		stunConn:   stunConn,
		client:     client,

		writeTrackingPacketConn: writeTrackingPacketConn,

		natType: NATUnknown,
		addr:    nil,
	}
	return s, otherDataConn
}

func (s *Service) Serve(ctx context.Context) error {
	defer func() {
		s.setNATType(NATUnknown)
		s.setExternalAddress(nil, "")
	}()

	util.OnDone(ctx, func() { _ = s.stunConn.Close() })

	timer := time.NewTimer(time.Millisecond)

	for {
	disabled:
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-timer.C:
		}

		if s.cfg.Options().IsStunDisabled() {
			timer.Reset(time.Second)
			continue
		}

		l.Debugf("Starting stun for %s", s)

		for _, addr := range s.cfg.Options().StunServers() {
			// This blocks until we hit an exit condition or there are issues with the STUN server.
			// This returns a boolean signifying if a different STUN server should be tried (oppose to the whole thing
			// shutting down and this winding itself down.
			s.runStunForServer(ctx, addr)

			// Have we been asked to stop?
			select {
			case <-ctx.Done():
				return ctx.Err()
			default:
			}

			// Are we disabled?
			if s.cfg.Options().IsStunDisabled() {
				l.Infoln("STUN disabled")
				s.setNATType(NATUnknown)
				s.setExternalAddress(nil, "")
				goto disabled
			}

			// Unpunchable NAT? Chillout for some time.
			if !s.isCurrentNATTypePunchable() {
				break
			}
		}

		// We failed to contact all provided stun servers or the nat is not punchable.
		// Chillout for a while.
		timer.Reset(stunRetryInterval)
	}
}

func (s *Service) runStunForServer(ctx context.Context, addr string) {
	l.Debugf("Running stun for %s via %s", s, addr)

	// Resolve the address, so that in case the server advertises two
	// IPs, we always hit the same one, as otherwise, the mapping might
	// expire as we hit the other address, and cause us to flip flop
	// between servers/external addresses, as a result flooding discovery
	// servers.
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		l.Debugf("%s stun addr resolution on %s: %s", s, addr, err)
		return
	}
	s.client.SetServerAddr(udpAddr.String())

	var natType stun.NATType
	var extAddr *stun.Host
	err = util.CallWithContext(ctx, func() error {
		natType, extAddr, err = s.client.Discover()
		return err
	})
	if err != nil || extAddr == nil {
		l.Debugf("%s stun discovery on %s: %s", s, addr, err)
		return
	}

	// The stun server is most likely borked, try another one.
	if natType == NATError || natType == NATUnknown || natType == NATBlocked {
		l.Debugf("%s stun discovery on %s resolved to %s", s, addr, natType)
		return
	}

	s.setNATType(natType)
	s.setExternalAddress(extAddr, addr)
	l.Debugf("%s detected NAT type: %s via %s", s, natType, addr)

	// We can't punch through this one, so no point doing keepalives
	// and such, just let the caller check the nat type and work it out themselves.
	if !s.isCurrentNATTypePunchable() {
		l.Debugf("%s cannot punch %s, skipping", s, natType)
		return
	}

	s.stunKeepAlive(ctx, addr, extAddr)
}

func (s *Service) stunKeepAlive(ctx context.Context, addr string, extAddr *Host) {
	var err error
	nextSleep := time.Duration(s.cfg.Options().StunKeepaliveStartS) * time.Second

	l.Debugf("%s starting stun keepalive via %s, next sleep %s", s, addr, nextSleep)

	for {
		if areDifferent(s.addr, extAddr) {
			// If the port has changed (addresses are not equal but the hosts are equal),
			// we're probably spending too much time between keepalives, reduce the sleep.
			if s.addr != nil && extAddr != nil && s.addr.IP() == extAddr.IP() {
				nextSleep /= 2
				l.Debugf("%s stun port change (%s to %s), next sleep %s", s, s.addr.TransportAddr(), extAddr.TransportAddr(), nextSleep)
			}

			s.setExternalAddress(extAddr, addr)

			// The stun server is probably stuffed, we've gone beyond min timeout, yet the address keeps changing.
			minSleep := time.Duration(s.cfg.Options().StunKeepaliveMinS) * time.Second
			if nextSleep < minSleep {
				l.Debugf("%s keepalive aborting, sleep below min: %s < %s", s, nextSleep, minSleep)
				return
			}
		}

		// Adjust the keepalives to fire only nextSleep after last write.
		lastWrite := s.writeTrackingPacketConn.getLastWrite()
		minSleep := time.Duration(s.cfg.Options().StunKeepaliveMinS) * time.Second
		if nextSleep < minSleep {
			nextSleep = minSleep
		}
	tryLater:
		sleepFor := nextSleep

		timeUntilNextKeepalive := time.Until(lastWrite.Add(sleepFor))
		if timeUntilNextKeepalive > 0 {
			sleepFor = timeUntilNextKeepalive
		}

		l.Debugf("%s stun sleeping for %s", s, sleepFor)

		select {
		case <-time.After(sleepFor):
		case <-ctx.Done():
			l.Debugf("%s stopping, aborting stun", s)
			return
		}

		if s.cfg.Options().IsStunDisabled() {
			// Disabled, give up
			l.Debugf("%s disabled, aborting stun ", s)
			return
		}

		// Check if any writes happened while we were sleeping, if they did, sleep again
		lastWrite = s.writeTrackingPacketConn.getLastWrite()
		if gap := time.Since(lastWrite); gap < nextSleep {
			l.Debugf("%s stun last write gap less than next sleep: %s < %s. Will try later", s, gap, nextSleep)
			goto tryLater
		}

		l.Debugf("%s stun keepalive", s)

		extAddr, err = s.client.Keepalive()
		if err != nil {
			l.Debugf("%s stun keepalive on %s: %s (%v)", s, addr, err, extAddr)
			return
		}
	}
}

func (s *Service) setNATType(natType NATType) {
	if natType != s.natType {
		l.Debugf("Notifying %s of NAT type change: %s", s.subscriber, natType)
		s.subscriber.OnNATTypeChanged(natType)
	}
	s.natType = natType
}

func (s *Service) setExternalAddress(addr *Host, via string) {
	if areDifferent(s.addr, addr) {
		l.Debugf("Notifying %s of address change: %s via %s", s.subscriber, addr, via)
		s.subscriber.OnExternalAddressChanged(addr, via)
	}
	s.addr = addr
}

func (s *Service) String() string {
	return s.name
}

func (s *Service) isCurrentNATTypePunchable() bool {
	return s.natType == NATNone || s.natType == NATPortRestricted || s.natType == NATRestricted || s.natType == NATFull || s.natType == NATSymmetricUDPFirewall
}

func areDifferent(first, second *Host) bool {
	if (first == nil) != (second == nil) {
		return true
	}
	if first != nil {
		return first.TransportAddr() != second.TransportAddr()
	}
	return false
}
<commit_msg>lib/stun: Don't notify about address changes if it's not useful (fixes #7144) (#7159)<commit_after>// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package stun

import (
	"context"
	"net"
	"sync/atomic"
	"time"

	"github.com/AudriusButkevicius/pfilter"
	"github.com/ccding/go-stun/stun"

	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/util"
)

const stunRetryInterval = 5 * time.Minute

type Host = stun.Host
type NATType = stun.NATType

// NAT types.

const (
	NATError                = stun.NATError
	NATUnknown              = stun.NATUnknown
	NATNone                 = stun.NATNone
	NATBlocked              = stun.NATBlocked
	NATFull                 = stun.NATFull
	NATSymmetric            = stun.NATSymmetric
	NATRestricted           = stun.NATRestricted
	NATPortRestricted       = stun.NATPortRestricted
	NATSymmetricUDPFirewall = stun.NATSymmetricUDPFirewall
)

type writeTrackingPacketConn struct {
	lastWrite int64 // atomic, must remain 64-bit aligned
	net.PacketConn
}

func (c *writeTrackingPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
	atomic.StoreInt64(&c.lastWrite, time.Now().Unix())
	return c.PacketConn.WriteTo(p, addr)
}

func (c *writeTrackingPacketConn) getLastWrite() time.Time {
	unix := atomic.LoadInt64(&c.lastWrite)
	return time.Unix(unix, 0)
}

type Subscriber interface {
	OnNATTypeChanged(natType NATType)
	OnExternalAddressChanged(address *Host, via string)
}

type Service struct {
	name       string
	cfg        config.Wrapper
	subscriber Subscriber
	stunConn   net.PacketConn
	client     *stun.Client

	writeTrackingPacketConn *writeTrackingPacketConn

	natType NATType
	addr    *Host
}

func New(cfg config.Wrapper, subscriber Subscriber, conn net.PacketConn) (*Service, net.PacketConn) {
	// Wrap the original connection to track writes on it
	writeTrackingPacketConn := &writeTrackingPacketConn{lastWrite: 0, PacketConn: conn}

	// Wrap it in a filter and split it up, so that stun packets arrive on stun conn, others arrive on the data conn
	filterConn := pfilter.NewPacketFilter(writeTrackingPacketConn)
	otherDataConn := filterConn.NewConn(otherDataPriority, nil)
	stunConn := filterConn.NewConn(stunFilterPriority, &stunFilter{
		ids: make(map[string]time.Time),
	})

	filterConn.Start()

	// Construct the client to use the stun conn
	client := stun.NewClientWithConnection(stunConn)
	client.SetSoftwareName("") // Explicitly unset this, seems to freak some servers out.

	// Return the service and the other conn to the client
	s := &Service{
		name: "Stun@" + conn.LocalAddr().Network() + "://" + conn.LocalAddr().String(),

		cfg:        cfg,
		subscriber: subscriber,
		stunConn:   stunConn,
		client:     client,

		writeTrackingPacketConn: writeTrackingPacketConn,

		natType: NATUnknown,
		addr:    nil,
	}
	return s, otherDataConn
}

func (s *Service) Serve(ctx context.Context) error {
	defer func() {
		s.setNATType(NATUnknown)
		s.setExternalAddress(nil, "")
	}()

	util.OnDone(ctx, func() { _ = s.stunConn.Close() })

	timer := time.NewTimer(time.Millisecond)

	for {
	disabled:
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-timer.C:
		}

		if s.cfg.Options().IsStunDisabled() {
			timer.Reset(time.Second)
			continue
		}

		l.Debugf("Starting stun for %s", s)

		for _, addr := range s.cfg.Options().StunServers() {
			// This blocks until we hit an exit condition or there are issues with the STUN server.
			// This returns a boolean signifying if a different STUN server should be tried (oppose to the whole thing
			// shutting down and this winding itself down.
			s.runStunForServer(ctx, addr)

			// Have we been asked to stop?
			select {
			case <-ctx.Done():
				return ctx.Err()
			default:
			}

			// Are we disabled?
			if s.cfg.Options().IsStunDisabled() {
				l.Infoln("STUN disabled")
				s.setNATType(NATUnknown)
				s.setExternalAddress(nil, "")
				goto disabled
			}

			// Unpunchable NAT? Chillout for some time.
			if !s.isCurrentNATTypePunchable() {
				break
			}
		}

		// We failed to contact all provided stun servers or the nat is not punchable.
		// Chillout for a while.
		timer.Reset(stunRetryInterval)
	}
}

func (s *Service) runStunForServer(ctx context.Context, addr string) {
	l.Debugf("Running stun for %s via %s", s, addr)

	// Resolve the address, so that in case the server advertises two
	// IPs, we always hit the same one, as otherwise, the mapping might
	// expire as we hit the other address, and cause us to flip flop
	// between servers/external addresses, as a result flooding discovery
	// servers.
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		l.Debugf("%s stun addr resolution on %s: %s", s, addr, err)
		return
	}
	s.client.SetServerAddr(udpAddr.String())

	var natType stun.NATType
	var extAddr *stun.Host
	err = util.CallWithContext(ctx, func() error {
		natType, extAddr, err = s.client.Discover()
		return err
	})
	if err != nil || extAddr == nil {
		l.Debugf("%s stun discovery on %s: %s", s, addr, err)
		return
	}

	// The stun server is most likely borked, try another one.
	if natType == NATError || natType == NATUnknown || natType == NATBlocked {
		l.Debugf("%s stun discovery on %s resolved to %s", s, addr, natType)
		return
	}

	s.setNATType(natType)
	l.Debugf("%s detected NAT type: %s via %s", s, natType, addr)

	// We can't punch through this one, so no point doing keepalives
	// and such, just let the caller check the nat type and work it out themselves.
	if !s.isCurrentNATTypePunchable() {
		l.Debugf("%s cannot punch %s, skipping", s, natType)
		return
	}

	s.setExternalAddress(extAddr, addr)

	s.stunKeepAlive(ctx, addr, extAddr)
}

func (s *Service) stunKeepAlive(ctx context.Context, addr string, extAddr *Host) {
	var err error
	nextSleep := time.Duration(s.cfg.Options().StunKeepaliveStartS) * time.Second

	l.Debugf("%s starting stun keepalive via %s, next sleep %s", s, addr, nextSleep)

	for {
		if areDifferent(s.addr, extAddr) {
			// If the port has changed (addresses are not equal but the hosts are equal),
			// we're probably spending too much time between keepalives, reduce the sleep.
			if s.addr != nil && extAddr != nil && s.addr.IP() == extAddr.IP() {
				nextSleep /= 2
				l.Debugf("%s stun port change (%s to %s), next sleep %s", s, s.addr.TransportAddr(), extAddr.TransportAddr(), nextSleep)
			}

			s.setExternalAddress(extAddr, addr)

			// The stun server is probably stuffed, we've gone beyond min timeout, yet the address keeps changing.
			minSleep := time.Duration(s.cfg.Options().StunKeepaliveMinS) * time.Second
			if nextSleep < minSleep {
				l.Debugf("%s keepalive aborting, sleep below min: %s < %s", s, nextSleep, minSleep)
				return
			}
		}

		// Adjust the keepalives to fire only nextSleep after last write.
		lastWrite := s.writeTrackingPacketConn.getLastWrite()
		minSleep := time.Duration(s.cfg.Options().StunKeepaliveMinS) * time.Second
		if nextSleep < minSleep {
			nextSleep = minSleep
		}
	tryLater:
		sleepFor := nextSleep

		timeUntilNextKeepalive := time.Until(lastWrite.Add(sleepFor))
		if timeUntilNextKeepalive > 0 {
			sleepFor = timeUntilNextKeepalive
		}

		l.Debugf("%s stun sleeping for %s", s, sleepFor)

		select {
		case <-time.After(sleepFor):
		case <-ctx.Done():
			l.Debugf("%s stopping, aborting stun", s)
			return
		}

		if s.cfg.Options().IsStunDisabled() {
			// Disabled, give up
			l.Debugf("%s disabled, aborting stun ", s)
			return
		}

		// Check if any writes happened while we were sleeping, if they did, sleep again
		lastWrite = s.writeTrackingPacketConn.getLastWrite()
		if gap := time.Since(lastWrite); gap < nextSleep {
			l.Debugf("%s stun last write gap less than next sleep: %s < %s. Will try later", s, gap, nextSleep)
			goto tryLater
		}

		l.Debugf("%s stun keepalive", s)

		extAddr, err = s.client.Keepalive()
		if err != nil {
			l.Debugf("%s stun keepalive on %s: %s (%v)", s, addr, err, extAddr)
			return
		}
	}
}

func (s *Service) setNATType(natType NATType) {
	if natType != s.natType {
		l.Debugf("Notifying %s of NAT type change: %s", s.subscriber, natType)
		s.subscriber.OnNATTypeChanged(natType)
	}
	s.natType = natType
}

func (s *Service) setExternalAddress(addr *Host, via string) {
	if areDifferent(s.addr, addr) {
		l.Debugf("Notifying %s of address change: %s via %s", s.subscriber, addr, via)
		s.subscriber.OnExternalAddressChanged(addr, via)
	}
	s.addr = addr
}

func (s *Service) String() string {
	return s.name
}

func (s *Service) isCurrentNATTypePunchable() bool {
	return s.natType == NATNone || s.natType == NATPortRestricted || s.natType == NATRestricted || s.natType == NATFull || s.natType == NATSymmetricUDPFirewall
}

func areDifferent(first, second *Host) bool {
	if (first == nil) != (second == nil) {
		return true
	}
	if first != nil {
		return first.TransportAddr() != second.TransportAddr()
	}
	return false
}
<|endoftext|>


<commit_before>// Copyright 2014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file contains the list access primitive functions.

package golisp

import (
	"errors"
)

func RegisterListFunctionsPrimitives() {
	MakePrimitiveFunction("map", 2, MapImpl)
	MakePrimitiveFunction("reduce", 3, ReduceImpl)
	MakePrimitiveFunction("memq", 2, MemqImpl)
	MakePrimitiveFunction("memp", 2, MempImpl)
}

func MapImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f, err := Eval(First(args), env)
	if err != nil {
		return
	}
	if !FunctionP(f) {
		err = errors.New("map needs a function as its first argument")
		return
	}

	col, err := Eval(Second(args), env)
	if err != nil {
		return
	}
	if !ListP(col) {
		err = errors.New("map needs a list as its second argument")
		return
	}

	var d []*Data = make([]*Data, 0, Length(col))
	var v *Data
	for c := col; NotNilP(c); c = Cdr(c) {
		v, err = ApplyWithoutEval(f, Cons(Car(c), nil), env)
		if err != nil {
			return
		}
		d = append(d, v)
	}

	return ArrayToList(d), nil
}

func ReduceImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f, err := Eval(First(args), env)
	if err != nil {
		return
	}
	if !FunctionP(f) {
		err = errors.New("reduce needs a function as its first argument")
		return
	}

	initial, err := Eval(Second(args), env)
	if err != nil {
		return
	}

	col, err := Eval(Third(args), env)
	if err != nil {
		return
	}
	if !ListP(col) {
		err = errors.New("map needs a list as its third argument")
		return
	}

	if Length(col) == 0 {
		return initial, nil
	}

	if Length(col) == 1 {
		return Car(col), nil
	}

	result = Car(col)
	for c := Cdr(col); NotNilP(c); c = Cdr(c) {
		result, err = ApplyWithoutEval(f, InternalMakeList(result, Car(c)), env)
		if err != nil {
			return
		}
	}

	return
}

func MemqImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	key, err := Eval(First(args), env)
	if err != nil {
		return
	}

	l, err := Eval(Second(args), env)
	if err != nil {
		return
	}

	for c := l; NotNilP(c); c = Cdr(c) {
		if IsEqual(key, Car(c)) {
			return c, nil
		}
	}

	return False, nil
}

func MempImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f, err := Eval(First(args), env)
	if err != nil {
		return
	}
	if !FunctionP(f) {
		err = errors.New("memp needs a function as its first argument")
		return
	}

	l, err := Eval(Second(args), env)
	if err != nil {
		return
	}

	var found *Data
	for c := l; NotNilP(c); c = Cdr(c) {
		found, err = ApplyWithoutEval(f, InternalMakeList(Car(c)), env)
		if BooleanValue(found) {
			return c, nil
		}
	}

	return False, nil
}
<commit_msg>Improve error messages.<commit_after>// Copyright 2014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file contains the list access primitive functions.

package golisp

import (
	"errors"
	"fmt"
)

func RegisterListFunctionsPrimitives() {
	MakePrimitiveFunction("map", 2, MapImpl)
	MakePrimitiveFunction("reduce", 3, ReduceImpl)
	MakePrimitiveFunction("memq", 2, MemqImpl)
	MakePrimitiveFunction("memp", 2, MempImpl)
}

func MapImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f, err := Eval(First(args), env)
	if err != nil {
		return
	}
	if !FunctionP(f) {
		err = errors.New(fmt.Sprintf("map needs a function as its first argument, but got %s.", String(f)))
		return
	}

	col, err := Eval(Second(args), env)
	if err != nil {
		return
	}
	if !ListP(col) {
		err = errors.New(fmt.Sprintf("map needs a list as its second argument, but got %s.", String(col)))
		return
	}

	var d []*Data = make([]*Data, 0, Length(col))
	var v *Data
	for c := col; NotNilP(c); c = Cdr(c) {
		v, err = ApplyWithoutEval(f, Cons(Car(c), nil), env)
		if err != nil {
			return
		}
		d = append(d, v)
	}

	return ArrayToList(d), nil
}

func ReduceImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f, err := Eval(First(args), env)
	if err != nil {
		return
	}
	if !FunctionP(f) {
		err = errors.New("reduce needs a function as its first argument")
		return
	}

	initial, err := Eval(Second(args), env)
	if err != nil {
		return
	}

	col, err := Eval(Third(args), env)
	if err != nil {
		return
	}
	if !ListP(col) {
		err = errors.New("map needs a list as its third argument")
		return
	}

	if Length(col) == 0 {
		return initial, nil
	}

	if Length(col) == 1 {
		return Car(col), nil
	}

	result = Car(col)
	for c := Cdr(col); NotNilP(c); c = Cdr(c) {
		result, err = ApplyWithoutEval(f, InternalMakeList(result, Car(c)), env)
		if err != nil {
			return
		}
	}

	return
}

func MemqImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	key, err := Eval(First(args), env)
	if err != nil {
		return
	}

	l, err := Eval(Second(args), env)
	if err != nil {
		return
	}

	for c := l; NotNilP(c); c = Cdr(c) {
		if IsEqual(key, Car(c)) {
			return c, nil
		}
	}

	return False, nil
}

func MempImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f, err := Eval(First(args), env)
	if err != nil {
		return
	}
	if !FunctionP(f) {
		err = errors.New("memp needs a function as its first argument")
		return
	}

	l, err := Eval(Second(args), env)
	if err != nil {
		return
	}

	var found *Data
	for c := l; NotNilP(c); c = Cdr(c) {
		found, err = ApplyWithoutEval(f, InternalMakeList(Car(c)), env)
		if BooleanValue(found) {
			return c, nil
		}
	}

	return False, nil
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// Version of IronFunctions
var Version = "0.1.10"

func handleVersion(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"version": Version})
}
<commit_msg>functions: 0.1.11 release [skip ci]<commit_after>package server

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// Version of IronFunctions
var Version = "0.1.11"

func handleVersion(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"version": Version})
}
<|endoftext|>


<commit_before><commit_msg>chaincfg: Remove Bitcoin block chain checkpoints and move port to avoid conflict with Bitcoin<commit_after><|endoftext|>


<commit_before><commit_msg>Bump version 0.1.4<commit_after><|endoftext|>


<commit_before>package events

import (
	"fmt"
	"time"
	"strconv"
	"github.com/acmacalister/skittles"
	"github.com/synw/microb/libmicrob/datatypes"
	"github.com/synw/microb/libmicrob/conf"
)

var Config = conf.GetConf()
var Verbosity = Config["verbosity"].(int)

var info_m = "["+skittles.Green("info")+"]"
var event_m = "["+skittles.Yellow("event")+"]"
var command_m = "[=> "+skittles.Cyan("command")+"]"
var error_m = "["+skittles.BoldRed("error")+"]"
var metric_m = "["+skittles.Cyan("metric")+"]"
var ok_m = "["+skittles.Green("ok")+"]"
var report_m = "[-> "+skittles.Cyan("report")+"]"

func getTime() string {
	t := time.Now()
	return t.Format("15:04:05")
}

func printMsg(event_class string, event *datatypes.Event) {
	t := getTime()
	out := t+" "
	msg := event.Message
	if event_class == "simple" {
		out = out+msg
	} else {
		if (event_class == "info") {
			out = out+info_m+" "+msg
		} else if (event_class == "event") {
			out = out+event_m+" "+msg
		} else if (event_class == "command") {
			out = out+command_m+" "+msg
		} else if (event_class == "error") {
			out = out+error_m+" "+"from "+event.From+": "+msg
		} else if (event_class == "metric") {
			out = out+metric_m+" "+msg
		} else if (event_class == "ok") {
			out = out+ok_m+" "+msg
		} else if (event_class == "report") {
			out = out+report_m+" "+msg
		} else if (event_class == "request" || event_class == "request_error") {
			sc := event.Data["status_code"].(int)
			var sc_str string
			if sc == 404 {
				sc_str = skittles.Red(strconv.Itoa(sc))
			} else if sc == 200 {
				sc_str = skittles.Green(strconv.Itoa(sc))
			} else if sc == 500 {
				sc_str = skittles.BoldRed(strconv.Itoa(sc))
			}
			out = out+" "+sc_str+" "+msg
		}
	}
	fmt.Println(out)
}

func Handle(event *datatypes.Event) {
	event_class := event.Class
	if event.Class == "runtime_info" {
		event_class = "simple"
	}
	if event.Class == "request" {
		event_class = "request"
	}
	if Verbosity > 0 {
		printMsg(event_class, event)
	}
}

func Print(event_class string, from string, message string) {
	var d map[string]interface{}
	event := &datatypes.Event{event_class, from, message, d}
	printMsg(event_class, event)
}

func New(event_class string, from string, message string) {
	var d map[string]interface{}
	event := &datatypes.Event{event_class, from, message, d}
	Handle(event)
}

func Error(from string, err error) {
	var d map[string]interface{}
	event := &datatypes.Event{"error", from, err.Error(), d}
	Handle(event)
}
<commit_msg>Refactoring events<commit_after>package events

import (
	"fmt"
	"time"
	"strconv"
	"github.com/acmacalister/skittles"
	"github.com/synw/microb/libmicrob/datatypes"
	"github.com/synw/microb/libmicrob/conf"
)

var Config = conf.GetConf()
var Verbosity = Config["verbosity"].(int)

func getEventOutputFlags() map[string]string {
	output_flags := make(map[string]string)
	output_flags["info"] = "["+skittles.Green("info")+"]"
	output_flags["event"] = "["+skittles.Yellow("event")+"]"
	output_flags["command"] = "[=> "+skittles.Cyan("command")+"]"
	output_flags["error"] = "["+skittles.BoldRed("error")+"]"
	output_flags["metric"] = "["+skittles.Cyan("metric")+"]"
	output_flags["report"] = "[-> report]"
	output_flags["request"] = ""
	output_flags["request_error"] = ""
	output_flags["runtime_info"] = ""
	return output_flags
}

func getTime() string {
	t := time.Now()
	return t.Format("15:04:05")
}

func formatStatusCode(sc int) string {
	var sc_str string
	if sc == 404 {
		sc_str = skittles.Red(strconv.Itoa(sc))
	} else if sc == 200 {
		sc_str = skittles.Green(strconv.Itoa(sc))
	} else if sc == 500 {
		sc_str = skittles.BoldRed(strconv.Itoa(sc))
	}
	return sc_str
}

func printMsg(event_class string, event *datatypes.Event) {
	t := getTime()
	out := t+" "
	msg := event.Message
	output_flags := getEventOutputFlags()
	out = out+output_flags[event_class]
	if (event_class == "request" || event_class == "request_error") {
		sc := event.Data["status_code"].(int)
		out = out+formatStatusCode(sc)
	}
	out = out+" "+msg
	fmt.Println(out)
}

func Handle(event *datatypes.Event) {
	if Verbosity > 0 {
		printMsg(event.Class, event)
	}
}

func Print(event_class string, from string, message string) {
	var d map[string]interface{}
	event := &datatypes.Event{event_class, from, message, d}
	printMsg(event_class, event)
}

func New(event_class string, from string, message string) {
	var d map[string]interface{}
	event := &datatypes.Event{event_class, from, message, d}
	Handle(event)
}

func Error(from string, err error) {
	var d map[string]interface{}
	event := &datatypes.Event{"error", from, err.Error(), d}
	Handle(event)
}
<|endoftext|>


<commit_before><commit_msg>Use worker thread to fix #191<commit_after><|endoftext|>


<commit_before>package blockproposer

import (
	"encoding/hex"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/bytom/bytom/account"
	"github.com/bytom/bytom/config"
	"github.com/bytom/bytom/consensus"
	"github.com/bytom/bytom/event"
	"github.com/bytom/bytom/proposal"
	"github.com/bytom/bytom/protocol"
)

const (
	logModule         = "blockproposer"
	warnTimeNum       = 1
	warnTimeDenom     = 5
	criticalTimeNum   = 2
	criticalTimeDenom = 5
)

// BlockProposer propose several block in specified time range
type BlockProposer struct {
	sync.Mutex
	chain           *protocol.Chain
	accountManager  *account.Manager
	started         bool
	quit            chan struct{}
	eventDispatcher *event.Dispatcher
}

// generateBlocks is a worker that is controlled by the proposeWorkerController.
// It is self contained in that it creates block templates and attempts to solve
// them while detecting when it is performing stale work and reacting
// accordingly by generating a new block template.  When a block is verified, it
// is submitted.
//
// It must be run as a goroutine.
func (b *BlockProposer) generateBlocks() {
	xpub := config.CommonConfig.PrivateKey().XPub()
	xpubStr := hex.EncodeToString(xpub[:])
	ticker := time.NewTicker(time.Duration(consensus.ActiveNetParams.BlockTimeInterval) * time.Millisecond / 4)
	defer ticker.Stop()

	for {
		select {
		case <-b.quit:
			return
		case <-ticker.C:
		}

		bestBlockHeader := b.chain.BestBlockHeader()
		bestBlockHash := bestBlockHeader.Hash()

		now := uint64(time.Now().UnixNano() / 1e6)
		base := bestBlockHeader.Timestamp
		if now > bestBlockHeader.Timestamp+consensus.ActiveNetParams.BlockTimeInterval+consensus.ActiveNetParams.BlockTimeInterval/10 {
			base = now - consensus.ActiveNetParams.BlockTimeInterval
		}
		minTimeToNextBlock := consensus.ActiveNetParams.BlockTimeInterval - base%consensus.ActiveNetParams.BlockTimeInterval
		nextBlockTime := base + minTimeToNextBlock
		if (nextBlockTime - now) < consensus.ActiveNetParams.BlockTimeInterval/10 {
			nextBlockTime += consensus.ActiveNetParams.BlockTimeInterval
		}

		if nextBlockTime > now {
			continue
		}

		validator, err := b.chain.GetValidator(&bestBlockHash, nextBlockTime)
		if err != nil {
			log.WithFields(log.Fields{"module": logModule, "error": err, "pubKey": xpubStr}).Error("fail on check is next blocker")
			continue
		}

		if xpubStr != validator.PubKey {
			continue
		}

		warnDuration := time.Duration(consensus.ActiveNetParams.BlockTimeInterval*warnTimeNum/warnTimeDenom) * time.Millisecond
		criticalDuration := time.Duration(consensus.ActiveNetParams.BlockTimeInterval*criticalTimeNum/criticalTimeDenom) * time.Millisecond
		block, err := proposal.NewBlockTemplate(b.chain, validator, b.accountManager, nextBlockTime, warnDuration, criticalDuration)
		if err != nil {
			log.WithFields(log.Fields{"module": logModule, "error": err}).Error("failed on create NewBlockTemplate")
			continue
		}

		isOrphan, err := b.chain.ProcessBlock(block)
		if err != nil {
			log.WithFields(log.Fields{"module": logModule, "height": block.BlockHeader.Height, "error": err}).Error("proposer fail on ProcessBlock")
			continue
		}

		log.WithFields(log.Fields{"module": logModule, "height": block.BlockHeader.Height, "isOrphan": isOrphan, "tx": len(block.Transactions)}).Info("proposer processed block")
		// Broadcast the block and announce chain insertion event
		if err = b.eventDispatcher.Post(event.NewProposedBlockEvent{Block: *block}); err != nil {
			log.WithFields(log.Fields{"module": logModule, "height": block.BlockHeader.Height, "error": err}).Error("proposer fail on post block")
		}
	}
}

// Start begins the block propose process as well as the speed monitor used to
// track hashing metrics.  Calling this function when the block proposer has
// already been started will have no effect.
//
// This function is safe for concurrent access.
func (b *BlockProposer) Start() {
	b.Lock()
	defer b.Unlock()

	// Nothing to do if the miner is already running
	if b.started {
		return
	}

	b.quit = make(chan struct{})
	go b.generateBlocks()

	b.started = true
	log.Infof("block proposer started")
}

// Stop gracefully stops the proposal process by signalling all workers, and the
// speed monitor to quit.  Calling this function when the block proposer has not
// already been started will have no effect.
//
// This function is safe for concurrent access.
func (b *BlockProposer) Stop() {
	b.Lock()
	defer b.Unlock()

	// Nothing to do if the miner is not currently running
	if !b.started {
		return
	}

	close(b.quit)
	b.started = false
	log.Info("block proposer stopped")
}

// IsProposing returns whether the block proposer has been started.
//
// This function is safe for concurrent access.
func (b *BlockProposer) IsProposing() bool {
	b.Lock()
	defer b.Unlock()

	return b.started
}

// NewBlockProposer returns a new instance of a block proposer for the provided configuration.
// Use Start to begin the proposal process.  See the documentation for BlockProposer
// type for more details.
func NewBlockProposer(c *protocol.Chain, accountManager *account.Manager, dispatcher *event.Dispatcher) *BlockProposer {
	return &BlockProposer{
		chain:           c,
		accountManager:  accountManager,
		eventDispatcher: dispatcher,
	}
}
<commit_msg>fix bug (#2070)<commit_after>package blockproposer

import (
	"encoding/hex"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/bytom/bytom/account"
	"github.com/bytom/bytom/config"
	"github.com/bytom/bytom/consensus"
	"github.com/bytom/bytom/event"
	"github.com/bytom/bytom/proposal"
	"github.com/bytom/bytom/protocol"
)

const (
	logModule         = "blockproposer"
	warnTimeNum       = 1
	warnTimeDenom     = 5
	criticalTimeNum   = 2
	criticalTimeDenom = 5
)

// BlockProposer propose several block in specified time range
type BlockProposer struct {
	sync.Mutex
	chain           *protocol.Chain
	accountManager  *account.Manager
	started         bool
	quit            chan struct{}
	eventDispatcher *event.Dispatcher
}

// generateBlocks is a worker that is controlled by the proposeWorkerController.
// It is self contained in that it creates block templates and attempts to solve
// them while detecting when it is performing stale work and reacting
// accordingly by generating a new block template.  When a block is verified, it
// is submitted.
//
// It must be run as a goroutine.
func (b *BlockProposer) generateBlocks() {
	xpub := config.CommonConfig.PrivateKey().XPub()
	xpubStr := hex.EncodeToString(xpub[:])
	ticker := time.NewTicker(time.Duration(consensus.ActiveNetParams.BlockTimeInterval) * time.Millisecond / 4)
	defer ticker.Stop()

	for {
		select {
		case <-b.quit:
			return
		case <-ticker.C:
		}

		bestBlockHeader := b.chain.BestBlockHeader()
		bestBlockHash := bestBlockHeader.Hash()

		now := uint64(time.Now().UnixNano() / 1e6)
		base := bestBlockHeader.Timestamp
		if now > bestBlockHeader.Timestamp+consensus.ActiveNetParams.BlockTimeInterval {
			base = now - consensus.ActiveNetParams.BlockTimeInterval
		}
		minTimeToNextBlock := consensus.ActiveNetParams.BlockTimeInterval - base%consensus.ActiveNetParams.BlockTimeInterval
		nextBlockTime := base + minTimeToNextBlock
		if (nextBlockTime - now) < consensus.ActiveNetParams.BlockTimeInterval/10 {
			nextBlockTime += consensus.ActiveNetParams.BlockTimeInterval
		}

		if nextBlockTime > now {
			continue
		}

		validator, err := b.chain.GetValidator(&bestBlockHash, nextBlockTime)
		if err != nil {
			log.WithFields(log.Fields{"module": logModule, "error": err, "pubKey": xpubStr}).Error("fail on check is next blocker")
			continue
		}

		if xpubStr != validator.PubKey {
			continue
		}

		warnDuration := time.Duration(consensus.ActiveNetParams.BlockTimeInterval*warnTimeNum/warnTimeDenom) * time.Millisecond
		criticalDuration := time.Duration(consensus.ActiveNetParams.BlockTimeInterval*criticalTimeNum/criticalTimeDenom) * time.Millisecond
		block, err := proposal.NewBlockTemplate(b.chain, validator, b.accountManager, nextBlockTime, warnDuration, criticalDuration)
		if err != nil {
			log.WithFields(log.Fields{"module": logModule, "error": err}).Error("failed on create NewBlockTemplate")
			continue
		}

		isOrphan, err := b.chain.ProcessBlock(block)
		if err != nil {
			log.WithFields(log.Fields{"module": logModule, "height": block.BlockHeader.Height, "error": err}).Error("proposer fail on ProcessBlock")
			continue
		}

		log.WithFields(log.Fields{"module": logModule, "height": block.BlockHeader.Height, "isOrphan": isOrphan, "tx": len(block.Transactions)}).Info("proposer processed block")
		// Broadcast the block and announce chain insertion event
		if err = b.eventDispatcher.Post(event.NewProposedBlockEvent{Block: *block}); err != nil {
			log.WithFields(log.Fields{"module": logModule, "height": block.BlockHeader.Height, "error": err}).Error("proposer fail on post block")
		}
	}
}

// Start begins the block propose process as well as the speed monitor used to
// track hashing metrics.  Calling this function when the block proposer has
// already been started will have no effect.
//
// This function is safe for concurrent access.
func (b *BlockProposer) Start() {
	b.Lock()
	defer b.Unlock()

	// Nothing to do if the miner is already running
	if b.started {
		return
	}

	b.quit = make(chan struct{})
	go b.generateBlocks()

	b.started = true
	log.Infof("block proposer started")
}

// Stop gracefully stops the proposal process by signalling all workers, and the
// speed monitor to quit.  Calling this function when the block proposer has not
// already been started will have no effect.
//
// This function is safe for concurrent access.
func (b *BlockProposer) Stop() {
	b.Lock()
	defer b.Unlock()

	// Nothing to do if the miner is not currently running
	if !b.started {
		return
	}

	close(b.quit)
	b.started = false
	log.Info("block proposer stopped")
}

// IsProposing returns whether the block proposer has been started.
//
// This function is safe for concurrent access.
func (b *BlockProposer) IsProposing() bool {
	b.Lock()
	defer b.Unlock()

	return b.started
}

// NewBlockProposer returns a new instance of a block proposer for the provided configuration.
// Use Start to begin the proposal process.  See the documentation for BlockProposer
// type for more details.
func NewBlockProposer(c *protocol.Chain, accountManager *account.Manager, dispatcher *event.Dispatcher) *BlockProposer {
	return &BlockProposer{
		chain:           c,
		accountManager:  accountManager,
		eventDispatcher: dispatcher,
	}
}
<|endoftext|>