<commit_before>// Package at provides a low level driver for AT modems.
package at

import (
	"bufio"
	"context"
	"errors"
	"fmt"
	"io"
	"strings"
	"sync"
	"time"
)

// AT represents a modem that can be managed using AT commands.
// Commands can be issued to the modem using the Command and SMSCommand methods.
// The AT closes the closed channel when the connection to the underlying
// modem is broken (Read returns EOF) .
// When closed, all outstanding commands return ErrClosed and the state of the
// underlying modem becomes unknown.
// Once closed the AT cannot be re-opened - it must be recreated.
type AT struct {
	cmdCh   chan func()
	indCh   chan func()
	closed  chan struct{}
	iLines  chan string
	cLines  chan string
	modem   io.ReadWriter
	inds    map[string]indication // only modified in nLoop
	wgmu    sync.Mutex            // covers guarded and wGuard
	guarded bool
	wGuard  <-chan time.Time
}

// New creates a new AT modem.
func New(modem io.ReadWriter) *AT {
	a := &AT{
		modem:  modem,
		cmdCh:  make(chan func()),
		indCh:  make(chan func()),
		iLines: make(chan string),
		cLines: make(chan string),
		closed: make(chan struct{}),
		inds:   make(map[string]indication),
	}
	go lineReader(a.modem, a.iLines)
	go a.nLoop(a.indCh, a.iLines, a.cLines)
	go cmdLoop(a.cmdCh, a.cLines, a.closed)
	return a
}

// Closed returns a channel which will block while the modem is not closed.
func (a *AT) Closed() <-chan struct{} {
	return a.closed
}

// Command issues the command to the modem and returns the result.
// The command should NOT include the AT prefix, or <CR><LF> suffix which is automatically added.
// The return value includes the info (the lines returned by the modem between the command and
// the status line), and an error which is non-nil if the command did not complete successfully.
func (a *AT) Command(ctx context.Context, cmd string) ([]string, error) {
	done := make(chan response)
	select {
	case <-a.closed:
		return nil, ErrClosed
	case a.cmdCh <- func() {
		done <- a.processReq(ctx, request{cmd: cmd})
	}:
		rsp := <-done
		return rsp.info, rsp.err
	}
}

// AddIndication adds a handler for a set of lines begining with the prefixed
// line and the following trailing lines.
// Each set of lines is returned via the returned channel.
// The return channel is closed when the AT closes.
func (a *AT) AddIndication(prefix string, trailingLines int) (<-chan []string, error) {
	done := make(chan chan []string)
	errs := make(chan error)
	select {
	case <-a.closed:
		return nil, ErrClosed
	case a.indCh <- func() {
		if _, ok := a.inds[prefix]; ok {
			errs <- ErrIndicationExists
			return
		}
		i := indication{prefix, trailingLines + 1, make(chan []string)}
		a.inds[prefix] = i
		done <- i.c
	}:
		select {
		case evtCh := <-done:
			return evtCh, nil
		case err := <-errs:
			return nil, err
		}
	}
}

// CancelIndication removes any indication corresponding to the prefix.
// If any such indication exists its return channel is closed and no further
// indications will be sent to it.
func (a *AT) CancelIndication(prefix string) {
	done := make(chan struct{})
	select {
	case <-a.closed:
		return
	case a.indCh <- func() {
		i, ok := a.inds[prefix]
		if ok {
			close(i.c)
			delete(a.inds, prefix)
		}
		close(done)
	}:
		<-done
	}
}

// Init initialises the modem by escaping any outstanding SMS commands
// and resetting the modem to factory defaults.
// The Init is intended to be called after creation and before any other commands
// are issued in order to get the modem into a known state.
// This is a bare minimum init.
func (a *AT) Init(ctx context.Context) error {
	// escape any outstanding SMS operations then CR to flush the command buffer
	a.modem.Write([]byte(string(27) + "\r\n\r\n"))
	// allow time for response, or at least any residual OK, to propagate and be disacarded.
	a.startWriteGuard()

	cmds := []string{
		"Z",       // reset to factory defaults (also clears the escape from the rx buffer)
		"^CURC=0", // disable general indications ^XXXX
	}
	for _, cmd := range cmds {
		_, err := a.Command(ctx, cmd)
		switch err {
		case nil:
		case context.DeadlineExceeded, context.Canceled:
			return err
		default:
			return fmt.Errorf("AT%s returned error '%v'", cmd, err)
		}
	}
	return nil
}

// SMSCommand issues an SMS command to the modem, and returns the result.
// An SMS command is issued in two steps; first the command line:
//   AT<command><CR>
// which the modem responds to with a ">" prompt, after which the SMS PDU is sent to the modem:
//   <sms><Ctrl-Z>
// The modem then completes the command as per other commands, such as those issued by Command.
// The format of the sms may be a text message or a hex coded SMS PDU, depending on the
// configuration of the modem (text or PDU mode).
func (a *AT) SMSCommand(ctx context.Context, cmd string, sms string) ([]string, error) {
	done := make(chan response)
	select {
	case <-a.closed:
		return nil, ErrClosed
	case a.cmdCh <- func() {
		done <- a.processReq(ctx, request{cmd: cmd, sms: &sms})
	}:
		rsp := <-done
		return rsp.info, rsp.err
	}
}

// cmdLoop is responsible for the interface to the modem.
// It serialises the issuing of commands and awaits the responses.
// The cmdLoop terminates when the downstream closes.
func cmdLoop(cmds chan func(), in <-chan string, out chan struct{}) {
	for {
		select {
		case cmd := <-cmds:
			cmd()
		case _, ok := <-in:
			if !ok {
				close(out)
				return
			}
		}
	}
}

func lineReader(m io.Reader, out chan string) {
	scanner := bufio.NewScanner(m)
	scanner.Split(scanLines)
	for scanner.Scan() {
		select {
		case out <- scanner.Text():
			break
		}
	}
	close(out) // tell pipeline we're done - end of pipeline will close the AT.
}

// nLoop is responsible for pulling indications from the stream of lines read from the modem,
// and forwarding them to handlers.  Non-indication lines are passed upstream.
// Indication trailing lines are assumed to arrive in a contiguous
// block immediately after the indication.
func (a *AT) nLoop(cmds chan func(), in <-chan string, out chan string) {
	defer func() {
		for k, v := range a.inds {
			close(v.c)
			delete(a.inds, k)
		}
	}()
	for {
		select {
		case cmd := <-cmds:
			cmd()
		case line, ok := <-in:
			if !ok {
				close(out)
				return
			}
			for k, v := range a.inds {
				if strings.HasPrefix(line, k) {
					n := make([]string, v.totalLines)
					n[0] = line
					for i := 1; i < v.totalLines; i++ {
						t, ok := <-in
						if !ok {
							return
						}
						n[i] = t
					}
					v.c <- n
					continue
				}
			}
			out <- line
		}
	}
}

func (a *AT) processReq(ctx context.Context, req request) response {
	a.waitWriteGuard()
	if err := a.writeCommand(req); err != nil {
		return response{err: err}
	}
	cmdID := parseCmdID(req.cmd)
	var rsp response
	for {
		select {
		case <-ctx.Done():
			if req.sms != nil {
				// cancel outstanding SMS request
				a.modem.Write([]byte(string(27) + "\r\n"))
				a.startWriteGuard()
			}
			rsp.err = ctx.Err()
			return rsp
		case line, ok := <-a.cLines:
			if !ok {
				return response{err: ErrClosed}
			}
			if line == "" {
				continue
			}
			switch parseRxLine(line, cmdID) {
			case rxlStatusOK:
				return rsp
			case rxlStatusError:
				rsp.err = newError(line)
				return rsp
			case rxlUnknown:
				if req.sms != nil && line[len(line)-1] == 26 && strings.HasPrefix(line, *req.sms) {
					// swallow echoed SMS PDU
					continue
				}
				fallthrough
			case rxlInfo:
				if rsp.info == nil {
					rsp.info = make([]string, 0)
				}
				rsp.info = append(rsp.info, line)
			case rxlSMSPrompt:
				if req.sms != nil {
					if err := a.writeSMS(*req.sms); err != nil {
						// escape SMS
						a.modem.Write([]byte(string(27) + "\r\n"))
						a.startWriteGuard()
						return response{err: err}
					}
				}
			}
		}
	}
}

func (a *AT) startWriteGuard() {
	a.wgmu.Lock()
	a.guarded = true
	a.wGuard = time.After(20 * time.Millisecond)
	a.wgmu.Unlock()
}

func (a *AT) waitWriteGuard() {
	a.wgmu.Lock()
	defer a.wgmu.Unlock()
	if a.guarded {
		for {
			select {
			case _, ok := <-a.cLines:
				if !ok {
					return
				}
			case <-a.wGuard:
				a.guarded = false
				a.wGuard = nil
				return
			}
		}
	}
}

// writeCommand writes a one line command to the modem.
func (a *AT) writeCommand(req request) error {
	cmdLine := "AT" + req.cmd + "\r\n"
	if req.sms != nil {
		cmdLine = cmdLine[:len(cmdLine)-1]
	}
	_, err := a.modem.Write([]byte(cmdLine))
	return err
}

// writeSMS writes the first line of a two line SMS command to the modem.
func (a *AT) writeSMS(sms string) error {
	_, err := a.modem.Write([]byte(sms + string(26)))
	return err
}

// CMEError indicates a CME Error was returned by the modem.
// The value is the error value, in string form, which may be the numeric or textual, depending
// on the modem configuration.
type CMEError string

// CMSError indicates a CMS Error was returned by the modem.
// The value is the error value, in string form, which may be the numeric or textual, depending
// on the modem configuration.
type CMSError string

func (e CMEError) Error() string {
	return string("CME Error: " + e)
}

func (e CMSError) Error() string {
	return string("CMS Error: " + e)
}

var (
	// ErrClosed indicates an operation cannot be performed as the modem has been closed.
	ErrClosed = errors.New("closed")
	// ErrError indicates the modem returned a generic AT ERROR in response to an operation.
	ErrError = errors.New("ERROR")
	// ErrIndicationExists indicates there is already a indication registered for
	// a prefix.
	ErrIndicationExists = errors.New("indication exists")
)

func newError(line string) error {
	var err error
	switch {
	case strings.HasPrefix(line, "ERROR"):
		err = ErrError
	case strings.HasPrefix(line, "+CMS ERROR:"):
		err = CMSError(strings.TrimSpace(line[11:]))
	case strings.HasPrefix(line, "+CME ERROR:"):
		err = CMEError(strings.TrimSpace(line[11:]))
	}
	return err
}

// request represents an operation to be performed on the modem.
type request struct {
	cmd string
	sms *string
}

// response represents the result of a request operation performed on the modem.
// info is the collection of lines returned between the command and the status line.
// err corresponds to any error returned by the modem or while interacting with the modem.
type response struct {
	info []string
	err  error
}

// Received line types.
type rxl int

const (
	rxlUnknown rxl = iota
	rxlEchoCmdLine
	rxlInfo
	rxlStatusOK
	rxlStatusError
	rxlAsync
	rxlSMSPrompt
)

// indication represents an unsolicited result code (URC) from the modem, such as a
// received SMS message.
// Indications are lines prefixed with a particular pattern,
// and may include a number of trailing lines.
// The matching lines are bundled into a slice and sent to the channel.
type indication struct {
	prefix     string
	totalLines int
	c          chan []string
}

// parseCmdID returns the identifier component of the command.
// This is the section prior to any '=' or '?' and is generally, but not
// always, used to prefix info lines corresponding to the command.
func parseCmdID(cmdLine string) string {
	switch idx := strings.IndexAny(cmdLine, "=?"); idx {
	case -1:
		return cmdLine
	default:
		return cmdLine[0:idx]
	}
}

