From cfa06ec4bc4ac784bc8f77d5cd135565b5485ef1 Mon Sep 17 00:00:00 2001 From: "Matthew R. Wilson" Date: Wed, 30 Dec 2020 15:55:31 -0800 Subject: [PATCH] Issue #4: add and use telnetRead() function --- README.md | 1 - response.go | 8 +++--- telnet.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3a5dadf..ad079f3 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,6 @@ I would like to add: Known Problems -------------- - - The telnet data is not checked for the special telnet byte value, 0xFF, which requires escaping while sending and unescaping while receiving data. If your 3270 data streams contain an FF character, things will probably break. However...I don't think 0xFF should ever appear in the datastream, so we're probably okay. - The telnet negotiation does not check for any errors or for any responses from the client. We just assume it goes well and we're actually talking to a tn3270 client. License diff --git a/response.go b/response.go index 0971608..90d9b0d 100644 --- a/response.go +++ b/response.go @@ -99,7 +99,7 @@ func readResponse(c net.Conn, fm fieldmap) (Response, error) { func readAID(c net.Conn) (AID, error) { buf := make([]byte, 1) for { - _, err := c.Read(buf) + _, err := telnetRead(c, buf) if err != nil { return AIDNone, err } @@ -122,7 +122,7 @@ func readPosition(c net.Conn) (row, col, addr int, err error) { // Read two bytes for i := 0; i < 2; i++ { - if _, err := c.Read(buf); err != nil { + if _, err := telnetRead(c, buf); err != nil { return 0, 0, 0, err } raw[i] = buf[0] @@ -150,7 +150,7 @@ func readFields(c net.Conn, fm fieldmap) (map[string]string, error) { // consume bytes until we get 0xffef for { // Read a byte - if _, err = c.Read(buf); err != nil { + if _, err = telnetRead(c, buf); err != nil { return nil, err } @@ -163,7 +163,7 @@ func readFields(c net.Conn, fm fieldmap) (map[string]string, error) { } // consume the next byte, which is probably 0xef - if _, err = c.Read(buf); err != nil { + if _, err = telnetRead(c, buf); err != nil { return nil, err } return values, nil diff --git a/telnet.go b/telnet.go index 957fa3b..e73478b 100644 --- a/telnet.go +++ b/telnet.go @@ -68,3 +68,77 @@ func flushConnection(conn net.Conn, timeout time.Duration) error { timeout = time.Second / 2 } } + +// telnetRead behaves similarly to conn.Read, except it filters out telnet +// commands. A single call to telnetRead() may perform multiple calls to +// c.Read() if awaiting completion of a command, or to ensure we do not return +// zero bytes in a non-error condition. +func telnetRead(c net.Conn, p []byte) (n int, err error) { + const ( + normal = iota + command + subneg + ) + + // Not the most efficient, but for now to keep the code easier we'll just + // read 1 byte at a time from the connection. + b := make([]byte, 1) + state := normal + + for n < len(p) { + bn, berr := c.Read(b) + + // When there are no bytes to process and we received an error, we + // are done no matter what state we're in. Any non-command bytes will + // already be in p, so we return. + if bn == 0 && berr != nil { + return n, berr + } + + // If we received 0 bytes but no error, we'll just read again. + if bn == 0 { + continue + } + + // We got a byte! Let's progress through our state machine. + switch state { + case normal: + if b[0] == iac { + state = command + debugf("entering telnet command state\n") + } else { + p[n] = b[0] + n++ + } + case command: + if b[0] == 0xff { + p[n] = 0xff + n++ + state = normal + debugf("leaving telnet command state; was an escaped 0xff\n") + } else if b[0] == sb { + state = subneg + debugf("entering telnet command subnegotiation state\n") + } else { + state = normal + debugf("leaving telnet command state; command was %02x\n", + b[0]) + } + case subneg: + if b[0] == se { + state = normal + debugf("leaving telnet command subnegotiation state\n") + } else { + // remain in subnegotiation consuming bytes until we get se + debugf("consumed telnet subnegotiation byte: %02x\n", b[0]) + } + } + + // Now we handle any errors that returned partial data + if berr != nil { + return n, berr + } + } + + return n, nil +}