From 569e8142a727f7572deb24d35726873125f616ab Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Thu, 6 Sep 2018 19:36:22 +0100 Subject: [PATCH 1/3] Run gofmt. --- data_reader.go | 15 +++------ echo_handler.go | 4 --- standard_caller.go | 11 +------ telsh/telnet_handler.go | 67 ++++++++++++++--------------------------- 4 files changed, 28 insertions(+), 69 deletions(-) diff --git a/data_reader.go b/data_reader.go index 6367df1..0d0422a 100644 --- a/data_reader.go +++ b/data_reader.go @@ -1,18 +1,15 @@ package telnet - import ( "bufio" "errors" "io" ) - var ( errCorrupted = errors.New("Corrupted") ) - // An internalDataReader deals with "un-escaping" according to the TELNET protocol. // // In the TELNET protocol byte value 255 is special. @@ -57,23 +54,21 @@ var ( // []byte{1, 55, 2, 155, 3, 255, 4, 40, 255, 30, 20} type internalDataReader struct { wrapped io.Reader - buffered *bufio.Reader + buffered *bufio.Reader } - // newDataReader creates a new DataReader reading from 'r'. func newDataReader(r io.Reader) *internalDataReader { buffered := bufio.NewReader(r) reader := internalDataReader{ - wrapped:r, - buffered:buffered, + wrapped: r, + buffered: buffered, } return &reader } - // Read reads the TELNET escaped data from the wrapped io.Reader, and "un-escapes" it into 'data'. func (r *internalDataReader) Read(data []byte) (n int, err error) { @@ -84,7 +79,7 @@ func (r *internalDataReader) Read(data []byte) (n int, err error) { const WILL = 251 const WONT = 252 - const DO = 253 + const DO = 253 const DONT = 254 p := data @@ -157,7 +152,7 @@ func (r *internalDataReader) Read(data []byte) (n int, err error) { } default: // If we get in here, this is not following the TELNET protocol. -//@TODO: Make a better error. + //@TODO: Make a better error. err = errCorrupted return n, err } diff --git a/echo_handler.go b/echo_handler.go index 0401009..8a45750 100644 --- a/echo_handler.go +++ b/echo_handler.go @@ -1,19 +1,15 @@ package telnet - import ( "github.com/reiver/go-oi" ) - // EchoHandler is a simple TELNET server which "echos" back to the client any (non-command) // data back to the TELNET client, it received from the TELNET client. var EchoHandler Handler = internalEchoHandler{} - type internalEchoHandler struct{} - func (handler internalEchoHandler) ServeTELNET(ctx Context, w Writer, r Reader) { var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up. diff --git a/standard_caller.go b/standard_caller.go index 17a9408..30a96b4 100644 --- a/standard_caller.go +++ b/standard_caller.go @@ -1,6 +1,5 @@ package telnet - import ( "github.com/reiver/go-oi" @@ -13,21 +12,17 @@ import ( "time" ) - // StandardCaller is a simple TELNET client which sends to the server any data it gets from os.Stdin // as TELNET (and TELNETS) data, and writes any TELNET (or TELNETS) data it receives from // the server to os.Stdout, and writes any error it has to os.Stderr. var StandardCaller Caller = internalStandardCaller{} - type internalStandardCaller struct{} - func (caller internalStandardCaller) CallTELNET(ctx Context, w Writer, r Reader) { standardCallerCallTELNET(os.Stdin, os.Stdout, os.Stderr, ctx, w, r) } - func standardCallerCallTELNET(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, ctx Context, w Writer, r Reader) { go func(writer io.Writer, reader io.Reader) { @@ -48,12 +43,10 @@ func standardCallerCallTELNET(stdin io.ReadCloser, stdout io.WriteCloser, stderr } }(stdout, r) - - var buffer bytes.Buffer var p []byte - var crlfBuffer [2]byte = [2]byte{'\r','\n'} + var crlfBuffer [2]byte = [2]byte{'\r', '\n'} crlf := crlfBuffer[:] scanner := bufio.NewScanner(stdin) @@ -75,7 +68,6 @@ func standardCallerCallTELNET(stdin io.ReadCloser, stdout io.WriteCloser, stderr return } - buffer.Reset() } @@ -83,7 +75,6 @@ func standardCallerCallTELNET(stdin io.ReadCloser, stdout io.WriteCloser, stderr time.Sleep(3 * time.Millisecond) } - func scannerSplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) { if atEOF { return 0, nil, nil diff --git a/telsh/telnet_handler.go b/telsh/telnet_handler.go index ca08c7b..1c9fbad 100644 --- a/telsh/telnet_handler.go +++ b/telsh/telnet_handler.go @@ -1,6 +1,5 @@ package telsh - import ( "github.com/reiver/go-oi" "github.com/reiver/go-telnet" @@ -11,7 +10,6 @@ import ( "sync" ) - const ( defaultExitCommandName = "exit" defaultPrompt = "ยง " @@ -19,10 +17,9 @@ const ( defaultExitMessage = "\r\nGoodbye!\r\n" ) - type ShellHandler struct { - muxtex sync.RWMutex - producers map[string]Producer + muxtex sync.RWMutex + producers map[string]Producer elseProducer Producer ExitCommandName string @@ -31,12 +28,11 @@ type ShellHandler struct { ExitMessage string } - func NewShellHandler() *ShellHandler { producers := map[string]Producer{} telnetHandler := ShellHandler{ - producers:producers, + producers: producers, Prompt: defaultPrompt, ExitCommandName: defaultExitCommandName, @@ -47,7 +43,6 @@ func NewShellHandler() *ShellHandler { return &telnetHandler } - func (telnetHandler *ShellHandler) Register(name string, producer Producer) error { telnetHandler.muxtex.Lock() @@ -65,7 +60,6 @@ func (telnetHandler *ShellHandler) MustRegister(name string, producer Producer) return telnetHandler } - func (telnetHandler *ShellHandler) RegisterHandlerFunc(name string, handlerFunc HandlerFunc) error { produce := func(ctx telnet.Context, name string, args ...string) Handler { @@ -85,7 +79,6 @@ func (telnetHandler *ShellHandler) MustRegisterHandlerFunc(name string, handlerF return telnetHandler } - func (telnetHandler *ShellHandler) RegisterElse(producer Producer) error { telnetHandler.muxtex.Lock() @@ -97,13 +90,12 @@ func (telnetHandler *ShellHandler) RegisterElse(producer Producer) error { func (telnetHandler *ShellHandler) MustRegisterElse(producer Producer) *ShellHandler { if err := telnetHandler.RegisterElse(producer); nil != err { - panic(err) + panic(err) } return telnetHandler } - func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet.Writer, reader telnet.Reader) { logger := ctx.Logger() @@ -111,23 +103,20 @@ func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet logger = internalDiscardLogger{} } - colonSpaceCommandNotFoundEL := []byte(": command not found\r\n") - - var prompt bytes.Buffer + var prompt bytes.Buffer var exitCommandName string - var welcomeMessage string - var exitMessage string + var welcomeMessage string + var exitMessage string prompt.WriteString(telnetHandler.Prompt) - promptBytes := prompt.Bytes() + promptBytes := prompt.Bytes() exitCommandName = telnetHandler.ExitCommandName - welcomeMessage = telnetHandler.WelcomeMessage - exitMessage = telnetHandler.ExitMessage - + welcomeMessage = telnetHandler.WelcomeMessage + exitMessage = telnetHandler.ExitMessage if _, err := oi.LongWriteString(writer, welcomeMessage); nil != err { logger.Errorf("Problem long writing welcome message: %v", err) @@ -140,7 +129,6 @@ func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet } logger.Debugf("Wrote prompt: %q.", promptBytes) - var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up. p := buffer[:] @@ -155,11 +143,9 @@ func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet break } - line.WriteByte(p[0]) //logger.Tracef("Received: %q (%d).", p[0], p[0]) - if '\n' == p[0] { lineString := line.String() @@ -171,8 +157,7 @@ func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet continue } - -//@TODO: support piping. + //@TODO: support piping. fields := strings.Fields(lineString) logger.Debugf("Have %d tokens.", len(fields)) logger.Tracef("Tokens: %v", fields) @@ -184,7 +169,6 @@ func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet continue } - field0 := fields[0] if exitCommandName == field0 { @@ -192,7 +176,6 @@ func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet return } - var producer Producer telnetHandler.muxtex.RLock() @@ -207,7 +190,7 @@ func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet } if nil == producer { -//@TODO: Don't convert that to []byte! think this creates "garbage" (for collector). + //@TODO: Don't convert that to []byte! think this creates "garbage" (for collector). oi.LongWrite(writer, []byte(field0)) oi.LongWrite(writer, colonSpaceCommandNotFoundEL) line.Reset() @@ -220,35 +203,33 @@ func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet handler := producer.Produce(ctx, field0, fields[1:]...) if nil == handler { oi.LongWrite(writer, []byte(field0)) -//@TODO: Need to use a different error message. + //@TODO: Need to use a different error message. oi.LongWrite(writer, colonSpaceCommandNotFoundEL) line.Reset() oi.LongWrite(writer, promptBytes) continue } -//@TODO: Wire up the stdin, stdout, stderr of the handler. + //@TODO: Wire up the stdin, stdout, stderr of the handler. if stdoutPipe, err := handler.StdoutPipe(); nil != err { -//@TODO: + //@TODO: } else if nil == stdoutPipe { -//@TODO: + //@TODO: } else { connect(ctx, writer, stdoutPipe) } - if stderrPipe, err := handler.StderrPipe(); nil != err { -//@TODO: + //@TODO: } else if nil == stderrPipe { -//@TODO: + //@TODO: } else { connect(ctx, writer, stderrPipe) } - if err := handler.Run(); nil != err { -//@TODO: + //@TODO: } line.Reset() if _, err := oi.LongWrite(writer, promptBytes); nil != err { @@ -256,25 +237,21 @@ func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet } } - -//@TODO: Are there any special errors we should be dealing with separately? + //@TODO: Are there any special errors we should be dealing with separately? if nil != err { break } } - oi.LongWriteString(writer, exitMessage) return } - - func connect(ctx telnet.Context, writer io.Writer, reader io.Reader) { logger := ctx.Logger() - go func(logger telnet.Logger){ + go func(logger telnet.Logger) { var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up. p := buffer[:] @@ -289,7 +266,7 @@ func connect(ctx telnet.Context, writer io.Writer, reader io.Reader) { } //logger.Tracef("Sending: %q.", p) -//@TODO: Should we be checking for errors? + //@TODO: Should we be checking for errors? oi.LongWrite(writer, p) //logger.Tracef("Sent: %q.", p) } From 330ec0bc6843c512f2e25a93f1b13fa21bd62dd5 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Thu, 6 Sep 2018 21:22:22 +0100 Subject: [PATCH 2/3] Replace bufio-based data reader with a state machine. This state machine guarantees that at most one Read will be issued to the underlying reader for every internalDataReader Read call. Preserving these semantics stops internalDataReader from blocking when the provided read buffer is larger than the amount of data available to read from the underlying reader. --- data_reader.go | 279 +++++++++++++++++++++++++++++++------------------ 1 file changed, 178 insertions(+), 101 deletions(-) diff --git a/data_reader.go b/data_reader.go index 0d0422a..7ea5618 100644 --- a/data_reader.go +++ b/data_reader.go @@ -1,8 +1,9 @@ package telnet import ( - "bufio" + "bytes" "errors" + "fmt" "io" ) @@ -10,6 +11,18 @@ var ( errCorrupted = errors.New("Corrupted") ) +const ( + IAC = 255 + + SB = 250 + SE = 240 + + WILL = 251 + WONT = 252 + DO = 253 + DONT = 254 +) + // An internalDataReader deals with "un-escaping" according to the TELNET protocol. // // In the TELNET protocol byte value 255 is special. @@ -53,116 +66,180 @@ var ( // // []byte{1, 55, 2, 155, 3, 255, 4, 40, 255, 30, 20} type internalDataReader struct { - wrapped io.Reader - buffered *bufio.Reader + wrapped io.Reader + state state } // newDataReader creates a new DataReader reading from 'r'. func newDataReader(r io.Reader) *internalDataReader { - buffered := bufio.NewReader(r) - reader := internalDataReader{ - wrapped: r, - buffered: buffered, + wrapped: r, + state: copyData, } return &reader } -// Read reads the TELNET escaped data from the wrapped io.Reader, and "un-escapes" it into 'data'. -func (r *internalDataReader) Read(data []byte) (n int, err error) { - - const IAC = 255 - - const SB = 250 - const SE = 240 - - const WILL = 251 - const WONT = 252 - const DO = 253 - const DONT = 254 - - p := data - - for len(p) > 0 { - var b byte - - b, err = r.buffered.ReadByte() - if nil != err { - return n, err - } - - if IAC == b { - var peeked []byte - - peeked, err = r.buffered.Peek(1) - if nil != err { - return n, err - } - - switch peeked[0] { - case WILL, WONT, DO, DONT: - _, err = r.buffered.Discard(2) - if nil != err { - return n, err - } - case IAC: - p[0] = IAC - n++ - p = p[1:] - - _, err = r.buffered.Discard(1) - if nil != err { - return n, err - } - case SB: - for { - var b2 byte - b2, err = r.buffered.ReadByte() - if nil != err { - return n, err - } - - if IAC == b2 { - peeked, err = r.buffered.Peek(1) - if nil != err { - return n, err - } - - if IAC == peeked[0] { - _, err = r.buffered.Discard(1) - if nil != err { - return n, err - } - } - - if SE == peeked[0] { - _, err = r.buffered.Discard(1) - if nil != err { - return n, err - } - break - } - } - } - case SE: - _, err = r.buffered.Discard(1) - if nil != err { - return n, err - } - default: - // If we get in here, this is not following the TELNET protocol. - //@TODO: Make a better error. - err = errCorrupted - return n, err - } - } else { - - p[0] = b - n++ - p = p[1:] - } +// Read reads the TELNET escaped data from the wrapped io.Reader, and "un-escapes" it into 'data'. +// It executes exactly one Read on the underlying reader every time it is called. +// Callers should be careful to truncate data to the number of bytes read, +// since this reader is expected to drop bytes from the underlying reader +// as described above when required to by the TELNET protocol. +func (r *internalDataReader) Read(data []byte) (int, error) { + mach := &machine{ + from: make([]byte, len(data)), + to: data, } + n, err := r.wrapped.Read(mach.from) + if err != nil { + return 0, err + } + mach.from = mach.from[:n] + for err == nil && mach.InputRemaining() { + r.state, err = r.state(mach) + } + return mach.written, err +} + +// Unescaping of data read from the underlying reader is done using +// a state machine so it is resumable across reads. +type machine struct { + from, to []byte + read, written int +} - return n, nil +// Index returns the offset from the read pointer of the first occurrence +// of the byte b, or -1 if that byte is not found in the remainder of the +// data read from the underlying reader. +func (m *machine) Index(b byte) int { + return bytes.Index(m.from[m.read:], []byte{b}) +} + +// Copy copies up to n bytes from the underlying reader to the destination +// buffer, advancing both the read and write pointers by this amount. +func (m *machine) Copy(n int) { + // Deliberately no bounds check here, because asking this + // code to read past the end of m.from should never happen. + copied := copy(m.to[m.written:], m.from[m.read:m.read+n]) + m.written += copied + m.read += copied +} + +// WriteByte writes the provided byte to the destnation buffer and advances +// the write pointer. +func (m *machine) WriteByte(b byte) { + m.to[m.written] = b + m.written++ +} + +// ConsumeByte reads and returns the next byte from the underlying reader, +// advancing the read pointer. +func (m *machine) ConsumeByte() byte { + b := m.from[m.read] + m.read++ + return b +} + +// InputRemaining returns true as long as there is still data available +// to read from the input buffer. +func (m *machine) InputRemaining() bool { + if m.read >= len(m.from) { + return false + } + return true +} + +// State machine states are functions that take the machine and +// return new states and optionally errors. +type state func(*machine) (state, error) + +// The copyData state copies data from machine.from to machine.to +// until it encounters an IAC byte or the end of from. +func copyData(mach *machine) (state, error) { + idx := mach.Index(IAC) + if idx < 0 { + // No escape bytes, so just copy remaining data and return. + mach.Copy(len(mach.from) - mach.read) + return copyData, nil + } + // Copy data up to IAC. + mach.Copy(idx) + return consumeIAC, nil +} + +// The consumeIAC state eats an IAC byte and returns consumeCmd. +func consumeIAC(mach *machine) (state, error) { + if b := mach.ConsumeByte(); b != IAC { + return copyData, fmt.Errorf("expected IAC byte, got %c", b) + } + return consumeCmd, nil +} + +// The consumeCmd state eats one of the known telnet command bytes. +func consumeCmd(mach *machine) (state, error) { + switch b := mach.ConsumeByte(); b { + case WILL, WONT, DO, DONT: + // WILL, WONT, DO and DONT have an extra command byte + // that shouldn't make it to the output slice. + // We need to consume it before going back to copying data. + return consumeWWDD, nil + case IAC: + // IAC IAC => un-escape; write IAC to output + // and go back to copying data. + mach.WriteByte(IAC) + return copyData, nil + case SB: + // IAC SB => switch to consuming status. + return consumeStatus, nil + case SE: + // IAC SE => go back to copying data. + return copyData, nil + default: + // IAC is a protocol error. + return copyData, fmt.Errorf("expected command byte, got %c", b) + } +} + +// The consumeWWDD state eats one byte then resumes copying data. +func consumeWWDD(mach *machine) (state, error) { + mach.ConsumeByte() + return copyData, nil +} + +// The consumeStatus state eats data until it encounters an IAC +// byte or the end of from. +func consumeStatus(mach *machine) (state, error) { + // We don't try to understand the status commands, + // we just strip them from the output, which means + // dropping input data until we read IAC SE. + idx := mach.Index(IAC) + if idx < 0 { + // No escape bytes, so just skip remaining data and return. + mach.read = len(mach.from) + return consumeStatus, nil + } + // Skip up to IAC. + mach.read += idx + return consumeStatusIAC, nil +} + +// The consumeStatusIAC state eats an IAC byte and returns consumeStatusCmd. +func consumeStatusIAC(mach *machine) (state, error) { + if b := mach.ConsumeByte(); b != IAC { + return consumeStatus, fmt.Errorf("expected IAC byte, got %c", b) + } + return consumeStatusCmd, nil +} + +// The consumeStatusCmd state eats a byte. If that byte is SE the machine +// goes back to copying data, otherwise it goes back to consuming status. +func consumeStatusCmd(mach *machine) (state, error) { + switch b := mach.ConsumeByte(); b { + case SE: + // IAC SE => go back to copying data normally + return copyData, nil + default: + // IAC => continue eating SB + return consumeStatus, nil + } } From 514a30bc07f4778038dbb946b30c3700a74d6c8b Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Thu, 6 Sep 2018 22:05:07 +0100 Subject: [PATCH 3/3] Modify code to use bufio and io.Copy where appropriate. --- echo_handler.go | 20 +---- standard_caller.go | 16 +--- telsh/telnet_handler.go | 190 +++++++++++++++------------------------- 3 files changed, 76 insertions(+), 150 deletions(-) diff --git a/echo_handler.go b/echo_handler.go index 8a45750..57c26cf 100644 --- a/echo_handler.go +++ b/echo_handler.go @@ -1,8 +1,6 @@ package telnet -import ( - "github.com/reiver/go-oi" -) +import "io" // EchoHandler is a simple TELNET server which "echos" back to the client any (non-command) // data back to the TELNET client, it received from the TELNET client. @@ -11,19 +9,5 @@ var EchoHandler Handler = internalEchoHandler{} type internalEchoHandler struct{} func (handler internalEchoHandler) ServeTELNET(ctx Context, w Writer, r Reader) { - - var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up. - p := buffer[:] - - for { - n, err := r.Read(p) - - if n > 0 { - oi.LongWrite(w, p[:n]) - } - - if nil != err { - break - } - } + io.Copy(w, r) } diff --git a/standard_caller.go b/standard_caller.go index 30a96b4..5db4e49 100644 --- a/standard_caller.go +++ b/standard_caller.go @@ -26,21 +26,7 @@ func (caller internalStandardCaller) CallTELNET(ctx Context, w Writer, r Reader) func standardCallerCallTELNET(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, ctx Context, w Writer, r Reader) { go func(writer io.Writer, reader io.Reader) { - - var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up. - p := buffer[:] - - for { - // Read 1 byte. - n, err := reader.Read(p) - if n <= 0 && nil == err { - continue - } else if n <= 0 && nil != err { - break - } - - oi.LongWrite(writer, p) - } + io.Copy(writer, reader) }(stdout, r) var buffer bytes.Buffer diff --git a/telsh/telnet_handler.go b/telsh/telnet_handler.go index 1c9fbad..9ff89d4 100644 --- a/telsh/telnet_handler.go +++ b/telsh/telnet_handler.go @@ -1,6 +1,8 @@ package telsh import ( + "bufio" + "github.com/reiver/go-oi" "github.com/reiver/go-telnet" @@ -129,118 +131,89 @@ func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet } logger.Debugf("Wrote prompt: %q.", promptBytes) - var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up. - p := buffer[:] + buffered := bufio.NewReader(reader) + + var err error + var line string + + for err == nil { + line, err = buffered.ReadString('\n') + if err != nil { + break + } - var line bytes.Buffer + if "\r\n" == line { + _, err = oi.LongWrite(writer, promptBytes) + continue + } - for { - // Read 1 byte. - n, err := reader.Read(p) - if n <= 0 && nil == err { + //@TODO: support piping. + fields := strings.Fields(line) + logger.Debugf("Have %d tokens.", len(fields)) + logger.Tracef("Tokens: %v", fields) + if len(fields) <= 0 { + _, err = oi.LongWrite(writer, promptBytes) continue - } else if n <= 0 && nil != err { + } + + field0 := fields[0] + + if exitCommandName == field0 { break } - line.WriteByte(p[0]) - //logger.Tracef("Received: %q (%d).", p[0], p[0]) - - if '\n' == p[0] { - lineString := line.String() - - if "\r\n" == lineString { - line.Reset() - if _, err := oi.LongWrite(writer, promptBytes); nil != err { - return - } - continue - } - - //@TODO: support piping. - fields := strings.Fields(lineString) - logger.Debugf("Have %d tokens.", len(fields)) - logger.Tracef("Tokens: %v", fields) - if len(fields) <= 0 { - line.Reset() - if _, err := oi.LongWrite(writer, promptBytes); nil != err { - return - } - continue - } - - field0 := fields[0] - - if exitCommandName == field0 { - oi.LongWriteString(writer, exitMessage) - return - } - - var producer Producer + var producer Producer + + telnetHandler.muxtex.RLock() + var ok bool + producer, ok = telnetHandler.producers[field0] + telnetHandler.muxtex.RUnlock() + if !ok { telnetHandler.muxtex.RLock() - var ok bool - producer, ok = telnetHandler.producers[field0] + producer = telnetHandler.elseProducer telnetHandler.muxtex.RUnlock() + } - if !ok { - telnetHandler.muxtex.RLock() - producer = telnetHandler.elseProducer - telnetHandler.muxtex.RUnlock() - } - - if nil == producer { - //@TODO: Don't convert that to []byte! think this creates "garbage" (for collector). - oi.LongWrite(writer, []byte(field0)) - oi.LongWrite(writer, colonSpaceCommandNotFoundEL) - line.Reset() - if _, err := oi.LongWrite(writer, promptBytes); nil != err { - return - } - continue - } - - handler := producer.Produce(ctx, field0, fields[1:]...) - if nil == handler { - oi.LongWrite(writer, []byte(field0)) - //@TODO: Need to use a different error message. - oi.LongWrite(writer, colonSpaceCommandNotFoundEL) - line.Reset() - oi.LongWrite(writer, promptBytes) - continue - } - - //@TODO: Wire up the stdin, stdout, stderr of the handler. - - if stdoutPipe, err := handler.StdoutPipe(); nil != err { - //@TODO: - } else if nil == stdoutPipe { - //@TODO: - } else { - connect(ctx, writer, stdoutPipe) - } - - if stderrPipe, err := handler.StderrPipe(); nil != err { - //@TODO: - } else if nil == stderrPipe { - //@TODO: - } else { - connect(ctx, writer, stderrPipe) - } - - if err := handler.Run(); nil != err { - //@TODO: - } - line.Reset() - if _, err := oi.LongWrite(writer, promptBytes); nil != err { - return - } + if nil == producer { + //@TODO: Don't convert that to []byte! think this creates "garbage" (for collector). + oi.LongWrite(writer, []byte(field0)) + oi.LongWrite(writer, colonSpaceCommandNotFoundEL) + _, err = oi.LongWrite(writer, promptBytes) + continue } - //@TODO: Are there any special errors we should be dealing with separately? - if nil != err { - break + handler := producer.Produce(ctx, field0, fields[1:]...) + if nil == handler { + oi.LongWrite(writer, []byte(field0)) + //@TODO: Need to use a different error message. + oi.LongWrite(writer, colonSpaceCommandNotFoundEL) + _, err = oi.LongWrite(writer, promptBytes) + continue + } + + //@TODO: Wire up the stdin, stdout, stderr of the handler. + + if stdoutPipe, err := handler.StdoutPipe(); nil != err { + //@TODO: + } else if nil == stdoutPipe { + //@TODO: + } else { + connect(ctx, writer, stdoutPipe) } + + if stderrPipe, err := handler.StderrPipe(); nil != err { + //@TODO: + } else if nil == stderrPipe { + //@TODO: + } else { + connect(ctx, writer, stderrPipe) + } + + if err = handler.Run(); nil != err { + //@TODO: + } + _, err = oi.LongWrite(writer, promptBytes) } oi.LongWriteString(writer, exitMessage) @@ -252,23 +225,6 @@ func connect(ctx telnet.Context, writer io.Writer, reader io.Reader) { logger := ctx.Logger() go func(logger telnet.Logger) { - - var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up. - p := buffer[:] - - for { - // Read 1 byte. - n, err := reader.Read(p) - if n <= 0 && nil == err { - continue - } else if n <= 0 && nil != err { - break - } - - //logger.Tracef("Sending: %q.", p) - //@TODO: Should we be checking for errors? - oi.LongWrite(writer, p) - //logger.Tracef("Sent: %q.", p) - } + io.Copy(writer, reader) }(logger) }