// parseRxLine parses a received line and identifies the line type.
func parseRxLine(line string, cmdID string) rxl {
	switch {
	case line == "OK":
		return rxlStatusOK
	case strings.HasPrefix(line, "ERROR"),
		strings.HasPrefix(line, "+CME ERROR:"),
		strings.HasPrefix(line, "+CMS ERROR:"):
		return rxlStatusError
	case strings.HasPrefix(line, cmdID+":"):
		return rxlInfo
	case line == ">":
		return rxlSMSPrompt
	case strings.HasPrefix(line, "AT"+cmdID):
		return rxlEchoCmdLine
	default:
		// Don't attempt to identify SMS PDUs at this level, so they will
		// be caught here, along with other unidentified lines.
		return rxlUnknown
	}
}

// scanLines is a custom line scanner for lineReader that recognises
// the prompt returned by the modem in response to SMS commands such as +CMGS.
func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
	// handle SMS prompt special case - no CR at prompt
	if len(data) >= 1 && data[0] == '>' {
		i := 1
		// there may be trailing space, so swallow that...
		for ; i < len(data) && data[i] == ' '; i++ {
		}
		return i, data[0:1], nil
	}
	return bufio.ScanLines(data, atEOF)
}
<commit_msg>Fix spelling typos<commit_after>// Package at provides a low level driver for AT modems.
package at

import (
	"bufio"
	"context"
	"errors"
	"fmt"
	"io"
	"strings"
	"sync"
	"time"
)

// AT represents a modem that can be managed using AT commands.
// Commands can be issued to the modem using the Command and SMSCommand methods.
// The AT closes the closed channel when the connection to the underlying
// modem is broken (Read returns EOF) .
// When closed, all outstanding commands return ErrClosed and the state of the
// underlying modem becomes unknown.
// Once closed the AT cannot be re-opened - it must be recreated.
type AT struct {
	cmdCh   chan func()
	indCh   chan func()
	closed  chan struct{}
	iLines  chan string
	cLines  chan string
	modem   io.ReadWriter
	inds    map[string]indication // only modified in nLoop
	wgmu    sync.Mutex            // covers guarded and wGuard
	guarded bool
	wGuard  <-chan time.Time
}

// New creates a new AT modem.
func New(modem io.ReadWriter) *AT {
	a := &AT{
		modem:  modem,
		cmdCh:  make(chan func()),
		indCh:  make(chan func()),
		iLines: make(chan string),
		cLines: make(chan string),
		closed: make(chan struct{}),
		inds:   make(map[string]indication),
	}
	go lineReader(a.modem, a.iLines)
	go a.nLoop(a.indCh, a.iLines, a.cLines)
	go cmdLoop(a.cmdCh, a.cLines, a.closed)
	return a
}

// Closed returns a channel which will block while the modem is not closed.
func (a *AT) Closed() <-chan struct{} {
	return a.closed
}

// Command issues the command to the modem and returns the result.
// The command should NOT include the AT prefix, or <CR><LF> suffix which is automatically added.
// The return value includes the info (the lines returned by the modem between the command and
// the status line), and an error which is non-nil if the command did not complete successfully.
func (a *AT) Command(ctx context.Context, cmd string) ([]string, error) {
	done := make(chan response)
	select {
	case <-a.closed:
		return nil, ErrClosed
	case a.cmdCh <- func() {
		done <- a.processReq(ctx, request{cmd: cmd})
	}:
		rsp := <-done
		return rsp.info, rsp.err
	}
}

// AddIndication adds a handler for a set of lines beginning with the prefixed
// line and the following trailing lines.
// Each set of lines is returned via the returned channel.
// The return channel is closed when the AT closes.
func (a *AT) AddIndication(prefix string, trailingLines int) (<-chan []string, error) {
	done := make(chan chan []string)
	errs := make(chan error)
	select {
	case <-a.closed:
		return nil, ErrClosed
	case a.indCh <- func() {
		if _, ok := a.inds[prefix]; ok {
			errs <- ErrIndicationExists
			return
		}
		i := indication{prefix, trailingLines + 1, make(chan []string)}
		a.inds[prefix] = i
		done <- i.c
	}:
		select {
		case evtCh := <-done:
			return evtCh, nil
		case err := <-errs:
			return nil, err
		}
	}
}

// CancelIndication removes any indication corresponding to the prefix.
// If any such indication exists its return channel is closed and no further
// indications will be sent to it.
func (a *AT) CancelIndication(prefix string) {
	done := make(chan struct{})
	select {
	case <-a.closed:
		return
	case a.indCh <- func() {
		i, ok := a.inds[prefix]
		if ok {
			close(i.c)
			delete(a.inds, prefix)
		}
		close(done)
	}:
		<-done
	}
}

// Init initialises the modem by escaping any outstanding SMS commands
// and resetting the modem to factory defaults.
// The Init is intended to be called after creation and before any other commands
// are issued in order to get the modem into a known state.
// This is a bare minimum init.
func (a *AT) Init(ctx context.Context) error {
	// escape any outstanding SMS operations then CR to flush the command buffer
	a.modem.Write([]byte(string(27) + "\r\n\r\n"))
	// allow time for response, or at least any residual OK, to propagate and be discarded.
	a.startWriteGuard()

	cmds := []string{
		"Z",       // reset to factory defaults (also clears the escape from the rx buffer)
		"^CURC=0", // disable general indications ^XXXX
	}
	for _, cmd := range cmds {
		_, err := a.Command(ctx, cmd)
		switch err {
		case nil:
		case context.DeadlineExceeded, context.Canceled:
			return err
		default:
			return fmt.Errorf("AT%s returned error '%v'", cmd, err)
		}
	}
	return nil
}

// SMSCommand issues an SMS command to the modem, and returns the result.
// An SMS command is issued in two steps; first the command line:
//   AT<command><CR>
// which the modem responds to with a ">" prompt, after which the SMS PDU is sent to the modem:
//   <sms><Ctrl-Z>
// The modem then completes the command as per other commands, such as those issued by Command.
// The format of the sms may be a text message or a hex coded SMS PDU, depending on the
// configuration of the modem (text or PDU mode).
func (a *AT) SMSCommand(ctx context.Context, cmd string, sms string) ([]string, error) {
	done := make(chan response)
	select {
	case <-a.closed:
		return nil, ErrClosed
	case a.cmdCh <- func() {
		done <- a.processReq(ctx, request{cmd: cmd, sms: &sms})
	}:
		rsp := <-done
		return rsp.info, rsp.err
	}
}

// cmdLoop is responsible for the interface to the modem.
// It serialises the issuing of commands and awaits the responses.
// The cmdLoop terminates when the downstream closes.
func cmdLoop(cmds chan func(), in <-chan string, out chan struct{}) {
	for {
		select {
		case cmd := <-cmds:
			cmd()
		case _, ok := <-in:
			if !ok {
				close(out)
				return
			}
		}
	}
}

func lineReader(m io.Reader, out chan string) {
	scanner := bufio.NewScanner(m)
	scanner.Split(scanLines)
	for scanner.Scan() {
		select {
		case out <- scanner.Text():
			break
		}
	}
	close(out) // tell pipeline we're done - end of pipeline will close the AT.
}

// nLoop is responsible for pulling indications from the stream of lines read from the modem,
// and forwarding them to handlers.  Non-indication lines are passed upstream.
// Indication trailing lines are assumed to arrive in a contiguous
// block immediately after the indication.
func (a *AT) nLoop(cmds chan func(), in <-chan string, out chan string) {
	defer func() {
		for k, v := range a.inds {
			close(v.c)
			delete(a.inds, k)
		}
	}()
	for {
		select {
		case cmd := <-cmds:
			cmd()
		case line, ok := <-in:
			if !ok {
				close(out)
				return
			}
			for k, v := range a.inds {
				if strings.HasPrefix(line, k) {
					n := make([]string, v.totalLines)
					n[0] = line
					for i := 1; i < v.totalLines; i++ {
						t, ok := <-in
						if !ok {
							return
						}
						n[i] = t
					}
					v.c <- n
					continue
				}
			}
			out <- line
		}
	}
}

func (a *AT) processReq(ctx context.Context, req request) response {
	a.waitWriteGuard()
	if err := a.writeCommand(req); err != nil {
		return response{err: err}
	}
	cmdID := parseCmdID(req.cmd)
	var rsp response
	for {
		select {
		case <-ctx.Done():
			if req.sms != nil {
				// cancel outstanding SMS request
				a.modem.Write([]byte(string(27) + "\r\n"))
				a.startWriteGuard()
			}
			rsp.err = ctx.Err()
			return rsp
		case line, ok := <-a.cLines:
			if !ok {
				return response{err: ErrClosed}
			}
			if line == "" {
				continue
			}
			switch parseRxLine(line, cmdID) {
			case rxlStatusOK:
				return rsp
			case rxlStatusError:
				rsp.err = newError(line)
				return rsp
			case rxlUnknown:
				if req.sms != nil && line[len(line)-1] == 26 && strings.HasPrefix(line, *req.sms) {
					// swallow echoed SMS PDU
					continue
				}
				fallthrough
			case rxlInfo:
				if rsp.info == nil {
					rsp.info = make([]string, 0)
				}
				rsp.info = append(rsp.info, line)
			case rxlSMSPrompt:
				if req.sms != nil {
					if err := a.writeSMS(*req.sms); err != nil {
						// escape SMS
						a.modem.Write([]byte(string(27) + "\r\n"))
						a.startWriteGuard()
						return response{err: err}
					}
				}
			}
		}
	}
}

func (a *AT) startWriteGuard() {
	a.wgmu.Lock()
	a.guarded = true
	a.wGuard = time.After(20 * time.Millisecond)
	a.wgmu.Unlock()
}

func (a *AT) waitWriteGuard() {
	a.wgmu.Lock()
	defer a.wgmu.Unlock()
	if a.guarded {
		for {
			select {
			case _, ok := <-a.cLines:
				if !ok {
					return
				}
			case <-a.wGuard:
				a.guarded = false
				a.wGuard = nil
				return
			}
		}
	}
}

// writeCommand writes a one line command to the modem.
func (a *AT) writeCommand(req request) error {
	cmdLine := "AT" + req.cmd + "\r\n"
	if req.sms != nil {
		cmdLine = cmdLine[:len(cmdLine)-1]
	}
	_, err := a.modem.Write([]byte(cmdLine))
	return err
}

// writeSMS writes the first line of a two line SMS command to the modem.
func (a *AT) writeSMS(sms string) error {
	_, err := a.modem.Write([]byte(sms + string(26)))
	return err
}

// CMEError indicates a CME Error was returned by the modem.
// The value is the error value, in string form, which may be the numeric or textual, depending
// on the modem configuration.
type CMEError string

// CMSError indicates a CMS Error was returned by the modem.
// The value is the error value, in string form, which may be the numeric or textual, depending
// on the modem configuration.
type CMSError string

func (e CMEError) Error() string {
	return string("CME Error: " + e)
}

func (e CMSError) Error() string {
	return string("CMS Error: " + e)
}

var (
	// ErrClosed indicates an operation cannot be performed as the modem has been closed.
	ErrClosed = errors.New("closed")
	// ErrError indicates the modem returned a generic AT ERROR in response to an operation.
	ErrError = errors.New("ERROR")
	// ErrIndicationExists indicates there is already a indication registered for
	// a prefix.
	ErrIndicationExists = errors.New("indication exists")
)

func newError(line string) error {
	var err error
	switch {
	case strings.HasPrefix(line, "ERROR"):
		err = ErrError
	case strings.HasPrefix(line, "+CMS ERROR:"):
		err = CMSError(strings.TrimSpace(line[11:]))
	case strings.HasPrefix(line, "+CME ERROR:"):
		err = CMEError(strings.TrimSpace(line[11:]))
	}
	return err
}

// request represents an operation to be performed on the modem.
type request struct {
	cmd string
	sms *string
}

// response represents the result of a request operation performed on the modem.
// info is the collection of lines returned between the command and the status line.
// err corresponds to any error returned by the modem or while interacting with the modem.
type response struct {
	info []string
	err  error
}

// Received line types.
type rxl int

const (
	rxlUnknown rxl = iota
	rxlEchoCmdLine
	rxlInfo
	rxlStatusOK
	rxlStatusError
	rxlAsync
	rxlSMSPrompt
)

// indication represents an unsolicited result code (URC) from the modem, such as a
// received SMS message.
// Indications are lines prefixed with a particular pattern,
// and may include a number of trailing lines.
// The matching lines are bundled into a slice and sent to the channel.
type indication struct {
	prefix     string
	totalLines int
	c          chan []string
}

// parseCmdID returns the identifier component of the command.
// This is the section prior to any '=' or '?' and is generally, but not
// always, used to prefix info lines corresponding to the command.
func parseCmdID(cmdLine string) string {
	switch idx := strings.IndexAny(cmdLine, "=?"); idx {
	case -1:
		return cmdLine
	default:
		return cmdLine[0:idx]
	}
}

// parseRxLine parses a received line and identifies the line type.
func parseRxLine(line string, cmdID string) rxl {
	switch {
	case line == "OK":
		return rxlStatusOK
	case strings.HasPrefix(line, "ERROR"),
		strings.HasPrefix(line, "+CME ERROR:"),
		strings.HasPrefix(line, "+CMS ERROR:"):
		return rxlStatusError
	case strings.HasPrefix(line, cmdID+":"):
		return rxlInfo
	case line == ">":
		return rxlSMSPrompt
	case strings.HasPrefix(line, "AT"+cmdID):
		return rxlEchoCmdLine
	default:
		// Don't attempt to identify SMS PDUs at this level, so they will
		// be caught here, along with other unidentified lines.
		return rxlUnknown
	}
}

// scanLines is a custom line scanner for lineReader that recognises
// the prompt returned by the modem in response to SMS commands such as +CMGS.
func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
	// handle SMS prompt special case - no CR at prompt
	if len(data) >= 1 && data[0] == '>' {
		i := 1
		// there may be trailing space, so swallow that...
		for ; i < len(data) && data[i] == ' '; i++ {
		}
		return i, data[0:1], nil
	}
	return bufio.ScanLines(data, atEOF)
}
<|endoftext|>


<commit_before>package agollo

import (
	"github.com/zouyx/agollo/agcache"
	"strconv"
	"sync"
)

type ConfigFileFormat string

const (
	Properties ConfigFileFormat = "Properties"
	XML        ConfigFileFormat = "xml"
	JSON       ConfigFileFormat = "json"
	YML        ConfigFileFormat = "yml"
	YAML       ConfigFileFormat = "yaml"
)

const (
	apolloConfigFormat = "%s.%s"

	empty = ""

	//1 minute
	configCacheExpireTime = 120

	defaultNamespace="application"
	defaultContentKey="content"
)

var (
	currentConnApolloConfig = &currentApolloConfig{
		configs:make(map[string]*ApolloConnConfig,1),
	}

	//config from apollo
	apolloConfigCache = make(map[string]*Config,0)

	formatParser = make(map[ConfigFileFormat]ContentParser,0)
	defaultFormatParser =&DefaultParser{}
)

func init(){
	formatParser[Properties]=&PropertiesParser{}
}

func initDefaultConfig() {
	cacheFactory := &agcache.DefaultCacheFactory{}
	initConfigCache(cacheFactory)
}

func initConfigCache(cacheFactory *agcache.DefaultCacheFactory)  {
	createDefaultConfig(cacheFactory)
}

func createDefaultConfig(cacheFactory *agcache.DefaultCacheFactory){
	if appConfig==nil{
		logger.Warn("Config is nil,can not init agollo.")
		return
	}
	splitNamespaces(appConfig.NamespaceName, func(namespace string) {
		if apolloConfigCache[namespace]!=nil{
			return
		}
		c:=&Config{
			namespace:namespace,
			cache:cacheFactory.Create(),
		}
		apolloConfigCache[namespace]=c
	})
}

type currentApolloConfig struct {
	l      sync.RWMutex
	configs map[string]*ApolloConnConfig
}

//Config apollo配置项
type Config struct {
	namespace string
	cache agcache.CacheInterface
}
//getConfigValue 获取配置值
func (this *Config) getConfigValue(key string) interface{} {
	value, err := this.cache.Get(key)
	if err != nil {
		logger.Errorf("get config value fail!key:%s,err:%s", key, err)
		return empty
	}

	return string(value)
}

//getValue 获取配置值（string）
func (this *Config) getValue(key string) string {
	value := this.getConfigValue(key)
	if value == nil {
		return empty
	}

	return value.(string)
}

//GetStringValue 获取配置值（string），获取不到则取默认值
func (this *Config) GetStringValue(key string, defaultValue string) string {
	value := this.getValue(key)
	if value == empty {
		return defaultValue
	}

	return value
}

//GetIntValue 获取配置值（int），获取不到则取默认值
func (this *Config) GetIntValue(key string, defaultValue int) int {
	value := this.getValue(key)

	i, err := strconv.Atoi(value)
	if err != nil {
		logger.Debug("convert to int fail!error:", err)
		return defaultValue
	}

	return i
}

//GetFloatValue 获取配置值（float），获取不到则取默认值
func (this *Config) GetFloatValue(key string, defaultValue float64) float64 {
	value := this.getValue(key)

	i, err := strconv.ParseFloat(value, 64)
	if err != nil {
		logger.Debug("convert to float fail!error:", err)
		return defaultValue
	}

	return i
}

//GetBoolValue 获取配置值（bool），获取不到则取默认值
func (this *Config) GetBoolValue(key string, defaultValue bool) bool {
	value := this.getValue(key)

	b, err := strconv.ParseBool(value)
	if err != nil {
		logger.Debug("convert to bool fail!error:", err)
		return defaultValue
	}

	return b
}

//GetConfig 根据namespace获取apollo配置
func GetConfig(namespace string) *Config{
	return apolloConfigCache[namespace]
}

//GetConfigCache 根据namespace获取apollo配置的缓存
func GetConfigCache(namespace string) agcache.CacheInterface{
	config := apolloConfigCache[namespace]
	if config==nil{
		return nil
	}
	return config.cache
}

func getDefaultConfigCache()agcache.CacheInterface{
	config := apolloConfigCache[defaultNamespace]
	if config!=nil{
		return config.cache
	}
	return nil
}

func updateApolloConfig(apolloConfig *ApolloConfig, isBackupConfig bool) {
	if apolloConfig == nil {
		logger.Error("apolloConfig is null,can't update!")
		return
	}
	//get change list
	changeList := updateApolloConfigCache(apolloConfig.Configurations, configCacheExpireTime,apolloConfig.NamespaceName)

	if len(changeList) > 0 {
		//create config change event base on change list
		event := createConfigChangeEvent(changeList, apolloConfig.NamespaceName)

		//push change event to channel
		pushChangeEvent(event)
	}

	//update apollo connection config
	currentConnApolloConfig.l.Lock()
	defer currentConnApolloConfig.l.Unlock()

	currentConnApolloConfig.configs[apolloConfig.NamespaceName] = &apolloConfig.ApolloConnConfig

	if isBackupConfig {
		//write config file async
		go writeConfigFile(apolloConfig, appConfig.getBackupConfigPath())
	}
}

func updateApolloConfigCache(configurations map[string]string, expireTime int,namespace string) map[string]*ConfigChange {
	config := GetConfig(namespace)
	if config==nil{
		return nil
	}

	if (configurations == nil || len(configurations) == 0) && config.cache.EntryCount() == 0 {
		return nil
	}

	//get old keys
	mp := map[string]bool{}
	config.cache.Range(func(key, value interface{}) bool {
		mp[key.(string)] = true
		return true
	})

	changes := make(map[string]*ConfigChange)

	if configurations != nil {
		// update new
		// keys
		for key, value := range configurations {
			//key state insert or update
			//insert
			if !mp[key] {
				changes[key] = createAddConfigChange(value)
			} else {
				//update
				oldValue, _ := config.cache.Get(key)
				if string(oldValue) != value {
					changes[key] = createModifyConfigChange(string(oldValue), value)
				}
			}

			config.cache.Set(key, []byte(value), expireTime)
			delete(mp, string(key))
		}
	}

	// remove del keys
	for key := range mp {
		//get old value and del
		oldValue, _ :=config.cache.Get(key)
		changes[key] = createDeletedConfigChange(string(oldValue))

		config.cache.Del(key)
	}

	return changes
}

//base on changeList create Change event
func createConfigChangeEvent(changes map[string]*ConfigChange, nameSpace string) *ChangeEvent {
	return &ChangeEvent{
		Namespace: nameSpace,
		Changes:   changes,
	}
}

func touchApolloConfigCache() error {
	return nil
}

//GetApolloConfigCache 获取默认namespace的apollo配置
func GetApolloConfigCache() agcache.CacheInterface {
	return getDefaultConfigCache()
}

//GetCurrentApolloConfig 获取Apollo链接配置
func GetCurrentApolloConfig() map[string]*ApolloConnConfig {
	currentConnApolloConfig.l.RLock()
	defer currentConnApolloConfig.l.RUnlock()

	return currentConnApolloConfig.configs
}

func getCurrentApolloConfigReleaseKey(namespace string) string {
	currentConnApolloConfig.l.RLock()
	defer currentConnApolloConfig.l.RUnlock()
	config:= currentConnApolloConfig.configs[namespace]
	if config==nil{
		return empty
	}

	return config.ReleaseKey
}

func getConfigValue(key string) interface{} {
	value, err := getDefaultConfigCache().Get(key)
	if err != nil {
		logger.Errorf("get config value fail!key:%s,err:%s", key, err)
		return empty
	}

	return string(value)
}

func getValue(key string) string {
	value := getConfigValue(key)
	if value == nil {
		return empty
	}

	return value.(string)
}

func GetStringValue(key string, defaultValue string) string {
	value := getValue(key)
	if value == empty {
		return defaultValue
	}

	return value
}

func GetIntValue(key string, defaultValue int) int {
	value := getValue(key)

	i, err := strconv.Atoi(value)
	if err != nil {
		logger.Debug("convert to int fail!error:", err)
		return defaultValue
	}

	return i
}

func GetFloatValue(key string, defaultValue float64) float64 {
	value := getValue(key)

	i, err := strconv.ParseFloat(value, 64)
	if err != nil {
		logger.Debug("convert to float fail!error:", err)
		return defaultValue
	}

	return i
}

func GetBoolValue(key string, defaultValue bool) bool {
	value := getValue(key)

	b, err := strconv.ParseBool(value)
	if err != nil {
		logger.Debug("convert to bool fail!error:", err)
		return defaultValue
	}

	return b
}

//GetContent 获取配置文件内容
func (c *Config)GetContent(format ConfigFileFormat) string {
	parser := formatParser[format]
	if parser==nil{
		parser=defaultFormatParser
	}
	s, err:= parser.parse(c.cache)
	if err!=nil{
		logger.Debug("GetContent fail ! error:", err)
	}
	return s
}<commit_msg>[Feature]fix problem<commit_after>package agollo

import (
	"github.com/zouyx/agollo/agcache"
	"strconv"
	"sync"
)

//ConfigFileFormat 配置文件类型
type ConfigFileFormat string

const (
	//Properties
	Properties ConfigFileFormat = "Properties"
	//XML
	XML        ConfigFileFormat = "xml"
	//JSON
	JSON       ConfigFileFormat = "json"
	//YML
	YML        ConfigFileFormat = "yml"
	//YAML
	YAML       ConfigFileFormat = "yaml"
)

const (
	apolloConfigFormat = "%s.%s"

	empty = ""

	//1 minute
	configCacheExpireTime = 120

	defaultNamespace="application"
	defaultContentKey="content"
)

var (
	currentConnApolloConfig = &currentApolloConfig{
		configs:make(map[string]*ApolloConnConfig,1),
	}

	//config from apollo
	apolloConfigCache = make(map[string]*Config,0)

	formatParser = make(map[ConfigFileFormat]ContentParser,0)
	defaultFormatParser =&DefaultParser{}
)

func init(){
	formatParser[Properties]=&PropertiesParser{}
}

func initDefaultConfig() {
	cacheFactory := &agcache.DefaultCacheFactory{}
	initConfigCache(cacheFactory)
}

func initConfigCache(cacheFactory *agcache.DefaultCacheFactory)  {
	createDefaultConfig(cacheFactory)
}

func createDefaultConfig(cacheFactory *agcache.DefaultCacheFactory){
	if appConfig==nil{
		logger.Warn("Config is nil,can not init agollo.")
		return
	}
	splitNamespaces(appConfig.NamespaceName, func(namespace string) {
		if apolloConfigCache[namespace]!=nil{
			return
		}
		c:=&Config{
			namespace:namespace,
			cache:cacheFactory.Create(),
		}
		apolloConfigCache[namespace]=c
	})
}

type currentApolloConfig struct {
	l      sync.RWMutex
	configs map[string]*ApolloConnConfig
}

//Config apollo配置项
type Config struct {
	namespace string
	cache agcache.CacheInterface
}
//getConfigValue 获取配置值
func (this *Config) getConfigValue(key string) interface{} {
	value, err := this.cache.Get(key)
	if err != nil {
		logger.Errorf("get config value fail!key:%s,err:%s", key, err)
		return empty
	}

	return string(value)
}

//getValue 获取配置值（string）
func (this *Config) getValue(key string) string {
	value := this.getConfigValue(key)
	if value == nil {
		return empty
	}

	return value.(string)
}

//GetStringValue 获取配置值（string），获取不到则取默认值
func (this *Config) GetStringValue(key string, defaultValue string) string {
	value := this.getValue(key)
	if value == empty {
		return defaultValue
	}

	return value
}

//GetIntValue 获取配置值（int），获取不到则取默认值
func (this *Config) GetIntValue(key string, defaultValue int) int {
	value := this.getValue(key)

	i, err := strconv.Atoi(value)
	if err != nil {
		logger.Debug("convert to int fail!error:", err)
		return defaultValue
	}

	return i
}

//GetFloatValue 获取配置值（float），获取不到则取默认值
func (this *Config) GetFloatValue(key string, defaultValue float64) float64 {
	value := this.getValue(key)

	i, err := strconv.ParseFloat(value, 64)
	if err != nil {
		logger.Debug("convert to float fail!error:", err)
		return defaultValue
	}

	return i
}

//GetBoolValue 获取配置值（bool），获取不到则取默认值
func (this *Config) GetBoolValue(key string, defaultValue bool) bool {
	value := this.getValue(key)

	b, err := strconv.ParseBool(value)
	if err != nil {
		logger.Debug("convert to bool fail!error:", err)
		return defaultValue
	}

	return b
}

//GetConfig 根据namespace获取apollo配置
func GetConfig(namespace string) *Config{
	return apolloConfigCache[namespace]
}

//GetConfigCache 根据namespace获取apollo配置的缓存
func GetConfigCache(namespace string) agcache.CacheInterface{
	config := apolloConfigCache[namespace]
	if config==nil{
		return nil
	}
	return config.cache
}

func getDefaultConfigCache()agcache.CacheInterface{
	config := apolloConfigCache[defaultNamespace]
	if config!=nil{
		return config.cache
	}
	return nil
}

func updateApolloConfig(apolloConfig *ApolloConfig, isBackupConfig bool) {
	if apolloConfig == nil {
		logger.Error("apolloConfig is null,can't update!")
		return
	}
	//get change list
	changeList := updateApolloConfigCache(apolloConfig.Configurations, configCacheExpireTime,apolloConfig.NamespaceName)

	if len(changeList) > 0 {
		//create config change event base on change list
		event := createConfigChangeEvent(changeList, apolloConfig.NamespaceName)

		//push change event to channel
		pushChangeEvent(event)
	}

	//update apollo connection config
	currentConnApolloConfig.l.Lock()
	defer currentConnApolloConfig.l.Unlock()

	currentConnApolloConfig.configs[apolloConfig.NamespaceName] = &apolloConfig.ApolloConnConfig

	if isBackupConfig {
		//write config file async
		go writeConfigFile(apolloConfig, appConfig.getBackupConfigPath())
	}
}

func updateApolloConfigCache(configurations map[string]string, expireTime int,namespace string) map[string]*ConfigChange {
	config := GetConfig(namespace)
	if config==nil{
		return nil
	}

	if (configurations == nil || len(configurations) == 0) && config.cache.EntryCount() == 0 {
		return nil
	}

	//get old keys
	mp := map[string]bool{}
	config.cache.Range(func(key, value interface{}) bool {
		mp[key.(string)] = true
		return true
	})

	changes := make(map[string]*ConfigChange)

	if configurations != nil {
		// update new
		// keys
		for key, value := range configurations {
			//key state insert or update
			//insert
			if !mp[key] {
				changes[key] = createAddConfigChange(value)
			} else {
				//update
				oldValue, _ := config.cache.Get(key)
				if string(oldValue) != value {
					changes[key] = createModifyConfigChange(string(oldValue), value)
				}
			}

			config.cache.Set(key, []byte(value), expireTime)
			delete(mp, string(key))
		}
	}

	// remove del keys
	for key := range mp {
		//get old value and del
		oldValue, _ :=config.cache.Get(key)
		changes[key] = createDeletedConfigChange(string(oldValue))

		config.cache.Del(key)
	}

	return changes
}

//base on changeList create Change event
func createConfigChangeEvent(changes map[string]*ConfigChange, nameSpace string) *ChangeEvent {
	return &ChangeEvent{
		Namespace: nameSpace,
		Changes:   changes,
	}
}

func touchApolloConfigCache() error {
	return nil
}

//GetApolloConfigCache 获取默认namespace的apollo配置
func GetApolloConfigCache() agcache.CacheInterface {
	return getDefaultConfigCache()
}

//GetCurrentApolloConfig 获取Apollo链接配置
func GetCurrentApolloConfig() map[string]*ApolloConnConfig {
	currentConnApolloConfig.l.RLock()
	defer currentConnApolloConfig.l.RUnlock()

	return currentConnApolloConfig.configs
}

func getCurrentApolloConfigReleaseKey(namespace string) string {
	currentConnApolloConfig.l.RLock()
	defer currentConnApolloConfig.l.RUnlock()
	config:= currentConnApolloConfig.configs[namespace]
	if config==nil{
		return empty
	}

	return config.ReleaseKey
}

func getConfigValue(key string) interface{} {
	value, err := getDefaultConfigCache().Get(key)
	if err != nil {
		logger.Errorf("get config value fail!key:%s,err:%s", key, err)
		return empty
	}

	return string(value)
}

func getValue(key string) string {
	value := getConfigValue(key)
	if value == nil {
		return empty
	}

	return value.(string)
}

func GetStringValue(key string, defaultValue string) string {
	value := getValue(key)
	if value == empty {
		return defaultValue
	}

	return value
}

func GetIntValue(key string, defaultValue int) int {
	value := getValue(key)

	i, err := strconv.Atoi(value)
	if err != nil {
		logger.Debug("convert to int fail!error:", err)
		return defaultValue
	}

	return i
}

func GetFloatValue(key string, defaultValue float64) float64 {
	value := getValue(key)

	i, err := strconv.ParseFloat(value, 64)
	if err != nil {
		logger.Debug("convert to float fail!error:", err)
		return defaultValue
	}

	return i
}

func GetBoolValue(key string, defaultValue bool) bool {
	value := getValue(key)

	b, err := strconv.ParseBool(value)
	if err != nil {
		logger.Debug("convert to bool fail!error:", err)
		return defaultValue
	}

	return b
}

//GetContent 获取配置文件内容
func (c *Config)GetContent(format ConfigFileFormat) string {
	parser := formatParser[format]
	if parser==nil{
		parser=defaultFormatParser
	}
	s, err:= parser.parse(c.cache)
	if err!=nil{
		logger.Debug("GetContent fail ! error:", err)
	}
	return s
}<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"
)

// urlsInProgress is a wait group, for concurrency
var urlsInProgress sync.WaitGroup

// Responses is a channel to store the concurrent responses from the target
var responses chan *http.Response

// UniqueResponses is a map to store all compared responses, and the number of similar responses found
var uniqueResponses map[*http.Response]int

// RedirectError is a custom error type for following redirects, and can be safely ignored
type RedirectError struct {
	RedirectRequest *http.Request
}

// Error method returns a string of the error
func (err *RedirectError) Error() string {
	return fmt.Sprintf("Redirect not followed to: %v", err.RedirectRequest.URL.String())
}

// Request body content
var body string

// Target URL value
var targetURL *url.URL

// Cookie jar value
var jar *cookiejar.Jar

// Number of requests
var numRequests int

// Request type
var requestMethod string

// Follow redirects
var followRedirects bool

// Verbose logging enabled
var verbose bool

// Command-line flags
var flagTargetURL = flag.String("url", "", "URL to send the request to.")
var flagBodyFile = flag.String("body", "", "The location (relative or absolute path) of a file containing the body of the request.")
var flagCookiesFile = flag.String("cookies", "", "The location (relative or absolute path) of a file containing newline-separate cookie values being sent along with the request. Cookie names and values are separated by a comma. For example: cookiename,cookieval")
var flagNumRequests = flag.Int("requests", 100, "The number of requests to send to the destination URL.")
var flagRequestMethod = flag.String("method", "POST", "The request type. Can be either `POST, GET, HEAD, PUT`.")
var flagFollowRedirects = flag.Bool("redirects", false, "Follow redirects (3xx status code in responses)")
var flagVerbose = flag.Bool("v", false, "Enable verbose logging.")

func main() {
	// Change output location of logs
	log.SetOutput(os.Stdout)

	// Check the flags
	err := checkFlags()
	if err != nil {
		log.Println(err.Error())
		flag.Usage()
		os.Exit(1)
	}

	// Make sure all response bodies are closed- memory leaks otherwise
	defer func() {
		for resp := range responses {
			resp.Body.Close()
		}
		for resp := range uniqueResponses {
			resp.Body.Close()
		}
	}()

	// Initialize an error channel
	var errChan chan error

	// Send the requests concurrently
	log.Println("Requests begin.")
	errChan = sendRequests()
	if len(errChan) != 0 {
		for err := range errChan {
			log.Printf("[ERROR] %s\n", err.Error())
		}
	}

	// Compare the responses for uniqueness
	uniqueResponses, errChan = compareResponses()
	if len(errChan) != 0 {
		for err := range errChan {
			log.Printf("[ERROR] %s\n", err.Error())
		}
	}

	// Output the responses
	outputResponses(uniqueResponses)

	// Echo completion
	log.Println("Complete.")
}

// Function checkFlags checks that all necessary flags are entered, and parses them for contents.
// Returns a custom error if something went wrong.
func checkFlags() error {
	// Parse the flags
	flag.Parse()

	// Set verbose logging explicitely
	verbose = *flagVerbose

	// Determine whether to follow redirects
	followRedirects = *flagFollowRedirects

	// Set the request type
	switch strings.ToUpper(*flagRequestMethod) {
	case "POST":
		requestMethod = "POST"
	case "GET":
		requestMethod = "GET"
	case "PUT":
		requestMethod = "PUT"
	case "HEAD":
		requestMethod = "HEAD"
	default:
		// Invalid request type specified
		return fmt.Errorf("Invalid request type specified.")
	}

	// Ensure that the destination URL is present
	if *flagTargetURL == "" {
		return fmt.Errorf("Destination URL required.")
	}

	// Parse the URL
	var err error
	targetURL, err = url.Parse(*flagTargetURL)
	if err != nil {
		return fmt.Errorf("Invalid URL provided: %s", *flagTargetURL)
	}

	// Get the request body content
	if *flagBodyFile != "" {
		buf, err := ioutil.ReadFile(*flagBodyFile)
		if err != nil {
			// Error opening the file
			return fmt.Errorf("Unable to open the file: %s\n", *flagBodyFile)
		}
		body = string(buf)
	} else {
		// Body file flag not present, exit.
		return fmt.Errorf("Request body contents required.")
	}

	// Check the number of requests used for testing
	numRequests = *flagNumRequests

	// Initialize the cookie jar
	jar, _ = cookiejar.New(nil)
	var cookies []*http.Cookie
	// Get the cookies to pass to the request
	if *flagCookiesFile != "" {
		file, err := os.Open(*flagCookiesFile)
		if err != nil {
			// Error opening the file
			return fmt.Errorf("Unable to open the file: %s", *flagCookiesFile)
		}

		// Ensure the file is closed
		defer file.Close()

		// Initialize the file scanner
		scanner := bufio.NewScanner(file)

		// Iterate through the file to get the cookies
		for scanner.Scan() {
			// Parse the line to separate the cookie names and values
			nextLine := scanner.Text()
			vals := strings.Split(nextLine, ",")
			cookieName := strings.TrimSpace(vals[0])
			cookieValue := strings.TrimSpace(vals[1])

			// Create the cookie
			cookie := &http.Cookie{
				Name:  cookieName,
				Value: cookieValue,
			}

			// Add the cookie to the existing slice of cookies
			cookies = append(cookies, cookie)
		}

		// Set the cookies to the appropriate URL
		jar.SetCookies(targetURL, cookies)

	}

	// Made it through with no errors, return
	return nil
}

// Function sendRequests takes care of sending the requests to the target concurrently.
// Errors are passed back in a channel of errors. If the length is zero, there were no errors.
func sendRequests() chan error {
	// Initialize the concurrency objects
	responses = make(chan *http.Response, numRequests)
	errorChannel := make(chan error, numRequests)
	urlsInProgress.Add(numRequests)

	// VERBOSE
	if verbose {
		log.Printf("[VERBOSE] Sending %d %s requests to %s\n", numRequests, requestMethod, targetURL.String())
		if body != "" {
			log.Printf("[VERBOSE] Request body: %s", body)
		}
	}
	for i := 0; i < numRequests; i++ {
		go func(index int) {
			// Ensure that the waitgroup element is returned
			defer urlsInProgress.Done()

			// Convert the request body to an io.Reader interface, to pass to the request.
			// This must be done in the loop, because any call to client.Do() will
			// read the body contents on the first time, but not any subsequent requests.
			requestBody := strings.NewReader(body)

			// Declare HTTP request method and URL
			req, err := http.NewRequest(requestMethod, targetURL.String(), requestBody)
			if err != nil {
				errorChannel <- fmt.Errorf("Error in forming request: %v", err.Error())
				return
			}

			// Create the HTTP client
			// Using Cookie jar
			// Ignoring TLS errors
			// Ignoring redirects (more accurate output), depending on user flag
			// Implementing a connection timeouts, for slow clients & servers (especially important with race conditions on the server)
			var client http.Client
			if followRedirects {
				client = http.Client{
					Jar: jar,
					Transport: &http.Transport{
						TLSClientConfig: &tls.Config{
							InsecureSkipVerify: true,
						},
					},
					Timeout: 20 * time.Second,
				}
			} else {
				client = http.Client{
					Jar: jar,
					Transport: &http.Transport{
						TLSClientConfig: &tls.Config{
							InsecureSkipVerify: true,
						},
					},
					CheckRedirect: func(req *http.Request, via []*http.Request) error {
						// Craft the custom error
						redirectError := RedirectError{req}
						return &redirectError
					},
					Timeout: 20 * time.Second,
				}
			}

			// Make the request
			resp, err := client.Do(req)
			// Check the error type from the request
			if err != nil {
				if uErr, ok := err.(*url.Error); ok {
					if rErr, ok2 := uErr.Err.(*RedirectError); ok2 {
						// Redirect error
						// VERBOSE
						if verbose {
							log.Printf("[VERBOSE] %v\n", rErr)
						}
						// Add the response to the responses channel, because it is still valid
						responses <- resp
					} else {
						// URL Error, but not a redirect error
						errorChannel <- fmt.Errorf("Error in request #%v: %v\n", index, err)
					}
				} else {
					// Other type of error
					errorChannel <- fmt.Errorf("Error in request #%v: %v\n", index, err)
				}
			} else {
				// Add the response to the responses channel
				responses <- resp
			}
		}(i)
	}

	// Wait for the URLs to finish sending
	urlsInProgress.Wait()

	// VERBOSE
	if verbose {
		log.Printf("[VERBOSE] Requests complete.")
	}

	// Close the response and error chanels, so they don't block on the range read
	close(responses)
	close(errorChannel)

	return errorChannel
}

// Function compareResponses compares the responses returned from the requests,
// and adds them to a map, where the key is an *http.Response, and the value is
// the number of similar responses observed.
func compareResponses() (newResponses map[*http.Response]int, errorChannel chan error) {
	// Initialize the unique responses map
	newResponses = make(map[*http.Response]int)

	// Initialize the error channel
	errorChannel = make(chan error, len(responses))

	// VERBOSE
	if verbose {
		log.Printf("[VERBOSE] Unique response comparison begin.\n")
	}

	// Compare the responses, one at a time
	for resp := range responses {
		// Read the response body
		respBody, err := readResponseBody(resp)
		if err != nil {
			errorChannel <- fmt.Errorf("Error reading response body: %s", err.Error())

			// Exit this loop
			continue
		}

		// Add an entry, if the unique responses map is empty
		if len(newResponses) == 0 {
			newResponses[resp] = 0
		} else {
			// Add to the unique responses map, if no similar ones exist
			for uResp := range newResponses {
				// Read the unique response body
				uRespBody, err := readResponseBody(uResp)
				if err != nil {
					errorChannel <- fmt.Errorf("Error reading unique response body: %s", err.Error())

					// Exit the inner loop
					continue
				}

				// Compare the response bodies
				respBodyMatch := false
				if string(respBody) == string(uRespBody) {
					respBodyMatch = true
				}

				// Compare response status code, body content, and content length
				if resp.StatusCode == uResp.StatusCode && resp.ContentLength == uResp.ContentLength && respBodyMatch {
					// Similar, increase count
					newResponses[uResp]++
					// Exit inner loop
					continue
				} else {
					// Unique, add to unique responses
					newResponses[resp] = 0
					// Exit inner loop
					continue
				}
			}
		}
	}

	// VERBOSE
	if verbose {
		log.Printf("[VERBOSE] Unique response comparision complete.\n")
	}

	// Close the error channel
	close(errorChannel)

	return
}

func outputResponses(uniqueResponses map[*http.Response]int) {
	// Display the responses
	log.Printf("Responses:\n")
	for resp, count := range uniqueResponses {
		fmt.Printf("Response:\n")
		fmt.Printf("[Status Code] %v\n", resp.StatusCode)
		fmt.Printf("[Protocol] %v\n", resp.Proto)
		if len(resp.Header) != 0 {
			fmt.Println("[Headers]")
			for header, value := range resp.Header {
				fmt.Printf("\t%v: %v\n", header, value)
			}
		}
		location, err := resp.Location()
		if err != http.ErrNoLocation {
			fmt.Printf("[Location] %v\n", location.String())
		}
		respBody, err := readResponseBody(resp)
		if err != nil {
			fmt.Println("[Body] ")
			fmt.Printf("[ERROR] Error reading body: %v.", err)
		} else {
			fmt.Printf("[Body]\n%s\n", respBody)
			// Close the response body
			resp.Body.Close()
		}
		fmt.Printf("Similar: %v\n\n", count)
	}
}

// Function readResponseBody is a helper function to read the content form a response's body,
// and refill the body with another io.ReadCloser, so that it can be read again.
func readResponseBody(resp *http.Response) (content []byte, err error) {
	// Get the content
	content, err = ioutil.ReadAll(resp.Body)

	// Reset the response body
	rCloser := ioutil.NopCloser(bytes.NewBuffer(content))
	resp.Body = rCloser

	return
}

// TODO: Optimize speed (more concurrency)
<commit_msg>Code clean-up.<commit_after>package main

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"
)

// urlsInProgress is a wait group, for concurrency
var urlsInProgress sync.WaitGroup

// RedirectError is a custom error type for following redirects, and can be safely ignored
type RedirectError struct {
	RedirectRequest *http.Request
}

// Error method returns a string of the error
func (err *RedirectError) Error() string {
	return fmt.Sprintf("Redirect not followed to: %v", err.RedirectRequest.URL.String())
}

// Request body content
var body string

// Target URL value
var targetURL *url.URL

// Cookie jar value
var jar *cookiejar.Jar

// Number of requests
var numRequests int

// Request type
var requestMethod string

// Follow redirects
var followRedirects bool

// Verbose logging enabled
var verbose bool

// Command-line flags
var flagTargetURL = flag.String("url", "", "URL to send the request to.")
var flagBodyFile = flag.String("body", "", "The location (relative or absolute path) of a file containing the body of the request.")
var flagCookiesFile = flag.String("cookies", "", "The location (relative or absolute path) of a file containing newline-separate cookie values being sent along with the request. Cookie names and values are separated by a comma. For example: cookiename,cookieval")
var flagNumRequests = flag.Int("requests", 100, "The number of requests to send to the destination URL.")
var flagRequestMethod = flag.String("method", "POST", "The request type. Can be either `POST, GET, HEAD, PUT`.")
var flagFollowRedirects = flag.Bool("redirects", false, "Follow redirects (3xx status code in responses)")
var flagVerbose = flag.Bool("v", false, "Enable verbose logging.")

func main() {
	// Change output location of logs
	log.SetOutput(os.Stdout)

	// Check the flags
	err := checkFlags()
	if err != nil {
		log.Println(err.Error())
		flag.Usage()
		os.Exit(1)
	}

	// Send the requests concurrently
	log.Println("Requests begin.")
	responses, errors := sendRequests()
	if len(errors) != 0 {
		for err := range errors {
			log.Printf("[ERROR] %s\n", err.Error())
		}
	}

	// Make sure all response bodies are closed- memory leaks otherwise
	defer func() {
		for resp := range responses {
			resp.Body.Close()
		}
	}()

	// Compare the responses for uniqueness
	uniqueResponses, errors := compareResponses(responses)
	if len(errors) != 0 {
		for err := range errors {
			log.Printf("[ERROR] %s\n", err.Error())
		}
	}

	// Make sure all response bodies are closed- memory leaks otherwise
	defer func() {
		for resp := range uniqueResponses {
			resp.Body.Close()
		}
	}()

	// Output the responses
	outputResponses(uniqueResponses)

	// Echo completion
	log.Println("Complete.")
}

// Function checkFlags checks that all necessary flags are entered, and parses them for contents.
// Returns a custom error if something went wrong.
func checkFlags() error {
	// Parse the flags
	flag.Parse()

	// Set verbose logging explicitely
	verbose = *flagVerbose

	// Determine whether to follow redirects
	followRedirects = *flagFollowRedirects

	// Set the request type
	switch strings.ToUpper(*flagRequestMethod) {
	case "POST":
		requestMethod = "POST"
	case "GET":
		requestMethod = "GET"
	case "PUT":
		requestMethod = "PUT"
	case "HEAD":
		requestMethod = "HEAD"
	default:
		// Invalid request type specified
		return fmt.Errorf("Invalid request type specified.")
	}

	// Ensure that the destination URL is present
	if *flagTargetURL == "" {
		return fmt.Errorf("Destination URL required.")
	}

	// Parse the URL
	var err error
	targetURL, err = url.Parse(*flagTargetURL)
	if err != nil {
		return fmt.Errorf("Invalid URL provided: %s", *flagTargetURL)
	}

	// Get the request body content
	if *flagBodyFile != "" {
		buf, err := ioutil.ReadFile(*flagBodyFile)
		if err != nil {
			// Error opening the file
			return fmt.Errorf("Unable to open the file: %s\n", *flagBodyFile)
		}
		body = string(buf)
	} else {
		// Body file flag not present, exit.
		return fmt.Errorf("Request body contents required.")
	}

	// Check the number of requests used for testing
	numRequests = *flagNumRequests

	// Initialize the cookie jar
	jar, _ = cookiejar.New(nil)
	var cookies []*http.Cookie
	// Get the cookies to pass to the request
	if *flagCookiesFile != "" {
		file, err := os.Open(*flagCookiesFile)
		if err != nil {
			// Error opening the file
			return fmt.Errorf("Unable to open the file: %s", *flagCookiesFile)
		}

		// Ensure the file is closed
		defer file.Close()

		// Initialize the file scanner
		scanner := bufio.NewScanner(file)

		// Iterate through the file to get the cookies
		for scanner.Scan() {
			// Parse the line to separate the cookie names and values
			nextLine := scanner.Text()
			vals := strings.Split(nextLine, ",")
			cookieName := strings.TrimSpace(vals[0])
			cookieValue := strings.TrimSpace(vals[1])

			// Create the cookie
			cookie := &http.Cookie{
				Name:  cookieName,
				Value: cookieValue,
			}

			// Add the cookie to the existing slice of cookies
			cookies = append(cookies, cookie)
		}

		// Set the cookies to the appropriate URL
		jar.SetCookies(targetURL, cookies)

	}

	// Made it through with no errors, return
	return nil
}

// Function sendRequests takes care of sending the requests to the target concurrently.
// Errors are passed back in a channel of errors. If the length is zero, there were no errors.
func sendRequests() (responses chan *http.Response, errors chan error) {
	// Initialize the concurrency objects
	responses = make(chan *http.Response, numRequests)
	errors = make(chan error, numRequests)
	urlsInProgress.Add(numRequests)

	// VERBOSE
	if verbose {
		log.Printf("[VERBOSE] Sending %d %s requests to %s\n", numRequests, requestMethod, targetURL.String())
		if body != "" {
			log.Printf("[VERBOSE] Request body: %s", body)
		}
	}
	for i := 0; i < numRequests; i++ {
		go func(index int) {
			// Ensure that the waitgroup element is returned
			defer urlsInProgress.Done()

			// Convert the request body to an io.Reader interface, to pass to the request.
			// This must be done in the loop, because any call to client.Do() will
			// read the body contents on the first time, but not any subsequent requests.
			requestBody := strings.NewReader(body)

			// Declare HTTP request method and URL
			req, err := http.NewRequest(requestMethod, targetURL.String(), requestBody)
			if err != nil {
				errors <- fmt.Errorf("Error in forming request: %v", err.Error())
				return
			}

			// Create the HTTP client
			// Using Cookie jar
			// Ignoring TLS errors
			// Ignoring redirects (more accurate output), depending on user flag
			// Implementing a connection timeouts, for slow clients & servers (especially important with race conditions on the server)
			var client http.Client
			if followRedirects {
				client = http.Client{
					Jar: jar,
					Transport: &http.Transport{
						TLSClientConfig: &tls.Config{
							InsecureSkipVerify: true,
						},
					},
					Timeout: 20 * time.Second,
				}
			} else {
				client = http.Client{
					Jar: jar,
					Transport: &http.Transport{
						TLSClientConfig: &tls.Config{
							InsecureSkipVerify: true,
						},
					},
					CheckRedirect: func(req *http.Request, via []*http.Request) error {
						// Craft the custom error
						redirectError := RedirectError{req}
						return &redirectError
					},
					Timeout: 20 * time.Second,
				}
			}

			// Make the request
			resp, err := client.Do(req)
			// Check the error type from the request
			if err != nil {
				if uErr, ok := err.(*url.Error); ok {
					if rErr, ok2 := uErr.Err.(*RedirectError); ok2 {
						// Redirect error
						// VERBOSE
						if verbose {
							log.Printf("[VERBOSE] %v\n", rErr)
						}
						// Add the response to the responses channel, because it is still valid
						responses <- resp
					} else {
						// URL Error, but not a redirect error
						errors <- fmt.Errorf("Error in request #%v: %v\n", index, err)
					}
				} else {
					// Other type of error
					errors <- fmt.Errorf("Error in request #%v: %v\n", index, err)
				}
			} else {
				// Add the response to the responses channel
				responses <- resp
			}
		}(i)
	}

	// Wait for the URLs to finish sending
	urlsInProgress.Wait()

	// VERBOSE
	if verbose {
		log.Printf("[VERBOSE] Requests complete.")
	}

	// Close the response and error chanels, so they don't block on the range read
	close(responses)
	close(errors)

	return
}

// Function compareResponses compares the responses returned from the requests,
// and adds them to a map, where the key is an *http.Response, and the value is
// the number of similar responses observed.
func compareResponses(responses chan *http.Response) (newResponses map[*http.Response]int, errors chan error) {
	// Initialize the unique responses map
	newResponses = make(map[*http.Response]int)

	// Initialize the error channel
	errors = make(chan error, len(responses))

	// VERBOSE
	if verbose {
		log.Printf("[VERBOSE] Unique response comparison begin.\n")
	}

	// Compare the responses, one at a time
	for resp := range responses {
		// Read the response body
		respBody, err := readResponseBody(resp)
		if err != nil {
			errors <- fmt.Errorf("Error reading response body: %s", err.Error())

			// Exit this loop
			continue
		}

		// Add an entry, if the unique responses map is empty
		if len(newResponses) == 0 {
			newResponses[resp] = 0
		} else {
			// Add to the unique responses map, if no similar ones exist
			for uResp := range newResponses {
				// Read the unique response body
				uRespBody, err := readResponseBody(uResp)
				if err != nil {
					errors <- fmt.Errorf("Error reading unique response body: %s", err.Error())

					// Exit the inner loop
					continue
				}

				// Compare the response bodies
				respBodyMatch := false
				if string(respBody) == string(uRespBody) {
					respBodyMatch = true
				}

				// Compare response status code, body content, and content length
				if resp.StatusCode == uResp.StatusCode && resp.ContentLength == uResp.ContentLength && respBodyMatch {
					// Similar, increase count
					newResponses[uResp]++
					// Exit inner loop
					continue
				} else {
					// Unique, add to unique responses
					newResponses[resp] = 0
					// Exit inner loop
					continue
				}
			}
		}
	}

	// VERBOSE
	if verbose {
		log.Printf("[VERBOSE] Unique response comparision complete.\n")
	}

	// Close the error channel
	close(errors)

	return
}

func outputResponses(uniqueResponses map[*http.Response]int) {
	// Display the responses
	log.Printf("Responses:\n")
	for resp, count := range uniqueResponses {
		fmt.Printf("Response:\n")
		fmt.Printf("[Status Code] %v\n", resp.StatusCode)
		fmt.Printf("[Protocol] %v\n", resp.Proto)
		if len(resp.Header) != 0 {
			fmt.Println("[Headers]")
			for header, value := range resp.Header {
				fmt.Printf("\t%v: %v\n", header, value)
			}
		}
		location, err := resp.Location()
		if err != http.ErrNoLocation {
			fmt.Printf("[Location] %v\n", location.String())
		}
		respBody, err := readResponseBody(resp)
		if err != nil {
			fmt.Println("[Body] ")
			fmt.Printf("[ERROR] Error reading body: %v.", err)
		} else {
			fmt.Printf("[Body]\n%s\n", respBody)
			// Close the response body
			resp.Body.Close()
		}
		fmt.Printf("Similar: %v\n\n", count)
	}
}

// Function readResponseBody is a helper function to read the content form a response's body,
// and refill the body with another io.ReadCloser, so that it can be read again.
func readResponseBody(resp *http.Response) (content []byte, err error) {
	// Get the content
	content, err = ioutil.ReadAll(resp.Body)

	// Reset the response body
	rCloser := ioutil.NopCloser(bytes.NewBuffer(content))
	resp.Body = rCloser

	return
}

// TODO: Optimize speed (more concurrency)
// TODO: Add option to send a second request at the same time, the same number of times (useful for adding 2 values to a database)
// TODO: Add option to include multiple session cookie values. Cookies for each request will be semicolon-delimited, and newline characters will delimit cookies for different requests.
<|endoftext|>


<commit_before>package plugin

import (
	"context"
	"os/exec"
	"path/filepath"
	"sort"
	"sync"

	"github.com/hashicorp/go-plugin"
	"github.com/heptio/developer-dash/internal/log"
	"github.com/heptio/developer-dash/internal/portforward"
	"github.com/heptio/developer-dash/pkg/plugin/api"
	"github.com/heptio/developer-dash/pkg/view/component"
	"github.com/pkg/errors"
	"k8s.io/apimachinery/pkg/runtime"
)

// ClientFactory is a factory for creating clients.
type ClientFactory interface {
	// Init initializes a client.
	Init(ctx context.Context, cmd string) Client
}

// DefaultClientFactory is the default client factory
type DefaultClientFactory struct{}

var _ ClientFactory = (*DefaultClientFactory)(nil)

// NewDefaultClientFactory creates an instance of DefaultClientFactory.
func NewDefaultClientFactory() *DefaultClientFactory {
	return &DefaultClientFactory{}
}

// Init creates a new client.
func (f *DefaultClientFactory) Init(ctx context.Context, cmd string) Client {
	loggerAdapter := &zapAdapter{
		dashLogger: log.From(ctx),
	}

	return plugin.NewClient(&plugin.ClientConfig{
		HandshakeConfig: Handshake,
		Plugins:         pluginMap,
		Cmd:             exec.Command(cmd),
		AllowedProtocols: []plugin.Protocol{
			plugin.ProtocolGRPC,
		},
		Logger: loggerAdapter,
	})
}

// Client is an interface that describes a plugin client.
type Client interface {
	Client() (plugin.ClientProtocol, error)
	Kill()
}

// ManagerStore is the data store for Manager.
type ManagerStore interface {
	Store(name string, client Client, metadata Metadata)
	GetMetadata(name string) (Metadata, error)
	GetService(name string) (Service, error)
	Clients() map[string]Client
	ClientNames() []string
}

// DefaultStore is the default implement of ManagerStore.
type DefaultStore struct {
	clients  map[string]Client
	metadata map[string]Metadata
}

var _ ManagerStore = (*DefaultStore)(nil)

// NewDefaultStore creates an instance of DefaultStore.
func NewDefaultStore() *DefaultStore {
	return &DefaultStore{
		clients:  make(map[string]Client),
		metadata: make(map[string]Metadata),
	}
}

// Store stores information for a plugin.
func (s *DefaultStore) Store(name string, client Client, metadata Metadata) {
	s.clients[name] = client
	s.metadata[name] = metadata
}

// GetService gets the service for a plugin.
func (s *DefaultStore) GetService(name string) (Service, error) {
	client, ok := s.clients[name]
	if !ok {
		return nil, errors.Errorf("plugin %q doesn't have a client", name)
	}

	rpcClient, err := client.Client()
	if err != nil {
		return nil, err
	}

	raw, err := rpcClient.Dispense("plugin")
	if err != nil {
		return nil, errors.Wrapf(err, "dispensing plugin for %q", name)
	}

	service, ok := raw.(Service)
	if !ok {
		return nil, errors.Errorf("unknown type for plugin %q: %T", name, raw)
	}

	return service, nil
}

// GetMetadata gets the metadata for a plugin.
func (s *DefaultStore) GetMetadata(name string) (Metadata, error) {
	metadata, ok := s.metadata[name]
	if !ok {
		return Metadata{}, errors.Errorf("plugin %q doesn't have metadata", name)
	}

	return metadata, nil
}

// Clients returns all the clients in the store.
func (s *DefaultStore) Clients() map[string]Client {
	return s.clients
}

// ClientNames returns the client names in the store.
func (s *DefaultStore) ClientNames() []string {
	var list []string
	for name := range s.Clients() {
		list = append(list, name)
	}
	return list
}

type config struct {
	cmd  string
	name string
}

// ManagerOption is an option for configuring Manager.
type ManagerOption func(*Manager)

// Manager manages plugins
type Manager struct {
	PortForwarder portforward.PortForwarder
	API           api.API
	Store         ManagerStore
	ClientFactory ClientFactory

	Runners Runners

	configs []config

	lock sync.Mutex
}

// NewManager creates an instance of Manager.
func NewManager(apiService api.API, options ...ManagerOption) *Manager {
	m := &Manager{
		Store:         NewDefaultStore(),
		ClientFactory: NewDefaultClientFactory(),
		Runners:       newDefaultRunners(),
		API:           apiService,
	}

	for _, option := range options {
		option(m)
	}

	return m
}

// Load loads a plugin.
func (m *Manager) Load(cmd string) error {
	m.lock.Lock()
	defer m.lock.Unlock()

	name := filepath.Base(cmd)

	for _, c := range m.configs {
		if name == c.name {
			return errors.Errorf("tried to load plugin %q more than once", name)
		}
	}

	c := config{
		name: name,
		cmd:  cmd,
	}

	m.configs = append(m.configs, c)

	return nil
}

// Start stars all plugins.
func (m *Manager) Start(ctx context.Context) error {
	if m.Store == nil {
		return errors.New("manager store is nil")
	}

	if m.ClientFactory == nil {
		return errors.New("manager client factory is nil")
	}

	if err := m.API.Start(ctx); err != nil {
		return errors.Wrap(err, "start api service")
	}

	logger := log.From(ctx)
	logger.With("addr", m.API.Addr()).Debugf("starting plugin api service")

	m.lock.Lock()
	defer m.lock.Unlock()

	for i := range m.configs {
		c := m.configs[i]

		pluginLogger := logger.With("plugin-name", c.name)

		client := m.ClientFactory.Init(ctx, c.cmd)

		rpcClient, err := client.Client()
		if err != nil {
			return errors.Wrapf(err, "get rpc client for %q", c.name)
		}

		raw, err := rpcClient.Dispense("plugin")
		if err != nil {
			return errors.Wrapf(err, "dispensing plugin for %q", c.name)
		}

		p, ok := raw.(Service)
		if !ok {
			return errors.Errorf("unknown type for plugin %q: %T", c.name, raw)
		}

		metadata, err := p.Register(m.API.Addr())
		if err != nil {
			return errors.Wrapf(err, "register plugin %q", c.name)
		}

		m.Store.Store(c.name, client, metadata)

		pluginLogger.With(
			"cmd", c.cmd,
			"metadata", metadata,
		).Debugf("registered plugin")
	}

	return nil
}

// Stop stops all plugins.
func (m *Manager) Stop(ctx context.Context) {
	logger := log.From(ctx)

	m.lock.Lock()
	defer m.lock.Unlock()

	for name, client := range m.Store.Clients() {
		logger.With("plugin-name", name).Debugf("stopping plugin")
		client.Kill()
	}
}

// Print prints an object with plugins which are configured to print the objects's
// GVK.
func (m *Manager) Print(object runtime.Object) (*PrintResponse, error) {
	if m.Runners == nil {
		return nil, errors.New("runners is nil")
	}

	runner, ch := m.Runners.Print(m.Store)
	done := make(chan bool)

	var pr PrintResponse

	go func() {
		for resp := range ch {
			pr.Config = append(pr.Config, resp.Config...)
			pr.Status = append(pr.Status, resp.Status...)
			pr.Items = append(pr.Items, resp.Items...)
		}

		done <- true
	}()

	if err := runner.Run(object, m.Store.ClientNames()); err != nil {
		return nil, err
	}
	close(ch)

	<-done
	return &pr, nil
}

// Tabs queries plugins for tabs for an object.
func (m *Manager) Tabs(object runtime.Object) ([]component.Tab, error) {
	if m.Runners == nil {
		return nil, errors.New("runners is nil")
	}

	runner, ch := m.Runners.Tab(m.Store)
	done := make(chan bool)

	var tabs []component.Tab

	go func() {
		for tab := range ch {
			tabs = append(tabs, tab)
		}

		done <- true
	}()

	if err := runner.Run(object, m.Store.ClientNames()); err != nil {
		return nil, err
	}

	close(ch)
	<-done

	sort.Slice(tabs, func(i, j int) bool {
		return tabs[i].Name < tabs[j].Name
	})

	return tabs, nil
}
<commit_msg>log plugins more visibly<commit_after>package plugin

import (
	"context"
	"os/exec"
	"path/filepath"
	"sort"
	"sync"

	"github.com/hashicorp/go-plugin"
	"github.com/heptio/developer-dash/internal/log"
	"github.com/heptio/developer-dash/internal/portforward"
	"github.com/heptio/developer-dash/pkg/plugin/api"
	"github.com/heptio/developer-dash/pkg/view/component"
	"github.com/pkg/errors"
	"k8s.io/apimachinery/pkg/runtime"
)

// ClientFactory is a factory for creating clients.
type ClientFactory interface {
	// Init initializes a client.
	Init(ctx context.Context, cmd string) Client
}

// DefaultClientFactory is the default client factory
type DefaultClientFactory struct{}

var _ ClientFactory = (*DefaultClientFactory)(nil)

// NewDefaultClientFactory creates an instance of DefaultClientFactory.
func NewDefaultClientFactory() *DefaultClientFactory {
	return &DefaultClientFactory{}
}

// Init creates a new client.
func (f *DefaultClientFactory) Init(ctx context.Context, cmd string) Client {
	loggerAdapter := &zapAdapter{
		dashLogger: log.From(ctx),
	}

	return plugin.NewClient(&plugin.ClientConfig{
		HandshakeConfig: Handshake,
		Plugins:         pluginMap,
		Cmd:             exec.Command(cmd),
		AllowedProtocols: []plugin.Protocol{
			plugin.ProtocolGRPC,
		},
		Logger: loggerAdapter,
	})
}

// Client is an interface that describes a plugin client.
type Client interface {
	Client() (plugin.ClientProtocol, error)
	Kill()
}

// ManagerStore is the data store for Manager.
type ManagerStore interface {
	Store(name string, client Client, metadata Metadata)
	GetMetadata(name string) (Metadata, error)
	GetService(name string) (Service, error)
	Clients() map[string]Client
	ClientNames() []string
}

// DefaultStore is the default implement of ManagerStore.
type DefaultStore struct {
	clients  map[string]Client
	metadata map[string]Metadata
}

var _ ManagerStore = (*DefaultStore)(nil)

// NewDefaultStore creates an instance of DefaultStore.
func NewDefaultStore() *DefaultStore {
	return &DefaultStore{
		clients:  make(map[string]Client),
		metadata: make(map[string]Metadata),
	}
}

// Store stores information for a plugin.
func (s *DefaultStore) Store(name string, client Client, metadata Metadata) {
	s.clients[name] = client
	s.metadata[name] = metadata
}

// GetService gets the service for a plugin.
func (s *DefaultStore) GetService(name string) (Service, error) {
	client, ok := s.clients[name]
	if !ok {
		return nil, errors.Errorf("plugin %q doesn't have a client", name)
	}

	rpcClient, err := client.Client()
	if err != nil {
		return nil, err
	}

	raw, err := rpcClient.Dispense("plugin")
	if err != nil {
		return nil, errors.Wrapf(err, "dispensing plugin for %q", name)
	}

	service, ok := raw.(Service)
	if !ok {
		return nil, errors.Errorf("unknown type for plugin %q: %T", name, raw)
	}

	return service, nil
}

// GetMetadata gets the metadata for a plugin.
func (s *DefaultStore) GetMetadata(name string) (Metadata, error) {
	metadata, ok := s.metadata[name]
	if !ok {
		return Metadata{}, errors.Errorf("plugin %q doesn't have metadata", name)
	}

	return metadata, nil
}

// Clients returns all the clients in the store.
func (s *DefaultStore) Clients() map[string]Client {
	return s.clients
}

// ClientNames returns the client names in the store.
func (s *DefaultStore) ClientNames() []string {
	var list []string
	for name := range s.Clients() {
		list = append(list, name)
	}
	return list
}

type config struct {
	cmd  string
	name string
}

// ManagerOption is an option for configuring Manager.
type ManagerOption func(*Manager)

// Manager manages plugins
type Manager struct {
	PortForwarder portforward.PortForwarder
	API           api.API
	Store         ManagerStore
	ClientFactory ClientFactory

	Runners Runners

	configs []config

	lock sync.Mutex
}

// NewManager creates an instance of Manager.
func NewManager(apiService api.API, options ...ManagerOption) *Manager {
	m := &Manager{
		Store:         NewDefaultStore(),
		ClientFactory: NewDefaultClientFactory(),
		Runners:       newDefaultRunners(),
		API:           apiService,
	}

	for _, option := range options {
		option(m)
	}

	return m
}

// Load loads a plugin.
func (m *Manager) Load(cmd string) error {
	m.lock.Lock()
	defer m.lock.Unlock()

	name := filepath.Base(cmd)

	for _, c := range m.configs {
		if name == c.name {
			return errors.Errorf("tried to load plugin %q more than once", name)
		}
	}

	c := config{
		name: name,
		cmd:  cmd,
	}

	m.configs = append(m.configs, c)

	return nil
}

// Start stars all plugins.
func (m *Manager) Start(ctx context.Context) error {
	if m.Store == nil {
		return errors.New("manager store is nil")
	}

	if m.ClientFactory == nil {
		return errors.New("manager client factory is nil")
	}

	if err := m.API.Start(ctx); err != nil {
		return errors.Wrap(err, "start api service")
	}

	logger := log.From(ctx)
	logger.With("addr", m.API.Addr()).Debugf("starting plugin api service")

	m.lock.Lock()
	defer m.lock.Unlock()

	for i := range m.configs {
		c := m.configs[i]

		pluginLogger := logger.With("plugin-name", c.name)

		client := m.ClientFactory.Init(ctx, c.cmd)

		rpcClient, err := client.Client()
		if err != nil {
			return errors.Wrapf(err, "get rpc client for %q", c.name)
		}

		raw, err := rpcClient.Dispense("plugin")
		if err != nil {
			return errors.Wrapf(err, "dispensing plugin for %q", c.name)
		}

		p, ok := raw.(Service)
		if !ok {
			return errors.Errorf("unknown type for plugin %q: %T", c.name, raw)
		}

		metadata, err := p.Register(m.API.Addr())
		if err != nil {
			return errors.Wrapf(err, "register plugin %q", c.name)
		}

		m.Store.Store(c.name, client, metadata)

		pluginLogger.With(
			"cmd", c.cmd,
			"metadata", metadata,
		).Infof("registered plugin %q", metadata.Name)
	}

	return nil
}

// Stop stops all plugins.
func (m *Manager) Stop(ctx context.Context) {
	logger := log.From(ctx)

	m.lock.Lock()
	defer m.lock.Unlock()

	for name, client := range m.Store.Clients() {
		logger.With("plugin-name", name).Debugf("stopping plugin")
		client.Kill()
	}
}

// Print prints an object with plugins which are configured to print the objects's
// GVK.
func (m *Manager) Print(object runtime.Object) (*PrintResponse, error) {
	if m.Runners == nil {
		return nil, errors.New("runners is nil")
	}

	runner, ch := m.Runners.Print(m.Store)
	done := make(chan bool)

	var pr PrintResponse

	go func() {
		for resp := range ch {
			pr.Config = append(pr.Config, resp.Config...)
			pr.Status = append(pr.Status, resp.Status...)
			pr.Items = append(pr.Items, resp.Items...)
		}

		done <- true
	}()

	if err := runner.Run(object, m.Store.ClientNames()); err != nil {
		return nil, err
	}
	close(ch)

	<-done
	return &pr, nil
}

// Tabs queries plugins for tabs for an object.
func (m *Manager) Tabs(object runtime.Object) ([]component.Tab, error) {
	if m.Runners == nil {
		return nil, errors.New("runners is nil")
	}

	runner, ch := m.Runners.Tab(m.Store)
	done := make(chan bool)

	var tabs []component.Tab

	go func() {
		for tab := range ch {
			tabs = append(tabs, tab)
		}

		done <- true
	}()

	if err := runner.Run(object, m.Store.ClientNames()); err != nil {
		return nil, err
	}

	close(ch)
	<-done

	sort.Slice(tabs, func(i, j int) bool {
		return tabs[i].Name < tabs[j].Name
	})

	return tabs, nil
}
<|endoftext|>


<commit_before>/*
Package portping provides simple functions to ping TCP ports.
It also includes a simple command line interface.
 */
package portping

import (
	"net"
	"time"
)

// Ping connects to the address on the named network,
// using net.DialTimeout. If a successful connection is made,
// return nil. Otherwise return the error.
func Ping(network, address string, timeout time.Duration) error {
	conn, err := net.DialTimeout(network, address, timeout)
	if conn != nil {
		defer conn.Close()
	}
	return err
}

// PingN calls Ping the specified number of times,
// and sends the results to the given channel.
func PingN(network, address string, timeout time.Duration, count int, c chan <- error) {
	for i := 0; i < count; i++ {
		c <- Ping(network, address, timeout)
	}
}
<commit_msg>adjusted doc string<commit_after>/*
Package portping provides simple functions to ping TCP ports.
It also includes a simple command line interface.
 */
package portping

import (
	"net"
	"time"
)

// Ping connects to the address on the named network,
// using net.DialTimeout, and immediately closes it.
// It returns the connection error. A nil value means success.
// For examples of valid values of network and address,
// see the documentation of net.Dial
func Ping(network, address string, timeout time.Duration) error {
	conn, err := net.DialTimeout(network, address, timeout)
	if conn != nil {
		defer conn.Close()
	}
	return err
}

// PingN calls Ping the specified number of times,
// and sends the results to the given channel.
func PingN(network, address string, timeout time.Duration, count int, c chan <- error) {
	for i := 0; i < count; i++ {
		c <- Ping(network, address, timeout)
	}
}
<|endoftext|>


<commit_before>package sourcemap

import (
	"encoding/json"
	"fmt"
	"net/url"
	"path"
	"sort"
)

type sourceMap struct {
	Version        int           `json:"version"`
	File           string        `json:"file"`
	SourceRoot     string        `json:"sourceRoot"`
	Sources        []string      `json:"sources"`
	SourcesContent []string      `json:"sourcesContent"`
	Names          []json.Number `json:"names"`
	Mappings       string        `json:"mappings"`

	mappings []mapping
}

type v3 struct {
	sourceMap
	Sections []section `json:"sections"`
}

func (m *sourceMap) parse(mapURL string) error {
	if err := checkVersion(m.Version); err != nil {
		return err
	}

	var sourceRootURL *url.URL
	if m.SourceRoot != "" {
		u, err := url.Parse(m.SourceRoot)
		if err != nil {
			return err
		}
		if u.IsAbs() {
			sourceRootURL = u
		}
	} else if mapURL != "" {
		u, err := url.Parse(mapURL)
		if err != nil {
			return err
		}
		if u.IsAbs() {
			u.Path = path.Dir(u.Path)
			sourceRootURL = u
		}
	}

	for i, src := range m.Sources {
		m.Sources[i] = m.absSource(sourceRootURL, src)
	}

	mappings, err := parseMappings(m.Mappings)
	if err != nil {
		return err
	}

	m.mappings = mappings
	// Free memory.
	m.Mappings = ""

	return nil
}

func (m *sourceMap) absSource(root *url.URL, source string) string {
	if path.IsAbs(source) {
		return source
	}

	if u, err := url.Parse(source); err == nil && u.IsAbs() {
		return source
	}

	if root != nil {
		u := *root
		u.Path = path.Join(u.Path, source)
		return u.String()
	}

	if m.SourceRoot != "" {
		return path.Join(m.SourceRoot, source)
	}

	return source
}

type section struct {
	Offset struct {
		Line   int `json:"line"`
		Column int `json:"column"`
	} `json:"offset"`
	Map *sourceMap `json:"map"`
}

type Consumer struct {
	file     string
	sections []section
}

func Parse(mapURL string, b []byte) (*Consumer, error) {
	v3 := new(v3)
	err := json.Unmarshal(b, v3)
	if err != nil {
		return nil, err
	}

	if err := checkVersion(v3.Version); err != nil {
		return nil, err
	}

	if len(v3.Sections) == 0 {
		v3.Sections = append(v3.Sections, section{
			Map: &v3.sourceMap,
		})
	}

	for _, s := range v3.Sections {
		err := s.Map.parse(mapURL)
		if err != nil {
			return nil, err
		}
	}

	reverse(v3.Sections)
	return &Consumer{
		file:     v3.File,
		sections: v3.Sections,
	}, nil
}

// File returns an optional name of the generated code
// that this source map is associated with.
func (c *Consumer) File() string {
	return c.file
}

// Source returns the original source, name, line, and column information
// for the generated source's line and column positions.
func (c *Consumer) Source(
	genLine, genColumn int,
) (source, name string, line, column int, ok bool) {
	for i := range c.sections {
		s := &c.sections[i]
		if s.Offset.Line < genLine ||
			(s.Offset.Line+1 == genLine && s.Offset.Column <= genColumn) {
			genLine -= s.Offset.Line
			genColumn -= s.Offset.Column
			return c.source(s.Map, genLine, genColumn)
		}
	}
	return
}

func (c *Consumer) source(
	m *sourceMap, genLine, genColumn int,
) (source, name string, line, column int, ok bool) {
	i := sort.Search(len(m.mappings), func(i int) bool {
		m := &m.mappings[i]
		if int(m.genLine) == genLine {
			return int(m.genColumn) >= genColumn
		}
		return int(m.genLine) >= genLine
	})

	// Mapping not found.
	if i == len(m.mappings) {
		return
	}

	match := &m.mappings[i]

	// Fuzzy match.
	if int(match.genLine) > genLine || int(match.genColumn) > genColumn {
		if i == 0 {
			return
		}
		match = &m.mappings[i-1]
	}

	if match.sourcesInd >= 0 {
		source = m.Sources[match.sourcesInd]
	}
	if match.namesInd >= 0 {
		name = string(m.Names[match.namesInd])
	}
	line = int(match.sourceLine)
	column = int(match.sourceColumn)
	ok = true
	return
}

// SourceContent returns the original source content for the source.
func (c *Consumer) SourceContent(source string) string {
	for i := range c.sections {
		s := &c.sections[i]
		for i, src := range s.Map.Sources {
			if src == source {
				if i < len(s.Map.SourcesContent) {
					return s.Map.SourcesContent[i]
				}
				break
			}
		}
	}
	return ""
}

func checkVersion(version int) error {
	if version == 3 || version == 0 {
		return nil
	}
	return fmt.Errorf(
		"sourcemap: got version=%d, but only 3rd version is supported",
		version,
	)
}

func reverse(ss []section) {
	last := len(ss) - 1
	for i := 0; i < len(ss)/2; i++ {
		ss[i], ss[last-i] = ss[last-i], ss[i]
	}
}
<commit_msg>Add Consumer.SourcemapURL<commit_after>package sourcemap

import (
	"encoding/json"
	"fmt"
	"net/url"
	"path"
	"sort"
)

type sourceMap struct {
	Version        int           `json:"version"`
	File           string        `json:"file"`
	SourceRoot     string        `json:"sourceRoot"`
	Sources        []string      `json:"sources"`
	SourcesContent []string      `json:"sourcesContent"`
	Names          []json.Number `json:"names"`
	Mappings       string        `json:"mappings"`

	mappings []mapping
}

type v3 struct {
	sourceMap
	Sections []section `json:"sections"`
}

func (m *sourceMap) parse(sourcemapURL string) error {
	if err := checkVersion(m.Version); err != nil {
		return err
	}

	var sourceRootURL *url.URL
	if m.SourceRoot != "" {
		u, err := url.Parse(m.SourceRoot)
		if err != nil {
			return err
		}
		if u.IsAbs() {
			sourceRootURL = u
		}
	} else if sourcemapURL != "" {
		u, err := url.Parse(sourcemapURL)
		if err != nil {
			return err
		}
		if u.IsAbs() {
			u.Path = path.Dir(u.Path)
			sourceRootURL = u
		}
	}

	for i, src := range m.Sources {
		m.Sources[i] = m.absSource(sourceRootURL, src)
	}

	mappings, err := parseMappings(m.Mappings)
	if err != nil {
		return err
	}

	m.mappings = mappings
	// Free memory.
	m.Mappings = ""

	return nil
}

func (m *sourceMap) absSource(root *url.URL, source string) string {
	if path.IsAbs(source) {
		return source
	}

	if u, err := url.Parse(source); err == nil && u.IsAbs() {
		return source
	}

	if root != nil {
		u := *root
		u.Path = path.Join(u.Path, source)
		return u.String()
	}

	if m.SourceRoot != "" {
		return path.Join(m.SourceRoot, source)
	}

	return source
}

type section struct {
	Offset struct {
		Line   int `json:"line"`
		Column int `json:"column"`
	} `json:"offset"`
	Map *sourceMap `json:"map"`
}

type Consumer struct {
	sourcemapURL string
	file         string
	sections     []section
}

func Parse(sourcemapURL string, b []byte) (*Consumer, error) {
	v3 := new(v3)
	err := json.Unmarshal(b, v3)
	if err != nil {
		return nil, err
	}

	if err := checkVersion(v3.Version); err != nil {
		return nil, err
	}

	if len(v3.Sections) == 0 {
		v3.Sections = append(v3.Sections, section{
			Map: &v3.sourceMap,
		})
	}

	for _, s := range v3.Sections {
		err := s.Map.parse(sourcemapURL)
		if err != nil {
			return nil, err
		}
	}

	reverse(v3.Sections)
	return &Consumer{
		sourcemapURL: sourcemapURL,
		file:         v3.File,
		sections:     v3.Sections,
	}, nil
}

func (c *Consumer) SourcemapURL() string {
	return c.sourcemapURL
}

// File returns an optional name of the generated code
// that this source map is associated with.
func (c *Consumer) File() string {
	return c.file
}

// Source returns the original source, name, line, and column information
// for the generated source's line and column positions.
func (c *Consumer) Source(
	genLine, genColumn int,
) (source, name string, line, column int, ok bool) {
	for i := range c.sections {
		s := &c.sections[i]
		if s.Offset.Line < genLine ||
			(s.Offset.Line+1 == genLine && s.Offset.Column <= genColumn) {
			genLine -= s.Offset.Line
			genColumn -= s.Offset.Column
			return c.source(s.Map, genLine, genColumn)
		}
	}
	return
}

func (c *Consumer) source(
	m *sourceMap, genLine, genColumn int,
) (source, name string, line, column int, ok bool) {
	i := sort.Search(len(m.mappings), func(i int) bool {
		m := &m.mappings[i]
		if int(m.genLine) == genLine {
			return int(m.genColumn) >= genColumn
		}
		return int(m.genLine) >= genLine
	})

	// Mapping not found.
	if i == len(m.mappings) {
		return
	}

	match := &m.mappings[i]

	// Fuzzy match.
	if int(match.genLine) > genLine || int(match.genColumn) > genColumn {
		if i == 0 {
			return
		}
		match = &m.mappings[i-1]
	}

	if match.sourcesInd >= 0 {
		source = m.Sources[match.sourcesInd]
	}
	if match.namesInd >= 0 {
		name = string(m.Names[match.namesInd])
	}
	line = int(match.sourceLine)
	column = int(match.sourceColumn)
	ok = true
	return
}

// SourceContent returns the original source content for the source.
func (c *Consumer) SourceContent(source string) string {
	for i := range c.sections {
		s := &c.sections[i]
		for i, src := range s.Map.Sources {
			if src == source {
				if i < len(s.Map.SourcesContent) {
					return s.Map.SourcesContent[i]
				}
				break
			}
		}
	}
	return ""
}

func checkVersion(version int) error {
	if version == 3 || version == 0 {
		return nil
	}
	return fmt.Errorf(
		"sourcemap: got version=%d, but only 3rd version is supported",
		version,
	)
}

func reverse(ss []section) {
	last := len(ss) - 1
	for i := 0; i < len(ss)/2; i++ {
		ss[i], ss[last-i] = ss[last-i], ss[i]
	}
}
<|endoftext|>